Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
3a714dc
perf: Optimize EventsListScreen scrolling and filtering
google-labs-jules[bot] Feb 17, 2026
6591664
fix(ci): Downgrade url_launcher constraint for Dart 3.0 compatibility
google-labs-jules[bot] Feb 17, 2026
75a73a2
fix(ci): Downgrade share_plus for Dart 3.0 compatibility
google-labs-jules[bot] Feb 17, 2026
8f20d74
fix(ci): Downgrade share_plus for Dart 3.0 compatibility
google-labs-jules[bot] Feb 17, 2026
05dd6ca
fix(ci): Fix analysis errors and downgrade dependencies for Dart 3.0
google-labs-jules[bot] Feb 17, 2026
faa82f6
fix(ci): Fix analysis errors and downgrade dependencies for Dart 3.0
google-labs-jules[bot] Feb 17, 2026
ad00412
fix(android): Remove GeneratedPluginRegistrant.java to resolve embedd…
google-labs-jules[bot] Feb 17, 2026
a3a669f
fix(android): Resolve deprecated embedding warnings and dependency co…
google-labs-jules[bot] Feb 17, 2026
f149170
fix(ci): Upgrade workmanager to ^0.9.0 to resolve Android build failure
google-labs-jules[bot] Feb 17, 2026
cce78be
ci: Upgrade Flutter version to 3.22.0
google-labs-jules[bot] Feb 17, 2026
5aa5ea7
ci: Upgrade Flutter to 3.24.0 for Dart 3.5 support
google-labs-jules[bot] Feb 17, 2026
7a8da00
fix(deps): Downgrade workmanager to ^0.7.0 for CI compatibility
google-labs-jules[bot] Feb 17, 2026
37d5aaa
fix(ui): Revert Flutter 3.27+ APIs to support Flutter 3.24 (CI)
google-labs-jules[bot] Feb 17, 2026
fd489d2
fix(ui): Revert Flutter 3.27+ APIs to support Flutter 3.24 (CI)
google-labs-jules[bot] Feb 17, 2026
1efed53
Fix static analysis issues and warnings
google-labs-jules[bot] Feb 17, 2026
1d66027
Merge pull request #3 from axcdeng/analysis-fixes-5198801876914313214
axcdeng Feb 17, 2026
543ec9c
Fix analysis issues in models and UI
google-labs-jules[bot] Feb 17, 2026
b601330
Merge pull request #4 from axcdeng/fix-analysis-issues-90942734308753…
axcdeng Feb 17, 2026
58c8751
fix(ui): Revert Flutter 3.27+ APIs to support Flutter 3.24 (CI)
google-labs-jules[bot] Feb 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 33 additions & 6 deletions lib/src/state/events_list_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -82,25 +82,29 @@ class EventsListState {
final Map<int, List<Event>> weekCache; // Key is index in `weeks`
final EventFilters filters;
final bool isIndexing;
final List<String> availableCountries;

const EventsListState({
this.weeks = const [],
this.weekCache = const {},
this.filters = const EventFilters(),
this.isIndexing = false,
this.availableCountries = const [],
});

EventsListState copyWith({
List<DateTime>? weeks,
Map<int, List<Event>>? weekCache,
EventFilters? filters,
bool? isIndexing,
List<String>? availableCountries,
}) {
return EventsListState(
weeks: weeks ?? this.weeks,
weekCache: weekCache ?? this.weekCache,
filters: filters ?? this.filters,
isIndexing: isIndexing ?? this.isIndexing,
availableCountries: availableCountries ?? this.availableCountries,
);
}
}
Expand Down Expand Up @@ -212,17 +216,18 @@ class EventsListController extends StateNotifier<EventsListState> {

// 4. Compute
// Threshold for using isolate
Map<int, List<Event>> resultMap;
_ComputeResult result;
if (allEvents.length > 500) {
resultMap = await compute(_buildWeekCacheIsolated, params);
result = await compute(_buildWeekCacheIsolated, params);
} else {
resultMap = _buildWeekCacheIsolated(params);
result = _buildWeekCacheIsolated(params);
}

// 5. Update state
if (mounted) {
state = state.copyWith(
weekCache: resultMap,
weekCache: result.weekCache,
availableCountries: result.availableCountries,
isIndexing: false,
);
}
Expand All @@ -246,7 +251,18 @@ class _ComputeParams {
});
}

Map<int, List<Event>> _buildWeekCacheIsolated(_ComputeParams params) {
@immutable
class _ComputeResult {
final Map<int, List<Event>> weekCache;
final List<String> availableCountries;

const _ComputeResult({
required this.weekCache,
required this.availableCountries,
});
}

_ComputeResult _buildWeekCacheIsolated(_ComputeParams params) {
final cache = <int, List<Event>>{};
final searchQuery = params.filters.searchQuery.toLowerCase();
final countries = params.filters.countries;
Expand Down Expand Up @@ -349,7 +365,18 @@ Map<int, List<Event>> _buildWeekCacheIsolated(_ComputeParams params) {
list.sort((a, b) => b.startDate.compareTo(a.startDate));
}

return cache;
// Compute available countries from ALL events (not filtered)
final availableCountries = params.allEvents
.map((e) => e.country)
.whereType<String>()
.toSet()
.toList()
..sort();

return _ComputeResult(
weekCache: cache,
availableCountries: availableCountries,
);
}

// -----------------------------------------------------------------------------
Expand Down
219 changes: 106 additions & 113 deletions lib/src/ui/screens/events_list_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,8 @@ class _EventsListViewState extends ConsumerState<EventsListView> {
final filters = listState.filters;
final isSearching = filters.searchQuery.isNotEmpty;

final populatedWeekIndices = displayCache.keys.toList()..sort();

final content = SafeArea(
child: Column(
children: [
Expand Down Expand Up @@ -431,19 +433,19 @@ class _EventsListViewState extends ConsumerState<EventsListView> {
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index >= weeks.length) return null;
if (index >= populatedWeekIndices.length) return null;

// Only show weeks that have matching events
if (!displayCache.containsKey(index))
return const SizedBox.shrink();
final weekIndex = populatedWeekIndices[index];
// Safety check for array bounds, although weekIndex is derived from cache
if (weekIndex < 0 || weekIndex >= weeks.length) return const SizedBox.shrink();

final weekStart = weeks[index];
final weekEvents = displayCache[index] ?? [];
final weekStart = weeks[weekIndex];
final weekEvents = displayCache[weekIndex]!;

return _buildWeekSectionWidget(
context, weekStart, weekEvents, index);
context, weekStart, weekEvents, weekIndex);
},
childCount: weeks.length,
childCount: populatedWeekIndices.length,
),
),
],
Expand Down Expand Up @@ -507,12 +509,14 @@ class _EventsListViewState extends ConsumerState<EventsListView> {
? Border.all(color: primaryColor.withOpacity(0.4), width: 1)
: null,
),
child: IntrinsicHeight(
child: Row(
children: [
// Accent bar
Container(
width: 4,
child: Stack(
children: [
Positioned(
left: 0,
top: 0,
bottom: 0,
width: 4,
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
Expand All @@ -530,103 +534,103 @@ class _EventsListViewState extends ConsumerState<EventsListView> {
),
),
),
// Content
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 14, vertical: 14),
child: Row(
children: [
Icon(
CupertinoIcons.calendar,
size: 20,
color: isCurrentWeek
? primaryColor
: CupertinoColors.systemGrey,
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
'${weekStart.month}/${weekStart.day} – ${weekEnd.month}/${weekEnd.day}',
style: TextStyle(
fontWeight: FontWeight.w700,
fontSize: 16,
color: isCurrentWeek
? CupertinoColors.label
.resolveFrom(context)
: CupertinoColors.secondaryLabel
.resolveFrom(context),
letterSpacing: -0.3,
),
),
Padding(
padding: const EdgeInsets.only(left: 4),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 14, vertical: 14),
child: Row(
children: [
Icon(
CupertinoIcons.calendar,
size: 20,
color: isCurrentWeek
? primaryColor
: CupertinoColors.systemGrey,
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
'${weekStart.month}/${weekStart.day} – ${weekEnd.month}/${weekEnd.day}',
style: TextStyle(
fontWeight: FontWeight.w700,
fontSize: 16,
color: isCurrentWeek
? CupertinoColors.label
.resolveFrom(context)
: CupertinoColors.secondaryLabel
.resolveFrom(context),
letterSpacing: -0.3,
),
if (isCurrentWeek) ...[
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color:
primaryColor.withOpacity(0.15),
borderRadius:
BorderRadius.circular(4),
),
child: Text('NOW',
style: TextStyle(
fontSize: 9,
fontWeight: FontWeight.w800,
color: primaryColor,
letterSpacing: 0.5)),
),
if (isCurrentWeek) ...[
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color:
primaryColor.withOpacity(0.15),
borderRadius:
BorderRadius.circular(4),
),
],
child: Text('NOW',
style: TextStyle(
fontSize: 9,
fontWeight: FontWeight.w800,
color: primaryColor,
letterSpacing: 0.5)),
),
],
),
],
),
],
),
],
),
),
// Event count badge
Container(
padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: isCurrentWeek
? primaryColor.withOpacity(0.15)
: CupertinoColors
.tertiarySystemGroupedBackground
.resolveFrom(context),
borderRadius: BorderRadius.circular(12),
),
// Event count badge
Container(
padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 4),
decoration: BoxDecoration(
child: Text(
'${weekEvents.length}',
style: TextStyle(
fontWeight: FontWeight.w800,
fontSize: 14,
color: isCurrentWeek
? primaryColor.withOpacity(0.15)
: CupertinoColors
.tertiarySystemGroupedBackground
? primaryColor
: CupertinoColors.secondaryLabel
.resolveFrom(context),
borderRadius: BorderRadius.circular(12),
),
child: Text(
'${weekEvents.length}',
style: TextStyle(
fontWeight: FontWeight.w800,
fontSize: 14,
color: isCurrentWeek
? primaryColor
: CupertinoColors.secondaryLabel
.resolveFrom(context),
),
),
),
const SizedBox(width: 8),
Icon(
isExpanded
? CupertinoIcons.chevron_down
: CupertinoIcons.chevron_right,
size: 13,
color: CupertinoColors.systemGrey
.resolveFrom(context),
),
],
),
),
const SizedBox(width: 8),
Icon(
isExpanded
? CupertinoIcons.chevron_down
: CupertinoIcons.chevron_right,
size: 13,
color: CupertinoColors.systemGrey
.resolveFrom(context),
),
],
),
),
],
),
),
],
),
),
),
Expand Down Expand Up @@ -727,18 +731,7 @@ class _EventsListViewState extends ConsumerState<EventsListView> {
}

Widget _buildFilterBar(BuildContext context, EventFilters filters) {
// We need to fetch unique countries/regions to show in pickers.
// Ideally this is also computed in the background, but for now we can
// read from the repo briefly or add it to the state.
// A quick way is to just grab all events from repo once here.
final allEvents = ref.read(eventsRepositoryProvider).getAllEvents();

final countries = allEvents
.map((e) => e.country)
.whereType<String>()
.toSet()
.toList()
..sort();
final countries = ref.watch(eventsListControllerProvider).availableCountries;

String _getLabel(String base, List<String> selected) {
if (selected.isEmpty) return base;
Expand Down
Loading
Loading