From f37dd65fb73a2c50b7251f91cc20aa83a21bd982 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 7 Feb 2026 08:41:30 +0000 Subject: [PATCH 01/10] Fix 3 critical bugs: parallel API fetching, dart:io web breakage, festival URL sync - Parallelize beverage type fetching with Future.wait for ~7x faster load times - Remove dart:io import and SocketException handler that breaks web builds - Update URL via GoRouter when switching festivals in the selector https://claude.ai/code/session_01Vzxb1yS3rRonMVEXEdivwx --- lib/providers/beer_provider.dart | 3 --- lib/services/beer_api_service.dart | 26 +++++++++++++++++--------- lib/widgets/festival_menu_sheets.dart | 6 ++++++ test/provider_test.dart | 22 ++++++++++------------ 4 files changed, 33 insertions(+), 24 deletions(-) diff --git a/lib/providers/beer_provider.dart b/lib/providers/beer_provider.dart index 51c52705..7802f560 100644 --- a/lib/providers/beer_provider.dart +++ b/lib/providers/beer_provider.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:io'; import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../models/models.dart'; @@ -361,8 +360,6 @@ class BeerProvider extends ChangeNotifier { } else { return 'Could not load festivals. Please check your connection.'; } - } else if (error is SocketException) { - return 'No internet connection. Please check your network.'; } else if (error is TimeoutException) { return 'Request timed out. Please check your connection and try again.'; } else { diff --git a/lib/services/beer_api_service.dart b/lib/services/beer_api_service.dart index 967b1ab6..a19586a5 100644 --- a/lib/services/beer_api_service.dart +++ b/lib/services/beer_api_service.dart @@ -37,7 +37,8 @@ class BeerApiService { } /// Fetches all available drinks from a festival (all beverage types) - /// + /// + /// Fetches all beverage types in parallel for faster loading. /// Throws [BeerApiException] if ALL beverage types fail to load or return /// no drinks. Individual failures are tracked and reported in the exception /// message to help diagnose issues like CORS or network problems. @@ -45,14 +46,21 @@ class BeerApiService { final allDrinks = []; final errors = {}; - for (final beverageType in festival.availableBeverageTypes) { - try { - final drinks = await fetchDrinks(festival, beverageType); - allDrinks.addAll(drinks); - } catch (e) { - // Track the error for this beverage type - errors[beverageType] = e.toString(); - } + // Fetch all beverage types in parallel for faster loading + final results = await Future.wait( + festival.availableBeverageTypes.map((beverageType) async { + try { + return await fetchDrinks(festival, beverageType); + } catch (e) { + // Track the error for this beverage type + errors[beverageType] = e.toString(); + return []; + } + }), + ); + + for (final drinks in results) { + allDrinks.addAll(drinks); } // If we got no drinks at all and there were errors, throw with details diff --git a/lib/widgets/festival_menu_sheets.dart b/lib/widgets/festival_menu_sheets.dart index 5f470eb5..f1801beb 100644 --- a/lib/widgets/festival_menu_sheets.dart +++ b/lib/widgets/festival_menu_sheets.dart @@ -187,6 +187,12 @@ class FestivalSelectorSheet extends StatelessWidget { onTap: () { provider.setFestival(festival); Navigator.pop(context); + // Update URL to reflect the new festival + try { + GoRouter.of(context).go('/${festival.id}'); + } catch (_) { + // GoRouter not available (e.g., in tests) + } }, onInfoTap: () { Navigator.pop(context); diff --git a/test/provider_test.dart b/test/provider_test.dart index 79c9f14b..195650c9 100644 --- a/test/provider_test.dart +++ b/test/provider_test.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; import 'package:cambridge_beer_festival/providers/beer_provider.dart'; import 'package:cambridge_beer_festival/services/services.dart'; @@ -145,7 +144,7 @@ void main() { expect(provider.error, isNot(contains('TimeoutException'))); }); - test('shows user-friendly message for no internet connection', () async { + test('shows generic friendly message for network-like errors', () async { final provider = BeerProvider( drinkRepository: mockDrinkRepository, festivalRepository: mockFestivalRepository, @@ -153,16 +152,15 @@ void main() { ); await provider.initialize(); - // Mock SocketException (no internet) + // On web, network errors surface as generic exceptions (not SocketException) when(mockDrinkRepository.getDrinks(any)) - .thenThrow(const SocketException('Failed host lookup')); + .thenThrow(Exception('Failed host lookup')); await provider.loadDrinks(); expect(provider.error, isNotNull); - expect(provider.error, contains('No internet connection')); - expect(provider.error, contains('check your network')); - expect(provider.error, isNot(contains('SocketException'))); + expect(provider.error, contains('Something went wrong')); + expect(provider.error, contains('try again')); expect(provider.error, isNot(contains('Failed host lookup'))); }); @@ -308,22 +306,22 @@ void main() { expect(provider.festivalsError, isNot(contains('503'))); }); - test('shows user-friendly message for festival network errors', () async { + test('shows generic friendly message for festival network errors', () async { final provider = BeerProvider( drinkRepository: mockDrinkRepository, festivalRepository: mockFestivalRepository, analyticsService: mockAnalyticsService, ); - // Mock SocketException + // On web, network errors surface as generic exceptions (not SocketException) when(mockFestivalRepository.getFestivals()) - .thenThrow(const SocketException('Network unreachable')); + .thenThrow(Exception('Network unreachable')); await provider.loadFestivals(); expect(provider.festivalsError, isNotNull); - expect(provider.festivalsError, contains('No internet connection')); - expect(provider.festivalsError, isNot(contains('SocketException'))); + expect(provider.festivalsError, contains('Something went wrong')); + expect(provider.festivalsError, isNot(contains('Network unreachable'))); }); test('shows connection message for FestivalServiceException without status', From 9243d02e1fc5afeb4655645a7c409d1d12f6f6a4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 7 Feb 2026 08:42:08 +0000 Subject: [PATCH 02/10] Add generated mock file for utf8 encoding test https://claude.ai/code/session_01Vzxb1yS3rRonMVEXEdivwx --- test/utf8_encoding_test.mocks.dart | 284 +++++++++++++++++++++++++++++ 1 file changed, 284 insertions(+) create mode 100644 test/utf8_encoding_test.mocks.dart diff --git a/test/utf8_encoding_test.mocks.dart b/test/utf8_encoding_test.mocks.dart new file mode 100644 index 00000000..95a316ed --- /dev/null +++ b/test/utf8_encoding_test.mocks.dart @@ -0,0 +1,284 @@ +// Mocks generated by Mockito 5.4.6 from annotations +// in cambridge_beer_festival/test/utf8_encoding_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i3; +import 'dart:convert' as _i4; +import 'dart:typed_data' as _i6; + +import 'package:http/http.dart' as _i2; +import 'package:mockito/mockito.dart' as _i1; +import 'package:mockito/src/dummies.dart' as _i5; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class +// ignore_for_file: invalid_use_of_internal_member + +class _FakeResponse_0 extends _i1.SmartFake implements _i2.Response { + _FakeResponse_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeStreamedResponse_1 extends _i1.SmartFake + implements _i2.StreamedResponse { + _FakeStreamedResponse_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [Client]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockClient extends _i1.Mock implements _i2.Client { + MockClient() { + _i1.throwOnMissingStub(this); + } + + @override + _i3.Future<_i2.Response> head( + Uri? url, { + Map? headers, + }) => + (super.noSuchMethod( + Invocation.method( + #head, + [url], + {#headers: headers}, + ), + returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_0( + this, + Invocation.method( + #head, + [url], + {#headers: headers}, + ), + )), + ) as _i3.Future<_i2.Response>); + + @override + _i3.Future<_i2.Response> get( + Uri? url, { + Map? headers, + }) => + (super.noSuchMethod( + Invocation.method( + #get, + [url], + {#headers: headers}, + ), + returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_0( + this, + Invocation.method( + #get, + [url], + {#headers: headers}, + ), + )), + ) as _i3.Future<_i2.Response>); + + @override + _i3.Future<_i2.Response> post( + Uri? url, { + Map? headers, + Object? body, + _i4.Encoding? encoding, + }) => + (super.noSuchMethod( + Invocation.method( + #post, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_0( + this, + Invocation.method( + #post, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + )), + ) as _i3.Future<_i2.Response>); + + @override + _i3.Future<_i2.Response> put( + Uri? url, { + Map? headers, + Object? body, + _i4.Encoding? encoding, + }) => + (super.noSuchMethod( + Invocation.method( + #put, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_0( + this, + Invocation.method( + #put, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + )), + ) as _i3.Future<_i2.Response>); + + @override + _i3.Future<_i2.Response> patch( + Uri? url, { + Map? headers, + Object? body, + _i4.Encoding? encoding, + }) => + (super.noSuchMethod( + Invocation.method( + #patch, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_0( + this, + Invocation.method( + #patch, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + )), + ) as _i3.Future<_i2.Response>); + + @override + _i3.Future<_i2.Response> delete( + Uri? url, { + Map? headers, + Object? body, + _i4.Encoding? encoding, + }) => + (super.noSuchMethod( + Invocation.method( + #delete, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_0( + this, + Invocation.method( + #delete, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + )), + ) as _i3.Future<_i2.Response>); + + @override + _i3.Future read( + Uri? url, { + Map? headers, + }) => + (super.noSuchMethod( + Invocation.method( + #read, + [url], + {#headers: headers}, + ), + returnValue: _i3.Future.value(_i5.dummyValue( + this, + Invocation.method( + #read, + [url], + {#headers: headers}, + ), + )), + ) as _i3.Future); + + @override + _i3.Future<_i6.Uint8List> readBytes( + Uri? url, { + Map? headers, + }) => + (super.noSuchMethod( + Invocation.method( + #readBytes, + [url], + {#headers: headers}, + ), + returnValue: _i3.Future<_i6.Uint8List>.value(_i6.Uint8List(0)), + ) as _i3.Future<_i6.Uint8List>); + + @override + _i3.Future<_i2.StreamedResponse> send(_i2.BaseRequest? request) => + (super.noSuchMethod( + Invocation.method( + #send, + [request], + ), + returnValue: + _i3.Future<_i2.StreamedResponse>.value(_FakeStreamedResponse_1( + this, + Invocation.method( + #send, + [request], + ), + )), + ) as _i3.Future<_i2.StreamedResponse>); + + @override + void close() => super.noSuchMethod( + Invocation.method( + #close, + [], + ), + returnValueForMissingStub: null, + ); +} From 201caad747ee29c13af7c9fc3f252934e5852961 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 7 Feb 2026 21:26:28 +0000 Subject: [PATCH 03/10] Address code review: safer router capture, web-safe network error handling - Use GoRouter.maybeOf instead of try/catch for null-safe router lookup - Capture router reference before Navigator.pop to avoid stale context - Replace SocketException with http.ClientException for cross-platform network error detection (works on web, Android, and iOS) - Restore specific "No internet connection" error message for users https://claude.ai/code/session_01Vzxb1yS3rRonMVEXEdivwx --- lib/providers/beer_provider.dart | 3 +++ lib/widgets/festival_menu_sheets.dart | 8 ++------ test/provider_test.dart | 22 ++++++++++++---------- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/lib/providers/beer_provider.dart b/lib/providers/beer_provider.dart index 7802f560..dc7c221e 100644 --- a/lib/providers/beer_provider.dart +++ b/lib/providers/beer_provider.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; import 'package:shared_preferences/shared_preferences.dart'; import '../models/models.dart'; import '../services/services.dart'; @@ -360,6 +361,8 @@ class BeerProvider extends ChangeNotifier { } else { return 'Could not load festivals. Please check your connection.'; } + } else if (error is http.ClientException) { + return 'No internet connection. Please check your network.'; } else if (error is TimeoutException) { return 'Request timed out. Please check your connection and try again.'; } else { diff --git a/lib/widgets/festival_menu_sheets.dart b/lib/widgets/festival_menu_sheets.dart index f1801beb..92f0b75a 100644 --- a/lib/widgets/festival_menu_sheets.dart +++ b/lib/widgets/festival_menu_sheets.dart @@ -185,14 +185,10 @@ class FestivalSelectorSheet extends StatelessWidget { sortedFestivals: festivals, isSelected: isSelected, onTap: () { + final router = GoRouter.maybeOf(context); provider.setFestival(festival); Navigator.pop(context); - // Update URL to reflect the new festival - try { - GoRouter.of(context).go('/${festival.id}'); - } catch (_) { - // GoRouter not available (e.g., in tests) - } + router?.go('/${festival.id}'); }, onInfoTap: () { Navigator.pop(context); diff --git a/test/provider_test.dart b/test/provider_test.dart index 195650c9..98f70831 100644 --- a/test/provider_test.dart +++ b/test/provider_test.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:flutter_test/flutter_test.dart'; import 'package:cambridge_beer_festival/providers/beer_provider.dart'; import 'package:cambridge_beer_festival/services/services.dart'; +import 'package:http/http.dart' as http; import 'package:cambridge_beer_festival/models/models.dart'; import 'package:cambridge_beer_festival/domain/models/models.dart'; import 'package:cambridge_beer_festival/domain/repositories/repositories.dart'; @@ -144,7 +145,7 @@ void main() { expect(provider.error, isNot(contains('TimeoutException'))); }); - test('shows generic friendly message for network-like errors', () async { + test('shows user-friendly message for no internet connection', () async { final provider = BeerProvider( drinkRepository: mockDrinkRepository, festivalRepository: mockFestivalRepository, @@ -152,15 +153,16 @@ void main() { ); await provider.initialize(); - // On web, network errors surface as generic exceptions (not SocketException) + // http.ClientException is thrown on network failures across all platforms when(mockDrinkRepository.getDrinks(any)) - .thenThrow(Exception('Failed host lookup')); + .thenThrow(http.ClientException('Failed host lookup')); await provider.loadDrinks(); expect(provider.error, isNotNull); - expect(provider.error, contains('Something went wrong')); - expect(provider.error, contains('try again')); + expect(provider.error, contains('No internet connection')); + expect(provider.error, contains('check your network')); + expect(provider.error, isNot(contains('ClientException'))); expect(provider.error, isNot(contains('Failed host lookup'))); }); @@ -306,22 +308,22 @@ void main() { expect(provider.festivalsError, isNot(contains('503'))); }); - test('shows generic friendly message for festival network errors', () async { + test('shows user-friendly message for festival network errors', () async { final provider = BeerProvider( drinkRepository: mockDrinkRepository, festivalRepository: mockFestivalRepository, analyticsService: mockAnalyticsService, ); - // On web, network errors surface as generic exceptions (not SocketException) + // http.ClientException is thrown on network failures across all platforms when(mockFestivalRepository.getFestivals()) - .thenThrow(Exception('Network unreachable')); + .thenThrow(http.ClientException('Network unreachable')); await provider.loadFestivals(); expect(provider.festivalsError, isNotNull); - expect(provider.festivalsError, contains('Something went wrong')); - expect(provider.festivalsError, isNot(contains('Network unreachable'))); + expect(provider.festivalsError, contains('No internet connection')); + expect(provider.festivalsError, isNot(contains('ClientException'))); }); test('shows connection message for FestivalServiceException without status', From 72e5e194db78e07768050c4dc96d53615c44b6b3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 16:04:58 +0000 Subject: [PATCH 04/10] Initial plan From 2872d6a2b05b43fad440f2b2c2ec11ec635343e8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 16:09:41 +0000 Subject: [PATCH 05/10] Use routing helpers in festival selector and preserve user's tab Co-authored-by: richardthe3rd <573334+richardthe3rd@users.noreply.github.com> --- lib/widgets/festival_menu_sheets.dart | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/widgets/festival_menu_sheets.dart b/lib/widgets/festival_menu_sheets.dart index 92f0b75a..ad9f8e75 100644 --- a/lib/widgets/festival_menu_sheets.dart +++ b/lib/widgets/festival_menu_sheets.dart @@ -188,7 +188,18 @@ class FestivalSelectorSheet extends StatelessWidget { final router = GoRouter.maybeOf(context); provider.setFestival(festival); Navigator.pop(context); - router?.go('/${festival.id}'); + + // Smart routing: preserve user's current tab + try { + final currentLocation = GoRouterState.of(context).uri.toString(); + final targetPath = currentLocation.endsWith('/favorites') + ? buildFavoritesPath(festival.id) + : buildFestivalHome(festival.id); + router?.go(targetPath); + } catch (e) { + // Fallback to festival home if GoRouterState is unavailable + router?.go(buildFestivalHome(festival.id)); + } }, onInfoTap: () { Navigator.pop(context); From 221b547b4777f35913d4ab1dea4e180bcf4c8e16 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 16:11:04 +0000 Subject: [PATCH 06/10] Address code review: capture context before pop, use more robust path check Co-authored-by: richardthe3rd <573334+richardthe3rd@users.noreply.github.com> --- lib/widgets/festival_menu_sheets.dart | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/widgets/festival_menu_sheets.dart b/lib/widgets/festival_menu_sheets.dart index ad9f8e75..8c6d52ab 100644 --- a/lib/widgets/festival_menu_sheets.dart +++ b/lib/widgets/festival_menu_sheets.dart @@ -187,19 +187,23 @@ class FestivalSelectorSheet extends StatelessWidget { onTap: () { final router = GoRouter.maybeOf(context); provider.setFestival(festival); - Navigator.pop(context); // Smart routing: preserve user's current tab + // Capture current path before popping context + String targetPath; try { - final currentLocation = GoRouterState.of(context).uri.toString(); - final targetPath = currentLocation.endsWith('/favorites') + final currentPath = GoRouterState.of(context).uri.path; + // Check if user is on favorites tab (more robust than endsWith) + targetPath = currentPath.contains('/favorites') ? buildFavoritesPath(festival.id) : buildFestivalHome(festival.id); - router?.go(targetPath); } catch (e) { // Fallback to festival home if GoRouterState is unavailable - router?.go(buildFestivalHome(festival.id)); + targetPath = buildFestivalHome(festival.id); } + + Navigator.pop(context); + router?.go(targetPath); }, onInfoTap: () { Navigator.pop(context); From 7559252cb5529e971c58c8a9d90c36dfbcdb3d78 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 16:12:15 +0000 Subject: [PATCH 07/10] Use precise endsWith check and add debug logging Co-authored-by: richardthe3rd <573334+richardthe3rd@users.noreply.github.com> --- lib/widgets/festival_menu_sheets.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/widgets/festival_menu_sheets.dart b/lib/widgets/festival_menu_sheets.dart index 8c6d52ab..c715f09a 100644 --- a/lib/widgets/festival_menu_sheets.dart +++ b/lib/widgets/festival_menu_sheets.dart @@ -193,12 +193,13 @@ class FestivalSelectorSheet extends StatelessWidget { String targetPath; try { final currentPath = GoRouterState.of(context).uri.path; - // Check if user is on favorites tab (more robust than endsWith) - targetPath = currentPath.contains('/favorites') + // Check if user is on favorites tab (path ends with /favorites) + targetPath = currentPath.endsWith('/favorites') ? buildFavoritesPath(festival.id) : buildFestivalHome(festival.id); } catch (e) { - // Fallback to festival home if GoRouterState is unavailable + // Fallback to festival home if GoRouterState is unavailable (e.g., in tests) + debugPrint('Festival selector: Unable to get current route, defaulting to home: $e'); targetPath = buildFestivalHome(festival.id); } From 82383c2a7190d89c390056873b4b0f2314bfb2f2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 16:13:40 +0000 Subject: [PATCH 08/10] Capture GoRouterState first to ensure context validity Co-authored-by: richardthe3rd <573334+richardthe3rd@users.noreply.github.com> --- lib/widgets/festival_menu_sheets.dart | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/widgets/festival_menu_sheets.dart b/lib/widgets/festival_menu_sheets.dart index c715f09a..bc81363c 100644 --- a/lib/widgets/festival_menu_sheets.dart +++ b/lib/widgets/festival_menu_sheets.dart @@ -185,11 +185,7 @@ class FestivalSelectorSheet extends StatelessWidget { sortedFestivals: festivals, isSelected: isSelected, onTap: () { - final router = GoRouter.maybeOf(context); - provider.setFestival(festival); - - // Smart routing: preserve user's current tab - // Capture current path before popping context + // Capture current path first, before any state changes String targetPath; try { final currentPath = GoRouterState.of(context).uri.path; @@ -203,6 +199,8 @@ class FestivalSelectorSheet extends StatelessWidget { targetPath = buildFestivalHome(festival.id); } + final router = GoRouter.maybeOf(context); + provider.setFestival(festival); Navigator.pop(context); router?.go(targetPath); }, From d3796282a84a565cdea5d13593ce50745b99f70e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 16:14:46 +0000 Subject: [PATCH 09/10] Simplify error handling with initialized targetPath default Co-authored-by: richardthe3rd <573334+richardthe3rd@users.noreply.github.com> --- lib/widgets/festival_menu_sheets.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/widgets/festival_menu_sheets.dart b/lib/widgets/festival_menu_sheets.dart index bc81363c..3fe31c78 100644 --- a/lib/widgets/festival_menu_sheets.dart +++ b/lib/widgets/festival_menu_sheets.dart @@ -186,17 +186,17 @@ class FestivalSelectorSheet extends StatelessWidget { isSelected: isSelected, onTap: () { // Capture current path first, before any state changes - String targetPath; + // Default to festival home if GoRouterState is unavailable + String targetPath = buildFestivalHome(festival.id); try { final currentPath = GoRouterState.of(context).uri.path; // Check if user is on favorites tab (path ends with /favorites) - targetPath = currentPath.endsWith('/favorites') - ? buildFavoritesPath(festival.id) - : buildFestivalHome(festival.id); + if (currentPath.endsWith('/favorites')) { + targetPath = buildFavoritesPath(festival.id); + } } catch (e) { - // Fallback to festival home if GoRouterState is unavailable (e.g., in tests) + // GoRouterState unavailable (e.g., in tests), use default debugPrint('Festival selector: Unable to get current route, defaulting to home: $e'); - targetPath = buildFestivalHome(festival.id); } final router = GoRouter.maybeOf(context); From 6bd24cef79a2278b2c26b5591febbcdd8d6266c9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 16:15:58 +0000 Subject: [PATCH 10/10] Improve code comments for clarity Co-authored-by: richardthe3rd <573334+richardthe3rd@users.noreply.github.com> --- lib/widgets/festival_menu_sheets.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/widgets/festival_menu_sheets.dart b/lib/widgets/festival_menu_sheets.dart index 3fe31c78..246fb3a3 100644 --- a/lib/widgets/festival_menu_sheets.dart +++ b/lib/widgets/festival_menu_sheets.dart @@ -186,17 +186,17 @@ class FestivalSelectorSheet extends StatelessWidget { isSelected: isSelected, onTap: () { // Capture current path first, before any state changes - // Default to festival home if GoRouterState is unavailable + // Default to festival home; override if currently on favorites String targetPath = buildFestivalHome(festival.id); try { final currentPath = GoRouterState.of(context).uri.path; - // Check if user is on favorites tab (path ends with /favorites) + // Preserve user's tab: if on favorites, stay on favorites if (currentPath.endsWith('/favorites')) { targetPath = buildFavoritesPath(festival.id); } } catch (e) { - // GoRouterState unavailable (e.g., in tests), use default - debugPrint('Festival selector: Unable to get current route, defaulting to home: $e'); + // GoRouterState unavailable (e.g., in tests), keep default path + debugPrint('Festival selector: Unable to get current route, using default: $e'); } final router = GoRouter.maybeOf(context);