feat(app): 客户端展示多语言并支持筛选分页

This commit is contained in:
Zhou Weidong committed 2026-10-24 17:20:24 +08:00
1 parent 8481611372
commit 8b50b5bd06
68 files changed
+2742 -1120

No files matched your search

+84 -47
View File
@@ -20,11 +20,13 @@ class HomeGridCategoryShortcut {
const HomeGridCategoryShortcut({
required this.label,
required this.categoryName,
required this.categoryId,
required this.tab,
});
final String label;
final String categoryName;
final String categoryId;
final HomePrimaryTab tab;
}
@@ -47,15 +49,15 @@ class HomeGridHeader extends StatelessWidget {
super.key,
required this.callbacks,
required this.categories,
required this.selectedPrimary,
required this.onPrimarySelected,
required this.selectedIndex,
required this.onSectionSelected,
this.walletBadge,
});
final HomeGridCallbacks callbacks;
final List<HomeGridCategoryShortcut> categories;
final HomePrimaryTab selectedPrimary;
final ValueChanged<HomePrimaryTab> onPrimarySelected;
final int selectedIndex;
final ValueChanged<int> onSectionSelected;
final int? walletBadge;
@override
@@ -67,7 +69,7 @@ class HomeGridHeader extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.start,
children: [
_searchRow(l10n),
_primaryRow(l10n),
_primaryRow(),
const SizedBox(height: AppDimens.homeSecondaryGridGap),
],
);
@@ -225,7 +227,73 @@ class HomeGridHeader extends StatelessWidget {
);
}
Widget _primaryRow(AppLocalizations l10n) {
Widget _primaryRow() {
return _CategoryBar(
categories: categories,
selectedIndex: selectedIndex,
onSectionSelected: onSectionSelected,
);
}
}
class _CategoryBar extends StatefulWidget {
const _CategoryBar({
required this.categories,
required this.selectedIndex,
required this.onSectionSelected,
});
final List<HomeGridCategoryShortcut> categories;
final int selectedIndex;
final ValueChanged<int> onSectionSelected;
@override
State<_CategoryBar> createState() => _CategoryBarState();
}
class _CategoryBarState extends State<_CategoryBar> {
final List<GlobalKey> _itemKeys = [];
@override
void initState() {
super.initState();
_syncKeys();
WidgetsBinding.instance.addPostFrameCallback((_) => _revealSelected());
}
@override
void didUpdateWidget(_CategoryBar oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.categories.length != widget.categories.length) {
_syncKeys();
}
if (oldWidget.selectedIndex != widget.selectedIndex ||
oldWidget.categories.length != widget.categories.length) {
WidgetsBinding.instance.addPostFrameCallback((_) => _revealSelected());
}
}
void _syncKeys() {
_itemKeys
..clear()
..addAll(List.generate(widget.categories.length, (_) => GlobalKey()));
}
void _revealSelected() {
final index = widget.selectedIndex;
if (!mounted || index < 0 || index >= _itemKeys.length) return;
final target = _itemKeys[index].currentContext;
if (target == null) return;
Scrollable.ensureVisible(
target,
alignment: 0.5,
duration: AppDimens.homePrimaryTabAnimationDuration,
curve: Curves.easeOut,
);
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
key: const Key('home-primary-sections'),
scrollDirection: Axis.horizontal,
@@ -234,60 +302,29 @@ class HomeGridHeader extends StatelessWidget {
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
_primaryItem(
key: const Key('home-section-featured'),
label: l10n.homeSectionFeatured,
tab: HomePrimaryTab.featured,
),
_primaryItem(
key: const Key('home-section-new'),
label: l10n.homeSectionNew,
tab: HomePrimaryTab.newReleases,
),
_primaryItem(
key: const Key('home-section-categories'),
label: l10n.homeSectionCategories,
tab: HomePrimaryTab.categories,
),
_primaryItem(
key: const Key('home-section-ranking'),
label: l10n.homeSectionRanking,
tab: HomePrimaryTab.ranking,
),
for (final category in categories)
_primaryItem(
key: Key(
category.categoryName.isEmpty
? 'home-section-${category.tab.name}'
: 'home-section-category-${category.categoryName}',
),
label: category.label,
tab: category.tab,
),
for (var index = 0; index < widget.categories.length; index++)
_item(index),
],
),
);
}
Widget _primaryItem({
required Key key,
required String label,
required HomePrimaryTab tab,
}) {
final selected = selectedPrimary == tab;
Widget _item(int index) {
final category = widget.categories[index];
final selected = widget.selectedIndex == index;
return InkWell(
key: key,
onTap: () => onPrimarySelected(tab),
key: _itemKeys[index],
onTap: () => widget.onSectionSelected(index),
child: Padding(
key: Key('home-section-category-${category.categoryName}'),
padding: const EdgeInsets.only(right: AppDimens.homePrimaryCategoryGap),
child: SizedBox(
height: AppDimens.homePrimaryRowHeight,
child: Align(
alignment: Alignment.bottomLeft,
child: AnimatedDefaultTextStyle(
key: Key('home-primary-label-${tab.name}'),
key: Key('home-primary-label-${category.categoryId}'),
duration: AppDimens.homePrimaryTabAnimationDuration,
curve: Curves.easeOut,
style: TextStyle(
@@ -300,7 +337,7 @@ class HomeGridHeader extends StatelessWidget {
fontWeight: selected ? FontWeight.w700 : FontWeight.w400,
letterSpacing: 0,
),
child: Text(label, textAlign: TextAlign.left),
child: Text(category.label, textAlign: TextAlign.left),
),
),
),