Files
xianyan/lib/features/classics/classics_pages.dart
Developer f91be94e9c refactor: 完成项目架构重构,统一模块导入路径
- 清理大量废弃的 barrel 导出文件,移除冗余的中间导出层
- 修复所有相对路径导入错误,统一调整为扁平化模块引用
- 更新多平台 pubspec 版本号与依赖库版本
- 补充后端功能问题管理后台与脚本工具
- 调整部分页面的快捷方式文案适配新功能
- 更新部分翻译覆盖率与API文档
2026-06-12 08:53:57 +08:00

278 lines
9.1 KiB
Dart

// ============================================================
// 闲言APP — 经典名句页面(分类入口 + 列表页)
// 创建时间: 2026-06-12
// 更新时间: 2026-06-12
// 作用: 合并经典名句分类入口页与列表页,统一管理页面组件
// 上次更新: 合并 classics_page.dart + classics_list_page.dart 为单文件
// ============================================================
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../core/theme/app_radius.dart';
import '../../core/theme/app_theme.dart';
import '../../core/theme/app_typography.dart';
import '../../shared/widgets/containers/glass_container.dart';
import '../../shared/widgets/adaptive/adaptive_back_button.dart';
import '../home/feed_model.dart';
import 'classics_core.dart';
// ==================== 分类入口页 ====================
/// 经典名句主页 — 展示古籍分类入口,点击进入对应名句列表
class ClassicsPage extends ConsumerWidget {
const ClassicsPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final ext = Theme.of(context).extension<AppThemeExtension>();
return CupertinoPageScaffold(
navigationBar: const CupertinoNavigationBar(
leading: AdaptiveBackButton(),
middle: Text('📖 经典名句'),
previousPageTitle: '返回',
),
child: SafeArea(
child: GridView.count(
crossAxisCount: 2,
padding: const EdgeInsets.all(16),
mainAxisSpacing: 12,
crossAxisSpacing: 12,
childAspectRatio: 1.5,
children: ClassicsService.categories.map((cat) {
return _CategoryCard(
category: cat,
ext: ext,
onTap: () => _navigateToList(context, cat),
);
}).toList(),
),
),
);
}
void _navigateToList(BuildContext context, ClassicsCategory cat) {
Navigator.push<void>(
context,
CupertinoPageRoute<void>(builder: (_) => ClassicsListPage(category: cat)),
);
}
}
/// 分类卡片组件
class _CategoryCard extends StatelessWidget {
const _CategoryCard({
required this.category,
required this.ext,
required this.onTap,
});
final ClassicsCategory category;
final AppThemeExtension? ext;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: GlassContainer(
borderRadius: AppRadius.lgBorder,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
children: [
Text(category.emoji, style: const TextStyle(fontSize: 28)),
const SizedBox(width: 8),
Expanded(
child: Text(
category.name,
style: AppTypography.title3.copyWith(
color: ext?.textPrimary ?? CupertinoColors.label,
fontWeight: FontWeight.w600,
),
overflow: TextOverflow.ellipsis,
),
),
],
),
const SizedBox(height: 8),
Text(
category.desc,
style: AppTypography.caption1.copyWith(
color: ext?.textSecondary ?? CupertinoColors.secondaryLabel,
),
),
],
),
),
),
);
}
}
// ==================== 列表页 ====================
/// 经典名句列表页 — 展示某类古籍的列表,支持搜索和分页
class ClassicsListPage extends ConsumerStatefulWidget {
const ClassicsListPage({super.key, required this.category});
final ClassicsCategory category;
@override
ConsumerState<ClassicsListPage> createState() => _ClassicsListPageState();
}
class _ClassicsListPageState extends ConsumerState<ClassicsListPage> {
final _searchController = TextEditingController();
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
ref
.read(classicsProvider.notifier)
.loadList(type: widget.category.id, refresh: true);
});
}
@override
void dispose() {
_searchController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final state = ref.watch(classicsProvider);
final ext = Theme.of(context).extension<AppThemeExtension>();
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
leading: const AdaptiveBackButton(),
middle: Text('${widget.category.emoji} ${widget.category.name}'),
previousPageTitle: '国学',
),
child: SafeArea(
child: Column(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 4),
child: CupertinoSearchTextField(
controller: _searchController,
placeholder: '搜索${widget.category.name}...',
onSubmitted: (keyword) {
if (keyword.trim().isEmpty) {
ref
.read(classicsProvider.notifier)
.loadList(type: widget.category.id, refresh: true);
} else {
ref
.read(classicsProvider.notifier)
.search(keyword: keyword, type: widget.category.id);
}
},
),
),
Expanded(
child: state.isLoading && state.items.isEmpty
? const Center(child: CupertinoActivityIndicator())
: state.items.isEmpty
? Center(
child: Text(
'暂无内容',
style: AppTypography.body.copyWith(
color:
ext?.textHint ?? CupertinoColors.placeholderText,
),
),
)
: CupertinoScrollbar(
child: ListView.builder(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
itemCount: state.items.length + 1,
itemBuilder: (context, index) {
if (index == state.items.length) {
if (state.hasMore && !state.isLoading) {
ref
.read(classicsProvider.notifier)
.loadList(type: widget.category.id);
}
return state.isLoading
? const Padding(
padding: EdgeInsets.all(16),
child: Center(
child: CupertinoActivityIndicator(),
),
)
: const SizedBox.shrink();
}
final item = state.items[index];
return _ListItem(item: item, ext: ext);
},
),
),
),
],
),
),
);
}
}
/// 列表项组件
class _ListItem extends StatelessWidget {
const _ListItem({required this.item, required this.ext});
final FeedItem item;
final AppThemeExtension? ext;
@override
Widget build(BuildContext context) {
final title = item.title;
final content = item.content.isNotEmpty ? item.content : item.summary;
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: GlassContainer(
borderRadius: AppRadius.mdBorder,
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (title.isNotEmpty)
Text(
title,
style: AppTypography.subhead.copyWith(
fontWeight: FontWeight.w600,
color: ext?.textPrimary ?? CupertinoColors.label,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
if (content.isNotEmpty) ...[
const SizedBox(height: 6),
Text(
content,
style: AppTypography.footnote.copyWith(
color: ext?.textSecondary ?? CupertinoColors.secondaryLabel,
),
maxLines: 4,
overflow: TextOverflow.ellipsis,
),
],
],
),
),
),
);
}
}