Skip to content

Commit 634e94e

Browse files
authored
Add search to HTTP logs (#2852)
1 parent c96f947 commit 634e94e

File tree

3 files changed

+64
-14
lines changed

3 files changed

+64
-14
lines changed

lib/src/model/log/http_log_paginator.dart

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,30 @@ part 'http_log_paginator.freezed.dart';
99
const _pageSize = 20;
1010

1111
/// A provider for [HttpLogPaginator].
12-
final httpLogPaginatorProvider = AsyncNotifierProvider.autoDispose<HttpLogPaginator, HttpLogState>(
13-
HttpLogPaginator.new,
14-
name: 'HttpLogPaginatorProvider',
15-
);
12+
///
13+
/// The family argument is the optional search query string.
14+
final httpLogPaginatorProvider = AsyncNotifierProvider.autoDispose
15+
.family<HttpLogPaginator, HttpLogState, String?>(
16+
HttpLogPaginator.new,
17+
name: 'HttpLogPaginatorProvider',
18+
);
1619

1720
/// A Riverpod controller for managing HTTP logs.
18-
///
1921
/// The `HttpLogController` class is responsible for fetching and managing
2022
/// paginated HTTP log entries from the storage. It uses a throttler to limit
2123
/// the rate of fetching new pages.
2224
class HttpLogPaginator extends AsyncNotifier<HttpLogState> {
25+
HttpLogPaginator(this._searchQuery);
26+
27+
final String? _searchQuery;
28+
2329
@override
2430
Future<HttpLogState> build() async {
2531
final storage = await ref.read(httpLogStorageProvider.future);
2632
return HttpLogState(
27-
data: IList.new([await AsyncValue.guard(() => storage.page(limit: _pageSize))]),
33+
data: IList.new([
34+
await AsyncValue.guard(() => storage.page(limit: _pageSize, searchQuery: _searchQuery)),
35+
]),
2836
);
2937
}
3038

@@ -36,7 +44,11 @@ class HttpLogPaginator extends AsyncNotifier<HttpLogState> {
3644
if (state.hasValue && state.requireValue.hasMore) {
3745
final storage = await ref.read(httpLogStorageProvider.future);
3846
final asyncPage = await AsyncValue.guard(
39-
() => storage.page(limit: _pageSize, cursor: state.requireValue.nextPage),
47+
() => storage.page(
48+
limit: _pageSize,
49+
cursor: state.requireValue.nextPage,
50+
searchQuery: _searchQuery,
51+
),
4052
);
4153
state = AsyncValue.data(
4254
state.requireValue.copyWith(data: state.requireValue.data.add(asyncPage)),

lib/src/model/log/http_log_storage.dart

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,28 @@ class HttpLogStorage {
2222
final Database _db;
2323

2424
/// Retrieves a paginated list of [HttpLogEntry] entries from the database.
25-
Future<HttpLog> page({int? cursor, int limit = 100}) async {
25+
///
26+
/// [searchQuery] filters entries whose request method, URL, or error message contain the query.
27+
Future<HttpLog> page({int? cursor, String? searchQuery, int limit = 100}) async {
28+
final whereParts = <String>[];
29+
final args = <dynamic>[];
30+
31+
if (cursor != null) {
32+
whereParts.add('id <= ?');
33+
args.add(cursor);
34+
}
35+
if (searchQuery != null && searchQuery.isNotEmpty) {
36+
whereParts.add('(requestMethod LIKE ? OR requestUrl LIKE ? OR errorMessage LIKE ?)');
37+
final pattern = '%$searchQuery%';
38+
args.addAll([pattern, pattern, pattern]);
39+
}
40+
2641
final res = await _db.query(
2742
kHttpLogStorageTable,
2843
limit: limit + 1,
2944
orderBy: 'id DESC',
30-
where: cursor != null ? 'id <= $cursor' : null,
45+
where: whereParts.isNotEmpty ? whereParts.join(' AND ') : null,
46+
whereArgs: args.isNotEmpty ? args : null,
3147
);
3248
return HttpLog(
3349
items: res.take(limit).map(HttpLogEntry.fromJson).toIList(),

lib/src/view/settings/http_log_screen.dart

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'package:lichess_mobile/src/styles/styles.dart';
88
import 'package:lichess_mobile/src/utils/navigation.dart';
99
import 'package:lichess_mobile/src/widgets/adaptive_action_sheet.dart';
1010
import 'package:lichess_mobile/src/widgets/haptic_refresh_indicator.dart';
11+
import 'package:lichess_mobile/src/widgets/platform_search_bar.dart';
1112

1213
class HttpLogScreen extends ConsumerStatefulWidget {
1314
const HttpLogScreen({super.key});
@@ -23,6 +24,8 @@ class HttpLogScreen extends ConsumerStatefulWidget {
2324
class _HttpLogScreenState extends ConsumerState<HttpLogScreen> {
2425
final ScrollController _scrollController = ScrollController();
2526
final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey = GlobalKey<RefreshIndicatorState>();
27+
final TextEditingController _searchController = TextEditingController();
28+
String? _searchQuery;
2629

2730
@override
2831
void initState() {
@@ -34,26 +37,27 @@ class _HttpLogScreenState extends ConsumerState<HttpLogScreen> {
3437
void dispose() {
3538
_scrollController.removeListener(_scrollListener);
3639
_scrollController.dispose();
40+
_searchController.dispose();
3741
super.dispose();
3842
}
3943

4044
void _scrollListener() {
4145
if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 300) {
42-
final currentState = ref.read(httpLogPaginatorProvider);
46+
final currentState = ref.read(httpLogPaginatorProvider(_searchQuery));
4347
if (currentState.hasValue && !currentState.isLoading && currentState.requireValue.hasMore) {
44-
ref.read(httpLogPaginatorProvider.notifier).next();
48+
ref.read(httpLogPaginatorProvider(_searchQuery).notifier).next();
4549
}
4650
}
4751
}
4852

4953
Future<void> _onRefresh() async {
5054
await Future<void>.delayed(const Duration(milliseconds: 300));
51-
return ref.read(httpLogPaginatorProvider.notifier).refresh();
55+
return ref.read(httpLogPaginatorProvider(_searchQuery).notifier).refresh();
5256
}
5357

5458
@override
5559
Widget build(BuildContext context) {
56-
final asyncState = ref.watch(httpLogPaginatorProvider);
60+
final asyncState = ref.watch(httpLogPaginatorProvider(_searchQuery));
5761
return Scaffold(
5862
appBar: AppBar(
5963
title: const Text('HTTP logs'),
@@ -68,11 +72,29 @@ class _HttpLogScreenState extends ConsumerState<HttpLogScreen> {
6872
context,
6973
// TODO localize
7074
title: const Text('Delete all logs'),
71-
onConfirm: () => ref.read(httpLogPaginatorProvider.notifier).deleteAll(),
75+
onConfirm: () =>
76+
ref.read(httpLogPaginatorProvider(_searchQuery).notifier).deleteAll(),
7277
);
7378
},
7479
),
7580
],
81+
bottom: PreferredSize(
82+
preferredSize: const Size.fromHeight(60.0),
83+
child: Padding(
84+
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
85+
child: PlatformSearchBar(
86+
controller: _searchController,
87+
hintText: 'Search logs...',
88+
onChanged: (value) => setState(() {
89+
_searchQuery = value.isEmpty ? null : value;
90+
}),
91+
onClear: () => setState(() {
92+
_searchQuery = null;
93+
_searchController.clear();
94+
}),
95+
),
96+
),
97+
),
7698
),
7799
body: _HttpLogList(
78100
scrollController: _scrollController,

0 commit comments

Comments
 (0)