feat(app): 客户端展示多语言并支持筛选分页
This commit is contained in:
1 parent
8481611372
commit
8b50b5bd06
68 files changed
+2742
-1120
No files matched your search
@@ -23,30 +23,18 @@ import '../../core/widgets/catalog_debug_info.dart';
|
||||
import '../../core/widgets/catalog_endpoint_editor.dart';
|
||||
import '../../core/widgets/empty_state.dart';
|
||||
import '../../l10n/app_localizations.dart';
|
||||
import '../filter/filter_page.dart';
|
||||
import '../new_releases/new_releases_page.dart';
|
||||
import '../player/open_player.dart';
|
||||
import '../ranking/ranking_page.dart';
|
||||
import '../reserve/reserve_page.dart';
|
||||
import 'discover_binding.dart';
|
||||
import 'discover_logic.dart';
|
||||
import 'discover_types.dart';
|
||||
import 'widgets/discover_category_tab.dart';
|
||||
import 'widgets/discover_header.dart';
|
||||
import 'widgets/discover_search_results.dart';
|
||||
import 'widgets/discover_toolbar.dart';
|
||||
import 'widgets/drama_grid.dart';
|
||||
|
||||
const Color _filterIconColor = AppColors.blueSoft;
|
||||
const Color _newIconColor = AppColors.blueSoft;
|
||||
const double _toolbarHeight = 36;
|
||||
const double _toolbarGap = 10;
|
||||
const double _toolbarRadius = 10;
|
||||
const double _categoryTabHeight = 44;
|
||||
const double _categoryTabGap = 20;
|
||||
const double _categorySelectedFontSize = 16;
|
||||
const double _categoryFontSize = 15;
|
||||
const double _categoryUnderlineWidth = 20;
|
||||
const double _categoryUnderlineHeight = 3;
|
||||
const double _categoryUnderlineStrokeWidth = 2;
|
||||
const double _stickyHeaderHeight = 44;
|
||||
const Duration _stickyHeaderTransition = Duration(milliseconds: 160);
|
||||
const Duration _categoryScrollDuration = Duration(milliseconds: 200);
|
||||
@@ -295,7 +283,7 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
||||
List<Widget> _theaterBlocks(AppLocalizations l10n) {
|
||||
final dramas = theater.dramas;
|
||||
return [
|
||||
_toolbar(l10n),
|
||||
_toolbar(),
|
||||
if (dramas.isEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 40),
|
||||
@@ -318,6 +306,16 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
),
|
||||
),
|
||||
)
|
||||
else if (dramas.isNotEmpty && !theater.hasMore)
|
||||
Padding(
|
||||
key: const Key('paged-list-no-more'),
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: Text(
|
||||
l10n.noMoreItems,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(color: AppColors.textHint, fontSize: 12),
|
||||
),
|
||||
),
|
||||
];
|
||||
}
|
||||
@@ -335,8 +333,7 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_categoryTab(
|
||||
key: const Key('theater-category-all'),
|
||||
DiscoverCategoryTab(
|
||||
anchorKey: _anchorFor('all'),
|
||||
id: 'all',
|
||||
label: l10n.all,
|
||||
@@ -345,8 +342,7 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
||||
),
|
||||
for (final category in categories.categories) ...[
|
||||
const SizedBox(width: _categoryTabGap),
|
||||
_categoryTab(
|
||||
key: Key('theater-category-${category.id}'),
|
||||
DiscoverCategoryTab(
|
||||
anchorKey: _anchorFor(category.id),
|
||||
id: category.id,
|
||||
label: category.name,
|
||||
@@ -360,52 +356,6 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _categoryTab({
|
||||
required Key key,
|
||||
required GlobalKey anchorKey,
|
||||
required String id,
|
||||
required String label,
|
||||
required bool selected,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return KeyedSubtree(
|
||||
key: anchorKey,
|
||||
child: GestureDetector(
|
||||
key: key,
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: onTap,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
color: selected ? AppColors.onSurface : AppColors.muted,
|
||||
fontSize: selected
|
||||
? _categorySelectedFontSize
|
||||
: _categoryFontSize,
|
||||
fontWeight: selected ? FontWeight.w800 : FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
CustomPaint(
|
||||
key: Key('theater-category-underline-$id'),
|
||||
size: const Size(
|
||||
_categoryUnderlineWidth,
|
||||
_categoryUnderlineHeight,
|
||||
),
|
||||
painter: CategoryUnderlinePainter(
|
||||
color: selected ? AppColors.blue : AppColors.transparent,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
GlobalKey _anchorFor(String id) {
|
||||
return _categoryAnchors.putIfAbsent(id, GlobalKey.new);
|
||||
}
|
||||
@@ -444,97 +394,8 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
||||
return true;
|
||||
}
|
||||
|
||||
Widget _toolbar(AppLocalizations l10n) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(14, 2, 14, 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _toolButton(
|
||||
key: const Key('theater-toolbar-filter'),
|
||||
icon: Icons.tune_rounded,
|
||||
color: _filterIconColor,
|
||||
label: l10n.filter,
|
||||
onTap: () => _openPage(context, const FilterPage()),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: _toolbarGap),
|
||||
Expanded(
|
||||
child: _toolButton(
|
||||
key: const Key('theater-toolbar-ranking'),
|
||||
icon: Icons.local_fire_department_rounded,
|
||||
color: AppColors.blue,
|
||||
label: l10n.rankingTab,
|
||||
onTap: () => _openPage(context, const RankingPage()),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: _toolbarGap),
|
||||
Expanded(
|
||||
child: _toolButton(
|
||||
key: const Key('theater-toolbar-new'),
|
||||
icon: Icons.play_circle_fill_rounded,
|
||||
color: _newIconColor,
|
||||
label: l10n.newDramas,
|
||||
onTap: () => _openPage(context, const NewReleasesPage()),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: _toolbarGap),
|
||||
Expanded(
|
||||
child: _toolButton(
|
||||
key: const Key('theater-toolbar-reserve'),
|
||||
icon: Icons.calendar_month_rounded,
|
||||
color: AppColors.blue,
|
||||
label: l10n.reserve,
|
||||
onTap: () => _openPage(context, const ReservePage()),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _toolButton({
|
||||
required Key key,
|
||||
required IconData icon,
|
||||
required Color color,
|
||||
required String label,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return Material(
|
||||
color: AppColors.card,
|
||||
borderRadius: BorderRadius.circular(_toolbarRadius),
|
||||
child: InkWell(
|
||||
key: key,
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(_toolbarRadius),
|
||||
child: Container(
|
||||
height: _toolbarHeight,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(_toolbarRadius),
|
||||
border: Border.all(color: AppColors.border),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(icon, size: 16, color: color),
|
||||
const SizedBox(width: 4),
|
||||
Flexible(
|
||||
child: Text(
|
||||
label,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
color: AppColors.onSurface,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
Widget _toolbar() {
|
||||
return DiscoverToolbar(onOpen: (page) => _openPage(context, page));
|
||||
}
|
||||
|
||||
void _openPage(BuildContext context, Widget page) {
|
||||
@@ -601,27 +462,3 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
||||
AppRootTab.mine => DiscoverPageAction.mine,
|
||||
};
|
||||
}
|
||||
|
||||
/// 分类 tab 的弧形选中线。
|
||||
class CategoryUnderlinePainter extends CustomPainter {
|
||||
const CategoryUnderlinePainter({required this.color});
|
||||
|
||||
final Color color;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()
|
||||
..color = color
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = _categoryUnderlineStrokeWidth
|
||||
..strokeCap = StrokeCap.round;
|
||||
final path = Path()
|
||||
..moveTo(1, 1)
|
||||
..quadraticBezierTo(size.width / 2, size.height + 1, size.width - 1, 1);
|
||||
canvas.drawPath(path, paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CategoryUnderlinePainter oldDelegate) =>
|
||||
color != oldDelegate.color;
|
||||
}
|
||||
Reference in new issue
Block a user