fix: 对app进行瘦身
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
+4
-6
@@ -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-7,1代表周一,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()),
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -8,7 +8,7 @@ import 'package:flutter/services.dart';
|
||||
// import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';
|
||||
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter_stripe/flutter_stripe.dart';
|
||||
// import 'package:flutter_stripe/flutter_stripe.dart';
|
||||
|
||||
import 'package:kaer_with_panels/app/themes/kr_theme_service.dart';
|
||||
import 'package:kaer_with_panels/app/localization/getx_translations.dart';
|
||||
@@ -48,11 +48,11 @@ void main() async {
|
||||
// 初始化日志系统
|
||||
KRLogUtil.kr_init();
|
||||
|
||||
Stripe.publishableKey = 'pk_live_51SbuYxAawOMH8rEEkz6f4mnxAUjGC72eQ6qdm5tT6whC4hULkxxdbiPsB4gSCIMnNIGCsIgeASTXBakUcbOuUwQO00jSWjuufx';
|
||||
Stripe.merchantIdentifier = 'merchant.com.taw.hifastvpn';
|
||||
if (Platform.isIOS) {
|
||||
await Stripe.instance.applySettings();
|
||||
}
|
||||
// Stripe.publishableKey = 'pk_live_51SbuYxAawOMH8rEEkz6f4mnxAUjGC72eQ6qdm5tT6whC4hULkxxdbiPsB4gSCIMnNIGCsIgeASTXBakUcbOuUwQO00jSWjuufx';
|
||||
// Stripe.merchantIdentifier = 'merchant.com.taw.hifastvpn';
|
||||
// if (Platform.isIOS) {
|
||||
// await Stripe.instance.applySettings();
|
||||
// }
|
||||
// 初始化 Hive
|
||||
await KRSecureStorage().kr_initHive();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user