feat(learn): a Viewer suggests new flashcards and notes
ADR 0044's step 6b (D48) in the app: where an Editor has Add material, a Viewer of a shared studyset has Suggest material, offering a flashcard or a note. The composer and the note editor open suggesting from the start, saying that what is suggested joins the studyset once its owner or an editor takes it in; Send suggestion asks why, and Cancel or sending closes it. Nothing is written into the studyset: the fields go into the suggestion alone, which goes into the Viewer's own space as the changes they suggest do. Whats-New: In a studyset shared with you as a viewer, Suggest material proposes a new flashcard or note to its owner and editors. Whats-New-De: In einem Lernset, das du nur ansehen darfst, schlägst du über „Material vorschlagen“ neue Karteikarten und Notizen vor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
d09625d43e
commit
72cac92365
12 changed files with 310 additions and 27 deletions
|
|
@ -2318,5 +2318,9 @@
|
||||||
"suggestInSpace": "Änderung vorschlagen",
|
"suggestInSpace": "Änderung vorschlagen",
|
||||||
"suggestInSpaceIntro": "Besitzer:in und Bearbeiter:innen dieses Lernsets sehen es neben ihrer Fassung und übernehmen es oder nicht. Alle darin lesen es, mit deinem öffentlichen Profil.",
|
"suggestInSpaceIntro": "Besitzer:in und Bearbeiter:innen dieses Lernsets sehen es neben ihrer Fassung und übernehmen es oder nicht. Alle darin lesen es, mit deinem öffentlichen Profil.",
|
||||||
"suggestingNote": "Du schlägst eine Änderung vor: Nichts ändert sich, bis Besitzer:in oder Bearbeiter:innen sie übernehmen.",
|
"suggestingNote": "Du schlägst eine Änderung vor: Nichts ändert sich, bis Besitzer:in oder Bearbeiter:innen sie übernehmen.",
|
||||||
"suggestSend": "Vorschlag senden"
|
"suggestSend": "Vorschlag senden",
|
||||||
|
"studysetSuggestMaterial": "Material vorschlagen",
|
||||||
|
"suggestNewFlashcard": "Karteikarte vorschlagen",
|
||||||
|
"suggestNewNote": "Notiz vorschlagen",
|
||||||
|
"suggestingNew": "Du schlägst etwas Neues vor: Es kommt ins Lernset, sobald Besitzer:in oder Bearbeiter:innen es annehmen."
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2966,5 +2966,13 @@
|
||||||
"suggestingNote": "You're suggesting a change: nothing changes until the owner or an editor takes it in.",
|
"suggestingNote": "You're suggesting a change: nothing changes until the owner or an editor takes it in.",
|
||||||
"@suggestingNote": {"description": "The line above a card or note a Viewer changes as a suggestion"},
|
"@suggestingNote": {"description": "The line above a card or note a Viewer changes as a suggestion"},
|
||||||
"suggestSend": "Send suggestion",
|
"suggestSend": "Send suggestion",
|
||||||
"@suggestSend": {"description": "Sends a Viewer's changed card or note as a suggestion"}
|
"@suggestSend": {"description": "Sends a Viewer's changed card or note as a suggestion"},
|
||||||
|
"studysetSuggestMaterial": "Suggest material",
|
||||||
|
"@studysetSuggestMaterial": {"description": "A Viewer's menu in a shared studyset, in place of Add material (Halkyon's ADR 0044, D44): suggesting new flashcards or notes"},
|
||||||
|
"suggestNewFlashcard": "Suggest a flashcard",
|
||||||
|
"@suggestNewFlashcard": {"description": "Suggest material's item: opens the composer, suggesting a new flashcard"},
|
||||||
|
"suggestNewNote": "Suggest a note",
|
||||||
|
"@suggestNewNote": {"description": "Suggest material's item: opens the note editor, suggesting a new note"},
|
||||||
|
"suggestingNew": "You're suggesting something new: it joins the studyset once the owner or an editor takes it in.",
|
||||||
|
"@suggestingNew": {"description": "Above a flashcard or note a Viewer suggests anew, with Send suggestion and Cancel"}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13693,6 +13693,30 @@ abstract class AppLocalizations {
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Send suggestion'**
|
/// **'Send suggestion'**
|
||||||
String get suggestSend;
|
String get suggestSend;
|
||||||
|
|
||||||
|
/// A Viewer's menu in a shared studyset, in place of Add material (Halkyon's ADR 0044, D44): suggesting new flashcards or notes
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Suggest material'**
|
||||||
|
String get studysetSuggestMaterial;
|
||||||
|
|
||||||
|
/// Suggest material's item: opens the composer, suggesting a new flashcard
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Suggest a flashcard'**
|
||||||
|
String get suggestNewFlashcard;
|
||||||
|
|
||||||
|
/// Suggest material's item: opens the note editor, suggesting a new note
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Suggest a note'**
|
||||||
|
String get suggestNewNote;
|
||||||
|
|
||||||
|
/// Above a flashcard or note a Viewer suggests anew, with Send suggestion and Cancel
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'You\'re suggesting something new: it joins the studyset once the owner or an editor takes it in.'**
|
||||||
|
String get suggestingNew;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppLocalizationsDelegate
|
class _AppLocalizationsDelegate
|
||||||
|
|
|
||||||
|
|
@ -8747,4 +8747,17 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get suggestSend => 'Vorschlag senden';
|
String get suggestSend => 'Vorschlag senden';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get studysetSuggestMaterial => 'Material vorschlagen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get suggestNewFlashcard => 'Karteikarte vorschlagen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get suggestNewNote => 'Notiz vorschlagen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get suggestingNew =>
|
||||||
|
'Du schlägst etwas Neues vor: Es kommt ins Lernset, sobald Besitzer:in oder Bearbeiter:innen es annehmen.';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8668,6 +8668,19 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get suggestSend => 'Send suggestion';
|
String get suggestSend => 'Send suggestion';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get studysetSuggestMaterial => 'Suggest material';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get suggestNewFlashcard => 'Suggest a flashcard';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get suggestNewNote => 'Suggest a note';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get suggestingNew =>
|
||||||
|
'You\'re suggesting something new: it joins the studyset once the owner or an editor takes it in.';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The translations for English, as used in the United Kingdom (`en_GB`).
|
/// The translations for English, as used in the United Kingdom (`en_GB`).
|
||||||
|
|
|
||||||
|
|
@ -169,7 +169,13 @@ class _CardComposerScreenState() extends State<CardComposerScreen> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// A studyset without cards opens on a new one, once.
|
// A studyset without cards opens on a new one, once.
|
||||||
if (!_seeded && state.items.isEmpty) {
|
// A new card to start with where there are none, and for a
|
||||||
|
// Viewer suggesting one (Halkyon's ADR 0044, D48), who has no
|
||||||
|
// button to add one.
|
||||||
|
if (!_seeded &&
|
||||||
|
(state.items.isEmpty ||
|
||||||
|
(widget.initialCardId == null &&
|
||||||
|
StudysetAccess.viewOnlyNow(context)))) {
|
||||||
_seeded = true;
|
_seeded = true;
|
||||||
_addDraft();
|
_addDraft();
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -160,8 +160,12 @@ class _CardDraftEditorState() extends State<CardDraftEditor> {
|
||||||
/// (Halkyon's ADR 0044, D44): it goes nowhere until sent as a suggestion.
|
/// (Halkyon's ADR 0044, D44): it goes nowhere until sent as a suggestion.
|
||||||
CardContent? _suggested;
|
CardContent? _suggested;
|
||||||
|
|
||||||
/// Whether a Viewer is suggesting a change.
|
/// Whether a Viewer is suggesting a change; a card a Viewer writes anew
|
||||||
var _suggesting = false;
|
/// is a suggestion from the start (Halkyon's ADR 0044, D48).
|
||||||
|
late var _suggesting = _isNew && StudysetAccess.viewOnlyNow(context);
|
||||||
|
|
||||||
|
/// Whether the card is a new one, not written yet.
|
||||||
|
bool get _isNew => widget.draft.cardId == null;
|
||||||
|
|
||||||
/// The card as this pane shows it.
|
/// The card as this pane shows it.
|
||||||
CardContent get _current => _suggested ?? _sent;
|
CardContent get _current => _suggested ?? _sent;
|
||||||
|
|
@ -298,12 +302,19 @@ class _CardDraftEditorState() extends State<CardDraftEditor> {
|
||||||
_change(_current.withField(CardField.explanation, RichContent.empty()));
|
_change(_current.withField(CardField.explanation, RichContent.empty()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Leaves suggesting, the fields as the card is.
|
/// Leaves suggesting, the fields as the card is; a new card suggested,
|
||||||
void _stopSuggesting() => setState(() {
|
/// or not after all, leaves the composer.
|
||||||
_suggesting = false;
|
void _stopSuggesting() {
|
||||||
_suggested = null;
|
if (_isNew) {
|
||||||
_revision++;
|
Navigator.of(context).pop();
|
||||||
});
|
return;
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_suggesting = false;
|
||||||
|
_suggested = null;
|
||||||
|
_revision++;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Sends the card as the Viewer would have it as a suggestion, asking
|
/// Sends the card as the Viewer would have it as a suggestion, asking
|
||||||
/// why; leaves suggesting once it went.
|
/// why; leaves suggesting once it went.
|
||||||
|
|
@ -363,6 +374,7 @@ class _CardDraftEditorState() extends State<CardDraftEditor> {
|
||||||
if (access.viewOnly)
|
if (access.viewOnly)
|
||||||
SuggestBar(
|
SuggestBar(
|
||||||
suggesting: _suggesting,
|
suggesting: _suggesting,
|
||||||
|
isNew: _isNew,
|
||||||
onStart: () => setState(() => _suggesting = true),
|
onStart: () => setState(() => _suggesting = true),
|
||||||
onCancel: _stopSuggesting,
|
onCancel: _stopSuggesting,
|
||||||
onSend: () => unawaited(_sendSuggestion()),
|
onSend: () => unawaited(_sendSuggestion()),
|
||||||
|
|
|
||||||
|
|
@ -122,12 +122,14 @@ Future<bool> suggestInSpace(
|
||||||
/// Above what a Viewer opens in a shared studyset (Halkyon's ADR 0044,
|
/// Above what a Viewer opens in a shared studyset (Halkyon's ADR 0044,
|
||||||
/// D35, D44): that it is theirs to view and study, with Suggest a change;
|
/// D35, D44): that it is theirs to view and study, with Suggest a change;
|
||||||
/// while [suggesting], that nothing changes until it is taken in, with
|
/// while [suggesting], that nothing changes until it is taken in, with
|
||||||
/// Cancel and Send suggestion.
|
/// Cancel and Send suggestion; for something [isNew], that it joins the
|
||||||
|
/// studyset once taken in.
|
||||||
class const SuggestBar({
|
class const SuggestBar({
|
||||||
required final bool suggesting,
|
required final bool suggesting,
|
||||||
required final VoidCallback onStart,
|
required final VoidCallback onStart,
|
||||||
required final VoidCallback onCancel,
|
required final VoidCallback onCancel,
|
||||||
required final VoidCallback onSend,
|
required final VoidCallback onSend,
|
||||||
|
final bool isNew = false,
|
||||||
super.key,
|
super.key,
|
||||||
}) extends StatelessWidget {
|
}) extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
|
|
@ -157,7 +159,7 @@ class const SuggestBar({
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
l10n.suggestingNote,
|
isNew ? l10n.suggestingNew : l10n.suggestingNote,
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -144,22 +144,33 @@ class const _Note({
|
||||||
|
|
||||||
class _NoteState() extends State<_Note> {
|
class _NoteState() extends State<_Note> {
|
||||||
/// Whether a Viewer is suggesting a change, and the note as they would
|
/// Whether a Viewer is suggesting a change, and the note as they would
|
||||||
/// have it, which goes nowhere until sent.
|
/// have it, which goes nowhere until sent. A note a Viewer writes anew
|
||||||
var _suggesting = false;
|
/// is a suggestion from the start (Halkyon's ADR 0044, D48).
|
||||||
|
late var _suggesting = _isNew && StudysetAccess.viewOnlyNow(context);
|
||||||
NoteContent? _suggested;
|
NoteContent? _suggested;
|
||||||
|
|
||||||
|
/// Whether the note is a new one, not written yet.
|
||||||
|
bool get _isNew => state.noteId == null;
|
||||||
|
|
||||||
/// Shows the editor anew as suggesting starts or stops.
|
/// Shows the editor anew as suggesting starts or stops.
|
||||||
var _round = 0;
|
var _round = 0;
|
||||||
|
|
||||||
NoteEditorState get state => widget.state;
|
NoteEditorState get state => widget.state;
|
||||||
TextEditingController get title => widget.title;
|
TextEditingController get title => widget.title;
|
||||||
|
|
||||||
void _stopSuggesting() => setState(() {
|
void _stopSuggesting() {
|
||||||
_suggesting = false;
|
// A new note suggested, or not after all, leaves nothing to show.
|
||||||
_suggested = null;
|
if (_isNew) {
|
||||||
title.text = state.content.title;
|
Navigator.of(context).pop();
|
||||||
_round++;
|
return;
|
||||||
});
|
}
|
||||||
|
setState(() {
|
||||||
|
_suggesting = false;
|
||||||
|
_suggested = null;
|
||||||
|
title.text = state.content.title;
|
||||||
|
_round++;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _sendSuggestion() async {
|
Future<void> _sendSuggestion() async {
|
||||||
final content = _suggested ?? state.content;
|
final content = _suggested ?? state.content;
|
||||||
|
|
@ -205,6 +216,7 @@ class _NoteState() extends State<_Note> {
|
||||||
if (viewOnly)
|
if (viewOnly)
|
||||||
SuggestBar(
|
SuggestBar(
|
||||||
suggesting: _suggesting,
|
suggesting: _suggesting,
|
||||||
|
isNew: _isNew,
|
||||||
onStart: () => setState(() {
|
onStart: () => setState(() {
|
||||||
_suggesting = true;
|
_suggesting = true;
|
||||||
_round++;
|
_round++;
|
||||||
|
|
|
||||||
|
|
@ -450,8 +450,39 @@ class _StudysetViewState() extends State<_StudysetView> {
|
||||||
child: _StudyButton(studyset: studyset, inAppBar: true),
|
child: _StudyButton(studyset: studyset, inAppBar: true),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// A Viewer adds nothing (Halkyon's ADR 0044, D30).
|
// A Viewer adds nothing (Halkyon's ADR 0044, D30), and
|
||||||
if (!studyset.viewOnly)
|
// suggests material instead (D44, D48).
|
||||||
|
if (studyset.viewOnly)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
child: _SuggestMaterial(
|
||||||
|
studysetId: studyset.id,
|
||||||
|
controller: _addMaterial,
|
||||||
|
builder: (context, menu) => roomy
|
||||||
|
? FilledButton.tonalIcon(
|
||||||
|
style: FilledButton.styleFrom(
|
||||||
|
minimumSize: const Size(0, 48),
|
||||||
|
),
|
||||||
|
onPressed: () =>
|
||||||
|
menu.isOpen ? menu.close() : menu.open(),
|
||||||
|
icon: const Icon(LucideIcons.pencilLine),
|
||||||
|
label: Text(
|
||||||
|
AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
).studysetSuggestMaterial,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: IconButton.filledTonal(
|
||||||
|
tooltip: AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
).studysetSuggestMaterial,
|
||||||
|
onPressed: () =>
|
||||||
|
menu.isOpen ? menu.close() : menu.open(),
|
||||||
|
icon: const Icon(LucideIcons.pencilLine),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
child: _AddMaterial(
|
child: _AddMaterial(
|
||||||
|
|
@ -1129,6 +1160,37 @@ class const _StudyPill({
|
||||||
/// language where the instance has one. What is added shows in its own tab ([ContentTabCubit]): what is
|
/// language where the instance has one. What is added shows in its own tab ([ContentTabCubit]): what is
|
||||||
/// written there once the person comes back, what is uploaded once it is
|
/// written there once the person comes back, what is uploaded once it is
|
||||||
/// in. Imports are under the options.
|
/// in. Imports are under the options.
|
||||||
|
/// What a Viewer suggests anew within a shared studyset (Halkyon's ADR
|
||||||
|
/// 0044, D48): a flashcard or a note, written in the composer or the note
|
||||||
|
/// editor as a suggestion from the start.
|
||||||
|
class const _SuggestMaterial({
|
||||||
|
required final String studysetId,
|
||||||
|
required final MenuController controller,
|
||||||
|
required final Widget Function(BuildContext context, MenuController menu)
|
||||||
|
builder,
|
||||||
|
}) extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
return MenuAnchor(
|
||||||
|
controller: controller,
|
||||||
|
menuChildren: [
|
||||||
|
MenuItemButton(
|
||||||
|
leadingIcon: const Icon(LucideIcons.plus),
|
||||||
|
onPressed: () => context.push(RoutePaths.newCard(studysetId)),
|
||||||
|
child: Text(l10n.suggestNewFlashcard),
|
||||||
|
),
|
||||||
|
MenuItemButton(
|
||||||
|
leadingIcon: const Icon(LucideIcons.notebookPen),
|
||||||
|
onPressed: () => context.push(RoutePaths.newNote(studysetId)),
|
||||||
|
child: Text(l10n.suggestNewNote),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
builder: (context, menu, _) => builder(context, menu),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class const _AddMaterial({
|
class const _AddMaterial({
|
||||||
required final String studysetId,
|
required final String studysetId,
|
||||||
required final MenuController controller,
|
required final MenuController controller,
|
||||||
|
|
@ -1409,8 +1471,34 @@ class const _StudyBar({
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
// A Viewer adds nothing (Halkyon's ADR 0044, D30).
|
// A Viewer adds nothing (Halkyon's ADR 0044, D30), and
|
||||||
if (!studyset.viewOnly) ...[
|
// suggests material instead (D44, D48).
|
||||||
|
if (studyset.viewOnly) ...[
|
||||||
|
_SuggestMaterial(
|
||||||
|
studysetId: studyset.id,
|
||||||
|
controller: addMaterial,
|
||||||
|
builder: (context, menu) => Tooltip(
|
||||||
|
message: l10n.studysetSuggestMaterial,
|
||||||
|
child: Material(
|
||||||
|
elevation: 6,
|
||||||
|
color: scheme.secondaryContainer,
|
||||||
|
shape: const CircleBorder(),
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => menu.isOpen ? menu.close() : menu.open(),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: Icon(
|
||||||
|
LucideIcons.pencilLine,
|
||||||
|
color: scheme.onSecondaryContainer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
] else ...[
|
||||||
_AddMaterial(
|
_AddMaterial(
|
||||||
studysetId: studyset.id,
|
studysetId: studyset.id,
|
||||||
controller: addMaterial,
|
controller: addMaterial,
|
||||||
|
|
|
||||||
|
|
@ -238,6 +238,105 @@ void main() {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets("a Viewer's studyset offers to suggest a flashcard or a note, "
|
||||||
|
'never to add one', (tester) async {
|
||||||
|
shared(viewOnly: true);
|
||||||
|
await showApp(tester, RoutePaths.studyset('japanese'));
|
||||||
|
|
||||||
|
expect(find.text('Add material'), findsNothing);
|
||||||
|
await tester.tap(find.text('Suggest material'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.text('Suggest a flashcard'), findsOneWidget);
|
||||||
|
expect(find.text('Suggest a note'), findsOneWidget);
|
||||||
|
expect(find.text('Create flashcards'), findsNothing);
|
||||||
|
expect(find.text('Create a note'), findsNothing);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('an Editor adds material, suggesting none', (tester) async {
|
||||||
|
shared(viewOnly: false);
|
||||||
|
await showApp(tester, RoutePaths.studyset('japanese'));
|
||||||
|
|
||||||
|
expect(find.text('Add material'), findsOneWidget);
|
||||||
|
expect(find.text('Suggest material'), findsNothing);
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Opens what a Viewer suggests anew from the studyset's menu: [item],
|
||||||
|
/// "Suggest a flashcard" or "Suggest a note".
|
||||||
|
Future<void> suggestNew(WidgetTester tester, String item) async {
|
||||||
|
shared(viewOnly: true);
|
||||||
|
await showApp(tester, RoutePaths.studyset('japanese'));
|
||||||
|
await tester.tap(find.text('Suggest material'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await tester.tap(find.text(item));
|
||||||
|
await tester.pump();
|
||||||
|
await tester.pump(const Duration(seconds: 1));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
}
|
||||||
|
|
||||||
|
testWidgets("a Viewer's new note goes as a suggestion for the studyset, "
|
||||||
|
'which it joins only once taken in', (tester) async {
|
||||||
|
await suggestNew(tester, 'Suggest a note');
|
||||||
|
|
||||||
|
// Suggesting from the start, with nothing to change.
|
||||||
|
expect(
|
||||||
|
find.textContaining('suggesting something new', findRichText: true),
|
||||||
|
findsOneWidget,
|
||||||
|
);
|
||||||
|
expect(find.text('Suggest a change'), findsNothing);
|
||||||
|
await tester.enterText(find.byType(TextField).first, 'Particles が');
|
||||||
|
await tester.pump();
|
||||||
|
await send(tester, 'The other particle.');
|
||||||
|
|
||||||
|
final thread = written.threads.single;
|
||||||
|
expect(thread.space, 'space-1');
|
||||||
|
expect(thread.json['target'], {
|
||||||
|
'kind': 'newNote',
|
||||||
|
'item': 'japanese',
|
||||||
|
'version': 0,
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
(thread.json['proposed']! as Map<String, Object?>)['title'],
|
||||||
|
'Particles が',
|
||||||
|
);
|
||||||
|
// Nothing added; back at the studyset.
|
||||||
|
final notes = NoteRepository(noteGateway);
|
||||||
|
addTearDown(notes.dispose);
|
||||||
|
expect(await notes.notesOf('japanese'), hasLength(1));
|
||||||
|
expect(find.text('Suggest material'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets("a Viewer's new flashcard goes as a suggestion; cancelled, "
|
||||||
|
'nothing goes and nothing is added', (tester) async {
|
||||||
|
await suggestNew(tester, 'Suggest a flashcard');
|
||||||
|
expect(
|
||||||
|
find.textContaining('suggesting something new', findRichText: true),
|
||||||
|
findsOneWidget,
|
||||||
|
);
|
||||||
|
await tester.tap(find.text('Cancel'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(written.threads, isEmpty);
|
||||||
|
expect(find.text('Suggest material'), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.tap(find.text('Suggest material'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await tester.tap(find.text('Suggest a flashcard'));
|
||||||
|
await tester.pump();
|
||||||
|
await tester.pump(const Duration(seconds: 1));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await send(tester, 'A card on Kyoto.');
|
||||||
|
|
||||||
|
expect(written.threads.single.json['target'], {
|
||||||
|
'kind': 'newCard',
|
||||||
|
'item': 'japanese',
|
||||||
|
'version': 0,
|
||||||
|
});
|
||||||
|
final cards = CardRepository(cardGateway);
|
||||||
|
addTearDown(cards.dispose);
|
||||||
|
expect(await cards.cardsIn(['japanese']), hasLength(1));
|
||||||
|
expect(find.text('Suggest material'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets("a shared studyset's menu offers its suggestions", (
|
testWidgets("a shared studyset's menu offers its suggestions", (
|
||||||
tester,
|
tester,
|
||||||
) async {
|
) async {
|
||||||
|
|
|
||||||
|
|
@ -381,8 +381,10 @@ member reads everything in the space.
|
||||||
where it names the studyset's own files and brings none; a Viewer's
|
where it names the studyset's own files and brings none; a Viewer's
|
||||||
card and note offer Suggest a change, and a shared studyset's menu its
|
card and note offer Suggest a change, and a shared studyset's menu its
|
||||||
Suggestions, where its Owner and Editors accept or decline. New cards
|
Suggestions, where its Owner and Editors accept or decline. New cards
|
||||||
and notes are suggested through the repository (the agent, step 6c);
|
and notes are suggested through the repository (the agent, step 6c),
|
||||||
the app offers a Viewer no button for them yet.
|
and in the app from a Viewer's Suggest material, in place of Add
|
||||||
|
material: the composer or note editor opens suggesting from the
|
||||||
|
start, Cancel or Send closing it, nothing added.
|
||||||
c. The agent itself, its CLI and MCP server, on a pure-Dart core (D7, D41, D49 to
|
c. The agent itself, its CLI and MCP server, on a pure-Dart core (D7, D41, D49 to
|
||||||
D52).
|
D52).
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue