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:
@@ -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,21 +1580,50 @@ 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(
|
||||
height: 140,
|
||||
child: ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
// 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,
|
||||
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: [
|
||||
_categoryChip(
|
||||
label: 'All Events',
|
||||
@@ -1601,8 +1631,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
||||
selected: _selectedTypeId == -1,
|
||||
onTap: () => _onSelectType(-1),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
for (final t in _types) ...[
|
||||
for (final t in _types)
|
||||
_categoryChip(
|
||||
label: t.name,
|
||||
imageUrl: t.iconUrl,
|
||||
@@ -1610,14 +1639,12 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
||||
selected: _selectedTypeId == t.id,
|
||||
onTap: () => _onSelectType(t.id),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
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,14 +1659,55 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
||||
)),
|
||||
)
|
||||
else
|
||||
Column(
|
||||
children: [
|
||||
for (final t in _types)
|
||||
if (_allFilteredByDate.where((e) => e.eventTypeId == t.id).isNotEmpty) ...[
|
||||
_buildTypeSection(t),
|
||||
const SizedBox(height: 18),
|
||||
],
|
||||
],
|
||||
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) ...[
|
||||
_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
|
||||
|
||||
Reference in New Issue
Block a user