import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import 'package:nomo/app/base/base_view.dart'; import 'package:nomo/app/widgets/click_opacity.dart'; import 'package:nomo/app/widgets/ix_app_bar.dart'; import 'package:nomo/app/widgets/ix_image.dart'; import 'package:nomo/config/theme/theme_extensions/theme_extension.dart'; import '../../../../../config/translations/strings_enum.dart'; import '../../../../../utils/event_bus.dart'; import '../../../../constants/iconfont/iconfont.dart'; import '../../../../widgets/state/state_wrapper.dart'; import '../controllers/splittunneling_selectapp_controller.dart'; class SplittunnelingSelectappView extends BaseView { SplittunnelingSelectappView({super.key}); // 用于存储应用项的位置信息 final Map _selectedAppsKeys = {}; final Map _unselectedAppsKeys = {}; @override PreferredSizeWidget? get appBar => IXAppBar( title: Strings.splitTunneling.tr, onBackPressed: () { eventBus.fire( SplitTunnelingPageEvent(mode: controller.currentMode.value), ); Get.back(); }, ); @override Widget buildContent(BuildContext context) { return StateWrapper( controller: controller, onRefresh: () => controller.getApps(), child: SingleChildScrollView( padding: EdgeInsets.symmetric(horizontal: 14.w, vertical: 10.w), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 信息横幅 _buildInfoBanner(), // 已选择应用区域 _buildSelectedAppsSection(context), 10.verticalSpaceFromWidth, // 所有应用区域 _buildAllAppsSection(context), ], ), ), ); } /// 构建信息横幅 Widget _buildInfoBanner() { return Obx(() { final isExcludeMode = controller.currentMode.value == SplitTunnelingMode.exclude; final message = isExcludeMode ? Strings.selectAppsExclude.tr : Strings.selectAppsInclude.tr; return Container( padding: EdgeInsets.symmetric(horizontal: 14.w, vertical: 12.w), decoration: BoxDecoration( color: Get.reactiveTheme.cardColor, borderRadius: BorderRadius.circular(12.r), ), child: Row( children: [ Icon( IconFont.icon22, size: 20.w, color: Get.reactiveTheme.textTheme.bodyLarge!.color, ), SizedBox(width: 12.w), Expanded( child: Text( message, style: TextStyle( fontSize: 12.sp, color: Get.reactiveTheme.textTheme.bodyLarge!.color, fontWeight: FontWeight.w500, ), ), ), ], ), ); }); } /// 构建已选择应用区域 Widget _buildSelectedAppsSection(BuildContext context) { return Obx(() { if (controller.selectedApps.isEmpty) { return const SizedBox.shrink(); } return AnimatedContainer( duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ 10.verticalSpaceFromWidth, Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( Strings.selectApps.tr, style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w500, color: Get.reactiveTheme.hintColor, ), ), ClickOpacity( onTap: controller.deselectAllApps, child: Text( Strings.deselectAll.tr, style: TextStyle( fontSize: 14.sp, color: Get.reactiveTheme.shadowColor, fontWeight: FontWeight.w500, ), ), ), ], ), 10.verticalSpaceFromWidth, Container( decoration: BoxDecoration( color: Get.reactiveTheme.highlightColor, borderRadius: BorderRadius.circular(12.r), ), child: Column( children: controller.selectedApps.asMap().entries.map((entry) { final key = GlobalKey(); _selectedAppsKeys[entry.value.packageName] = key; return Container( key: key, child: _buildAppItem( context, entry.value, isLast: entry.key == controller.selectedApps.length - 1, ), ); }).toList(), ), ), ], ), ); }); } /// 构建所有应用区域 Widget _buildAllAppsSection(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( Strings.allApps.tr, style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w500, color: Get.reactiveTheme.hintColor, ), ), 10.verticalSpaceFromWidth, Obx(() { final unselectedApps = controller.unselectedApps; return Container( decoration: BoxDecoration( color: Get.reactiveTheme.highlightColor, borderRadius: BorderRadius.circular(12.r), ), child: Column( children: unselectedApps.asMap().entries.map((entry) { final key = GlobalKey(); _unselectedAppsKeys[entry.value.packageName] = key; return Container( key: key, child: _buildAppItem( context, entry.value, isLast: entry.key == unselectedApps.length - 1, ), ); }).toList(), ), ); }), ], ); } /// 构建应用项 Widget _buildAppItem( BuildContext context, AppInfo app, { bool isLast = false, }) { return AnimatedContainer( duration: const Duration(milliseconds: 200), curve: Curves.easeInOut, child: GestureDetector( onTap: () { // 直接切换选择状态 controller.toggleAppSelection(app); }, child: AnimatedContainer( duration: const Duration(milliseconds: 150), curve: Curves.easeInOut, child: Container( height: 68.w, padding: EdgeInsets.symmetric(horizontal: 14.w), decoration: BoxDecoration( border: isLast ? null : Border( bottom: BorderSide( color: Get.reactiveTheme.dividerColor, width: 1.w, ), ), ), child: Row( children: [ // 应用图标 AnimatedContainer( duration: const Duration(milliseconds: 200), curve: Curves.easeInOut, child: Transform.scale( scale: 1.0, child: IXImage( key: ValueKey('app_icon_${app.packageName}'), source: app.icon, width: 40.w, height: 40.w, sourceType: ImageSourceType.memory, ), ), ), 12.horizontalSpace, // 应用名称 Expanded( child: Text( app.name, style: TextStyle( fontSize: 14.sp, color: Get.reactiveTheme.textTheme.bodyLarge!.color, fontWeight: FontWeight.w500, ), ), ), // 选择状态指示器 Obx(() { final isSelected = controller.isAppSelected(app); return AnimatedContainer( duration: const Duration(milliseconds: 300), curve: Curves.elasticOut, width: 20.w, height: 20.w, decoration: BoxDecoration( shape: BoxShape.circle, color: isSelected ? Get.reactiveTheme.shadowColor : Colors.transparent, border: Border.all( color: isSelected ? Get.reactiveTheme.shadowColor : Colors.grey[400]!, width: 1.5.w, ), ), child: AnimatedScale( scale: isSelected ? 1.0 : 0.0, duration: const Duration(milliseconds: 300), curve: Curves.elasticOut, child: Icon(Icons.check, color: Colors.white, size: 14.w), ), ); }), ], ), ), ), ), ); } }