fix: 对app进行瘦身

This commit is contained in:
2025-12-18 03:40:33 -08:00
parent 4f09267b4b
commit 83d201b78d
5471 changed files with 16 additions and 3302 deletions
-1
View File
@@ -7,7 +7,6 @@ import 'package:get/get.dart';
import 'package:kaer_with_panels/app/common/app_config.dart';
import 'package:kaer_with_panels/app/model/enum/kr_request_type.dart';
import 'package:kaer_with_panels/app/modules/kr_main/controllers/kr_main_controller.dart';
import 'package:kaer_with_panels/app/services/kr_socket_service.dart';
import 'package:kaer_with_panels/app/utils/kr_secure_storage.dart';
import 'package:kaer_with_panels/app/services/kr_device_info_service.dart';
@@ -5,12 +5,10 @@ import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:get/get.dart';
import 'package:kaer_with_panels/app/common/app_run_data.dart';
import 'package:kaer_with_panels/app/services/kr_subscribe_service.dart';
import 'package:latlong2/latlong.dart';
import '../../../../singbox/model/singbox_proxy_type.dart';
import '../../../../singbox/model/singbox_status.dart';
import '../../../common/app_config.dart';
@@ -47,8 +45,6 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
/// 订阅服务
final KRSubscribeService kr_subscribeService = KRSubscribeService();
// 修改地图控制器为可空类型
MapController kr_mapController = MapController();
/// 当前视图状态,登录状态
final Rx<KRHomeViewsStatus> kr_currentViewStatus =
@@ -130,7 +126,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
final kr_isUserMoving = false.obs;
// 添加最后的地图中心点
final kr_lastMapCenter = LatLng(35.0, 105.0).obs;
final kr_lastMapCenter = 0.obs;
// 为"闪连"Checkbox添加一个响应式变量,默认为 false
final isQuickConnectEnabled = false.obs;
@@ -2052,46 +2048,19 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
// 移动到选中节点
void kr_moveToSelectedNode() {
try {
if (kr_cutSeletedTag.isEmpty) return;
final selectedNode = kr_subscribeService.keyList[kr_cutSeletedTag.value];
if (selectedNode == null) return;
final location = LatLng(selectedNode.latitude, selectedNode.longitude);
kr_moveToLocation(location);
} catch (e) {
KRLogUtil.kr_e('移动到选中节点失败: $e', tag: 'HomeController');
}
}
// 简化移动地图方法
void kr_moveToLocation(LatLng location, [double zoom = 5.0]) {
void kr_moveToLocation() {
try {
// 🔧 关键修复:约束坐标到有效范围,防止超出地图边界
final constrainedLocation = _constrainCoordinates(location);
kr_mapController.move(constrainedLocation, zoom);
kr_isUserMoving.value = false;
} catch (e) {
KRLogUtil.kr_e('移动地图失败: $e', tag: 'HomeController');
}
}
/// 🔧 新增:约束坐标到有效范围内
/// 确保坐标在 [-85, 85] 纬度和 [-180, 180] 经度范围内
LatLng _constrainCoordinates(LatLng coords) {
double lat = coords.latitude.clamp(-85.0, 85.0);
double lng = coords.longitude.clamp(-180.0, 180.0);
if (lat != coords.latitude || lng != coords.longitude) {
KRLogUtil.kr_w(
'⚠️ 坐标超出范围,已约束: (${coords.latitude}, ${coords.longitude}) → ($lat, $lng)',
tag: 'HomeController');
}
return LatLng(lat, lng);
}
// 添加一个方法来批量更新标记
void kr_updateMarkers(List<String> tags) {
// 使用Set来去重
@@ -11,7 +11,6 @@ import 'package:kaer_with_panels/app/widgets/dialogs/hi_dialog.dart';
import '../../../services/kr_subscribe_service.dart';
import '../controllers/kr_home_controller.dart';
import '../models/kr_home_views_status.dart';
import '../widgets/kr_home_map_view.dart';
import '../widgets/kr_subscribe_selector_view.dart';
import 'kr_home_bottom_panel.dart';
import 'kr_home_subscription_view.dart';
@@ -1,534 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:get/get.dart';
import 'package:latlong2/latlong.dart';
import '../../../utils/kr_fm_tc.dart';
import '../controllers/kr_home_controller.dart';
import '../../../utils/kr_log_util.dart';
import 'package:flutter/foundation.dart';
/// 首页地图视图组件
class KRHomeMapView extends GetView<KRHomeController> {
const KRHomeMapView({super.key});
@override
Widget build(BuildContext context) {
// 初始化地图缓存
//KRFMTC.kr_initMapCache();
return GetBuilder<KRHomeController>(
id: 'map_markers', // ① 指定唯一ID
builder: (controller) {
return FlutterMap(
mapController: controller.kr_mapController,
options: MapOptions(
initialCenter: _kr_getInitialMapCenter(),
initialZoom: 2.0,
minZoom: 2,
maxZoom: 5,
initialRotation: 0,
backgroundColor: Theme.of(context).primaryColor,
onMapEvent: (event) {
try {
if (event is MapEventMoveEnd) {
if (event.source == MapEventSource.dragEnd ||
event.source == MapEventSource.multiFingerEnd) {
controller.kr_isUserMoving.value = true;
// 地图移动结束后刷新标记
controller.showMarkersMap();
}
}
} catch (e) {
KRLogUtil.kr_e('地图事件处理失败: $e', tag: 'HomeMapView');
}
},
keepAlive: true,
interactionOptions: InteractionOptions(
enableMultiFingerGestureRace: true,
flags: InteractiveFlag.all & ~InteractiveFlag.rotate,
),
),
children: [
_kr_buildTileLayer(context),
_kr_buildMarkers(context),
],
);
},
);
}
/// 构建地图瓦片层
Widget _kr_buildTileLayer(BuildContext context) {
return TileLayer(
tileProvider: AssetTileProvider(),
urlTemplate: KRFMTC.kr_getTileUrl(),
subdomains: KRFMTC.kr_getTileSubdomains(),
userAgentPackageName: 'app.baertw123.com',//app.brAccelerator.com
/* tileProvider: KRFMTC.kr_getTileProvider(),*/
tileBuilder: (context, child, tileImage) {
return child;
},
);
}
MarkerLayer _kr_buildMarkers(BuildContext content){
int zoom = 2;
try {
zoom = controller.kr_mapController.camera.zoom.toInt();
} catch (e) {
KRLogUtil.kr_e('获取地图缩放级别失败: $e', tag: 'HomeMapView');
}
// 详细调试信息
KRLogUtil.kr_i('========== 地图标记调试信息 ==========', tag: 'HomeMapView');
KRLogUtil.kr_i('当前地图缩放级别: $zoom', tag: 'HomeMapView');
KRLogUtil.kr_i('节点总数: ${controller.kr_subscribeService.allList.length}', tag: 'HomeMapView');
KRLogUtil.kr_i('国家分组数: ${controller.kr_subscribeService.countryOutboundList.length}', tag: 'HomeMapView');
// 根据缩放级别显示不同的标记
if (zoom < 5) {
// 低缩放级别:显示国家级聚合标记
KRLogUtil.kr_i('使用国家聚合模式,显示 ${controller.kr_subscribeService.countryOutboundList.length} 个国家标记', tag: 'HomeMapView');
// 打印前3个国家的信息
for (int i = 0; i < 3 && i < controller.kr_subscribeService.countryOutboundList.length; i++) {
final country = controller.kr_subscribeService.countryOutboundList[i];
KRLogUtil.kr_i('国家[$i]: ${country.country}, 节点数: ${country.outboundList.length}', tag: 'HomeMapView');
}
return MarkerLayer(
markers: controller.kr_subscribeService.countryOutboundList
.map((item) => _buildStyledProvMarker(item))
.toList(),
);
} else {
// 高缩放级别:显示单个节点标记
KRLogUtil.kr_i('使用单节点模式,显示 ${controller.kr_subscribeService.allList.length} 个节点标记', tag: 'HomeMapView');
// 打印前3个节点的信息
for (int i = 0; i < 3 && i < controller.kr_subscribeService.allList.length; i++) {
final node = controller.kr_subscribeService.allList[i];
KRLogUtil.kr_i('节点[$i]: ${node.tag}, 位置: (${node.latitude}, ${node.longitude})', tag: 'HomeMapView');
}
return MarkerLayer(
markers: controller.kr_subscribeService.allList
.map((item) => _buildStyledMarker(item))
.toList(),
);
}
}
/// 构建国家级聚合标记
Marker _buildStyledProvMarker(dynamic node) {
double z = controller.kr_mapController.camera.zoom;
double lat = 0.0;
double lng = 0.0;
String id = "";
int len = -1;
if (node.outboundList.isNotEmpty) {
var f = node.outboundList.first;
len = node.outboundList.length;
id = f.id;
lat = f.latitudeCountry;
lng = f.longitudeCountry;
KRLogUtil.kr_i('构建国家标记: ${f.country}', tag: 'HomeMapView');
KRLogUtil.kr_i(' - latitudeCountry: $lat', tag: 'HomeMapView');
KRLogUtil.kr_i(' - longitudeCountry: $lng', tag: 'HomeMapView');
KRLogUtil.kr_i(' - 节点latitude: ${f.latitude}', tag: 'HomeMapView');
KRLogUtil.kr_i(' - 节点longitude: ${f.longitude}', tag: 'HomeMapView');
KRLogUtil.kr_i(' - 节点数量: $len', tag: 'HomeMapView');
// 如果国家坐标为0,使用节点坐标
if (lat == 0.0 && lng == 0.0) {
lat = f.latitude;
lng = f.longitude;
KRLogUtil.kr_w('国家坐标为0,使用节点坐标: ($lat, $lng)', tag: 'HomeMapView');
}
}
double fontSize = 8;
double radius = 0 + len * 10 + z * 10;
double radius2 = 20;
if (z < 3) {
fontSize = 8;
radius = 0 + len * 10 + z * 10;
radius2 = 15;
} else if (z < 4) {
fontSize = 8;
radius = 30 + len * 10 + z * 10;
radius2 = 20;
} else if (z < 5) {
fontSize = 10;
radius = 70 + len * 10 + z * 10;
radius2 = 20;
} else if (z < 6) {
fontSize = 12;
radius = 100 + len * 10 + z * 10;
radius2 = 30;
}
return Marker(
key: ValueKey('country_$id'),
point: LatLng(lat, lng),
width: radius,
height: radius,
child: GestureDetector(
onTap: () {
// 点击国家标记时,放大地图到该位置
KRLogUtil.kr_i('点击国家标记,放大到: lat=$lat lng=$lng', tag: 'HomeMapView');
controller.kr_moveToLocation(LatLng(lat, lng), 5.0);
},
child: _buildDoubleCircle(radius, radius2, Colors.blue, len, fontSize),
),
);
}
/// 构建样式化的标记
Marker _buildStyledMarker(dynamic node) {
if (kDebugMode) {
print("原始Marker${node}");
}
int type = 0;
MaterialColor markerColor = Colors.grey;
//如果没订阅过,就是灰色
bool status = controller.kr_isConnected.value;
if (kDebugMode) {
print("连接状态:$status");
}
if(status){
if (node.urlTestDelay.value == 0) {
// 延迟为0时使用默认颜色
type=0;
markerColor = Colors.blue;
} else if (node.urlTestDelay.value < 500) {
// 延迟小于500ms显示绿色
type=1;
markerColor = Colors.green;
} else if (node.urlTestDelay.value < 3000) {
// 延迟小于3000ms显示黄色
type=2;
markerColor = Colors.yellow;
} else if (node.urlTestDelay.value > 3000 ){
// 超时显示红色
type=3;
markerColor = Colors.red;
}
}else{
type=4;
markerColor = Colors.blue;
}
if(node.selected==1){
if(type==1||type==0){//只有绿色才有雷达效果
return RadarMarker(
key: ValueKey('key_${node.id}'),
point: LatLng(node.latitude, node.longitude),
radarSize1:75,
radarSize2:37,
radarSize3: 25.0,
innerColor: Colors.green,
outerColor: Colors.green,
animationDuration: const Duration(seconds: 4),
onTap: () => _onMarkerTap(node),
).toMarker();
}else{ //蓝色. 灰色 黄色
return Marker(
key: ValueKey('key_${node.id}'),
point: LatLng(node.latitude, node.longitude),
width: 150,
height: 150.0,
child: GestureDetector(
onTap: () => _onMarkerTap(node),
child: _buildDoubleCircle(150,20, markerColor, -1,14),
),
);
}
}else{
return Marker(
key: ValueKey('key_${node.id}'),
point: LatLng(node.latitude, node.longitude),
width: 30.0,
height: 30.0,
child: GestureDetector(
onTap: () => _onMarkerTap(node),
child: _buildDoubleCircle(30,10, markerColor, -1,14),
),
);
}
/*
return Marker(
point: LatLng(node.latitude, node.longitude),
width: 42.w,
height: 34.w,
child: GetBuilder<KRHomeController>(
id: node.tag,
builder: (controller) {
// 确定颜色
Color? markerColor;
if (node.urlTestDelay.value == 0) {
// 延迟为0时使用默认颜色
markerColor = null;
} else if (node.urlTestDelay.value < 500) {
// 延迟小于500ms显示绿色
markerColor = const Color(0xFF00E52B).withOpacity(0.7);
} else if (node.urlTestDelay.value < 3000) {
// 延迟小于3000ms显示黄色
markerColor = Colors.yellow;
} else {
// 超时显示红色
markerColor = Colors.red;
}
return KrLocalImage(
imageName: "location",
width: 42.w,
height: 34.w,
color: markerColor,
);
},
),
);*/
}
/// 处理标记点击事件
void _onMarkerTap(dynamic node) {
try {
KRLogUtil.kr_i('点击节点: ${node.tag}', tag: 'HomeMapView');
// 选择该节点
controller.kr_selectNode(node.tag);
// 如果有底部面板控制器,将面板收起到 30% 高度
// if (controller.kr_sheetController.isAttached) {
// controller.kr_sheetController.animateTo(
// 0.3,
// duration: const Duration(milliseconds: 300),
// curve: Curves.easeOut,
// );
// }
} catch (e) {
KRLogUtil.kr_e('处理标记点击失败: $e', tag: 'HomeMapView');
}
}
/// 获取初始地图中心点
LatLng _kr_getInitialMapCenter() {
if (controller.kr_isUserMoving.value) {
return controller.kr_lastMapCenter.value;
}
if (controller.kr_cutSeletedTag.isEmpty) {
return const LatLng(5.0, 105.0); // 修改默认位置为中国中部
}
final selectedNode = controller.kr_subscribeService.allList
.firstWhereOrNull((item) => item.tag == controller.kr_cutSeletedTag.value);
if (selectedNode == null) {
return const LatLng(5.0, 105.0); // 修改默认位置为中国中部35
}
//更新当前数据为选中
// controller.kr_subscribeService.allList[index].selected = 1;
// controller.selectMarkersMap(index);
// 更新最后的地图中心点
controller.kr_lastMapCenter.value =
LatLng(selectedNode.latitude, selectedNode.longitude);
return controller.kr_lastMapCenter.value;
}
}
// 构建双层圆形标记
Widget _buildDoubleCircle(double circleW1,double circleW2,MaterialColor markerColor, int num,double fontSize) {
return Stack(
alignment: Alignment.center,
children: [
// 外圈(淡蓝色,50%透明度)
Container(
width: circleW1,
height: circleW1,
decoration: BoxDecoration(
shape: BoxShape.circle,
color:markerColor.withOpacity(0.2), // 淡蓝色,50%透明度
),
),
// 内圈(蓝色,0%透明度)
Container(
width: circleW2,
height: circleW2,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: markerColor, // 蓝色,0%透明度(完全不透明)
),
),
// 中心数字文本
Text(
(num==-1)?' ':'$num',
style: TextStyle(
fontSize: fontSize,
fontWeight: FontWeight.bold,
color: Colors.white, // 白色文字
),
),
],
);
}
class RadarMarker extends StatefulWidget {
final LatLng point;
final double radarSize1;
final double radarSize2;
final double radarSize3;
final Color innerColor;
final Color outerColor;
final Duration animationDuration;
final VoidCallback? onTap;
const RadarMarker({
super.key,
required this.point,
this.radarSize1 = 120,
this.radarSize2 = 60,
this.radarSize3 = 40,
this.innerColor = Colors.blue,
this.outerColor = Colors.blue,
//this.animationDuration = const Duration(seconds: 1),
this.animationDuration = const Duration(milliseconds: 500),
this.onTap,
});
@override
State<RadarMarker> createState() => _RadarMarkerState();
}
class _RadarMarkerState extends State<RadarMarker>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scaleAnimation;
late Animation<double> _opacityAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: widget.animationDuration,
vsync: this,
)..repeat();
_scaleAnimation = Tween<double>(begin: 1.0, end: 2.5).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeOut),
);
_opacityAnimation = Tween<double>(begin: 1.0, end: 0.0).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeOut),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: widget.onTap,
child: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Stack(
alignment: Alignment.center,
children: [
// 第一层外圈雷达波纹
Transform.scale(
scale: _scaleAnimation.value,
child: Opacity(
opacity: _opacityAnimation.value,
child: Container(
width: widget.radarSize1,
height: widget.radarSize1,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: widget.outerColor.withOpacity(0.2),
border: Border.all(
color: widget.outerColor.withOpacity(0.2),
width: 2.0,
),
),
),
),
),
// 第二层波纹
Transform.scale(
scale: _scaleAnimation.value * 0.7,
child: Opacity(
opacity: _opacityAnimation.value * 0.7,
child: Container(
width: widget.radarSize2,
height: widget.radarSize2,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: widget.outerColor.withOpacity(0.3),
border: Border.all(
color: widget.outerColor.withOpacity(0.3),
width: 1.5,
),
),
),
),
),
// 中心圆形
Container(
width: widget.radarSize3,
height: widget.radarSize3,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: widget.innerColor,
boxShadow: [
BoxShadow(
color: widget.innerColor.withOpacity(0.5),
blurRadius: 10.0,
spreadRadius: 2.0,
),
],
border: Border.all(
color: Colors.white,
width: 2.0,
),
),
),
],
);
},
),
);
}
}
extension RadarMarkerExtension on RadarMarker {
Marker toMarker() {
return Marker(
point: point,
width: radarSize1,
height: radarSize1,
child: this,
);
}
}
class AssetTileProvider extends TileProvider {
@override
ImageProvider getImage(TileCoordinates coords, TileLayer options) {
// 构建assets路径:z/x/y.png
final path = 'assets/map_tiles/${coords.z}/${coords.x}/${coords.y}.png';
return AssetImage(path);
}
}
@@ -11,7 +11,6 @@ import 'package:flutter/material.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart';
import 'package:kaer_with_panels/app/modules/kr_main/controllers/kr_main_controller.dart';
import 'package:easy_refresh/easy_refresh.dart';
import 'package:kaer_with_panels/app/common/app_run_data.dart';
@@ -129,103 +128,6 @@ class KRInviteController extends GetxController {
KRCommonUtil.kr_showToast(AppTranslations.kr_invite.copiedToClipboard);
}
Future<void> kr_handleQRShare() async {
if (!await kr_checkLoginStatus()) {
Get.find<KRMainController>().kr_setPage(0);
return;
}
if (kr_referCode.isEmpty) {
await _kr_fetchUserInfo();
if (kr_referCode.isEmpty) {
KRCommonUtil.kr_showToast(AppTranslations.kr_invite.getInviteCodeFailed);
return;
}
}
final qrContent = kr_getQRCodeContent();
if (qrContent.isEmpty) {
KRCommonUtil.kr_showToast(AppTranslations.kr_invite.generateQRCodeFailed);
return;
}
// 只有在登录状态下才弹出二维码对话框
if (KRAppRunData.getInstance().kr_isLogin.value) {
Get.dialog(
Dialog(
backgroundColor: Theme.of(Get.context!).cardColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.r),
),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 24.h),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
AppTranslations.kr_invite.shareQR,
style: KrAppTextStyle(
fontSize: 17,
fontWeight: FontWeight.w600,
color: Theme.of(Get.context!).textTheme.titleMedium?.color,
),
),
SizedBox(height: 16.h),
Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.r),
border: Border.all(
color: Theme.of(Get.context!).dividerColor.withOpacity(0.1),
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: Offset(0, 2),
),
],
),
child: QrImageView(
data: qrContent,
version: QrVersions.auto,
size: 200.w,
backgroundColor: Colors.white,
foregroundColor: Colors.black,
),
),
SizedBox(height: 20.h),
Container(
width: double.infinity,
height: 44.w,
child: TextButton(
onPressed: () => Get.back(),
style: TextButton.styleFrom(
backgroundColor: Theme.of(Get.context!).primaryColor.withOpacity(0.1),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(22.r),
),
),
child: Text(
AppTranslations.kr_invite.close,
style: TextStyle(
color: Theme.of(Get.context!).textTheme.bodyMedium?.color,
fontSize: 16.sp,
fontWeight: FontWeight.w500,
fontFamily: 'AlibabaPuHuiTi-Regular',
),
),
),
),
],
),
),
),
);
}
}
void kr_viewRewardDetails() {
// TODO: 实现查看奖励明细功能
}
@@ -243,48 +145,6 @@ class KRInviteController extends GetxController {
void increment() => count.value++;
/// 处理链接分享
Future<void> kr_handleLinkShare() async {
if (!await kr_checkLoginStatus()) {
Get.find<KRMainController>().kr_setPage(0);
return;
}
if (kr_referCode.isEmpty) {
await _kr_fetchUserInfo();
if (kr_referCode.isEmpty) {
KRCommonUtil.kr_showToast(AppTranslations.kr_invite.getInviteCodeFailed);
return;
}
}
final shareLink = kr_getShareLink();
if (shareLink.isEmpty) {
KRCommonUtil.kr_showToast(AppTranslations.kr_invite.generateShareLinkFailed);
return;
}
await _kr_copyToClipboard(shareLink);
}
/// 处理复制邀请码
Future<void> kr_handleCopyInviteCode() async {
if (!await kr_checkLoginStatus()) {
Get.find<KRMainController>().kr_setPage(0);
return;
}
if (kr_referCode.isEmpty) {
await _kr_fetchUserInfo();
if (kr_referCode.isEmpty) {
KRCommonUtil.kr_showToast(AppTranslations.kr_invite.getInviteCodeFailed);
return;
}
}
await _kr_copyToClipboard(kr_referCode.value);
}
/// 处理绑定邀请码
Future<void> kr_handleBindInviteCode() async {
final text = otherInviteCodeController.text;
@@ -1,50 +0,0 @@
import 'package:get/get.dart';
import 'package:flutter/foundation.dart' show kDebugMode;
import 'package:kaer_with_panels/app/modules/kr_home/controllers/kr_home_controller.dart';
import 'package:kaer_with_panels/app/modules/kr_invite/controllers/kr_invite_controller.dart';
import 'package:kaer_with_panels/app/modules/kr_login/controllers/kr_login_controller.dart';
import 'package:kaer_with_panels/app/modules/kr_statistics/controllers/kr_statistics_controller.dart';
import 'package:kaer_with_panels/app/modules/kr_user_info/controllers/kr_user_info_controller.dart';
import '../controllers/kr_main_controller.dart';
class KRMainBinding extends Bindings {
@override
void dependencies() {
if (kDebugMode) {
print('🔧 KRMainBinding.dependencies 被调用');
}
// 🔧 MainController 仍然使用 lazyPut,因为它由 MainView 触发
Get.lazyPut<KRMainController>(
() {
if (kDebugMode) {
print('🏗️ 创建 KRMainController 实例');
}
return KRMainController();
},
);
// 🔧 关键修复:使用 lazyPut 但设置为单例,避免重复创建
// fenix: true 允许控制器在被删除后重新创建
// 使用 lazyPut 让 HomeController 在真正需要时才创建,避免过早初始化导致资源冲突
Get.lazyPut<KRHomeController>(
() {
if (kDebugMode) {
print('🏗️ 创建 KRHomeController 实例(仅此一次)');
}
return KRHomeController();
},
fenix: true,
);
Get.lazyPut(() => KRLoginController());
Get.lazyPut(() => KRInviteController());
Get.lazyPut(() => KRUserInfoController());
Get.lazyPut(() => KRStatisticsController());
if (kDebugMode) {
print('✅ KRMainBinding.dependencies 完成');
}
}
}
@@ -1,116 +0,0 @@
import 'package:flutter/widgets.dart';
import 'package:flutter/foundation.dart' show kDebugMode;
import 'package:get/get.dart';
import 'package:kaer_with_panels/app/modules/kr_home/views/kr_home_view.dart';
import 'package:kaer_with_panels/app/modules/kr_invite/views/kr_invite_view.dart';
import 'package:kaer_with_panels/app/modules/kr_statistics/views/kr_statistics_view.dart';
import 'package:kaer_with_panels/app/modules/kr_user_info/views/kr_user_info_view.dart';
import 'package:kaer_with_panels/app/modules/kr_user_info/controllers/kr_user_info_controller.dart';
import 'package:kaer_with_panels/app/widgets/kr_keep_alive_wrapper.dart';
import 'package:kaer_with_panels/app/common/app_run_data.dart';
import 'package:kaer_with_panels/app/localization/app_translations.dart';
import 'package:kaer_with_panels/app/widgets/dialogs/kr_dialog.dart';
import 'package:kaer_with_panels/app/routes/app_pages.dart';
import '../../../widgets/kr_language_switch_dialog.dart';
enum MainRoutes {
INDEX(0, '首页'),
DYNAMICS(1, '看看'),
TOTALLETTER(2, '邮筒'),
MESSAGELIST(3, '消息'),
USER_CENTER(4, '我的');
final int i;
final String title;
const MainRoutes(this.i, this.title);
}
class KRMainController extends GetxController {
static KRMainController get to => Get.find();
DateTime? lastPopTime;
var kr_currentIndex = 0.obs;
late final List<Widget> widgets;
/// 分页控制器
PageController pageController = PageController(keepPage: true);
@override
void onInit() {
super.onInit();
// 🔧 诊断:在 onInit 中创建 widgets 列表
if (kDebugMode) {
print('🎬 KRMainController.onInit 被调用');
print('📝 开始创建 widgets 列表...');
}
widgets = [
KRKeepAliveWrapper(KRHomeView()),
KRKeepAliveWrapper(KRInviteView()),
KRKeepAliveWrapper(KRStatisticsView()),
KRKeepAliveWrapper(KRUserInfoView()),
];
if (kDebugMode) {
print('✅ widgets 列表创建完成,包含 ${widgets.length} 个页面');
}
}
@override
void onReady() {
super.onReady();
}
@override
void onClose() {
super.onClose();
}
/// 到哪个页面,具体传值查看MainRoutes的枚举类
kr_setPage(int index) {
// 🔒 检查邀请页面(index=1)是否需要登录
if (index == 1) {
final appRunData = KRAppRunData.getInstance();
final isDeviceLogin = appRunData.isDeviceLogin();
final isLoggedIn = appRunData.kr_isLogin.value && !isDeviceLogin;
// 如果是游客模式,弹出登录提示对话框,不切换页面
if (!isLoggedIn) {
KRDialog.show(
title: AppTranslations.kr_dialog.deviceLoginBindingTitle,
message: AppTranslations.kr_dialog.deviceLoginBindingMessage,
confirmText: AppTranslations.kr_dialog.kr_ok,
cancelText: AppTranslations.kr_dialog.kr_cancel,
onConfirm: () {
Get.back(); // 关闭对话框
// 延迟后跳转到登录页面
Future.delayed(const Duration(milliseconds: 300), () {
Get.toNamed(Routes.MR_LOGIN);
});
},
onCancel: () {
Get.back(); // 关闭对话框
},
);
return; // 不切换页面
}
}
// 正常切换页面
kr_currentIndex.value = index;
pageController.jumpToPage(index);
// 监控页面进入
if (index == MainRoutes.USER_CENTER.i) {
final userInfoController = Get.find<KRUserInfoController>();
userInfoController.kr_onPageEnter();
}
}
}
@@ -1,54 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:kaer_with_panels/app/modules/kr_login/views/kr_login_view.dart';
import 'package:kaer_with_panels/app/modules/kr_main/views/kr_tabbar_view.dart';
import 'package:kaer_with_panels/app/widgets/kr_local_image.dart';
import '../controllers/kr_main_controller.dart';
class KRMainView extends GetView<KRMainController> {
const KRMainView({super.key});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context); // 获取当前主题
return Scaffold(
// 根据下标显示哪个页面
body: GetBuilder<KRMainController>(builder: (mc) {
return PageView(
children: mc.widgets,
controller: controller.pageController,
physics: const NeverScrollableScrollPhysics(),
);
}),
bottomNavigationBar: Obx(
() => KRCustomBottomNavBar(
backgroundColor: theme.scaffoldBackgroundColor,
currentIndex: controller.kr_currentIndex.value,
onTap: (i) => controller.kr_setPage(i),
items: [
KRCustomBottomNavBarItem(
imageName: "tab_home_n",
activeImageName: "tab_home_s",
),
KRCustomBottomNavBarItem(
imageName: "tab_invite_n",
activeImageName: "tab_invite_s",
),
KRCustomBottomNavBarItem(
imageName: "tab_statistics_n",
activeImageName: "tab_statistics_s",
),
KRCustomBottomNavBarItem(
imageName: "tab_my_n",
activeImageName: "tab_my_s",
),
],
),
),
);
}
}
@@ -1,109 +0,0 @@
import 'package:flutter/material.dart';
import 'package:kaer_with_panels/app/widgets/kr_local_image.dart';
/// 用来描述底部导航栏的每一项
class KRCustomBottomNavBarItem {
/// 未选中时的图片名称
final String imageName;
/// 选中时的图片名称(可选,不传则用同一张 imageName)
final String? activeImageName;
/// 标签(可选)
final String? label;
KRCustomBottomNavBarItem({
required this.imageName,
this.activeImageName,
this.label,
});
}
class KRCustomBottomNavBar extends StatelessWidget {
/// 传入当前选中的索引
final int currentIndex;
/// 导航栏的各个条目信息
final List<KRCustomBottomNavBarItem> items;
/// 点击某项时的回调
final ValueChanged<int> onTap;
/// 背景色
final Color backgroundColor;
/// 选中时 图标/文字 颜色
final Color selectedColor;
/// 未选中时 图标/文字 颜色
final Color unselectedColor;
/// 导航栏高度(不含安全区额外高度)
final double height;
/// 是否在内部自动使用 SafeArea
final bool useSafeArea;
const KRCustomBottomNavBar({
Key? key,
required this.currentIndex,
required this.items,
required this.onTap,
this.backgroundColor = Colors.white,
this.selectedColor = Colors.blue,
this.unselectedColor = Colors.grey,
this.height = 56.0,
this.useSafeArea = true,
}) : super(key: key);
@override
Widget build(BuildContext context) {
assert(items.isNotEmpty, 'The items list cannot be empty.');
assert(currentIndex >= 0 && currentIndex < items.length,
'The currentIndex must be within the bounds of the items list.');
Widget child = Container(
color: backgroundColor,
height: height,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: List.generate(items.length, (index) {
final item = items[index];
final bool isSelected = (index == currentIndex);
final iconName = isSelected
? (item.activeImageName ?? item.imageName)
: item.imageName;
final textColor = isSelected ? selectedColor : unselectedColor;
return Expanded(
child: InkWell(
onTap: () => onTap(index),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
KrLocalImage(
imageName: iconName,
height: 24,
width: 24,
),
if (item.label != null) ...[
const SizedBox(height: 4),
Text(
item.label!,
style: TextStyle(color: textColor, fontSize: 12),
),
],
],
),
),
);
}),
),
);
// 使用 SafeArea 包裹,避免底部被手势栏遮挡
return useSafeArea ? SafeArea(child: child) : child;
}
}
@@ -17,7 +17,7 @@ import '../../../services/api_service/kr_api.user.dart';
import '../../../utils/kr_event_bus.dart';
import '../../../network/http_util.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_stripe/flutter_stripe.dart';
// import 'package:flutter_stripe/flutter_stripe.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'dart:async';
import 'package:flutter/services.dart';
@@ -512,9 +512,6 @@ class KRPurchaseMembershipController extends GetxController {
/// 处理购买和结账流程
Future<void> kr_processPurchaseAndCheckout() async {
final selectedPlan = kr_plans[kr_selectedPlanIndex.value];
// =========================================================================
// 🔽 支付方式选择优化:iOS 优先使用 Stripe 🔽
// =========================================================================
final isIOS = Platform.isIOS;
// 1. 初始化为默认回退值
@@ -923,7 +920,8 @@ class KRPurchaseMembershipController extends GetxController {
},
);
} else if (checkoutResponse.type == 'stripe') {
// Stripe 类型:显示 Stripe 支付表单
KRCommonUtil.kr_showToast('不支持 stripe方式');
/* // Stripe 类型:显示 Stripe 支付表单
print(' Stripe Method: ${checkoutResponse.stripe?.method}');
print(
' Stripe Client Secret: ${checkoutResponse.stripe?.clientSecret}');
@@ -983,7 +981,7 @@ class KRPurchaseMembershipController extends GetxController {
'checkout_type': 'stripe',
'stripe': checkoutResponse.stripe,
},
);
);*/
} else if (checkoutResponse.type == 'balance') {
// Stripe 类型:显示 Stripe 支付表单
print(' Stripe Method: ${checkoutResponse.stripe?.method}');
@@ -1,12 +0,0 @@
import 'package:get/get.dart';
import '../../kr_home/controllers/kr_home_controller.dart';
import '../controllers/kr_statistics_controller.dart';
class KRStatisticsBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<KRHomeController>(() => KRHomeController());
Get.lazyPut<KRStatisticsController>(() => KRStatisticsController());
}
}
@@ -1,231 +0,0 @@
import 'package:get/get.dart';
import 'package:kaer_with_panels/app/services/api_service/kr_api.user.dart';
import '../../../common/app_run_data.dart';
import '../../../model/response/kr_user_online_duration.dart';
import '../../../modules/kr_home/controllers/kr_home_controller.dart';
import '../../../utils/kr_common_util.dart';
import '../../../utils/kr_log_util.dart';
import 'package:kaer_with_panels/app/localization/app_translations.dart';
import 'package:easy_refresh/easy_refresh.dart';
class KRStatisticsController extends GetxController {
/// VPN连接状态
final RxString kr_vpnStatus = ''.obs;
/// IP地址
final RxString kr_ipAddress = ''.obs;
/// 连接时间
final RxString kr_connectTime = ''.obs;
/// 协议类型
final RxString kr_protocol = ''.obs;
/// 当前连续记录(天)
final RxInt kr_currentStreak = 0.obs;
/// 最高记录(天)
final RxInt kr_highestStreak = 0.obs;
/// 最长连接时间(天)
final RxInt kr_longestConnection = 0.obs;
/// 每周保护时间数据
final RxList<double> kr_weeklyData = <double>[0, 0, 0, 0, 0, 0, 0].obs;
final RxBool kr_isConnected = false.obs;
late final KRHomeController kr_homeController;
/// 开始时间
final RxInt kr_startTime = 0.obs;
/// 结束时间
final RxInt kr_endTime = 0.obs;
/// 最后连接日期
final RxString kr_lastConnectDate = ''.obs;
final KRUserApi kr_userApi = KRUserApi();
final EasyRefreshController refreshController = EasyRefreshController(
controlFinishRefresh: true,
);
@override
void onInit() {
super.onInit();
kr_homeController = Get.find<KRHomeController>();
_kr_initializeListeners();
_kr_initializeValues();
kr_isConnected.value = kr_homeController.kr_isConnected.value;
ever(kr_homeController.kr_isConnected, (bool connected) {
kr_isConnected.value = connected;
});
ever(KRAppRunData.getInstance().kr_isLogin, (bool isLogin) {
if (!isLogin) {
kr_currentStreak.value = 0;
kr_longestConnection.value = 0;
kr_highestStreak.value = 0;
kr_weeklyData.value = <double>[0, 0, 0, 0, 0, 0, 0];
return;
}
kr_getUserSubscribeTrafficLogs();
});
}
/// 初始化监听器
void _kr_initializeListeners() {
ever(kr_homeController.kr_connectText, _kr_updateVpnStatus);
ever(kr_homeController.kr_currentIp, _kr_updateIpAddress);
ever(kr_homeController.kr_connectionTime, _kr_updateConnectionTime);
ever(kr_homeController.kr_currentProtocol, _kr_updateProtocol);
}
/// 初始化值
void _kr_initializeValues() {
kr_vpnStatus.value = kr_homeController.kr_connectText.value;
kr_ipAddress.value = kr_homeController.kr_currentIp.value;
kr_connectTime.value = kr_homeController.kr_connectionTime.value;
kr_protocol.value = kr_homeController.kr_currentProtocol.value;
// 这里可以添加其他统计数据的初始化
_kr_updateStatistics();
kr_getUserSubscribeTrafficLogs();
}
/// 获取本周的流量日志
Future<void> kr_getUserSubscribeTrafficLogs() async {
if (!KRAppRunData.getInstance().kr_isLogin.value) {
return;
}
// ⚠️ 已废弃:新版本后端不再提供 kr_getUserInfo 接口
// 用户ID 现在从其他途径获取,此处注释掉
// final either0 = await KRUserApi().kr_getUserInfo();
// either0.fold(
// (error) => KRCommonUtil.kr_showToast(error.msg),
// (userInfo) {
// // kr_homeController.kr_userId.value = userInfo.id.toString();
// },
// );
// 获取本周的开始和结束时间戳
final DateTime now = DateTime.now();
final DateTime weekStart = now.subtract(Duration(days: now.weekday - 1));
final DateTime weekStartDate = DateTime(weekStart.year, weekStart.month, weekStart.day);
final DateTime weekEndDate = weekStartDate.add(const Duration(days: 7));
final int startTimestamp = weekStartDate.millisecondsSinceEpoch ~/ 1000;
final int endTimestamp = weekEndDate.millisecondsSinceEpoch ~/ 1000;
// 更新时间戳
kr_startTime.value = startTimestamp;
kr_endTime.value = endTimestamp;
final either = await KRUserApi().kr_getUserOnlineTimeStatistics();
either.fold(
(error) => KRCommonUtil.kr_showToast(error.msg),
(trafficLogs) {
// 处理流量日志数据
_kr_processTrafficLogs(trafficLogs);
},
);
}
/// 处理流量日志数据
void _kr_processTrafficLogs(KRUserOnlineDurationResponse trafficLogs) {
try {
// 更新连续记录数据
kr_currentStreak.value = trafficLogs.connectionRecords.currentContinuousDays;
kr_highestStreak.value = trafficLogs.connectionRecords.historyContinuousDays;
// 将小时转换为天数并向上取整
kr_longestConnection.value = (trafficLogs.connectionRecords.longestSingleConnection / 24).ceil();
// 更新每周数据
final List<double> weeklyHours = List.filled(7, 0.0);
for (final stat in trafficLogs.weeklyStats) {
if (stat.day >= 1 && stat.day <= 7) {
weeklyHours[stat.day - 1] = stat.hours;
}
}
kr_weeklyData.value = weeklyHours;
} catch (e) {
KRCommonUtil.kr_showToast('statistics.processTrafficFailed'.tr);
KRLogUtil.kr_e('处理流量日志数据失败: $e', tag: 'Statistics');
}
}
void _kr_updateVpnStatus(String value) {
kr_vpnStatus.value = value;
}
void _kr_updateIpAddress(String value) {
kr_ipAddress.value = value.isEmpty ? '0.0.0.0' : value;
}
void _kr_updateConnectionTime(String value) {
kr_connectTime.value = value.isEmpty ? '00:00:00' : value;
}
void _kr_updateProtocol(String value) {
kr_protocol.value = value.isEmpty ? 'UDP' : value;
}
/// 更新统计数据
void _kr_updateStatistics() {
// 不再需要本地更新统计数据,完全依赖接口返回
}
@override
void onReady() {
super.onReady();
}
@override
void onClose() {
refreshController.dispose();
super.onClose();
}
/// 获取根据当前日期调整后的星期标题数组
List<String> kr_getAdjustedWeekTitles() {
final DateTime now = DateTime.now();
final int currentWeekday = now.weekday; // 1-71代表周一,7代表周日
final List<String> weekTitles = [
AppTranslations.kr_statistics.monday,
AppTranslations.kr_statistics.tuesday,
AppTranslations.kr_statistics.wednesday,
AppTranslations.kr_statistics.thursday,
AppTranslations.kr_statistics.friday,
AppTranslations.kr_statistics.saturday,
AppTranslations.kr_statistics.sunday
];
// 重新排序数组,使当前日期对应的星期显示在最后
final List<String> adjustedTitles = [
...weekTitles.sublist(currentWeekday),
...weekTitles.sublist(0, currentWeekday)
];
return adjustedTitles;
}
// 刷新数据
Future<void> kr_onRefresh() async {
if (!KRAppRunData.getInstance().kr_isLogin.value) {
refreshController.finishRefresh();
return;
}
try {
await kr_getUserSubscribeTrafficLogs();
} finally {
refreshController.finishRefresh();
}
}
}
@@ -1,389 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart';
import '../controllers/kr_statistics_controller.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:kaer_with_panels/app/localization/app_translations.dart';
import 'package:easy_refresh/easy_refresh.dart';
class KRStatisticsView extends GetView<KRStatisticsController> {
const KRStatisticsView({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
extendBodyBehindAppBar: true,
backgroundColor: Theme.of(context).primaryColor ,
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
title: Align(
alignment: Alignment.centerLeft,
child: Text(
AppTranslations.kr_statistics.title,
style: KrAppTextStyle(
color: Theme.of(context).textTheme.bodyMedium?.color,
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
),
),
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color.fromRGBO(23, 151, 255, 0.15), // 渐变开始颜色
Color.fromRGBO(23, 151, 255, 0.05), // 中间过渡颜色
// 非渐变色区域
],
stops: [0.0, 0.28], // 调整渐变结束位置
),
),
child: EasyRefresh(
controller: controller.refreshController,
onRefresh: controller.kr_onRefresh,
header: DeliveryHeader(
triggerOffset: 50.0,
springRebound: true,
),
child: SingleChildScrollView(
padding: EdgeInsets.zero,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// SizedBox(height: kToolbarHeight + 20.w),
_kr_buildStatusGrid(context),
_kr_buildWeeklyChart(context),
_kr_buildConnectionRecords(context),
],
),
),
),
),
);
}
// 构建状态网格
Widget _kr_buildStatusGrid(BuildContext context) {
// 根据平台调整卡片高度比例 - 优化桌面版本高度
final double aspectRatio = GetPlatform.isDesktop ? 165 / 38 : 165 / 82;
return Container(
padding: EdgeInsets.all(16.r),
child: GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 2,
mainAxisSpacing: 12.h,
crossAxisSpacing: 12.w,
childAspectRatio: aspectRatio, // 使用优化后的高度比例
children: [
Obx(() => _kr_buildStatusCard(
context,
AppTranslations.kr_statistics.vpnStatus,
controller.kr_vpnStatus.value,
Icons.link,
isError: true,
vpnStatusColor: controller.kr_vpnStatus.value == '已连接'
? const Color(0xFF67C23A)
: controller.kr_vpnStatus.value == '连接中...'
? const Color(0xFFE6A23C)
: const Color(0xFFF56C6C),
)),
Obx(() => _kr_buildStatusCard(
context,
AppTranslations.kr_statistics.ipAddress,
controller.kr_ipAddress.value,
Icons.language,
)),
Obx(() => _kr_buildStatusCard(
context,
AppTranslations.kr_statistics.connectionTime,
controller.kr_connectTime.value,
Icons.access_time,
)),
Obx(() => _kr_buildStatusCard(
context,
AppTranslations.kr_statistics.protocol,
controller.kr_protocol.value,
Icons.description_outlined,
)),
],
),
);
}
// 构建状态卡片
Widget _kr_buildStatusCard(BuildContext context, String title, String value, IconData icon, {bool isError = false, Color? vpnStatusColor}) {
return Container(
padding: EdgeInsets.all(16.r),
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.circular(12.r),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
icon,
color: vpnStatusColor ?? (isError ? Colors.red : Colors.blue),
size: 20.w,
),
SizedBox(width: 8.w),
Expanded(
child: FittedBox(
fit: BoxFit.scaleDown,
alignment: Alignment.centerLeft,
child: Text(
title,
style: KrAppTextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Theme.of(context).textTheme.bodyMedium?.color,
),
),
),
),
],
),
SizedBox(height: 8.h),
Text(
value,
style: KrAppTextStyle(
fontSize: 14,
color: vpnStatusColor ?? (isError ? Colors.red : Theme.of(context).textTheme.bodySmall?.color),
),
),
],
),
);
}
// 构建每周图表
Widget _kr_buildWeeklyChart(BuildContext context) {
return Container(
margin: EdgeInsets.all(16.r),
padding: EdgeInsets.fromLTRB(0, 16.r, 16.r, 16.r),
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.circular(12.r),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.only(left: 16.r),
child: Text(
AppTranslations.kr_statistics.weeklyProtectionTime,
style: KrAppTextStyle(
fontSize: 14,
color: Theme.of(context).textTheme.bodyMedium?.color,
fontWeight: FontWeight.w500,
),
),
),
SizedBox(height: 16.h),
SizedBox(
height: 200.h,
child: Obx(() => LineChart(
LineChartData(
gridData: FlGridData(show: false),
titlesData: FlTitlesData(
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 40,
interval: 5,
getTitlesWidget: (value, meta) {
return Padding(
padding: EdgeInsets.only(left: 16.w),
child: Text(
value.toInt().toString(),
style: KrAppTextStyle(
color: Theme.of(context).textTheme.bodySmall?.color,
fontSize: 12,
),
),
);
},
),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
interval: 1,
reservedSize: 30,
getTitlesWidget: (value, meta) {
final titles = controller.kr_getAdjustedWeekTitles();
int index = value.toInt();
if (index >= 0 && index < titles.length) {
return Text(
titles[index],
style: KrAppTextStyle(
color: Theme.of(context).textTheme.bodySmall?.color,
fontSize: 10,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
);
}
return const Text('');
},
),
),
rightTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
topTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
),
borderData: FlBorderData(show: false),
minX: 0,
maxX: 6,
minY: 0,
maxY: 20,
lineBarsData: [
LineChartBarData(
spots: controller.kr_weeklyData.asMap().entries.map((e) {
return FlSpot(e.key.toDouble(), e.value);
}).toList(),
isCurved: true,
color: Colors.blue,
barWidth: 2,
isStrokeCapRound: true,
dotData: FlDotData(show: false),
belowBarData: BarAreaData(
show: true,
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.blue.withOpacity(0.2),
Colors.blue.withOpacity(0.05),
],
),
),
),
],
),
)),
),
],
),
);
}
// 构建连接记录
Widget _kr_buildConnectionRecords(BuildContext context) {
return Container(
margin: EdgeInsets.all(16.r),
child: Column(
children: [
Row(
children: [
Expanded(
child: Obx(() => _kr_buildRecordCard(context, AppTranslations.kr_statistics.currentStreak, AppTranslations.kr_statistics.days(controller.kr_currentStreak.value))),
),
SizedBox(width: 16.w),
Expanded(
child: Obx(() => _kr_buildRecordCard(context, AppTranslations.kr_statistics.highestStreak, AppTranslations.kr_statistics.days(controller.kr_highestStreak.value))),
),
],
),
SizedBox(height: 16.h),
Obx(() => _kr_buildLongestConnection(context)),
],
),
);
}
// 构建记录卡片
Widget _kr_buildRecordCard(BuildContext context, String title, String value) {
return Container(
padding: EdgeInsets.all(16.r),
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.circular(12.r),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: KrAppTextStyle(
fontSize: 12,
color: Theme.of(context).textTheme.bodySmall?.color,
),
),
SizedBox(height: 8.h),
Text(
value,
style: KrAppTextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Theme.of(context).textTheme.bodyMedium?.color,
),
),
],
),
);
}
// 构建最长连接时间
Widget _kr_buildLongestConnection(BuildContext context) {
return Container(
padding: EdgeInsets.all(16.r),
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.circular(12.r),
),
child: Row(
children: [
Container(
width: 40.w,
height: 40.w,
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(
Icons.access_time,
color: Colors.blue,
size: 24.w,
),
),
SizedBox(width: 16.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppTranslations.kr_statistics.longestConnection,
style: KrAppTextStyle(
fontSize: 12,
color: Theme.of(context).textTheme.bodySmall?.color,
),
),
Text(
AppTranslations.kr_statistics.days(controller.kr_longestConnection.value),
style: KrAppTextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Theme.of(context).textTheme.bodyMedium?.color,
),
),
],
),
),
],
),
);
}
}
@@ -1,12 +0,0 @@
import 'package:get/get.dart';
import '../controllers/kr_user_info_controller.dart';
class KRUserInfoBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<KRUserInfoController>(
() => KRUserInfoController(),
);
}
}
@@ -1,253 +0,0 @@
import 'package:get/get.dart';
import 'package:kaer_with_panels/app/localization/app_translations.dart';
import 'package:kaer_with_panels/app/services/singbox_imp/kr_sing_box_imp.dart';
import 'package:kaer_with_panels/app/mixins/kr_app_bar_opacity_mixin.dart';
import '../../../common/app_config.dart';
import '../../../common/app_run_data.dart';
import '../../../services/api_service/kr_api.user.dart';
import '../../../utils/kr_common_util.dart';
import '../../../utils/kr_event_bus.dart';
import '../../../utils/kr_log_util.dart';
/// 网格项类型枚举
/// 用于区分不同的功能入口类型
enum KRGridItemType {
/// VPN官网入口
vpnWebsite,
/// 推特社交入口
telegram,
/// 邮箱联系入口
mail,
/// 电话联系入口
phone,
/// 人工客服支持入口
customerService,
/// 填写工单入口
workOrder,
}
/// 网格项数据模型
/// 用于统一管理功能入口的展示数据
class KRGridItem {
/// 功能图标
final String icon;
/// 功能标题
final String title;
/// 功能描述
final String subtitle;
/// 功能类型
final KRGridItemType type;
const KRGridItem({
required this.icon,
required this.title,
required this.subtitle,
required this.type,
});
}
/// 用户信息页面的控制器
/// 负责管理用户信息页面的状态和业务逻辑
class KRUserInfoController extends GetxController with KRAppBarOpacityMixin {
/// 广告拦截开关状态
/// true: 开启拦截, false: 关闭拦截
final RxBool kr_isAdBlockEnabled = true.obs;
/// NDS解锁开关状态
/// true: 已解锁, false: 未解锁
final RxBool kr_isNDSUnlockEnabled = true.obs;
/// 订阅状态
/// true: 有效订阅, false: 无效订阅
final RxBool kr_hasValidSubscription = false.obs;
/// 是否显示绑定提示
final RxBool kr_showBindingTip = false.obs;
/// 用户余额
RxDouble kr_balance = 0.0.obs;
/// 功能入口网格项列表
/// 包含所有可用的功能入口配置
final kr_gridItems = <KRGridItem>[
KRGridItem(
icon: "my_net_index",
title: AppTranslations.kr_userInfo.vpnWebsite,
subtitle: AppConfig.getInstance().kr_official_website,
type: KRGridItemType.vpnWebsite,
),
KRGridItem(
icon: "my_telegram",
title: AppTranslations.kr_userInfo.telegram,
subtitle: "telegram",
type: KRGridItemType.telegram,
),
KRGridItem(
icon: "my_email",
title: AppTranslations.kr_userInfo.mail,
subtitle: AppConfig.getInstance().kr_official_email,
type: KRGridItemType.mail,
),
KRGridItem(
icon: "my_phone",
title: AppTranslations.kr_userInfo.phone,
subtitle: AppConfig.getInstance().kr_official_telephone,
type: KRGridItemType.phone,
),
KRGridItem(
icon: "my_kf",
title: AppTranslations.kr_userInfo.customerService,
subtitle: "",
type: KRGridItemType.customerService,
),
KRGridItem(
icon: "my_kf_msg",
title: AppTranslations.kr_userInfo.workOrder,
subtitle: "",
type: KRGridItemType.workOrder,
),
].obs;
@override
void onInit() {
super.onInit();
kr_initData();
}
/// 页面进入时的处理
void kr_onPageEnter() {
KRLogUtil.kr_i('进入用户信息页面', tag: 'UserInfo');
_loadUserInfo();
}
@override
void onReady() {
super.onReady();
// 每次进入页面时执行
KRLogUtil.kr_i('进入用户信息页面', tag: 'UserInfo');
// 刷新用户信息
_loadUserInfo();
}
@override
void onClose() {
super.onClose();
}
/// 初始化控制器数据
/// 从服务器获取用户配置信息
void kr_initData() {
ever(KRAppRunData.getInstance().kr_isLogin, (bool isLogin) {
if (isLogin) {
_loadUserInfo();
} else {
kr_balance.value = 0.0;
}
});
ever(KRSingBoxImp().kr_blockAds, (bool bl) {
kr_isAdBlockEnabled.value = bl;
});
kr_isAdBlockEnabled.value = KRSingBoxImp().kr_blockAds.value;
// 监听所有支付相关消息
KREventBus().kr_listenMessages(
[KRMessageType.kr_payment, KRMessageType.kr_subscribe_update],
_kr_handleMessage,
);
}
/// 处理消息
void _kr_handleMessage(KRMessageData message) {
switch (message.kr_type) {
case KRMessageType.kr_payment:
_loadUserInfo();
break;
case KRMessageType.kr_subscribe_update:
break;
// TODO: Handle this case.
}
}
/// 处理广告拦截开关状态变化
/// [value] 新的开关状态
void kr_toggleAdBlock(bool value) {
kr_isAdBlockEnabled.value = value;
KRSingBoxImp().kr_updateAdBlockEnabled(value);
}
/// 处理NDS解锁开关状态变化
/// [value] 新的开关状态
void kr_toggleNDSUnlock(bool value) {
kr_isNDSUnlockEnabled.value = value;
// TODO: 实现保存设置到服务器的逻辑
}
/// 初始化用户信息
Future<void> _loadUserInfo() async {
if (!KRAppRunData.getInstance().kr_isLogin.value) {
return;
}
// ⚠️ 已废弃:新版本后端不再提供 kr_getUserInfo 接口
// 余额现在使用 AppConfig 中的固定值,等待新接口实现
// final either0 = await KRUserApi().kr_getUserInfo();
// either0.fold(
// (error) {
// KRLogUtil.kr_e(error.msg, tag: 'AppRunData');
// },
// (userInfo) async {
// kr_balance.value = userInfo.balance.toDouble() / 100;
// },
// );
// 使用 AppConfig 中的固定余额
kr_balance.value = KRAppRunData().kr_balance.toDouble() / 100;
}
/// 处理用户退出登录
/// 清理用户数据并返回登录页面
void kr_handleLogout() {
KRAppRunData.getInstance().kr_loginOut();
}
/// 重置流量使用量
/// 调用服务器API重置用户的流量使用量
Future<void> kr_resetTraffic() async {
try {
// TODO: 调用服务器API重置流量
KRCommonUtil.kr_showToast(
AppTranslations.kr_userInfo.resetTrafficSuccess);
} catch (e) {
KRCommonUtil.kr_showToast(AppTranslations.kr_userInfo.resetTrafficFailed);
}
}
String getTitle(KRGridItemType type) {
switch (type) {
case KRGridItemType.vpnWebsite:
return AppTranslations.kr_userInfo.vpnWebsite;
case KRGridItemType.telegram:
return AppTranslations.kr_userInfo.telegram;
case KRGridItemType.mail:
return AppTranslations.kr_userInfo.mail;
case KRGridItemType.phone:
return AppTranslations.kr_userInfo.phone;
case KRGridItemType.customerService:
return AppTranslations.kr_userInfo.customerService;
case KRGridItemType.workOrder:
return AppTranslations.kr_userInfo.workOrder;
}
}
}
@@ -1,973 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:kaer_with_panels/app/common/app_config.dart';
import 'package:kaer_with_panels/app/utils/kr_common_util.dart';
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:flutter/services.dart';
import 'dart:io' show Platform;
import 'package:kaer_with_panels/app/routes/app_pages.dart';
import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart';
import 'package:kaer_with_panels/app/widgets/kr_local_image.dart';
import 'package:kaer_with_panels/app/modules/kr_main/controllers/kr_main_controller.dart';
import 'package:kaer_with_panels/app/modules/kr_home/widgets/kr_subscribe_selector_view.dart';
import 'package:kaer_with_panels/app/utils/kr_subscribe_navigation_util.dart';
import '../../../common/app_run_data.dart';
import '../../../model/response/kr_user_available_subscribe.dart';
import '../../../services/kr_subscribe_service.dart';
import '../../../widgets/dialogs/kr_dialog.dart';
import '../controllers/kr_user_info_controller.dart';
import 'package:kaer_with_panels/app/localization/app_translations.dart';
import 'package:kaer_with_panels/app/modules/kr_home/controllers/kr_home_controller.dart';
class KRUserInfoView extends GetView<KRUserInfoController> {
const KRUserInfoView({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
extendBodyBehindAppBar: true,
backgroundColor: Theme.of(context).primaryColor,
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color.fromRGBO(23, 151, 255, 0.15),
Color.fromRGBO(23, 151, 255, 0.05),
],
stops: [0.0, 0.28],
),
),
child: Column(
children: [
AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
title: Align(
alignment: Alignment.centerLeft,
child: Text(
AppTranslations.kr_userInfo.title,
style: KrAppTextStyle(
color: Theme.of(context).textTheme.bodyMedium?.color,
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
),
actions: [
Padding(
padding: EdgeInsets.only(right: 8.w),
child: IconButton(
icon: Icon(
Icons.settings,
color: Theme.of(context).iconTheme.color,
size: 22.w,
),
onPressed: () => Get.toNamed(Routes.KR_SETTING),
),
),
],
),
Expanded(
child: Stack(
children: [
SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_kr_buildBindingTip(context),
_kr_buildSubscriptionCard(context),
_kr_buildShortcutSection(context),
_kr_buildOtherSection(context),
_kr_buildLogoutButton(context),
SizedBox(height: 30.h),
],
),
),
],
),
),
],
),
),
);
}
// 构建绑定提示
Widget _kr_buildBindingTip(BuildContext context) {
return Obx(() {
final appRunData = KRAppRunData.getInstance();
final isLoggedIn = appRunData.kr_isLogin.value;
final isDeviceLogin = appRunData.isDeviceLogin();
// 判断显示文字和颜色
String displayText;
Color displayColor;
IconData displayIcon;
bool shouldShowLoginPrompt = false;
if (!isLoggedIn) {
// 未登录
displayText = AppTranslations.kr_userInfo.bindingTip;
displayColor = Theme.of(context).colorScheme.error;
displayIcon = Icons.info_outline;
shouldShowLoginPrompt = false;
} else if (isDeviceLogin) {
// 设备登录(游客模式)
displayText = AppTranslations.kr_userInfo.loginRegister;
displayColor = const Color(0xFF1797FF); // 使用蓝色提示可以点击
displayIcon = Icons.touch_app;
shouldShowLoginPrompt = true;
} else {
// 正常登录
displayText = "${AppTranslations.kr_userInfo.myAccount} ${appRunData.kr_account.value}";
displayColor = Theme.of(context).textTheme.bodyMedium?.color ?? Colors.black;
displayIcon = Icons.info;
shouldShowLoginPrompt = false;
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
onTap: shouldShowLoginPrompt
? () {
// 跳转到登录页面
Get.toNamed(Routes.MR_LOGIN);
}
: null,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h),
decoration: shouldShowLoginPrompt
? BoxDecoration(
color: const Color(0xFF1797FF).withOpacity(0.05),
borderRadius: BorderRadius.circular(8.w),
)
: null,
child: Row(
children: [
Icon(
displayIcon,
color: displayColor,
size: 16.w,
),
SizedBox(width: 8.w),
Expanded(
child: Text(
displayText,
style: KrAppTextStyle(
color: displayColor,
fontSize: 12,
fontWeight: shouldShowLoginPrompt ? FontWeight.w600 : FontWeight.normal,
),
),
),
if (shouldShowLoginPrompt)
Icon(
Icons.arrow_forward_ios,
size: 14.w,
color: displayColor,
),
],
),
),
),
// 余额信息或游客ID
Visibility(
visible: KRAppRunData.getInstance().kr_isLogin.value &&
AppConfig.getInstance().kr_is_daytime,
child: Padding(
padding: EdgeInsets.only(left: 16.w, bottom: 16.w),
child: Row(
children: [
Icon(
appRunData.isDeviceLogin()
? Icons.person_outline
: Icons.account_balance_wallet_outlined,
color: Theme.of(context).textTheme.bodyMedium?.color,
size: 16.w,
),
SizedBox(width: 8.w),
Text(
appRunData.isDeviceLogin()
? AppTranslations.kr_userInfo.guestId((appRunData.kr_userId.value ?? 0) + 10000)
: "${AppTranslations.kr_userInfo.balance} ${controller.kr_balance.value.toString()}",
style: KrAppTextStyle(
fontSize: 12,
color: Theme.of(context).textTheme.bodyMedium?.color,
),
),
],
),
),
),
],
);
});
}
// 构建订阅卡片
Widget _kr_buildSubscriptionCard(BuildContext context) {
return Obx(() {
final isLoggedIn = KRAppRunData.getInstance().kr_isLogin.value;
final subscribe = KRSubscribeService().kr_currentSubscribe.value;
if (isLoggedIn && subscribe != null) {
return _kr_buildValidSubscriptionCard(context, subscribe);
} else {
return _kr_buildInvalidSubscriptionCard(context, isLoggedIn);
}
});
}
// 构建有效订阅卡片
Widget _kr_buildValidSubscriptionCard(
BuildContext context, KRUserAvailableSubscribeItem subscribe) {
final bool isExpired =
DateTime.parse(subscribe.expireTime).isBefore(DateTime.now());
return Container(
margin: EdgeInsets.symmetric(horizontal: 16.w),
padding: EdgeInsets.only(
left: 16.w, right: 16.w, top: 16.w, bottom: AppConfig().kr_is_daytime == false ? 0 : 16.w),
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.circular(12.w),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.03),
blurRadius: 10.w,
offset: Offset(0, 2.w),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 名称和切换按钮
Row(
children: [
Expanded(
child: Text(
subscribe.name,
style: KrAppTextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Theme.of(context).textTheme.bodyMedium?.color,
),
),
),
GestureDetector(
onTap: () {
if (KRSubscribeService().kr_currentStatus.value ==
KRSubscribeServiceStatus.kr_loading) {
return;
}
final homeController = Get.find<KRHomeController>();
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => Dialog(
backgroundColor: Colors.transparent,
child: KRSubscribeSelectorView(
controller: homeController,
),
),
);
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration(
color: Theme.of(context).cardColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(12.w),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
AppTranslations.kr_userInfo.switchSubscription,
style: KrAppTextStyle(
fontSize: 12,
color: Theme.of(context).textTheme.bodySmall?.color,
),
),
SizedBox(width: 4.w),
Icon(
Icons.swap_horiz,
color: Theme.of(context).textTheme.bodySmall?.color,
size: 16.w,
),
],
),
),
),
],
),
SizedBox(height: 16.h),
// 过期时间
Row(
children: [
Icon(
isExpired ? Icons.warning_amber_rounded : Icons.check_circle,
color: isExpired
? Theme.of(context).colorScheme.error
: Colors.green,
size: 16.w,
),
SizedBox(width: 8.w),
Text(
"${AppTranslations.kr_userInfo.expireTime}${subscribe.expireTime}",
style: KrAppTextStyle(
fontSize: 12,
color: isExpired
? Theme.of(context).colorScheme.error
: Theme.of(context).textTheme.bodySmall?.color,
),
),
],
),
SizedBox(height: 16.h),
// 流量进度条
_kr_buildTrafficProgress(context, subscribe),
SizedBox(height: 16.h),
// 操作按钮
_kr_buildSubscriptionActions(context, subscribe),
],
),
);
}
// 构建流量进度条
Widget _kr_buildTrafficProgress(
BuildContext context, KRUserAvailableSubscribeItem subscribe) {
final int totalTraffic = subscribe.traffic;
final int usedTraffic = subscribe.download + subscribe.upload;
// 模拟流量超出
var progress = totalTraffic > 0 ? usedTraffic / totalTraffic.toDouble() : 0;
KRLogUtil.kr_i(
"progress: ${AppTranslations.kr_userInfo.deviceLimit.trParams({
'count': subscribe.deviceLimit.toString()
})}",
tag: "KRUserInfoView");
final bool isTrafficExceeded = progress >= 1; // 模拟流量超出
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Icon(
Icons.info_outline,
size: 16.w,
color: Theme.of(context).textTheme.bodySmall?.color,
),
SizedBox(width: 4.w),
Text(
AppTranslations.kr_userInfo.deviceLimit
.trParams({'count': subscribe.deviceLimit.toString()}),
style: KrAppTextStyle(
fontSize: 12,
color: Theme.of(context).textTheme.bodySmall?.color,
),
),
],
),
Row(
children: [
if (isTrafficExceeded) ...[
Icon(
Icons.warning_amber_rounded,
size: 14.w,
color: Theme.of(context).colorScheme.error,
),
SizedBox(width: 4.w),
],
LayoutBuilder(
builder: (context, constraints) {
final text = totalTraffic == 0
? AppTranslations.kr_userInfo.trafficProgressUnlimited
: isTrafficExceeded
? KRCommonUtil.kr_formatBytes(usedTraffic)
: "${KRCommonUtil.kr_formatBytes(usedTraffic)} / ${KRCommonUtil.kr_formatBytes(totalTraffic)}";
// 根据文本长度和可用宽度计算合适的字体大小
final baseFontSize = 12.0;
final textLength = text.length;
final availableWidth = constraints.maxWidth;
final calculatedFontSize =
(availableWidth / (textLength * 0.8))
.clamp(8.0, baseFontSize);
return Text(
text,
style: KrAppTextStyle(
fontSize: calculatedFontSize,
color: isTrafficExceeded
? Theme.of(context).colorScheme.error
: Theme.of(context).textTheme.bodySmall?.color,
),
);
},
),
if (isTrafficExceeded) ...[
SizedBox(width: 8.w),
GestureDetector(
onTap: () {
final currentExpireTime =
DateTime.parse(subscribe.expireTime);
final newExpireTime =
currentExpireTime.subtract(const Duration(days: 30));
KRDialog.show(
title: AppTranslations.kr_userInfo.resetTrafficTitle,
message:
AppTranslations.kr_userInfo.resetTrafficMessage(
currentExpireTime.toString().split(' ')[0],
newExpireTime.toString().split(' ')[0],
),
cancelText: AppTranslations.kr_dialog.kr_cancel,
confirmText: AppTranslations.kr_dialog.kr_confirm,
onCancel: () => Get.back(),
onConfirm: () =>
KRSubscribeService().kr_resetSubscribePeriod(),
);
},
child: Container(
padding:
EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.error
.withOpacity(0.1),
borderRadius: BorderRadius.circular(12.w),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.refresh,
size: 14.w,
color: Theme.of(context).colorScheme.error,
),
SizedBox(width: 4.w),
Text(
AppTranslations.kr_userInfo.reset.tr,
style: KrAppTextStyle(
fontSize: 12,
color: Theme.of(context).colorScheme.error,
),
),
],
),
),
),
],
],
),
],
),
if (totalTraffic > 0) ...[
SizedBox(height: 6.h),
Stack(
children: [
Container(
height: 6.w,
decoration: BoxDecoration(
color: Theme.of(context).dividerColor.withOpacity(0.2),
borderRadius: BorderRadius.circular(3.w),
),
),
Container(
height: 6.w,
width: MediaQuery.of(context).size.width * progress,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: progress > 0.8
? [
const Color(0xFFFF6B6B), // 浅红色
const Color(0xFFFF4757), // 深红色
]
: [
const Color(0xFF00C6FF), // 亮蓝色
const Color(0xFF0072FF), // 深蓝色
],
),
borderRadius: BorderRadius.circular(3.w),
boxShadow: [
BoxShadow(
color: (progress > 0.8
? const Color(0xFFFF4757)
: const Color(0xFF0072FF))
.withOpacity(0.3),
blurRadius: 4.w,
offset: Offset(0, 2.w),
),
],
),
),
],
),
],
],
);
}
// 构建订阅操作按钮
Widget _kr_buildSubscriptionActions(
BuildContext context, KRUserAvailableSubscribeItem subscribe) {
if (!AppConfig.getInstance().kr_is_daytime) {
return SizedBox();
}
return Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () => KRSubscribeNavigationUtil.navigateToPurchase(tag: 'UserInfo'),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF1797FF),
foregroundColor: Colors.white,
elevation: 0,
padding: EdgeInsets.symmetric(vertical: 12.h),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.w),
),
),
child: Text(
AppTranslations.kr_userInfo.subscribeNow,
style: KrAppTextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white,
),
),
),
),
],
);
}
// 构建无效订阅卡片
Widget _kr_buildInvalidSubscriptionCard(
BuildContext context, bool isLoggedIn) {
return Container(
margin: EdgeInsets.symmetric(horizontal: 16.w),
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.circular(12.w),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.03),
blurRadius: 10.w,
offset: Offset(0, 2.w),
),
],
),
child: Row(
children: [
Icon(
Icons.warning_amber_rounded,
color: Theme.of(context).colorScheme.error,
size: 16.w,
),
SizedBox(width: 8.w),
Expanded(
child: Text(
!isLoggedIn
? AppTranslations.kr_userInfo.pleaseLogin
: AppTranslations.kr_userInfo.noValidSubscription,
style: KrAppTextStyle(
fontSize: 14,
color: Theme.of(context).textTheme.bodyMedium?.color,
),
),
),
SizedBox(width: 12.w),
ElevatedButton(
onPressed: !isLoggedIn
? () => Get.find<KRMainController>().kr_setPage(0)
: () => KRSubscribeNavigationUtil.navigateToPurchase(tag: 'UserInfo'),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF1797FF),
foregroundColor: Colors.white,
elevation: 0,
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 8.h),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.w),
),
),
child: Text(
!isLoggedIn
? AppTranslations.kr_userInfo.loginNow
: AppTranslations.kr_userInfo.subscribeNow,
style: KrAppTextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white,
),
),
),
],
),
);
}
// 构建快捷键区域
Widget _kr_buildShortcutSection(BuildContext context) {
return Obx(() {
final appRunData = KRAppRunData.getInstance();
final isLoggedIn = appRunData.kr_isLogin.value;
final isDeviceLogin = appRunData.isDeviceLogin();
// 只有正常登录用户(非游客)才显示设备管理
final showDeviceManagement = isLoggedIn && !isDeviceLogin;
return Container(
margin: EdgeInsets.fromLTRB(16.w, 24.w, 16.w, 16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppTranslations.kr_userInfo.shortcuts,
style: KrAppTextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Theme.of(context).textTheme.bodyMedium?.color,
),
),
SizedBox(height: 12.h),
Column(
children: [
_kr_buildShortcutContainer(
icon: "my_ads",
title: AppTranslations.kr_userInfo.adBlock,
value: controller.kr_isAdBlockEnabled,
onChanged: controller.kr_toggleAdBlock,
context: context,
),
_kr_buildShortcutContainer(
icon: "my_dns",
title: AppTranslations.kr_userInfo.ndsUnlock,
value: controller.kr_isNDSUnlockEnabled,
onChanged: controller.kr_toggleNDSUnlock,
context: context,
),
if (showDeviceManagement)
_kr_buildShortcutContainer(
icon: "my_dns",
title: AppTranslations.kr_userInfo.deviceManagement,
onTap: () {
Get.toNamed(Routes.KR_DEVICE_MANAGEMENT);
},
context: context,
),
_kr_buildShortcutContainer(
icon: "my_cn_us",
title: AppTranslations.kr_userInfo.contactUs,
onTap: () {
Get.toNamed(Routes.KR_CRISP);
},
context: context,
),
],
),
],
),
);
});
}
// 构建快捷键容器
Widget _kr_buildShortcutContainer({
required String icon,
required String title,
RxBool? value,
Function(bool)? onChanged,
VoidCallback? onTap,
required BuildContext context,
}) {
return GestureDetector(
onTap: onTap,
child: Container(
height: 62.w,
margin: EdgeInsets.symmetric(vertical: 6.h),
padding: EdgeInsets.only(left: 12.w, right: 12.w),
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.circular(12.w),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
KrLocalImage(
imageName: icon,
color: Theme.of(context).textTheme.bodyMedium?.color,
width: 40.w,
height: 40.w,
),
SizedBox(width: 12.w),
Expanded(
child: Text(
title,
style: KrAppTextStyle(
fontSize: 14,
color: Theme.of(context).textTheme.bodyMedium?.color,
fontWeight: FontWeight.w500,
),
),
),
if (value != null)
Obx(
() => CupertinoSwitch(
value: value.value,
onChanged: onChanged,
activeColor: Colors.blue,
),
)
else
Icon(
Icons.arrow_forward_ios,
color: Theme.of(context).textTheme.bodySmall?.color,
size: 16.w,
),
],
),
),
);
}
// 构建其他区域
Widget _kr_buildOtherSection(BuildContext context) {
return Container(
margin: EdgeInsets.symmetric(horizontal: 16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppTranslations.kr_userInfo.others,
style: KrAppTextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Theme.of(context).textTheme.bodyMedium?.color,
),
),
SizedBox(height: 12.h),
Obx(() => Wrap(
spacing: 12.w,
runSpacing: 12.w,
children: List.generate(
controller.kr_gridItems.length,
(index) => SizedBox(
width: (MediaQuery.of(context).size.width - 44.w) /
2, // 44.w = 左右margin(32.w) + 中间间距(12.w)
child: _kr_buildGridItem(
controller.kr_gridItems[index], index, context),
),
),
)),
],
),
);
}
// 构建网格项
Widget _kr_buildGridItem(KRGridItem item, int index, BuildContext context) {
return InkWell(
onTap: () => _kr_handleGridItemTap(item.type),
child: Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.circular(12.w),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
KrLocalImage(
imageName: item.icon,
width: 24.w,
height: 24.w,
),
KrLocalImage(
imageName: "my_et",
color: Theme.of(context).textTheme.bodySmall?.color,
width: 16.w,
height: 16.w,
),
],
),
SizedBox(height: 8.h),
Text(
controller.getTitle(item.type),
style: KrAppTextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Theme.of(context).textTheme.bodyMedium?.color,
),
overflow: TextOverflow.ellipsis,
maxLines: 2,
),
SizedBox(height: 4.h),
Text(
item.subtitle,
style: KrAppTextStyle(
fontSize: 12,
color: Theme.of(context).textTheme.bodySmall?.color,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
],
),
),
);
}
// 构建退出登录按钮
Widget _kr_buildLogoutButton(BuildContext context) {
return Obx(() {
final appRunData = KRAppRunData.getInstance();
// 只有账号密码登录才显示退出按钮,设备登录(游客模式)不显示
final isAccountLogin = appRunData.kr_isLogin.value && !appRunData.isDeviceLogin();
return Visibility(
visible: isAccountLogin,
child: Container(
width: double.infinity,
margin: EdgeInsets.all(16.w),
child: TextButton(
onPressed: () {
KRDialog.show(
title: AppTranslations.kr_userInfo.logoutConfirmTitle,
message: AppTranslations.kr_userInfo.logoutConfirmMessage,
cancelText: AppTranslations.kr_userInfo.logoutCancel,
onCancel: () => Get.back(),
onConfirm: () => controller.kr_handleLogout(),
);
},
style: TextButton.styleFrom(
backgroundColor: Theme.of(context).cardColor,
padding: EdgeInsets.symmetric(vertical: 12.h),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.w),
),
),
child: Text(
AppTranslations.kr_userInfo.logout,
style: KrAppTextStyle(
fontSize: 14,
color: Theme.of(context).textTheme.bodySmall?.color,
),
),
),
),
);
});
}
// 处理网格项点击
Future<void> _kr_handleGridItemTap(KRGridItemType type) async {
switch (type) {
case KRGridItemType.vpnWebsite:
final Uri url = Uri.parse(AppConfig.getInstance().kr_official_website);
if (await canLaunchUrl(url)) {
await launchUrl(url, mode: LaunchMode.externalApplication);
}
break;
case KRGridItemType.telegram:
final String tgUrl = AppConfig.getInstance().kr_official_telegram;
final String inviteCode = tgUrl.split('/').last.replaceAll('+', '');
if (Platform.isAndroid || Platform.isIOS) {
// 尝试多种 URL Scheme
final List<String> schemes = [
'tg://join?invite=$inviteCode', // Android 主要格式
'telegram://join?invite=$inviteCode', // iOS 可能使用的格式
];
bool launched = false;
for (String scheme in schemes) {
try {
final Uri tgAppUrl = Uri.parse(scheme);
if (await canLaunchUrl(tgAppUrl)) {
await launchUrl(tgAppUrl);
launched = true;
break;
}
} catch (e) {
continue;
}
}
if (!launched) {
KRCommonUtil.kr_showToast("common.tryBrowser".tr);
// 降级使用浏览器打开
try {
final Uri webUrl = Uri.parse(tgUrl);
if (await canLaunchUrl(webUrl)) {
await launchUrl(webUrl, mode: LaunchMode.externalApplication);
} else {
KRCommonUtil.kr_showToast("common.cannotOpenBrowser".tr);
}
} catch (e) {
KRCommonUtil.kr_showToast("common.openLinkFailed".tr);
}
}
} else {
// 桌面端处理
try {
final Uri webUrl = Uri.parse(tgUrl);
if (await canLaunchUrl(webUrl)) {
await launchUrl(webUrl);
} else {
KRCommonUtil.kr_showToast("common.cannotOpenTelegram".tr);
}
} catch (e) {
KRCommonUtil.kr_showToast("common.openLinkFailed".tr);
}
}
break;
case KRGridItemType.mail:
final String email = AppConfig.getInstance().kr_official_email;
await Clipboard.setData(ClipboardData(text: email));
KRCommonUtil.kr_showToast(AppTranslations.kr_userInfo.copySuccess);
break;
case KRGridItemType.phone:
final String phone = AppConfig.getInstance().kr_official_telephone;
if (Platform.isAndroid || Platform.isIOS) {
final Uri phoneUri = Uri.parse('tel:$phone');
if (await canLaunchUrl(phoneUri)) {
await launchUrl(phoneUri);
} else {
KRCommonUtil.kr_showToast(AppTranslations.kr_userInfo.notAvailable);
}
} else {
await Clipboard.setData(ClipboardData(text: phone));
KRCommonUtil.kr_showToast(AppTranslations.kr_userInfo.copySuccess);
}
break;
case KRGridItemType.customerService:
Get.toNamed(Routes.KR_CRISP);
break;
case KRGridItemType.workOrder:
Get.toNamed(Routes.KR_CRISP);
break;
}
}
}
-24
View File
@@ -22,18 +22,12 @@ import '../modules/kr_invite/bindings/kr_invite_binding.dart';
import '../modules/kr_invite/views/kr_invite_view.dart';
import '../modules/kr_login/bindings/kr_login_binding.dart';
import '../modules/kr_login/views/kr_login_view.dart';
import '../modules/kr_main/bindings/kr_main_binding.dart';
import '../modules/kr_main/views/kr_main_view.dart';
import '../modules/kr_message/bindings/kr_message_binding.dart';
import '../modules/kr_message/views/kr_message_view.dart';
import '../modules/kr_purchase_membership/bindings/kr_purchase_membership_binding.dart';
import '../modules/kr_purchase_membership/views/kr_purchase_membership_view.dart';
import '../modules/kr_setting/bindings/kr_setting_binding.dart';
import '../modules/kr_setting/views/kr_setting_view.dart';
import '../modules/kr_statistics/bindings/kr_statistics_binding.dart';
import '../modules/kr_statistics/views/kr_statistics_view.dart';
import '../modules/kr_user_info/bindings/kr_user_info_binding.dart';
import '../modules/kr_user_info/views/kr_user_info_view.dart';
import '../modules/kr_webview/bindings/kr_webview_binding.dart';
import '../modules/kr_webview/views/kr_webview_view.dart';
import '../modules/kr_order_status/bindings/kr_order_status_binding.dart';
@@ -61,12 +55,6 @@ class AppPages {
transition: Transition.fade,
transitionDuration: const Duration(milliseconds: 500),
),
GetPage(
name: _Paths.KR_MAIN,
page: () => SwipeWrapper.detect(() => const KRMainView()),
binding: KRMainBinding(),
popGesture: false,
),
GetPage(
name: _Paths.KR_HOME,
page: () => SwipeWrapper.detect(() => KRHomeView()),
@@ -102,24 +90,12 @@ class AppPages {
binding: KRSettingBinding(),
popGesture: false,
),
GetPage(
name: _Paths.KR_USER_INFO,
page: () => SwipeWrapper.detect(() => const KRUserInfoView()),
binding: KRUserInfoBinding(),
popGesture: false,
),
GetPage(
name: _Paths.KR_INVITE,
page: () => SwipeWrapper.detect(() => const KRInviteView()),
binding: KRInviteBinding(),
popGesture: false,
),
GetPage(
name: _Paths.KR_STATISTICS,
page: () => SwipeWrapper.detect(() => const KRStatisticsView()),
binding: KRStatisticsBinding(),
popGesture: false,
),
GetPage(
name: _Paths.KR_LANGUAGE_SELECTOR,
page: () => SwipeWrapper.detect(() => const KRLanguageSelectorView()),
-95
View File
@@ -1,95 +0,0 @@
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
// import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as path;
import 'dart:ui' as ui;
/// 地图瓦片缓存工具类
class KRFMTC {
static const String kr_storeName = 'kaer_map_store';
/// HTTP 客户端,用于复用连接
static late final http.Client _httpClient = http.Client();
/// 瓦片提供者实例
static late final TileProvider _tileProvider = NetworkTileProvider();
/// 判断是否在中国大陆
static bool _kr_isInMainlandChina() {
// 获取系统语言
final List<Locale> systemLocales = ui.window.locales;
final String? languageCode = systemLocales.isNotEmpty ? systemLocales.first.languageCode : null;
final String? countryCode = systemLocales.isNotEmpty ? systemLocales.first.countryCode : null;
// 获取系统时区
final String timeZoneName = DateTime.now().timeZoneName;
// 检查是否为中文语言环境
bool isChineseLanguage = languageCode == 'zh';
// 检查是否为中国地区代码
bool isChineseRegion = countryCode == 'CN';
// 检查是否为中国时区
bool isChineseTimezone = timeZoneName.contains('China') || timeZoneName == 'Asia/Shanghai';
// 如果同时满足语言和地区或时区条件,则认为在中国大陆
return (isChineseLanguage && (isChineseRegion || isChineseTimezone));
}
/// 获取地图瓦片URL
static String kr_getTileUrl() {
if (_kr_isInMainlandChina()) {
// 使用高德地图
return 'https://webst0{s}.is.autonavi.com/appmaptile?style=7&x={x}&y={y}&z={z}';
} else {
// 使用 OpenStreetMap
return 'https://tile.openstreetmap.org/{z}/{x}/{y}.png';
}
}
/// 获取地图瓦片子域名
static List<String> kr_getTileSubdomains() {
if (_kr_isInMainlandChina()) {
return ['1', '2', '3', '4'];
} else {
return ['a', 'b', 'c'];
}
}
/// 获取地图瓦片提供者
static TileProvider kr_getTileProvider() {
return _tileProvider;
}
/// 初始化地图缓存
static Future<void> kr_initMapCache() async {
// 不使用缓存,无需实现
}
/// 清理地图缓存
static Future<void> kr_clearMapCache() async {
// 不使用缓存,无需实现
}
/// 检查瓦片是否已缓存
static Future<bool> kr_isTileCached({
required TileCoordinates coords,
required TileLayer options,
}) async {
return false;
}
/// 获取缓存状态信息
static Future<({bool isAvailable, int cacheCount, String cacheSize})> kr_checkCacheStatus() async {
return (isAvailable: false, cacheCount: 0, cacheSize: '0 MB');
}
/// 释放资源
static void kr_dispose() {
_httpClient.close();
}
}