feat: fix View All buttons and category selection UX

- "View All" on "Events Around You" header now toggles between
  horizontal scroll and expanded wrap grid showing all categories
- Tapping a category chip replaces all shelf sections with a
  filtered vertical list of events for that category only
- Tapping "All Events" restores the shelf layout for all categories
- "View All" on each shelf header (Music, Festivals, etc.) selects
  that category in the chips and shows its filtered event list
- Added AnimatedSwitcher for smooth transition between views
- Added AnimatedCrossFade for chip expand/collapse animation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-30 20:32:54 +05:30
parent 206602fca6
commit 34a39ada31

View File

@@ -40,6 +40,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
List<EventModel> _events = [];
List<EventTypeModel> _types = [];
int _selectedTypeId = -1; // -1 == All
bool _categoriesExpanded = false;
bool _loading = true;
@@ -1579,18 +1580,22 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
),
),
TextButton(
onPressed: () {},
child: const Text(
'View All',
style: TextStyle(color: Color(0xFF2563EB), fontWeight: FontWeight.w600),
onPressed: () => setState(() => _categoriesExpanded = !_categoriesExpanded),
child: Text(
_categoriesExpanded ? 'Show Less' : 'View All',
style: const TextStyle(color: Color(0xFF2563EB), fontWeight: FontWeight.w600),
),
),
],
),
const SizedBox(height: 12),
// Category chips (card-style)
SizedBox(
// Category chips — horizontal scroll (collapsed) or wrap grid (expanded)
AnimatedCrossFade(
duration: const Duration(milliseconds: 300),
crossFadeState: _categoriesExpanded ? CrossFadeState.showSecond : CrossFadeState.showFirst,
// Collapsed: horizontal scroll
firstChild: SizedBox(
height: 140,
child: ListView(
scrollDirection: Axis.horizontal,
@@ -1615,9 +1620,31 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
],
),
),
// Expanded: wrap grid showing all categories
secondChild: Wrap(
spacing: 10,
runSpacing: 10,
children: [
_categoryChip(
label: 'All Events',
icon: Icons.grid_view_rounded,
selected: _selectedTypeId == -1,
onTap: () => _onSelectType(-1),
),
for (final t in _types)
_categoryChip(
label: t.name,
imageUrl: t.iconUrl,
icon: _getIconForType(t.name),
selected: _selectedTypeId == t.id,
onTap: () => _onSelectType(t.id),
),
],
),
),
const SizedBox(height: 16),
// Event sections by type — always show ALL categories
// Event sections — shelves (All) or filtered list (specific category)
if (_loading)
const Padding(
padding: EdgeInsets.all(40),
@@ -1632,7 +1659,14 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
)),
)
else
Column(
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
switchInCurve: Curves.easeOut,
switchOutCurve: Curves.easeIn,
child: _selectedTypeId == -1
// "All Events" — show category shelf sections
? Column(
key: const ValueKey('shelves'),
children: [
for (final t in _types)
if (_allFilteredByDate.where((e) => e.eventTypeId == t.id).isNotEmpty) ...[
@@ -1640,6 +1674,40 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
const SizedBox(height: 18),
],
],
)
// Specific category — show vertical list of events for that category only
: Column(
key: ValueKey('filtered_$_selectedTypeId'),
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Category name header
Padding(
padding: const EdgeInsets.only(bottom: 14),
child: Row(
children: [
Expanded(
child: Text(
_types.firstWhere((t) => t.id == _selectedTypeId, orElse: () => _types.first).name,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Color(0xFF111827)),
),
),
Text(
'${_allFilteredByDate.where((e) => e.eventTypeId == _selectedTypeId).length} events',
style: const TextStyle(fontSize: 13, color: Color(0xFF9CA3AF)),
),
],
),
),
// Event cards
for (final e in _allFilteredByDate.where((e) => e.eventTypeId == _selectedTypeId))
_buildFullWidthCard(e),
if (_allFilteredByDate.where((e) => e.eventTypeId == _selectedTypeId).isEmpty)
const Padding(
padding: EdgeInsets.all(40),
child: Center(child: Text('No events in this category', style: TextStyle(color: Color(0xFF9CA3AF)))),
),
],
),
),
// Bottom padding for nav bar