Skip to content

Commit b75b736

Browse files
committed
feat: filter history based on query
1 parent a346d77 commit b75b736

File tree

6 files changed

+121
-14
lines changed

6 files changed

+121
-14
lines changed

lib/presentation/history/bloc/history_bloc.dart

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ part 'history_state.dart';
1010
class HistoryBloc extends Bloc<HistoryEvent, HistoryState> {
1111
HistoryBloc(this.historyRepository) : super(const HistoryState()) {
1212
on<HistoryFetched>(_onHistoryFetched);
13+
on<HistoryFiltered>(_onHistoryFiltered);
1314
}
1415
final HistoryRepository historyRepository;
1516

@@ -21,9 +22,22 @@ class HistoryBloc extends Bloc<HistoryEvent, HistoryState> {
2122
emit(state.loading());
2223
// Fetch history from the database
2324
final history = await historyRepository.fetchTranslationHistory();
24-
emit(state.success(history));
25+
26+
emit(state.fetchSuccess(history));
2527
} on Exception catch (e) {
2628
emit(state.error(e));
2729
}
2830
}
31+
32+
void _onHistoryFiltered(
33+
HistoryFiltered event,
34+
Emitter<HistoryState> emit,
35+
) {
36+
final filteredHistory = historyRepository.filterHistory(
37+
state.history,
38+
event.query,
39+
);
40+
41+
emit(state.filterSuccess(filteredHistory, state.history));
42+
}
2943
}

lib/presentation/history/bloc/history_event.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ final class HistoryFetched extends HistoryEvent {
1515
List<Object?> get props => [];
1616
}
1717

18+
final class HistoryFiltered extends HistoryEvent {
19+
const HistoryFiltered(this.query);
20+
final String query;
21+
22+
@override
23+
List<Object?> get props => [query];
24+
}
25+
1826
final class HistoryDeleted extends HistoryEvent {
1927
const HistoryDeleted(this.historyId);
2028
final int historyId;

lib/presentation/history/bloc/history_state.dart

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@ final class HistoryState extends Equatable {
1818
const HistoryState({
1919
this.method = HistoryMethod.initial,
2020
this.status = HistoryStatus.initial,
21+
this.filteredHistory = const [],
2122
this.history = const [],
2223
this.exception,
2324
});
2425

2526
final HistoryMethod method;
2627
final HistoryStatus status;
28+
final List<TranslationHistory> filteredHistory;
2729
final List<TranslationHistory> history;
2830
final Exception? exception;
2931

@@ -32,17 +34,30 @@ final class HistoryState extends Equatable {
3234
history: history,
3335
);
3436

35-
HistoryState success(List<TranslationHistory> history) => HistoryState(
37+
HistoryState fetchSuccess(List<TranslationHistory> history) => HistoryState(
3638
status: HistoryStatus.success,
3739
history: history,
40+
filteredHistory: history,
3841
);
3942

43+
HistoryState filterSuccess(
44+
List<TranslationHistory> filteredHistory,
45+
List<TranslationHistory> history,
46+
) {
47+
return HistoryState(
48+
status: HistoryStatus.success,
49+
filteredHistory: filteredHistory,
50+
history: history,
51+
);
52+
}
53+
4054
HistoryState error(Exception e) => HistoryState(
4155
status: HistoryStatus.error,
4256
history: history,
4357
exception: e,
4458
);
4559

4660
@override
47-
List<Object?> get props => [method, status, history, exception];
61+
List<Object?> get props =>
62+
[method, status, filteredHistory, history, exception];
4863
}

lib/presentation/history/repositories/history_repository.dart

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,32 @@ final class HistoryRepository {
2626
}).toList(),
2727
);
2828
}
29+
30+
List<TranslationHistory> filterHistory(
31+
List<TranslationHistory> history,
32+
String query,
33+
) {
34+
if (query.isEmpty) return history;
35+
36+
final queryLower = query.toLowerCase().trim();
37+
38+
final matchingInput = history.where((entry) {
39+
final inputLower = entry.input.toLowerCase();
40+
return inputLower.contains(queryLower);
41+
}).toList();
42+
43+
// Include entries where any translation output matches the query
44+
// TODO: Rank the outputs based on how well the output matches the query
45+
final matchingOutput = history.where((entry) {
46+
// ignore: omit_local_variable_types
47+
for (final TranslationHistoryItem item in entry.items ?? []) {
48+
if (item.output.toLowerCase().contains(queryLower)) {
49+
return true;
50+
}
51+
}
52+
return false;
53+
}).toList();
54+
55+
return <TranslationHistory>{...matchingInput, ...matchingOutput}.toList();
56+
}
2957
}

lib/presentation/history/view/history_page.dart

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import 'package:flutter/material.dart';
22
import 'package:flutter_bloc/flutter_bloc.dart';
3+
import 'package:qack/constants/constants.dart';
34
import 'package:qack/layout/layout_handler.dart';
45
import 'package:qack/presentation/history/bloc/history_bloc.dart';
56
import 'package:qack/presentation/history/repositories/history_repository.dart';
7+
import 'package:qack/presentation/settings/models/translator_details.dart';
68
import 'package:qack/theme/theme.dart';
79
import 'package:qack/utils/database/database.dart';
810
import 'package:qack/widgets/input/input.dart';
@@ -17,15 +19,29 @@ class HistoryPage extends StatelessWidget {
1719
HistoryRepository(appDatabase: context.read<AppDatabase>()),
1820
),
1921
child: const LayoutHandler(
20-
mobile: HistoryView(),
21-
tablet: HistoryView(),
22+
mobile: HistoryView(
23+
searchBarPadding: EdgeInsets.only(
24+
top: 16,
25+
left: PaddingConstants.mobileHorizontalMarginValue,
26+
right: PaddingConstants.mobileHorizontalMarginValue,
27+
),
28+
),
29+
tablet: HistoryView(
30+
searchBarPadding: EdgeInsets.only(
31+
top: 24,
32+
left: PaddingConstants.tabletHorizontalMarginValue,
33+
right: PaddingConstants.tabletHorizontalMarginValue,
34+
),
35+
),
2236
),
2337
);
2438
}
2539
}
2640

2741
class HistoryView extends StatefulWidget {
28-
const HistoryView({super.key});
42+
const HistoryView({required this.searchBarPadding, super.key});
43+
44+
final EdgeInsets searchBarPadding;
2945

3046
@override
3147
State<HistoryView> createState() => _HistoryViewState();
@@ -45,10 +61,15 @@ class _HistoryViewState extends State<HistoryView> {
4561
body: SafeArea(
4662
child: CustomScrollView(
4763
slivers: [
48-
const SliverPadding(
49-
padding: EdgeInsets.only(top: 16),
64+
SliverPadding(
65+
padding: widget.searchBarPadding,
5066
sliver: SliverToBoxAdapter(
51-
child: AppSearchBar(),
67+
child: AppSearchBar(
68+
onChanged: (query) {
69+
context.read<HistoryBloc>().add(HistoryFiltered(query));
70+
},
71+
hintText: 'Search',
72+
),
5273
),
5374
),
5475
SliverPadding(
@@ -70,7 +91,7 @@ class _HistoryViewState extends State<HistoryView> {
7091
),
7192
),
7293
);
73-
} else if (state.history.isEmpty) {
94+
} else if (state.filteredHistory.isEmpty) {
7495
// TODO: only show this text if history and filter
7596
// are empty
7697
return const Center(
@@ -79,18 +100,22 @@ class _HistoryViewState extends State<HistoryView> {
79100
}
80101

81102
// Temporary history
82-
final translationHistory = state.history[index];
103+
final translationHistory = state.filteredHistory[index];
83104
return ListTile(
84105
title: Text(translationHistory.input),
85106
subtitle: Text(
86107
translationHistory.items
87-
?.map((item) => item.output)
88-
.join('\n') ??
108+
?.map(
109+
(item) =>
110+
'${item.translator.toTranslator()}: '
111+
'${item.output}',
112+
)
113+
.join('\n\n') ??
89114
'',
90115
),
91116
);
92117
},
93-
childCount: state.history.length,
118+
childCount: state.filteredHistory.length,
94119
),
95120
);
96121
},

lib/presentation/settings/models/translator_details.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,23 @@ enum Translator {
1212
youDao,
1313
}
1414

15+
extension TranslatorIntExt on int {
16+
String toTranslator() {
17+
switch (this) {
18+
case 0:
19+
return Translator.google.name;
20+
case 1:
21+
return Translator.baidu.name;
22+
case 2:
23+
return Translator.deepSeek.name;
24+
case 3:
25+
return Translator.youDao.name;
26+
default:
27+
throw ArgumentError('Invalid translator index: $this');
28+
}
29+
}
30+
}
31+
1532
typedef TranslatorApiKeys = Map<String, String>;
1633

1734
@freezed

0 commit comments

Comments
 (0)