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:
parent
660e5548e0
commit
25e73c8c80
9 changed files with 70 additions and 18 deletions
|
|
@ -48,6 +48,7 @@
|
||||||
"studysetCountNotes": "{count, plural, =1{1 Notiz} other{{count} Notizen}}",
|
"studysetCountNotes": "{count, plural, =1{1 Notiz} other{{count} Notizen}}",
|
||||||
"studysetCountFiles": "{count, plural, =1{1 Datei} other{{count} Dateien}}",
|
"studysetCountFiles": "{count, plural, =1{1 Datei} other{{count} Dateien}}",
|
||||||
"libraryRefresh": "Aktualisieren",
|
"libraryRefresh": "Aktualisieren",
|
||||||
|
"libraryOptions": "Optionen der Bibliothek",
|
||||||
"libraryImport": "Importieren",
|
"libraryImport": "Importieren",
|
||||||
"libraryImportAnkiHint": "Wird ein eigenes Lernset, aus einer .apkg- oder .colpkg-Datei",
|
"libraryImportAnkiHint": "Wird ein eigenes Lernset, aus einer .apkg- oder .colpkg-Datei",
|
||||||
"libraryImportArchive": "Halkyon-Archiv",
|
"libraryImportArchive": "Halkyon-Archiv",
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@
|
||||||
"studysetCountFiles": "{count, plural, =1{1 file} other{{count} files}}",
|
"studysetCountFiles": "{count, plural, =1{1 file} other{{count} files}}",
|
||||||
"@studysetCountFiles": {"placeholders": {"count": {"type": "int"}}},
|
"@studysetCountFiles": {"placeholders": {"count": {"type": "int"}}},
|
||||||
"libraryRefresh": "Refresh",
|
"libraryRefresh": "Refresh",
|
||||||
|
"libraryOptions": "Library options",
|
||||||
"libraryImport": "Import",
|
"libraryImport": "Import",
|
||||||
"libraryImportAnkiHint": "Becomes a studyset of its own, from an .apkg or .colpkg file",
|
"libraryImportAnkiHint": "Becomes a studyset of its own, from an .apkg or .colpkg file",
|
||||||
"libraryImportArchive": "Halkyon archive",
|
"libraryImportArchive": "Halkyon archive",
|
||||||
|
|
|
||||||
|
|
@ -346,6 +346,12 @@ abstract class AppLocalizations {
|
||||||
/// **'Refresh'**
|
/// **'Refresh'**
|
||||||
String get libraryRefresh;
|
String get libraryRefresh;
|
||||||
|
|
||||||
|
/// No description provided for @libraryOptions.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Library options'**
|
||||||
|
String get libraryOptions;
|
||||||
|
|
||||||
/// No description provided for @libraryImport.
|
/// No description provided for @libraryImport.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|
|
||||||
|
|
@ -191,6 +191,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get libraryRefresh => 'Aktualisieren';
|
String get libraryRefresh => 'Aktualisieren';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryOptions => 'Optionen der Bibliothek';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get libraryImport => 'Importieren';
|
String get libraryImport => 'Importieren';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -189,6 +189,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get libraryRefresh => 'Refresh';
|
String get libraryRefresh => 'Refresh';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get libraryOptions => 'Library options';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get libraryImport => 'Import';
|
String get libraryImport => 'Import';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
Future<void> _create(BuildContext context) async {
|
||||||
final bloc = context.read<StudysetListBloc>();
|
final bloc = context.read<StudysetListBloc>();
|
||||||
final content = await showStudysetSheet(context);
|
final content = await showStudysetSheet(context);
|
||||||
|
|
@ -207,20 +213,38 @@ class _LibraryState() extends State<_Library> {
|
||||||
),
|
),
|
||||||
onPressed: _toggleSearch,
|
onPressed: _toggleSearch,
|
||||||
),
|
),
|
||||||
IconButton(
|
// Import and Refresh behind one button: pulling the list
|
||||||
tooltip: l10n.libraryImport,
|
// down refreshes too, and is the quicker way on a phone.
|
||||||
icon: const Icon(LucideIcons.fileInput),
|
PopupMenuButton<_LibraryOption>(
|
||||||
onPressed: () => unawaited(_import(context)),
|
icon: const Icon(LucideIcons.ellipsisVertical),
|
||||||
),
|
tooltip: l10n.libraryOptions,
|
||||||
IconButton(
|
onSelected: (option) => switch (option) {
|
||||||
tooltip: l10n.libraryRefresh,
|
_LibraryOption.import => unawaited(_import(context)),
|
||||||
icon: const Icon(LucideIcons.refreshCw),
|
_LibraryOption.refresh => _refresh(context),
|
||||||
onPressed: () {
|
|
||||||
context.read<SyncStatusBloc>().add(
|
|
||||||
const SyncStatusEventSyncRequested(),
|
|
||||||
);
|
|
||||||
unawaited(context.read<PublishedCubit>().refresh());
|
|
||||||
},
|
},
|
||||||
|
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)
|
if (wide)
|
||||||
Padding(
|
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.
|
/// What the library imports as new studysets.
|
||||||
enum _LibraryImport() {
|
enum _LibraryImport() {
|
||||||
anki,
|
anki,
|
||||||
|
|
|
||||||
|
|
@ -144,7 +144,9 @@ void main() {
|
||||||
await tester.pump(const Duration(seconds: 1));
|
await tester.pump(const Duration(seconds: 1));
|
||||||
await tester.pumpAndSettle();
|
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.pumpAndSettle();
|
||||||
await tester.tap(find.text('Anki deck'));
|
await tester.tap(find.text('Anki deck'));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
@ -187,7 +189,9 @@ void main() {
|
||||||
await tester.pump(const Duration(seconds: 1));
|
await tester.pump(const Duration(seconds: 1));
|
||||||
await tester.pumpAndSettle();
|
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.pumpAndSettle();
|
||||||
await tester.tap(find.text('Anki deck'));
|
await tester.tap(find.text('Anki deck'));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,9 @@ void main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> choose(WidgetTester tester) async {
|
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.pumpAndSettle();
|
||||||
await tester.tap(find.text('Halkyon archive'));
|
await tester.tap(find.text('Halkyon archive'));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
|
||||||
|
|
@ -132,13 +132,15 @@ void main() {
|
||||||
expect(find.text('Japanese'), findsOneWidget);
|
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 {
|
'down', (tester) async {
|
||||||
stored('japanese', 'Japanese');
|
stored('japanese', 'Japanese');
|
||||||
await showLibrary(tester);
|
await showLibrary(tester);
|
||||||
final before = syncs;
|
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();
|
await tester.pumpAndSettle();
|
||||||
expect(syncs, before + 1);
|
expect(syncs, before + 1);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue