word_search: add history count bubble in appbar

This commit is contained in:
2025-07-14 19:02:57 +02:00
parent 072f855c13
commit ce78bed1a6
3 changed files with 87 additions and 4 deletions

View File

@@ -6,6 +6,7 @@
- Added infinite scroll for word search results.
- Added infinite scroll for history view.
- Added a bubble in the top bar of word search results to indicate how many times it has been searched before.
## Changes 🔧

View File

@@ -8,17 +8,20 @@ class HistoryEntry {
final String? kanji;
final String? word;
final DateTime lastTimestamp;
final int? timestampCount;
int? _timestampCount;
/// Whether this item is a kanji search or a word search
bool get isKanji => word == null;
int? get timestampCount => _timestampCount;
HistoryEntry.withKanji({
required this.id,
required this.kanji,
required this.lastTimestamp,
this.timestampCount,
timestampCount,
}) : word = null,
_timestampCount = timestampCount,
assert(
kanji!.runes.length == 1,
'Kanji must be a single character',
@@ -28,8 +31,9 @@ class HistoryEntry {
required this.id,
required this.word,
required this.lastTimestamp,
this.timestampCount,
timestampCount,
}) : kanji = null,
_timestampCount = timestampCount,
assert(
word == word!.trim(),
'Word must not contain leading or trailing whitespace',
@@ -67,6 +71,40 @@ class HistoryEntry {
: null,
);
static Future<HistoryEntry?> getWord(
DatabaseExecutor db,
String word,
) async {
final result = await db.query(
HistoryTableNames.historyEntryOrderedByTimestamp,
where: 'word = ?',
whereArgs: [word],
);
if (result.isEmpty) {
return null;
}
return HistoryEntry.fromDBMap(result.first);
}
static Future<HistoryEntry?> getKanji(
DatabaseExecutor db,
String kanji,
) async {
final result = await db.query(
HistoryTableNames.historyEntryOrderedByTimestamp,
where: 'kanji = ?',
whereArgs: [kanji],
);
if (result.isEmpty) {
return null;
}
return HistoryEntry.fromDBMap(result.first);
}
// TODO: There is a lot in common with
// insertKanji,
// insertWord,
@@ -395,6 +433,19 @@ class HistoryEntry {
return result.map((e) => HistoryEntry.fromDBMap(e)).toList();
}
Future<void> populateTimestampCount(DatabaseExecutor db) async {
final result = await db.query(
columns: ['COUNT(*) AS count'],
HistoryTableNames.historyEntryTimestamp,
where: 'entryId = ?',
whereArgs: [id],
);
final count = result.firstOrNull?['count'] as int? ?? 0;
_timestampCount = count;
}
Future<void> delete(DatabaseExecutor db) => db.delete(
HistoryTableNames.historyEntry,
where: 'id = ?',

View File

@@ -1,9 +1,12 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:jadb/models/word_search/word_search_result.dart';
import 'package:jadb/search.dart' show JaDBConnection;
import 'package:mdi/mdi.dart';
import 'package:mugiten/bloc/theme/theme_bloc.dart';
import 'package:mugiten/components/search/search_results_body/parts/circle_badge.dart';
import 'package:mugiten/models/history/history_entry.dart';
import 'package:mugiten/services/snackbar.dart';
import 'package:mugiten/settings.dart';
@@ -28,6 +31,7 @@ class WordSearchResultPage extends StatefulWidget {
class _WordSearchResultPageState extends State<WordSearchResultPage> {
bool addedToDatabase = false;
HistoryEntry? historyEntry;
late final _pagingController = PagingController<int, WordSearchResult>(
getNextPageKey: (state) =>
@@ -50,8 +54,25 @@ class _WordSearchResultPageState extends State<WordSearchResultPage> {
HistoryEntry.insertWord(
db: GetIt.instance.get<Database>(),
word: widget.searchTerm,
).then((historyEntry) async {
await historyEntry
.populateTimestampCount(GetIt.instance.get<Database>());
return historyEntry;
}).then(
(entry) => setState(() {
addedToDatabase = true;
historyEntry = entry;
}),
);
} else {
HistoryEntry.getWord(
GetIt.instance.get<Database>(),
widget.searchTerm,
).then(
(entry) => setState(() {
historyEntry = entry;
}),
);
addedToDatabase = true;
}
}
@@ -73,6 +94,16 @@ class _WordSearchResultPageState extends State<WordSearchResultPage> {
onPressed: () =>
showSnackbar(context, 'History tracking is disabled'),
),
if (historyEntry != null && (historyEntry!.timestampCount ?? 0) > 1)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: BlocBuilder<ThemeBloc, ThemeState>(
builder: (context, themeState) => CircleBadge(
color: themeState.theme.menuGreyNormal.background,
child: Text('${historyEntry!.timestampCount}'),
),
),
),
],
),
body: FutureBuilder(