191 lines
6.3 KiB
Dart
191 lines
6.3 KiB
Dart
// 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<String>
|
|
final List<String> 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<HICollapsibleItemWidget> createState() => _HICollapsibleItemWidgetState();
|
|
}
|
|
|
|
class _HICollapsibleItemWidgetState extends State<HICollapsibleItemWidget> {
|
|
bool _isExpanded = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_isExpanded = widget.initiallyExpanded;
|
|
}
|
|
|
|
List<TextSpan> _buildTextSpans(String text) {
|
|
final List<TextSpan> 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(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
} |