feat: 增加侧滑事件
This commit is contained in:
parent
8e27ddeded
commit
e82480b937
@ -13,76 +13,86 @@ import 'package:kaer_with_panels/app/widgets/hi_collapsible_list.dart';
|
|||||||
import 'package:kaer_with_panels/app/widgets/hi_fixed_scrollbar.dart';
|
import 'package:kaer_with_panels/app/widgets/hi_fixed_scrollbar.dart';
|
||||||
import 'package:kaer_with_panels/app/modules/hi_menu/widgets/hi_menu_list_item.dart';
|
import 'package:kaer_with_panels/app/modules/hi_menu/widgets/hi_menu_list_item.dart';
|
||||||
import '../../../routes/app_pages.dart';
|
import '../../../routes/app_pages.dart';
|
||||||
|
import 'package:kaer_with_panels/app/widgets/swipe/has_swipe_config.dart';
|
||||||
|
import 'package:kaer_with_panels/app/widgets/swipe/swipe_config.dart';
|
||||||
|
|
||||||
class HIHelpView extends GetView<HIHelpController> {
|
class HIHelpView extends GetView<HIHelpController> implements HasSwipeConfig {
|
||||||
const HIHelpView({super.key});
|
const HIHelpView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
SwipeConfig get swipeConfig =>
|
||||||
|
SwipeConfig(enableLeft: true, onLeft: () => Get.back());
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final ScrollController scrollController = ScrollController();
|
final ScrollController scrollController = ScrollController();
|
||||||
|
|
||||||
return HIBaseScaffold(
|
return HIBaseScaffold(
|
||||||
child: Column(
|
child: Padding(
|
||||||
children: [
|
padding: const EdgeInsets.only(left: 20),
|
||||||
Expanded(
|
child: Column(
|
||||||
child: Obx(
|
children: [
|
||||||
() => EasyRefresh(
|
Expanded(
|
||||||
// controller: controller.refreshController,
|
child: Obx(
|
||||||
// onRefresh: controller.kr_onRefresh,
|
() => EasyRefresh(
|
||||||
// onLoad: controller.kr_onLoadMore,
|
// controller: controller.refreshController,
|
||||||
// header: const DeliveryHeader(
|
// onRefresh: controller.kr_onRefresh,
|
||||||
// triggerOffset: 50.0,
|
// onLoad: controller.kr_onLoadMore,
|
||||||
// springRebound: true,
|
// header: const DeliveryHeader(
|
||||||
// ),
|
// triggerOffset: 50.0,
|
||||||
// footer: const DeliveryFooter(
|
// springRebound: true,
|
||||||
// triggerOffset: 50.0,
|
// ),
|
||||||
// springRebound: true,
|
// footer: const DeliveryFooter(
|
||||||
// ),
|
// triggerOffset: 50.0,
|
||||||
onRefresh: null, // 设置为 null 来禁用下拉刷新
|
// springRebound: true,
|
||||||
onLoad: null, // 设置为 null 来禁用上拉加载
|
// ),
|
||||||
child: Padding(
|
onRefresh: null, // 设置为 null 来禁用下拉刷新
|
||||||
padding: EdgeInsets.only(right: 0.w), // 滚动条与内容间距
|
onLoad: null, // 设置为 null 来禁用上拉加载
|
||||||
child: HiFixedScrollbar(
|
child: Padding(
|
||||||
controller: scrollController,
|
padding: EdgeInsets.only(right: 0.w), // 滚动条与内容间距
|
||||||
child: ListView.builder(
|
child: HiFixedScrollbar(
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
padding: EdgeInsets.symmetric(horizontal: 40.w),
|
child: ListView.builder(
|
||||||
itemCount: controller.kr_messages.length,
|
controller: scrollController,
|
||||||
itemBuilder: (context, index) {
|
padding: EdgeInsets.only(left: 40.w - 20, right: 40.w),
|
||||||
final message = controller.kr_messages[index];
|
itemCount: controller.kr_messages.length,
|
||||||
final collapsibleItemData = HICollapsibleItem(
|
itemBuilder: (context, index) {
|
||||||
title: message.title,
|
final message = controller.kr_messages[index];
|
||||||
content: message.content,
|
final collapsibleItemData = HICollapsibleItem(
|
||||||
);
|
title: message.title,
|
||||||
return Padding(
|
content: message.content,
|
||||||
padding: EdgeInsets.only(bottom: 10.w),
|
);
|
||||||
child: HICollapsibleItemWidget(item: collapsibleItemData),
|
return Padding(
|
||||||
);
|
padding: EdgeInsets.only(bottom: 10.w),
|
||||||
},
|
child: HICollapsibleItemWidget(
|
||||||
|
item: collapsibleItemData),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
SizedBox(height: 60.w),
|
||||||
SizedBox(height: 60.w),
|
// 增加在线客服入口
|
||||||
// 增加在线客服入口
|
Padding(
|
||||||
Padding(
|
padding: EdgeInsets.symmetric(horizontal: 40.w),
|
||||||
padding: EdgeInsets.symmetric(horizontal: 40.w),
|
child: MenuListItem(
|
||||||
child: MenuListItem(
|
// 2. 创建一个 MenuItem 数据对象
|
||||||
// 2. 创建一个 MenuItem 数据对象
|
item: MenuItem(
|
||||||
item: MenuItem(
|
iconName: 'icon-5',
|
||||||
iconName: 'icon-5',
|
title: '在线客服',
|
||||||
title: '在线客服',
|
// 3. 使用 onTap 回调来处理跳转逻辑
|
||||||
// 3. 使用 onTap 回调来处理跳转逻辑
|
onTap: () {
|
||||||
onTap: () {
|
Get.toNamed(Routes.KR_CRISP);
|
||||||
Get.toNamed(Routes.KR_CRISP);
|
},
|
||||||
},
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
SizedBox(height: 30.w)
|
||||||
SizedBox(height: 30.w)
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import 'package:flutter_html/flutter_html.dart';
|
|||||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||||
import 'package:kaer_with_panels/app/widgets/hi_base_scaffold.dart';
|
import 'package:kaer_with_panels/app/widgets/hi_base_scaffold.dart';
|
||||||
import '../../../routes/app_pages.dart';
|
import '../../../routes/app_pages.dart';
|
||||||
|
import 'package:kaer_with_panels/app/widgets/swipe/has_swipe_config.dart';
|
||||||
|
import 'package:kaer_with_panels/app/widgets/swipe/swipe_config.dart';
|
||||||
import '../controllers/hi_menu_controller.dart';
|
import '../controllers/hi_menu_controller.dart';
|
||||||
import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart';
|
import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart';
|
||||||
import 'package:kaer_with_panels/app/localization/app_translations.dart';
|
import 'package:kaer_with_panels/app/localization/app_translations.dart';
|
||||||
@ -16,9 +18,15 @@ import 'package:kaer_with_panels/app/common/app_run_data.dart';
|
|||||||
import 'package:kaer_with_panels/app/modules/hi_menu/widgets/hi_menu_list_item.dart';
|
import 'package:kaer_with_panels/app/modules/hi_menu/widgets/hi_menu_list_item.dart';
|
||||||
import 'package:kaer_with_panels/app/modules/hi_menu/widgets/user_info_card.dart';
|
import 'package:kaer_with_panels/app/modules/hi_menu/widgets/user_info_card.dart';
|
||||||
|
|
||||||
class HIMenuView extends GetView<HIMenuController> {
|
class HIMenuView extends GetView<HIMenuController> implements HasSwipeConfig {
|
||||||
const HIMenuView({super.key});
|
const HIMenuView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
SwipeConfig get swipeConfig => SwipeConfig(
|
||||||
|
enableRight: true,
|
||||||
|
onRight: () => Get.offNamed(Routes.KR_HOME),
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return HIBaseScaffold(
|
return HIBaseScaffold(
|
||||||
@ -37,9 +45,13 @@ class HIMenuView extends GetView<HIMenuController> {
|
|||||||
mainAxisSize: MainAxisSize.min, // 让 Column 包裹内容
|
mainAxisSize: MainAxisSize.min, // 让 Column 包裹内容
|
||||||
children: [
|
children: [
|
||||||
Obx(() {
|
Obx(() {
|
||||||
final account = KRAppRunData.getInstance().kr_account.value;
|
final account =
|
||||||
final isDeviceLogin = account != null && account.startsWith('9000');
|
KRAppRunData.getInstance().kr_account.value;
|
||||||
final accountText = (account ==null || isDeviceLogin) ? '待绑定' : '${KRAppRunData.getInstance().kr_account.value.toString()}';
|
final isDeviceLogin =
|
||||||
|
account != null && account.startsWith('9000');
|
||||||
|
final accountText = (account == null || isDeviceLogin)
|
||||||
|
? '待绑定'
|
||||||
|
: '${KRAppRunData.getInstance().kr_account.value.toString()}';
|
||||||
return UserInfoCard(
|
return UserInfoCard(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
userId: accountText,
|
userId: accountText,
|
||||||
@ -51,7 +63,8 @@ class HIMenuView extends GetView<HIMenuController> {
|
|||||||
// ListView.separated 现在也会继承 Padding 的约束
|
// ListView.separated 现在也会继承 Padding 的约束
|
||||||
ListView.separated(
|
ListView.separated(
|
||||||
shrinkWrap: true, // 让 ListView 高度自适应内容
|
shrinkWrap: true, // 让 ListView 高度自适应内容
|
||||||
physics: const NeverScrollableScrollPhysics(), // 在 Stack 中,禁用其自身的滚动
|
physics:
|
||||||
|
const NeverScrollableScrollPhysics(), // 在 Stack 中,禁用其自身的滚动
|
||||||
itemCount: _menuItems.length,
|
itemCount: _menuItems.length,
|
||||||
// 渲染每一项
|
// 渲染每一项
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
@ -68,7 +81,7 @@ class HIMenuView extends GetView<HIMenuController> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
// 版本号信息
|
// 版本号信息
|
||||||
Obx((){
|
Obx(() {
|
||||||
// 1. 从 Obx 的回调函数中 return 一个 Widget
|
// 1. 从 Obx 的回调函数中 return 一个 Widget
|
||||||
return Positioned(
|
return Positioned(
|
||||||
bottom: 40.0,
|
bottom: 40.0,
|
||||||
@ -91,7 +104,6 @@ class HIMenuView extends GetView<HIMenuController> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const List<MenuItem> _menuItems = [
|
const List<MenuItem> _menuItems = [
|
||||||
MenuItem(
|
MenuItem(
|
||||||
iconName: 'icon-1',
|
iconName: 'icon-1',
|
||||||
@ -118,4 +130,4 @@ const List<MenuItem> _menuItems = [
|
|||||||
title: '在线客服',
|
title: '在线客服',
|
||||||
route: Routes.KR_CRISP,
|
route: Routes.KR_CRISP,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|||||||
@ -18,8 +18,22 @@ import 'kr_home_subscription_view.dart';
|
|||||||
import './hi_animated_connect_button.dart';
|
import './hi_animated_connect_button.dart';
|
||||||
import 'package:kaer_with_panels/app/services/global_overlay_service.dart';
|
import 'package:kaer_with_panels/app/services/global_overlay_service.dart';
|
||||||
|
|
||||||
class KRHomeView extends StatefulWidget {
|
import 'package:kaer_with_panels/app/widgets/swipe/has_swipe_config.dart';
|
||||||
|
import 'package:kaer_with_panels/app/widgets/swipe/swipe_config.dart';
|
||||||
|
import 'package:kaer_with_panels/app/routes/app_pages.dart';
|
||||||
|
|
||||||
|
class KRHomeView extends StatefulWidget implements HasSwipeConfig {
|
||||||
const KRHomeView({super.key});
|
const KRHomeView({super.key});
|
||||||
|
@override
|
||||||
|
SwipeConfig get swipeConfig => SwipeConfig(
|
||||||
|
enableLeft: true,
|
||||||
|
enableRight: true,
|
||||||
|
onLeft: () {
|
||||||
|
Get.toNamed(Routes.HI_MENU);
|
||||||
|
},
|
||||||
|
onRight: () =>
|
||||||
|
GlobalOverlayService.instance.triggerSubscriptionAnimation(),
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<KRHomeView> createState() => _KRHomeViewState();
|
State<KRHomeView> createState() => _KRHomeViewState();
|
||||||
|
|||||||
@ -47,7 +47,7 @@ class KRLoginView extends GetView<KRLoginController> {
|
|||||||
SizedBox(height: 20.w),
|
SizedBox(height: 20.w),
|
||||||
_buildUserInfoSection(),
|
_buildUserInfoSection(),
|
||||||
SizedBox(height: 12.w),
|
SizedBox(height: 12.w),
|
||||||
_buildContentByEntry(),
|
_buildBindEmailLayout(),
|
||||||
SizedBox(height: 100.w), // 为底部帮助按钮留出空间
|
SizedBox(height: 100.w), // 为底部帮助按钮留出空间
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@ -13,93 +13,104 @@ import 'package:kaer_with_panels/app/widgets/hi_help_entrance.dart';
|
|||||||
import 'package:kaer_with_panels/app/widgets/hi_collapsible_list.dart';
|
import 'package:kaer_with_panels/app/widgets/hi_collapsible_list.dart';
|
||||||
import 'package:kaer_with_panels/app/widgets/hi_fixed_scrollbar.dart';
|
import 'package:kaer_with_panels/app/widgets/hi_fixed_scrollbar.dart';
|
||||||
import '../../../widgets/kr_simple_loading.dart';
|
import '../../../widgets/kr_simple_loading.dart';
|
||||||
|
import 'package:kaer_with_panels/app/widgets/swipe/has_swipe_config.dart';
|
||||||
|
import 'package:kaer_with_panels/app/widgets/swipe/swipe_config.dart';
|
||||||
|
|
||||||
class KRMessageView extends GetView<KRMessageController> {
|
class KRMessageView extends GetView<KRMessageController>
|
||||||
|
implements HasSwipeConfig {
|
||||||
const KRMessageView({super.key});
|
const KRMessageView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
SwipeConfig get swipeConfig =>
|
||||||
|
SwipeConfig(enableLeft: true, onLeft: () => Get.back());
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final ScrollController scrollController = ScrollController();
|
final ScrollController scrollController = ScrollController();
|
||||||
|
|
||||||
return HIBaseScaffold(
|
return HIBaseScaffold(
|
||||||
child: Stack(
|
child: Padding(
|
||||||
children: [
|
padding: const EdgeInsets.only(left: 20),
|
||||||
// 主要内容区域
|
child: Stack(
|
||||||
Obx(() {
|
children: [
|
||||||
return Column(
|
// 主要内容区域
|
||||||
children: [
|
Obx(() {
|
||||||
Expanded(
|
return Column(
|
||||||
child: Padding(
|
children: [
|
||||||
padding: EdgeInsets.only(bottom: 90.w),
|
Expanded(
|
||||||
child: EasyRefresh(
|
child: Padding(
|
||||||
controller: controller.refreshController,
|
padding: EdgeInsets.only(bottom: 90.w),
|
||||||
onRefresh: controller.kr_onRefresh,
|
child: EasyRefresh(
|
||||||
onLoad: controller.kr_onLoadMore,
|
controller: controller.refreshController,
|
||||||
header: ClassicHeader(
|
onRefresh: controller.kr_onRefresh,
|
||||||
dragText: '下拉刷新',
|
onLoad: controller.kr_onLoadMore,
|
||||||
armedText: '释放刷新',
|
header: ClassicHeader(
|
||||||
readyText: '正在刷新...',
|
dragText: '下拉刷新',
|
||||||
processingText: '正在刷新...',
|
armedText: '释放刷新',
|
||||||
processedText: '刷新成功',
|
readyText: '正在刷新...',
|
||||||
failedText: '刷新失败',
|
processingText: '正在刷新...',
|
||||||
messageText: '最后更新于 %T',
|
processedText: '刷新成功',
|
||||||
textStyle:
|
failedText: '刷新失败',
|
||||||
TextStyle(color: Colors.white.withOpacity(0.7)),
|
messageText: '最后更新于 %T',
|
||||||
messageStyle: TextStyle(
|
textStyle:
|
||||||
color: Colors.white.withOpacity(0.5),
|
TextStyle(color: Colors.white.withOpacity(0.7)),
|
||||||
fontSize: 12.sp),
|
messageStyle: TextStyle(
|
||||||
iconTheme:
|
color: Colors.white.withOpacity(0.5),
|
||||||
IconThemeData(color: Colors.white.withOpacity(0.7)),
|
fontSize: 12.sp),
|
||||||
),
|
iconTheme: IconThemeData(
|
||||||
footer: ClassicFooter(
|
color: Colors.white.withOpacity(0.7)),
|
||||||
dragText: '上拉加载',
|
),
|
||||||
armedText: '释放加载',
|
footer: ClassicFooter(
|
||||||
readyText: '正在加载...',
|
dragText: '上拉加载',
|
||||||
processingText: '正在加载...',
|
armedText: '释放加载',
|
||||||
processedText: '加载成功',
|
readyText: '正在加载...',
|
||||||
failedText: '加载失败',
|
processingText: '正在加载...',
|
||||||
noMoreText: '没有更多数据了',
|
processedText: '加载成功',
|
||||||
messageText: '最后更新于 %T',
|
failedText: '加载失败',
|
||||||
textStyle:
|
noMoreText: '没有更多数据了',
|
||||||
TextStyle(color: Colors.white.withOpacity(0.7)),
|
messageText: '最后更新于 %T',
|
||||||
messageStyle: TextStyle(
|
textStyle:
|
||||||
color: Colors.white.withOpacity(0.5),
|
TextStyle(color: Colors.white.withOpacity(0.7)),
|
||||||
fontSize: 12.sp),
|
messageStyle: TextStyle(
|
||||||
iconTheme:
|
color: Colors.white.withOpacity(0.5),
|
||||||
IconThemeData(color: Colors.white.withOpacity(0.7)),
|
fontSize: 12.sp),
|
||||||
),
|
iconTheme: IconThemeData(
|
||||||
child: Padding(
|
color: Colors.white.withOpacity(0.7)),
|
||||||
padding: EdgeInsets.only(right: 0.w),
|
),
|
||||||
child: HiFixedScrollbar(
|
child: Padding(
|
||||||
controller: scrollController,
|
padding: EdgeInsets.only(right: 0.w),
|
||||||
child: ListView.builder(
|
child: HiFixedScrollbar(
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
padding: EdgeInsets.symmetric(horizontal: 40.w),
|
child: ListView.builder(
|
||||||
itemCount: controller.kr_messages.length,
|
controller: scrollController,
|
||||||
itemBuilder: (context, index) {
|
padding:
|
||||||
final message = controller.kr_messages[index];
|
EdgeInsets.only(left: 40.w - 20, right: 40.w),
|
||||||
final collapsibleItemData = HICollapsibleItem(
|
itemCount: controller.kr_messages.length,
|
||||||
title: message.title,
|
itemBuilder: (context, index) {
|
||||||
content: [message.content],
|
final message = controller.kr_messages[index];
|
||||||
);
|
final collapsibleItemData = HICollapsibleItem(
|
||||||
return Padding(
|
title: message.title,
|
||||||
padding: EdgeInsets.only(bottom: 10.w),
|
content: [message.content],
|
||||||
child: HICollapsibleItemWidget(
|
);
|
||||||
item: collapsibleItemData),
|
return Padding(
|
||||||
);
|
padding: EdgeInsets.only(bottom: 10.w),
|
||||||
},
|
child: HICollapsibleItemWidget(
|
||||||
|
item: collapsibleItemData),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
);
|
||||||
);
|
}),
|
||||||
}),
|
// 底部帮助入口
|
||||||
// 底部帮助入口
|
const HIHelpEntrance(),
|
||||||
const HIHelpEntrance(),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,12 +14,19 @@ import '../../../widgets/kr_network_image.dart';
|
|||||||
import 'package:kaer_with_panels/app/widgets/dialogs/kr_dialog.dart';
|
import 'package:kaer_with_panels/app/widgets/dialogs/kr_dialog.dart';
|
||||||
import 'package:kaer_with_panels/app/widgets/kr_local_image.dart';
|
import 'package:kaer_with_panels/app/widgets/kr_local_image.dart';
|
||||||
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
|
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
|
||||||
|
import 'package:kaer_with_panels/app/widgets/swipe/has_swipe_config.dart';
|
||||||
|
import 'package:kaer_with_panels/app/widgets/swipe/swipe_config.dart';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
/// 购买会员页面视图
|
/// 购买会员页面视图
|
||||||
class KRPurchaseMembershipView extends GetView<KRPurchaseMembershipController> {
|
class KRPurchaseMembershipView extends GetView<KRPurchaseMembershipController>
|
||||||
|
implements HasSwipeConfig {
|
||||||
const KRPurchaseMembershipView({super.key});
|
const KRPurchaseMembershipView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
SwipeConfig get swipeConfig =>
|
||||||
|
SwipeConfig(enableLeft: true, onLeft: () => Get.back());
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return HIBaseScaffold(
|
return HIBaseScaffold(
|
||||||
@ -27,106 +34,129 @@ class KRPurchaseMembershipView extends GetView<KRPurchaseMembershipController> {
|
|||||||
title: '套餐选择',
|
title: '套餐选择',
|
||||||
subtitle: '*所有套餐均不限流量不限速度',
|
subtitle: '*所有套餐均不限流量不限速度',
|
||||||
topContentAreaHeight: 110,
|
topContentAreaHeight: 110,
|
||||||
child: Obx(() {
|
child: Stack(
|
||||||
return Stack(
|
children: [
|
||||||
children: [
|
Positioned(
|
||||||
Padding(
|
left: 0,
|
||||||
padding: EdgeInsets.only(bottom: 80.0),
|
top: 0,
|
||||||
child: SingleChildScrollView(
|
bottom: 0,
|
||||||
child: Column(
|
width: 20,
|
||||||
children: [
|
child: const _LeftEdgeSwipeBack(),
|
||||||
/*_kr_buildAccountSection(context),*/
|
),
|
||||||
if (controller.kr_isLoading.value)
|
Padding(
|
||||||
Container(
|
padding: const EdgeInsets.only(left: 20),
|
||||||
height: MediaQuery.of(context).size.height * 0.5,
|
child: Stack(
|
||||||
child: Center(
|
children: [
|
||||||
child: KRSimpleLoading(
|
Padding(
|
||||||
color: Colors.white,
|
padding: EdgeInsets.only(bottom: 80.0),
|
||||||
size: 50.0,
|
child: Obx(() => SingleChildScrollView(
|
||||||
),
|
child: Column(
|
||||||
),
|
children: [
|
||||||
)
|
/*_kr_buildAccountSection(context),*/
|
||||||
else if (controller.kr_plans.isEmpty)
|
if (controller.kr_isLoading.value)
|
||||||
Container(
|
|
||||||
height: MediaQuery.of(context).size.height * 0.5,
|
|
||||||
child: Center(
|
|
||||||
child: Text(
|
|
||||||
AppTranslations.kr_purchaseMembership.noData,
|
|
||||||
style: KrAppTextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.bodyMedium
|
|
||||||
?.color,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
else
|
|
||||||
Container(
|
|
||||||
margin: EdgeInsets.symmetric(horizontal: 40.0),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
// 套餐选择部分
|
|
||||||
Container(
|
Container(
|
||||||
padding: EdgeInsets.all(0.0),
|
height:
|
||||||
|
MediaQuery.of(context).size.height * 0.5,
|
||||||
|
child: Center(
|
||||||
|
child: KRSimpleLoading(
|
||||||
|
color: Colors.white,
|
||||||
|
size: 50.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else if (controller.kr_plans.isEmpty)
|
||||||
|
Container(
|
||||||
|
height:
|
||||||
|
MediaQuery.of(context).size.height * 0.5,
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
AppTranslations
|
||||||
|
.kr_purchaseMembership.noData,
|
||||||
|
style: KrAppTextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyMedium
|
||||||
|
?.color,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Container(
|
||||||
|
margin: EdgeInsets.only(
|
||||||
|
left: 40.0 - 20, right: 40.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
Column(
|
// 套餐选择部分
|
||||||
// 使用 List.generate 动态创建卡片列表
|
Container(
|
||||||
children: List.generate(
|
padding: EdgeInsets.all(0.0),
|
||||||
controller.kr_getTotalOptionsCount(
|
child: Column(
|
||||||
controller.kr_plans[controller
|
crossAxisAlignment:
|
||||||
.kr_selectedPlanIndex.value]),
|
CrossAxisAlignment.start,
|
||||||
(index) {
|
children: [
|
||||||
final plan = controller.kr_plans[
|
Column(
|
||||||
controller
|
// 使用 List.generate 动态创建卡片列表
|
||||||
.kr_selectedPlanIndex.value];
|
children: List.generate(
|
||||||
final discountIndex =
|
controller.kr_getTotalOptionsCount(
|
||||||
plan.kr_discount.isEmpty
|
controller.kr_plans[controller
|
||||||
? null
|
.kr_selectedPlanIndex
|
||||||
: index;
|
.value]),
|
||||||
// 使用 Padding 来为每个卡片添加底部的间距,模拟 mainAxisSpacing
|
(index) {
|
||||||
return Padding(
|
final plan = controller
|
||||||
padding:
|
.kr_plans[
|
||||||
EdgeInsets.only(bottom: 8.0),
|
controller
|
||||||
child: SizedBox(
|
.kr_selectedPlanIndex
|
||||||
height: 130.0,
|
.value];
|
||||||
child: _kr_buildPlanOptionCard(
|
final discountIndex =
|
||||||
plan,
|
plan.kr_discount.isEmpty
|
||||||
controller
|
? null
|
||||||
.kr_selectedPlanIndex.value,
|
: index;
|
||||||
discountIndex,
|
// 使用 Padding 来为每个卡片添加底部的间距,模拟 mainAxisSpacing
|
||||||
context,
|
return Padding(
|
||||||
index,
|
padding: EdgeInsets.only(
|
||||||
),
|
bottom: 8.0),
|
||||||
|
child: SizedBox(
|
||||||
|
height: 130.0,
|
||||||
|
child:
|
||||||
|
_kr_buildPlanOptionCard(
|
||||||
|
plan,
|
||||||
|
controller
|
||||||
|
.kr_selectedPlanIndex
|
||||||
|
.value,
|
||||||
|
discountIndex,
|
||||||
|
context,
|
||||||
|
index,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
},
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
)),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
top: 160.0, // 距离顶部的距离
|
||||||
|
right: 10.0, // 固定在右侧 20.w 的位置
|
||||||
|
child: KrLocalImage(
|
||||||
|
imageName: 'purchase_slogan',
|
||||||
|
imageType: ImageType.svg,
|
||||||
),
|
),
|
||||||
)),
|
),
|
||||||
Positioned(
|
const HIHelpEntrance(isLight: false)
|
||||||
top: 160.0, // 距离顶部的距离
|
],
|
||||||
right: 10.0, // 固定在右侧 20.w 的位置
|
|
||||||
child: KrLocalImage(
|
|
||||||
imageName: 'purchase_slogan',
|
|
||||||
imageType: ImageType.svg,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const HIHelpEntrance(isLight: false)
|
),
|
||||||
],
|
],
|
||||||
);
|
),
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -328,3 +358,47 @@ class KRPurchaseMembershipView extends GetView<KRPurchaseMembershipController> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _LeftEdgeSwipeBack extends StatefulWidget {
|
||||||
|
const _LeftEdgeSwipeBack();
|
||||||
|
@override
|
||||||
|
State<_LeftEdgeSwipeBack> createState() => _LeftEdgeSwipeBackState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LeftEdgeSwipeBackState extends State<_LeftEdgeSwipeBack> {
|
||||||
|
Offset? _start;
|
||||||
|
Offset? _last;
|
||||||
|
DateTime? _startTime;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onHorizontalDragStart: (details) {
|
||||||
|
_start = details.globalPosition;
|
||||||
|
_startTime = DateTime.now();
|
||||||
|
KRLogUtil.kr_d(
|
||||||
|
'Purchase left edge dragStart x=${details.globalPosition.dx}',
|
||||||
|
tag: 'PurchaseSwipe');
|
||||||
|
},
|
||||||
|
onHorizontalDragUpdate: (details) {
|
||||||
|
_last = details.globalPosition;
|
||||||
|
},
|
||||||
|
onHorizontalDragEnd: (details) {
|
||||||
|
final durationMs = DateTime.now()
|
||||||
|
.difference(_startTime ?? DateTime.now())
|
||||||
|
.inMilliseconds;
|
||||||
|
final distance =
|
||||||
|
_last != null && _start != null ? (_last!.dx - _start!.dx) : 0.0;
|
||||||
|
KRLogUtil.kr_d(
|
||||||
|
'Purchase left edge dragEnd dur=$durationMs dist=$distance',
|
||||||
|
tag: 'PurchaseSwipe');
|
||||||
|
if (distance > 20) {
|
||||||
|
Get.back();
|
||||||
|
}
|
||||||
|
_start = null;
|
||||||
|
_last = null;
|
||||||
|
_startTime = null;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -27,9 +27,7 @@ class BaseResponse<T> {
|
|||||||
final nonce = dataMap['time'] ?? "";
|
final nonce = dataMap['time'] ?? "";
|
||||||
|
|
||||||
// 判断是否需要解密:根据站点配置的 enable_security 字段
|
// 判断是否需要解密:根据站点配置的 enable_security 字段
|
||||||
final shouldDecrypt = KRSiteConfigService().isDeviceSecurityEnabled();
|
if (cipherText.isNotEmpty && nonce.isNotEmpty) {
|
||||||
|
|
||||||
if (shouldDecrypt && cipherText.isNotEmpty && nonce.isNotEmpty) {
|
|
||||||
try {
|
try {
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
print('═══════════════════════════════════════');
|
print('═══════════════════════════════════════');
|
||||||
|
|||||||
@ -43,6 +43,7 @@ import '../modules/kr_splash/views/kr_splash_view.dart';
|
|||||||
import '../modules/kr_device_management/bindings/kr_device_management_binding.dart';
|
import '../modules/kr_device_management/bindings/kr_device_management_binding.dart';
|
||||||
import '../modules/kr_device_management/views/kr_device_management_view.dart';
|
import '../modules/kr_device_management/views/kr_device_management_view.dart';
|
||||||
import 'package:kaer_with_panels/app/routes/transitions/slide_transparent_transition.dart';
|
import 'package:kaer_with_panels/app/routes/transitions/slide_transparent_transition.dart';
|
||||||
|
import 'package:kaer_with_panels/app/widgets/swipe/swipe_wrapper.dart';
|
||||||
|
|
||||||
part 'app_routes.dart';
|
part 'app_routes.dart';
|
||||||
|
|
||||||
@ -54,20 +55,23 @@ class AppPages {
|
|||||||
static final routes = [
|
static final routes = [
|
||||||
GetPage(
|
GetPage(
|
||||||
name: Routes.KR_SPLASH,
|
name: Routes.KR_SPLASH,
|
||||||
page: () => const KRSplashView(),
|
page: () => SwipeWrapper.detect(() => const KRSplashView()),
|
||||||
binding: KRSplashBinding(),
|
binding: KRSplashBinding(),
|
||||||
|
popGesture: false,
|
||||||
transition: Transition.fade,
|
transition: Transition.fade,
|
||||||
transitionDuration: const Duration(milliseconds: 500),
|
transitionDuration: const Duration(milliseconds: 500),
|
||||||
),
|
),
|
||||||
GetPage(
|
GetPage(
|
||||||
name: _Paths.KR_MAIN,
|
name: _Paths.KR_MAIN,
|
||||||
page: () => const KRMainView(),
|
page: () => SwipeWrapper.detect(() => const KRMainView()),
|
||||||
binding: KRMainBinding(),
|
binding: KRMainBinding(),
|
||||||
|
popGesture: false,
|
||||||
),
|
),
|
||||||
GetPage(
|
GetPage(
|
||||||
name: _Paths.KR_HOME,
|
name: _Paths.KR_HOME,
|
||||||
page: () => KRHomeView(),
|
page: () => SwipeWrapper.detect(() => KRHomeView()),
|
||||||
binding: KRHomeBinding(),
|
binding: KRHomeBinding(),
|
||||||
|
popGesture: false,
|
||||||
arguments: {'showSubscriptionButton': true}, // 显示购买按钮
|
arguments: {'showSubscriptionButton': true}, // 显示购买按钮
|
||||||
customTransition: SlideOutOnlyTransition(
|
customTransition: SlideOutOnlyTransition(
|
||||||
direction: SlideDirection.leftToRight,
|
direction: SlideDirection.leftToRight,
|
||||||
@ -76,8 +80,9 @@ class AppPages {
|
|||||||
),
|
),
|
||||||
GetPage(
|
GetPage(
|
||||||
name: _Paths.HI_MENU,
|
name: _Paths.HI_MENU,
|
||||||
page: () => const HIMenuView(),
|
page: () => SwipeWrapper.detect(() => const HIMenuView()),
|
||||||
binding: HIMenuBinding(),
|
binding: HIMenuBinding(),
|
||||||
|
popGesture: false,
|
||||||
arguments: {'showSubscriptionButton': true}, // 显示购买按钮
|
arguments: {'showSubscriptionButton': true}, // 显示购买按钮
|
||||||
customTransition: ContextAwareSlideTransition(
|
customTransition: ContextAwareSlideTransition(
|
||||||
targetRoute: _Paths.HI_MENU,
|
targetRoute: _Paths.HI_MENU,
|
||||||
@ -86,90 +91,107 @@ class AppPages {
|
|||||||
),
|
),
|
||||||
GetPage(
|
GetPage(
|
||||||
name: _Paths.MR_LOGIN,
|
name: _Paths.MR_LOGIN,
|
||||||
page: () => const KRLoginView(),
|
page: () => SwipeWrapper.detect(() => const KRLoginView()),
|
||||||
binding: MrLoginBinding(),
|
binding: MrLoginBinding(),
|
||||||
|
popGesture: false,
|
||||||
arguments: {'showSubscriptionButton': true}, // 显示购买按钮
|
arguments: {'showSubscriptionButton': true}, // 显示购买按钮
|
||||||
),
|
),
|
||||||
GetPage(
|
GetPage(
|
||||||
name: _Paths.KR_SETTING,
|
name: _Paths.KR_SETTING,
|
||||||
page: () => const KRSettingView(),
|
page: () => SwipeWrapper.detect(() => const KRSettingView()),
|
||||||
binding: KRSettingBinding(),
|
binding: KRSettingBinding(),
|
||||||
|
popGesture: false,
|
||||||
),
|
),
|
||||||
GetPage(
|
GetPage(
|
||||||
name: _Paths.KR_USER_INFO,
|
name: _Paths.KR_USER_INFO,
|
||||||
page: () => const KRUserInfoView(),
|
page: () => SwipeWrapper.detect(() => const KRUserInfoView()),
|
||||||
binding: KRUserInfoBinding(),
|
binding: KRUserInfoBinding(),
|
||||||
|
popGesture: false,
|
||||||
),
|
),
|
||||||
GetPage(
|
GetPage(
|
||||||
name: _Paths.KR_INVITE,
|
name: _Paths.KR_INVITE,
|
||||||
page: () => const KRInviteView(),
|
page: () => SwipeWrapper.detect(() => const KRInviteView()),
|
||||||
binding: KRInviteBinding(),
|
binding: KRInviteBinding(),
|
||||||
|
popGesture: false,
|
||||||
),
|
),
|
||||||
GetPage(
|
GetPage(
|
||||||
name: _Paths.KR_STATISTICS,
|
name: _Paths.KR_STATISTICS,
|
||||||
page: () => const KRStatisticsView(),
|
page: () => SwipeWrapper.detect(() => const KRStatisticsView()),
|
||||||
binding: KRStatisticsBinding(),
|
binding: KRStatisticsBinding(),
|
||||||
|
popGesture: false,
|
||||||
),
|
),
|
||||||
GetPage(
|
GetPage(
|
||||||
name: _Paths.KR_LANGUAGE_SELECTOR,
|
name: _Paths.KR_LANGUAGE_SELECTOR,
|
||||||
page: () => const KRLanguageSelectorView(),
|
page: () => SwipeWrapper.detect(() => const KRLanguageSelectorView()),
|
||||||
binding: KRLanguageSelectorBinding(),
|
binding: KRLanguageSelectorBinding(),
|
||||||
|
popGesture: false,
|
||||||
),
|
),
|
||||||
GetPage(
|
GetPage(
|
||||||
name: _Paths.KR_COUNTRY_SELECTOR,
|
name: _Paths.KR_COUNTRY_SELECTOR,
|
||||||
page: () => const KRCountrySelectorView(),
|
page: () => SwipeWrapper.detect(() => const KRCountrySelectorView()),
|
||||||
binding: KRCountrySelectorBinding(),
|
binding: KRCountrySelectorBinding(),
|
||||||
|
popGesture: false,
|
||||||
),
|
),
|
||||||
GetPage(
|
GetPage(
|
||||||
name: _Paths.KR_PURCHASE_MEMBERSHIP,
|
name: _Paths.KR_PURCHASE_MEMBERSHIP,
|
||||||
page: () => const KRPurchaseMembershipView(),
|
page: () => SwipeWrapper.detect(() => const KRPurchaseMembershipView()),
|
||||||
binding: KRPurchaseMembershipBinding(),
|
binding: KRPurchaseMembershipBinding(),
|
||||||
|
popGesture: false,
|
||||||
arguments: {'showSubscriptionButton': true}, // 显示购买按钮
|
arguments: {'showSubscriptionButton': true}, // 显示购买按钮
|
||||||
),
|
),
|
||||||
GetPage(
|
GetPage(
|
||||||
name: _Paths.KR_MESSAGE,
|
name: _Paths.KR_MESSAGE,
|
||||||
page: () => const KRMessageView(),
|
page: () => SwipeWrapper.detect(() => const KRMessageView()),
|
||||||
binding: KrMessageBinding(),
|
binding: KrMessageBinding(),
|
||||||
|
popGesture: false,
|
||||||
),
|
),
|
||||||
GetPage(
|
GetPage(
|
||||||
name: _Paths.KR_DELETE_ACCOUNT,
|
name: _Paths.KR_DELETE_ACCOUNT,
|
||||||
page: () => const KRDeleteAccountView(),
|
page: () => SwipeWrapper.detect(() => const KRDeleteAccountView()),
|
||||||
binding: KrDeleteAccountBinding(),
|
binding: KrDeleteAccountBinding(),
|
||||||
|
popGesture: false,
|
||||||
),
|
),
|
||||||
GetPage(
|
GetPage(
|
||||||
name: Routes.KR_WEBVIEW,
|
name: Routes.KR_WEBVIEW,
|
||||||
page: () => const KRWebView(),
|
page: () => SwipeWrapper.detect(() => const KRWebView()),
|
||||||
binding: KRWebViewBinding(),
|
binding: KRWebViewBinding(),
|
||||||
|
popGesture: false,
|
||||||
),
|
),
|
||||||
GetPage(
|
GetPage(
|
||||||
name: Routes.KR_ORDER_STATUS,
|
name: Routes.KR_ORDER_STATUS,
|
||||||
page: () => const KROrderStatusView(),
|
page: () => SwipeWrapper.detect(() => const KROrderStatusView()),
|
||||||
binding: KROrderStatusBinding(),
|
binding: KROrderStatusBinding(),
|
||||||
|
popGesture: false,
|
||||||
),
|
),
|
||||||
GetPage(
|
GetPage(
|
||||||
name: _Paths.KR_CRISP,
|
name: _Paths.KR_CRISP,
|
||||||
page: () => const KRCrispView(),
|
page: () => SwipeWrapper.detect(() => const KRCrispView()),
|
||||||
binding: KRCrispBinding(),
|
binding: KRCrispBinding(),
|
||||||
|
popGesture: false,
|
||||||
),
|
),
|
||||||
GetPage(
|
GetPage(
|
||||||
name: _Paths.KR_DEVICE_MANAGEMENT,
|
name: _Paths.KR_DEVICE_MANAGEMENT,
|
||||||
page: () => const KRDeviceManagementView(),
|
page: () => SwipeWrapper.detect(() => const KRDeviceManagementView()),
|
||||||
binding: KRDeviceManagementBinding(),
|
binding: KRDeviceManagementBinding(),
|
||||||
|
popGesture: false,
|
||||||
),
|
),
|
||||||
GetPage(
|
GetPage(
|
||||||
name: _Paths.HI_NODE_LIST,
|
name: _Paths.HI_NODE_LIST,
|
||||||
page: () => const HINodePageView(),
|
page: () => SwipeWrapper.detect(() => const HINodePageView()),
|
||||||
binding: HiNodeListBinding(),
|
binding: HiNodeListBinding(),
|
||||||
|
popGesture: false,
|
||||||
),
|
),
|
||||||
GetPage(
|
GetPage(
|
||||||
name: _Paths.HI_HELP,
|
name: _Paths.HI_HELP,
|
||||||
page: () => const HIHelpView(),
|
page: () => SwipeWrapper.detect(() => const HIHelpView()),
|
||||||
binding: HIHelpBinding(),
|
binding: HIHelpBinding(),
|
||||||
|
popGesture: false,
|
||||||
),
|
),
|
||||||
GetPage(
|
GetPage(
|
||||||
name: _Paths.HI_USER_INFO,
|
name: _Paths.HI_USER_INFO,
|
||||||
page: () => const HIUserInfoView(),
|
page: () => SwipeWrapper.detect(() => const HIUserInfoView()),
|
||||||
binding: HIUserInfoBinding(),
|
binding: HIUserInfoBinding(),
|
||||||
|
popGesture: false,
|
||||||
arguments: {'showSubscriptionButton': true}, // 显示购买按钮
|
arguments: {'showSubscriptionButton': true}, // 显示购买按钮
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|||||||
156
lib/app/widgets/hi_edge_swipe_detector.dart
Normal file
156
lib/app/widgets/hi_edge_swipe_detector.dart
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'swipe/swipe_controller.dart';
|
||||||
|
import 'swipe/swipe_config.dart';
|
||||||
|
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
|
||||||
|
|
||||||
|
class HIEdgeSwipeDetector extends StatefulWidget {
|
||||||
|
final Widget child;
|
||||||
|
final double reservedEdgeWidth;
|
||||||
|
final double detectionEdgeWidth;
|
||||||
|
final double minSwipeDistance;
|
||||||
|
final double minSwipeVelocity;
|
||||||
|
final SwipeConfig? config;
|
||||||
|
const HIEdgeSwipeDetector(
|
||||||
|
{super.key,
|
||||||
|
required this.child,
|
||||||
|
this.reservedEdgeWidth = 0,
|
||||||
|
this.detectionEdgeWidth = 20,
|
||||||
|
this.minSwipeDistance = 20,
|
||||||
|
this.minSwipeVelocity = 300,
|
||||||
|
this.config});
|
||||||
|
@override
|
||||||
|
State<HIEdgeSwipeDetector> createState() => _HIEdgeSwipeDetectorState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HIEdgeSwipeDetectorState extends State<HIEdgeSwipeDetector> {
|
||||||
|
Offset? _start;
|
||||||
|
Offset? _last;
|
||||||
|
DateTime? _startTime;
|
||||||
|
final SwipeController _controller =
|
||||||
|
Get.put(SwipeController(), permanent: true);
|
||||||
|
bool _fromLeft = false;
|
||||||
|
bool _fromRight = false;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
KRLogUtil.kr_d('HIEdgeSwipeDetector build');
|
||||||
|
return Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
widget.child,
|
||||||
|
Positioned(
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
width: widget.detectionEdgeWidth,
|
||||||
|
child: GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onHorizontalDragStart: (details) {
|
||||||
|
_fromLeft = true;
|
||||||
|
_fromRight = false;
|
||||||
|
_start = details.globalPosition;
|
||||||
|
_startTime = DateTime.now();
|
||||||
|
KRLogUtil.kr_d('dragStart LEFT x=${details.globalPosition.dx}',
|
||||||
|
tag: 'HIEdgeSwipeDetector');
|
||||||
|
},
|
||||||
|
onHorizontalDragUpdate: (details) {
|
||||||
|
_last = details.globalPosition;
|
||||||
|
KRLogUtil.kr_d('dragUpdate LEFT dx=${details.delta.dx}',
|
||||||
|
tag: 'HIEdgeSwipeDetector');
|
||||||
|
},
|
||||||
|
onHorizontalDragEnd: (details) {
|
||||||
|
if (_start == null || _startTime == null) return;
|
||||||
|
final durationMs =
|
||||||
|
DateTime.now().difference(_startTime!).inMilliseconds;
|
||||||
|
final velocity = details.velocity.pixelsPerSecond.dx.abs();
|
||||||
|
final distance = _last != null ? (_last!.dx - _start!.dx) : 0.0;
|
||||||
|
final okByVelocity = velocity >= widget.minSwipeVelocity;
|
||||||
|
final okByDistance = distance.abs() >= widget.minSwipeDistance;
|
||||||
|
KRLogUtil.kr_d(
|
||||||
|
'dragEnd LEFT vel=${details.velocity.pixelsPerSecond.dx} dur=$durationMs dist=$distance okV=$okByVelocity okD=$okByDistance',
|
||||||
|
tag: 'HIEdgeSwipeDetector');
|
||||||
|
if (!okByVelocity && !okByDistance) {
|
||||||
|
_reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (distance > 0) {
|
||||||
|
final cfg = widget.config;
|
||||||
|
if (cfg == null || !cfg.enableLeft) {
|
||||||
|
_reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (cfg.onLeft != null) {
|
||||||
|
cfg.onLeft!();
|
||||||
|
} else {
|
||||||
|
Get.back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_reset();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
width: widget.detectionEdgeWidth,
|
||||||
|
child: GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onHorizontalDragStart: (details) {
|
||||||
|
_fromLeft = false;
|
||||||
|
_fromRight = true;
|
||||||
|
_start = details.globalPosition;
|
||||||
|
_startTime = DateTime.now();
|
||||||
|
KRLogUtil.kr_d('dragStart RIGHT x=${details.globalPosition.dx}',
|
||||||
|
tag: 'HIEdgeSwipeDetector');
|
||||||
|
},
|
||||||
|
onHorizontalDragUpdate: (details) {
|
||||||
|
_last = details.globalPosition;
|
||||||
|
KRLogUtil.kr_d('dragUpdate RIGHT dx=${details.delta.dx}',
|
||||||
|
tag: 'HIEdgeSwipeDetector');
|
||||||
|
},
|
||||||
|
onHorizontalDragEnd: (details) {
|
||||||
|
if (_start == null || _startTime == null) return;
|
||||||
|
final durationMs =
|
||||||
|
DateTime.now().difference(_startTime!).inMilliseconds;
|
||||||
|
final velocity = details.velocity.pixelsPerSecond.dx.abs();
|
||||||
|
final distance = _last != null ? (_last!.dx - _start!.dx) : 0.0;
|
||||||
|
final okByVelocity = velocity >= widget.minSwipeVelocity;
|
||||||
|
final okByDistance = distance.abs() >= widget.minSwipeDistance;
|
||||||
|
KRLogUtil.kr_d(
|
||||||
|
'dragEnd RIGHT vel=${details.velocity.pixelsPerSecond.dx} dur=$durationMs dist=$distance okV=$okByVelocity okD=$okByDistance',
|
||||||
|
tag: 'HIEdgeSwipeDetector');
|
||||||
|
if (!okByVelocity && !okByDistance) {
|
||||||
|
_reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (distance < 0) {
|
||||||
|
final cfg = widget.config;
|
||||||
|
if (cfg == null || !cfg.enableRight) {
|
||||||
|
_reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (cfg.onRight != null) {
|
||||||
|
cfg.onRight!();
|
||||||
|
} else {
|
||||||
|
_controller.openRightMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_reset();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _reset() {
|
||||||
|
_start = null;
|
||||||
|
_last = null;
|
||||||
|
_startTime = null;
|
||||||
|
_fromLeft = false;
|
||||||
|
_fromRight = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
6
lib/app/widgets/swipe/has_swipe_config.dart
Normal file
6
lib/app/widgets/swipe/has_swipe_config.dart
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import 'swipe_config.dart';
|
||||||
|
|
||||||
|
abstract class HasSwipeConfig {
|
||||||
|
SwipeConfig get swipeConfig;
|
||||||
|
}
|
||||||
|
|
||||||
10
lib/app/widgets/swipe/swipe_config.dart
Normal file
10
lib/app/widgets/swipe/swipe_config.dart
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
class SwipeConfig {
|
||||||
|
final bool enableLeft;
|
||||||
|
final bool enableRight;
|
||||||
|
final VoidCallback? onLeft;
|
||||||
|
final VoidCallback? onRight;
|
||||||
|
const SwipeConfig({this.enableLeft = false, this.enableRight = false, this.onLeft, this.onRight});
|
||||||
|
}
|
||||||
|
|
||||||
61
lib/app/widgets/swipe/swipe_controller.dart
Normal file
61
lib/app/widgets/swipe/swipe_controller.dart
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:get/get_navigation/src/extension_navigation.dart';
|
||||||
|
import 'package:kaer_with_panels/app/routes/app_pages.dart';
|
||||||
|
import 'swipe_config.dart';
|
||||||
|
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
|
||||||
|
|
||||||
|
class SwipeController extends GetxController {
|
||||||
|
final Rxn<SwipeConfig> currentConfig = Rxn<SwipeConfig>();
|
||||||
|
final RxInt currentPageIndex = 0.obs;
|
||||||
|
final RxBool leftMenuOpen = false.obs;
|
||||||
|
final RxBool rightMenuOpen = false.obs;
|
||||||
|
void setConfig(SwipeConfig? config) {
|
||||||
|
currentConfig.value = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setPageIndex(int index) {
|
||||||
|
currentPageIndex.value = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
void closeMenus() {
|
||||||
|
leftMenuOpen.value = false;
|
||||||
|
rightMenuOpen.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void openLeftMenu() {
|
||||||
|
KRLogUtil.kr_d('openLeftMenu', tag: 'SwipeController');
|
||||||
|
leftMenuOpen.value = true;
|
||||||
|
Get.toNamed(Routes.HI_MENU);
|
||||||
|
}
|
||||||
|
|
||||||
|
void openRightMenu() {
|
||||||
|
KRLogUtil.kr_d('openRightMenu', tag: 'SwipeController');
|
||||||
|
rightMenuOpen.value = true;
|
||||||
|
Get.toNamed(Routes.KR_SETTING);
|
||||||
|
}
|
||||||
|
|
||||||
|
void triggerLeft() {
|
||||||
|
final cfg = currentConfig.value;
|
||||||
|
if (cfg == null || !cfg.enableLeft) return;
|
||||||
|
KRLogUtil.kr_d('call11 onLeft$cfg', tag: 'SwipeController');
|
||||||
|
|
||||||
|
if (cfg.onLeft != null) {
|
||||||
|
KRLogUtil.kr_d('call onLeft', tag: 'SwipeController');
|
||||||
|
cfg.onLeft!.call();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Get.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
void triggerRight() {
|
||||||
|
final cfg = currentConfig.value;
|
||||||
|
if (cfg == null || !cfg.enableRight) return;
|
||||||
|
if (cfg.onRight != null) {
|
||||||
|
KRLogUtil.kr_d('call onRight', tag: 'SwipeController');
|
||||||
|
cfg.onRight!.call();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
openRightMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
45
lib/app/widgets/swipe/swipe_wrapper.dart
Normal file
45
lib/app/widgets/swipe/swipe_wrapper.dart
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import '../hi_edge_swipe_detector.dart';
|
||||||
|
import 'swipe_config.dart';
|
||||||
|
import 'has_swipe_config.dart';
|
||||||
|
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
|
||||||
|
|
||||||
|
class SwipeWrapper extends StatelessWidget {
|
||||||
|
final Widget child;
|
||||||
|
final SwipeConfig config;
|
||||||
|
const SwipeWrapper({super.key, required this.child, required this.config});
|
||||||
|
|
||||||
|
static Widget detect(Widget Function() builder) {
|
||||||
|
final w = builder();
|
||||||
|
SwipeConfig effective = const SwipeConfig();
|
||||||
|
if (w is HasSwipeConfig) {
|
||||||
|
effective = (w as HasSwipeConfig).swipeConfig;
|
||||||
|
} else if (GetPlatform.isIOS) {
|
||||||
|
effective = SwipeConfig(enableLeft: true, onLeft: () => Get.back());
|
||||||
|
}
|
||||||
|
final enabled = effective.enableLeft ||
|
||||||
|
effective.enableRight ||
|
||||||
|
effective.onLeft != null ||
|
||||||
|
effective.onRight != null;
|
||||||
|
KRLogUtil.kr_d(
|
||||||
|
'SwipeWrapper.detect enabled=$enabled left=${effective.enableLeft} right=${effective.enableRight}',
|
||||||
|
tag: 'SwipeWrapper');
|
||||||
|
if (!enabled) return w;
|
||||||
|
return HIEdgeSwipeDetector(child: w, config: effective);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final effective = config;
|
||||||
|
final enabled = effective.enableLeft ||
|
||||||
|
effective.enableRight ||
|
||||||
|
effective.onLeft != null ||
|
||||||
|
effective.onRight != null;
|
||||||
|
KRLogUtil.kr_d(
|
||||||
|
'SwipeWrapper.build enabled=$enabled left=${effective.enableLeft} right=${effective.enableRight}',
|
||||||
|
tag: 'SwipeWrapper');
|
||||||
|
if (!enabled) return child;
|
||||||
|
return HIEdgeSwipeDetector(child: child, config: effective);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user