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<EventModel> _events = [];
List<EventTypeModel> _types = []; List<EventTypeModel> _types = [];
int _selectedTypeId = -1; // -1 == All int _selectedTypeId = -1; // -1 == All
bool _categoriesExpanded = false;
bool _loading = true; bool _loading = true;
@@ -1579,21 +1580,50 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
), ),
), ),
TextButton( TextButton(
onPressed: () {}, onPressed: () => setState(() => _categoriesExpanded = !_categoriesExpanded),
child: const Text( child: Text(
'View All', _categoriesExpanded ? 'Show Less' : 'View All',
style: TextStyle(color: Color(0xFF2563EB), fontWeight: FontWeight.w600), style: const TextStyle(color: Color(0xFF2563EB), fontWeight: FontWeight.w600),
), ),
), ),
], ],
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
// Category chips (card-style) // Category chips — horizontal scroll (collapsed) or wrap grid (expanded)
SizedBox( AnimatedCrossFade(
height: 140, duration: const Duration(milliseconds: 300),
child: ListView( crossFadeState: _categoriesExpanded ? CrossFadeState.showSecond : CrossFadeState.showFirst,
scrollDirection: Axis.horizontal, // Collapsed: horizontal scroll
firstChild: SizedBox(
height: 140,
child: ListView(
scrollDirection: Axis.horizontal,
children: [
_categoryChip(
label: 'All Events',
icon: Icons.grid_view_rounded,
selected: _selectedTypeId == -1,
onTap: () => _onSelectType(-1),
),
const SizedBox(width: 12),
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(width: 12),
],
],
),
),
// Expanded: wrap grid showing all categories
secondChild: Wrap(
spacing: 10,
runSpacing: 10,
children: [ children: [
_categoryChip( _categoryChip(
label: 'All Events', label: 'All Events',
@@ -1601,8 +1631,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
selected: _selectedTypeId == -1, selected: _selectedTypeId == -1,
onTap: () => _onSelectType(-1), onTap: () => _onSelectType(-1),
), ),
const SizedBox(width: 12), for (final t in _types)
for (final t in _types) ...[
_categoryChip( _categoryChip(
label: t.name, label: t.name,
imageUrl: t.iconUrl, imageUrl: t.iconUrl,
@@ -1610,14 +1639,12 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
selected: _selectedTypeId == t.id, selected: _selectedTypeId == t.id,
onTap: () => _onSelectType(t.id), onTap: () => _onSelectType(t.id),
), ),
const SizedBox(width: 12),
],
], ],
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// Event sections by type — always show ALL categories // Event sections — shelves (All) or filtered list (specific category)
if (_loading) if (_loading)
const Padding( const Padding(
padding: EdgeInsets.all(40), padding: EdgeInsets.all(40),
@@ -1632,14 +1659,55 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
)), )),
) )
else else
Column( AnimatedSwitcher(
children: [ duration: const Duration(milliseconds: 300),
for (final t in _types) switchInCurve: Curves.easeOut,
if (_allFilteredByDate.where((e) => e.eventTypeId == t.id).isNotEmpty) ...[ switchOutCurve: Curves.easeIn,
_buildTypeSection(t), child: _selectedTypeId == -1
const SizedBox(height: 18), // "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) ...[
_buildTypeSection(t),
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 // Bottom padding for nav bar