Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
136 changes: 96 additions & 40 deletions lib/src/view/broadcast/broadcast_boards_tab.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import 'package:lichess_mobile/src/view/broadcast/broadcast_game_screen.dart';
import 'package:lichess_mobile/src/view/broadcast/broadcast_player_widget.dart';
import 'package:lichess_mobile/src/widgets/board_thumbnail.dart';
import 'package:lichess_mobile/src/widgets/clock.dart';
import 'package:lichess_mobile/src/widgets/platform_search_bar.dart';
import 'package:visibility_detector/visibility_detector.dart';

// height of 1.0 is important because we need to determine the height of the text
Expand Down Expand Up @@ -74,7 +75,7 @@ class BroadcastBoardsTab extends ConsumerWidget {
}
}

class BroadcastPreview extends ConsumerWidget {
class BroadcastPreview extends ConsumerStatefulWidget {
const BroadcastPreview({
required this.tournamentId,
required this.roundId,
Expand All @@ -99,9 +100,28 @@ class BroadcastPreview extends ConsumerWidget {
final String title;
final String tournamentSlug;
final String roundSlug;
@override
ConsumerState<BroadcastPreview> createState() => _BroadcastPreviewState();
}

class _BroadcastPreviewState extends ConsumerState<BroadcastPreview> {
String _searchQuery = '';
late final TextEditingController _searchController;

@override
Widget build(BuildContext context, WidgetRef ref) {
void initState() {
super.initState();
_searchController = TextEditingController();
}

@override
void dispose() {
_searchController.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
final showEvaluationBar = ref.watch(
broadcastPreferencesProvider.select((value) => value.showEvaluationBar),
);
Expand All @@ -119,48 +139,79 @@ class BroadcastPreview extends ConsumerWidget {
Styles.horizontalBodyPadding.horizontal -
(numberOfBoardsByRow - 1) * boardSpacing) /
numberOfBoardsByRow;
final IList<BroadcastGame>? games = widget.games == null
? null
: _searchQuery.isEmpty
? widget.games
: widget.games!.where((game) => _containsPlayer(game, _searchQuery)).toIList();
final showSearchBar = widget.games != null && widget.games!.length > 6;

return GridView.builder(
padding: MediaQuery.paddingOf(context).add(Styles.bodyPadding),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: numberOfBoardsByRow,
crossAxisSpacing: boardSpacing,
mainAxisSpacing: boardSpacing,
mainAxisExtent: boardWithMaybeEvalBarWidth + 2 * headerAndFooterHeight,
childAspectRatio: 1 + boardThumbnailEvalGaugeAspectRatio,
),
itemCount: games == null ? numberLoadingBoards : games!.length,
itemBuilder: (context, index) {
final boardSize =
boardWithMaybeEvalBarWidth -
(showEvaluationBar
? boardThumbnailEvalGaugeAspectRatio * boardWithMaybeEvalBarWidth
: 0);
return CustomScrollView(
slivers: [
if (showSearchBar)
SliverPadding(
padding: Styles.bodyPadding.copyWith(bottom: 0.0),
sliver: SliverToBoxAdapter(
child: PlatformSearchBar(
controller: _searchController,
onChanged: (value) {
setState(() {
_searchQuery = value;
});
},
onClear: () {
_searchController.clear();
setState(() {
_searchQuery = '';
});
},
),
),
),
SliverPadding(
padding: MediaQuery.paddingOf(context).add(Styles.bodyPadding),
sliver: SliverGrid(
delegate: SliverChildBuilderDelegate((context, index) {
final boardSize =
boardWithMaybeEvalBarWidth -
(showEvaluationBar
? boardThumbnailEvalGaugeAspectRatio * boardWithMaybeEvalBarWidth
: 0);

if (games == null) {
return BoardThumbnail.loading(
size: boardSize,
header: _PlayerWidgetLoading(width: boardWithMaybeEvalBarWidth),
footer: _PlayerWidgetLoading(width: boardWithMaybeEvalBarWidth),
);
}
if (games == null) {
return BoardThumbnail.loading(
size: boardSize,
header: _PlayerWidgetLoading(width: boardWithMaybeEvalBarWidth),
footer: _PlayerWidgetLoading(width: boardWithMaybeEvalBarWidth),
);
}

final game = games![index];
final playingSide = Setup.parseFen(game.fen).turn;
final game = games[index];
final playingSide = Setup.parseFen(game.fen).turn;

return ObservedBoardThumbnail(
roundId: roundId,
game: game,
title: title,
tournamentId: tournamentId,
tournamentSlug: tournamentSlug,
roundSlug: roundSlug,
showEvaluationBar: showEvaluationBar,
boardSize: boardSize,
boardWithMaybeEvalBarWidth: boardWithMaybeEvalBarWidth,
playingSide: playingSide,
);
},
return ObservedBoardThumbnail(
roundId: widget.roundId,
game: game,
title: widget.title,
tournamentId: widget.tournamentId,
tournamentSlug: widget.tournamentSlug,
roundSlug: widget.roundSlug,
showEvaluationBar: showEvaluationBar,
boardSize: boardSize,
boardWithMaybeEvalBarWidth: boardWithMaybeEvalBarWidth,
playingSide: playingSide,
);
}, childCount: games == null ? numberLoadingBoards : games.length),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: numberOfBoardsByRow,
crossAxisSpacing: boardSpacing,
mainAxisSpacing: boardSpacing,
mainAxisExtent: boardWithMaybeEvalBarWidth + 2 * headerAndFooterHeight,
childAspectRatio: 1 + boardThumbnailEvalGaugeAspectRatio,
),
),
),
],
);
}
}
Expand Down Expand Up @@ -340,3 +391,8 @@ class _PlayerWidget extends StatelessWidget {
);
}
}

bool _containsPlayer(BroadcastGame game, String query) {
final q = query.toLowerCase();
return game.players.values.any((pwc) => pwc.player.name?.toLowerCase().contains(q) ?? false);
}
1 change: 1 addition & 0 deletions lib/src/widgets/platform_search_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ class _PlatformSearchBarState extends State<PlatformSearchBar> {
onChanged: widget.onChanged,
hintText: widget.hintText,
autoFocus: widget.autoFocus,
textInputAction: TextInputAction.search,
);
}
}
44 changes: 44 additions & 0 deletions test/view/broadcast/broadcast_round_screen_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,50 @@ void main() {

expect(find.text('00:07'), findsOneWidget);
});

testWidgets('Test search by player names', variant: kPlatformVariant, (tester) async {
final app = await makeTestProviderScopeApp(
tester,
home: BroadcastRoundScreen(broadcast: _liveBroadcast),
overrides: [
lichessClientProvider.overrideWith((ref) => LichessClient(_liveBroadcastClient(), ref)),
],
);

await tester.pumpWidget(app);

expect(find.byType(CircularProgressIndicator), findsOneWidget);

// Load the tournament
await tester.pump();

expect(find.byType(CircularProgressIndicator), findsOneWidget);

// Load the round
await tester.pump();

expect(find.byType(BoardThumbnail), findsAtLeastNWidgets(6));
// Enter a query that doesn't match any player
final searchField = find.byType(SearchBar);
expect(searchField, findsOneWidget);

await tester.enterText(searchField, 'aaaaaa');
await tester.pump();
// Filtered to zero results
expect(find.byType(BoardThumbnail), findsNothing);

await tester.enterText(searchField, 'giri');
await tester.pump();
// Filtered to one result
expect(find.byType(BoardThumbnail), findsOneWidget);
expect(find.text('Giri, Anish'), findsOneWidget);

await tester.enterText(searchField, '');
await tester.pump();
// Clear the field -> results restored
await tester.pump();
expect(find.byType(BoardThumbnail), findsAtLeastNWidgets(6));
});
});

group('Test overview tab', () {
Expand Down