feat(library): Import and Refresh behind one menu button

The library's top bar had a button each for Import and Refresh. Pulling
the list down refreshes too, and is the quicker way on a phone, so both
now sit in a menu ("Library options"), leaving the bar to Search.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-12 06:51:16 +02:00
commit 25e73c8c80
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
9 changed files with 70 additions and 18 deletions

View file

@ -48,6 +48,7 @@
"studysetCountNotes": "{count, plural, =1{1 Notiz} other{{count} Notizen}}",
"studysetCountFiles": "{count, plural, =1{1 Datei} other{{count} Dateien}}",
"libraryRefresh": "Aktualisieren",
"libraryOptions": "Optionen der Bibliothek",
"libraryImport": "Importieren",
"libraryImportAnkiHint": "Wird ein eigenes Lernset, aus einer .apkg- oder .colpkg-Datei",
"libraryImportArchive": "Halkyon-Archiv",

View file

@ -54,6 +54,7 @@
"studysetCountFiles": "{count, plural, =1{1 file} other{{count} files}}",
"@studysetCountFiles": {"placeholders": {"count": {"type": "int"}}},
"libraryRefresh": "Refresh",
"libraryOptions": "Library options",
"libraryImport": "Import",
"libraryImportAnkiHint": "Becomes a studyset of its own, from an .apkg or .colpkg file",
"libraryImportArchive": "Halkyon archive",

View file

@ -346,6 +346,12 @@ abstract class AppLocalizations {
/// **'Refresh'**
String get libraryRefresh;
/// No description provided for @libraryOptions.
///
/// In en, this message translates to:
/// **'Library options'**
String get libraryOptions;
/// No description provided for @libraryImport.
///
/// In en, this message translates to:

View file

@ -191,6 +191,9 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get libraryRefresh => 'Aktualisieren';
@override
String get libraryOptions => 'Optionen der Bibliothek';
@override
String get libraryImport => 'Importieren';

View file

@ -189,6 +189,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get libraryRefresh => 'Refresh';
@override
String get libraryOptions => 'Library options';
@override
String get libraryImport => 'Import';

View file

@ -158,6 +158,12 @@ class _LibraryState() extends State<_Library> {
}
}
/// Syncs, and asks again which studysets are published.
void _refresh(BuildContext context) {
context.read<SyncStatusBloc>().add(const SyncStatusEventSyncRequested());
unawaited(context.read<PublishedCubit>().refresh());
}
Future<void> _create(BuildContext context) async {
final bloc = context.read<StudysetListBloc>();
final content = await showStudysetSheet(context);
@ -207,20 +213,38 @@ class _LibraryState() extends State<_Library> {
),
onPressed: _toggleSearch,
),
IconButton(
tooltip: l10n.libraryImport,
icon: const Icon(LucideIcons.fileInput),
onPressed: () => unawaited(_import(context)),
),
IconButton(
tooltip: l10n.libraryRefresh,
icon: const Icon(LucideIcons.refreshCw),
onPressed: () {
context.read<SyncStatusBloc>().add(
const SyncStatusEventSyncRequested(),
);
unawaited(context.read<PublishedCubit>().refresh());
// Import and Refresh behind one button: pulling the list
// down refreshes too, and is the quicker way on a phone.
PopupMenuButton<_LibraryOption>(
icon: const Icon(LucideIcons.ellipsisVertical),
tooltip: l10n.libraryOptions,
onSelected: (option) => switch (option) {
_LibraryOption.import => unawaited(_import(context)),
_LibraryOption.refresh => _refresh(context),
},
itemBuilder: (context) => [
for (final (option, icon, text) in [
(
_LibraryOption.import,
LucideIcons.fileInput,
l10n.libraryImport,
),
(
_LibraryOption.refresh,
LucideIcons.refreshCw,
l10n.libraryRefresh,
),
])
PopupMenuItem(
value: option,
child: ListTile(
dense: true,
contentPadding: EdgeInsets.zero,
leading: Icon(icon),
title: Text(text),
),
),
],
),
if (wide)
Padding(
@ -615,6 +639,12 @@ class const _SearchRow({
}
}
/// The library's options, behind its menu button.
enum _LibraryOption() {
import,
refresh,
}
/// What the library imports as new studysets.
enum _LibraryImport() {
anki,

View file

@ -144,7 +144,9 @@ void main() {
await tester.pump(const Duration(seconds: 1));
await tester.pumpAndSettle();
await tester.tap(find.byTooltip('Import'));
await tester.tap(find.byTooltip('Library options'));
await tester.pumpAndSettle();
await tester.tap(find.text('Import'));
await tester.pumpAndSettle();
await tester.tap(find.text('Anki deck'));
await tester.pump();
@ -187,7 +189,9 @@ void main() {
await tester.pump(const Duration(seconds: 1));
await tester.pumpAndSettle();
await tester.tap(find.byTooltip('Import'));
await tester.tap(find.byTooltip('Library options'));
await tester.pumpAndSettle();
await tester.tap(find.text('Import'));
await tester.pumpAndSettle();
await tester.tap(find.text('Anki deck'));
await tester.pump();

View file

@ -44,7 +44,9 @@ void main() {
}
Future<void> choose(WidgetTester tester) async {
await tester.tap(find.byTooltip('Import'));
await tester.tap(find.byTooltip('Library options'));
await tester.pumpAndSettle();
await tester.tap(find.text('Import'));
await tester.pumpAndSettle();
await tester.tap(find.text('Halkyon archive'));
await tester.pump();

View file

@ -132,13 +132,15 @@ void main() {
expect(find.text('Japanese'), findsOneWidget);
});
testWidgets('Refresh syncs, from the app bar and by pulling the list '
testWidgets('Refresh syncs, from the menu and by pulling the list '
'down', (tester) async {
stored('japanese', 'Japanese');
await showLibrary(tester);
final before = syncs;
await tester.tap(find.byTooltip('Refresh'));
await tester.tap(find.byTooltip('Library options'));
await tester.pumpAndSettle();
await tester.tap(find.text('Refresh'));
await tester.pumpAndSettle();
expect(syncs, before + 1);