// lib/app/widgets/hi_collapsible_list.dart import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:kaer_with_panels/app/widgets/kr_local_image.dart'; import 'dart:math' as math; // 导入 math 库以使用 pi import 'package:flutter/gestures.dart'; import 'package:get/get.dart'; import 'package:kaer_with_panels/app/routes/app_pages.dart'; import 'package:url_launcher/url_launcher.dart'; /// 可折叠列表项的数据模型 class HICollapsibleItem { final String title; // 👇 核心改动 1: 将 content 类型从 String 改为 List final List content; const HICollapsibleItem({required this.title, required this.content}); } /// 一个独立的、自定义样式的可折叠面板组件 (HI 前缀) /// /// 它接收一个标题和一个内容字符串,并渲染成一个带边框、可展开/收起的面板。 class HICollapsibleItemWidget extends StatefulWidget { const HICollapsibleItemWidget({ super.key, required this.item, this.initiallyExpanded = false, }); /// 要显示的数据项 final HICollapsibleItem item; /// 初始是否展开 final bool initiallyExpanded; @override State createState() => _HICollapsibleItemWidgetState(); } class _HICollapsibleItemWidgetState extends State { bool _isExpanded = false; @override void initState() { super.initState(); _isExpanded = widget.initiallyExpanded; } List _buildTextSpans(String text) { final List spans = []; final RegExp linkRegExp = RegExp(r'\[link\](.*?)\[/link\]'); // 正则表达式匹配 [link]...[/link] text.splitMapJoin( linkRegExp, onMatch: (Match match) { final linkText = match.group(1)!; // 获取链接文本,例如 "点击这里" spans.add( TextSpan( text: linkText, style: const TextStyle( color: const Color(0xFFADFF5B), // 链接颜色 ), recognizer: TapGestureRecognizer() ..onTap = () async { final Uri url = Uri.parse('https://hifastvpn.com/help'); if (!await launchUrl(url, mode: LaunchMode.externalApplication)) { Get.snackbar('错误', '无法打开链接: $url'); } }, ), ); return ''; }, onNonMatch: (String nonMatch) { spans.add(TextSpan(text: nonMatch)); // 普通文本部分 return ''; }, ); return spans; } @override Widget build(BuildContext context) { final BorderRadius borderRadius = BorderRadius.circular(25); return AnimatedContainer( duration: const Duration(milliseconds: 200), curve: Curves.easeInOut, decoration: BoxDecoration( border: Border.all(color: Colors.white, width: 2.0), borderRadius: borderRadius, ), child: ClipRRect( borderRadius: borderRadius, child: Column( mainAxisSize: MainAxisSize.min, children: [ // ===== 标题部分 ===== GestureDetector( onTap: () { setState(() { _isExpanded = !_isExpanded; }); }, behavior: HitTestBehavior.translucent, child: Padding( padding: EdgeInsets.fromLTRB(24, 16, 24, 16), child: Row( children: [ Expanded( child: Text( widget.item.title, style: TextStyle( color: Colors.white, fontSize: 12, fontWeight: FontWeight.w600, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), AnimatedRotation( turns: _isExpanded ? 0.75 : 0.25, duration: const Duration(milliseconds: 200), child: KrLocalImage( imageName: 'arrow-right-icon', imageType: ImageType.svg, color: Colors.white, ), ), ], ), ), ), // ===== 内容部分 ===== AnimatedSize( duration: const Duration(milliseconds: 200), curve: Curves.easeInOut, child: _isExpanded ? Container( padding: EdgeInsets.fromLTRB(24, 0, 24, 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: widget.item.content.map((itemText) { return Padding( padding: EdgeInsets.only(bottom: 8.h), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: EdgeInsets.only(top: 11, right: 8), child: Container( width: 5.w, height: 5.w, decoration: const BoxDecoration( color: Colors.white, shape: BoxShape.circle, ), ), ), Expanded( child: Text.rich( TextSpan( style: TextStyle( color: Colors.white, fontSize: 13, height: 1.8, fontWeight: FontWeight.w300, ), children: _buildTextSpans(itemText), ), ), ), ], ), ); }).toList(), ), ) : const SizedBox.shrink(), ), ], ), ), ); } }