- 清理大量废弃的 barrel 导出文件,移除冗余的中间导出层 - 修复所有相对路径导入错误,统一调整为扁平化模块引用 - 更新多平台 pubspec 版本号与依赖库版本 - 补充后端功能问题管理后台与脚本工具 - 调整部分页面的快捷方式文案适配新功能 - 更新部分翻译覆盖率与API文档
278 lines
9.1 KiB
Dart
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,
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|