merge decks

master
gitea 4 weeks ago
parent afe86721ed
commit f83dfd5717

@ -1,4 +1,7 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../utils/top_snackbar.dart';
import 'package:practice_engine/practice_engine.dart';
import '../services/deck_storage.dart';
@ -149,6 +152,27 @@ class _DeckEditScreenState extends State<DeckEditScreen> {
Navigator.pop(context, updatedDeck);
}
void _copyQuestionsJson() {
if (_questionEditors.isEmpty) {
showTopSnackBar(context, message: 'No questions to copy');
return;
}
final questions = _questionEditors.map((e) => e.toQuestion()).toList();
final list = questions.map((q) => {
'id': q.id,
'prompt': q.prompt,
'answers': q.answers,
'correctAnswerIndices': q.correctAnswerIndices,
}).toList();
final jsonString = const JsonEncoder.withIndent(' ').convert(list);
Clipboard.setData(ClipboardData(text: jsonString));
showTopSnackBar(
context,
message: '${questions.length} question(s) copied as JSON',
backgroundColor: Colors.green,
);
}
@override
Widget build(BuildContext context) {
if (_deck == null) {
@ -161,6 +185,11 @@ class _DeckEditScreenState extends State<DeckEditScreen> {
appBar: AppBar(
title: const Text('Edit Deck'),
actions: [
IconButton(
icon: const Icon(Icons.copy),
onPressed: _copyQuestionsJson,
tooltip: 'Copy questions as JSON',
),
IconButton(
icon: const Icon(Icons.add),
onPressed: _addQuestion,

@ -586,7 +586,7 @@ class _DeckImportScreenState extends State<DeckImportScreen> {
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.merge_type, size: 20),
label: Text(_isLoading ? '...' : 'Merge with existing deck'),
label: Text(_isLoading ? '...' : 'Merge with deck'),
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
),

@ -102,6 +102,68 @@ class _DeckListScreenState extends State<DeckListScreen> {
});
}
void _mergeDeck(Deck sourceDeck) async {
final otherDecks = _decks.where((d) => d.id != sourceDeck.id).toList();
if (otherDecks.isEmpty) {
showTopSnackBar(
context,
message: 'No other deck to merge with',
backgroundColor: Colors.orange,
);
return;
}
final targetDeck = await showDialog<Deck>(
context: context,
builder: (context) => AlertDialog(
title: const Text('Merge with deck'),
content: SizedBox(
width: double.maxFinite,
child: ListView.builder(
shrinkWrap: true,
itemCount: otherDecks.length,
itemBuilder: (context, index) {
final deck = otherDecks[index];
return ListTile(
title: Text(deck.title),
subtitle: Text('${deck.questions.length} questions'),
onTap: () => Navigator.pop(context, deck),
);
},
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Cancel'),
),
],
),
);
if (targetDeck == null || !mounted) return;
final timestamp = DateTime.now().millisecondsSinceEpoch;
final mergedQuestions = [
...targetDeck.questions,
...sourceDeck.questions.asMap().entries.map((e) {
final q = e.value;
final i = e.key;
return q.copyWith(id: '${q.id}_merged_${timestamp}_$i');
}),
];
final mergedDeck = targetDeck.copyWith(questions: mergedQuestions);
_deckStorage.saveDeckSync(mergedDeck);
_loadDecks();
if (mounted) {
showTopSnackBar(
context,
message: 'Merged ${sourceDeck.questions.length} question(s) into "${targetDeck.title}"',
backgroundColor: Colors.green,
);
}
}
void _cloneDeck(Deck deck) {
// Create a copy of the deck with a new ID and reset progress
final clonedDeck = deck.copyWith(
@ -452,6 +514,21 @@ class _DeckListScreenState extends State<DeckListScreen> {
);
},
),
PopupMenuItem(
child: const Row(
children: [
Icon(Icons.merge_type, color: Colors.teal),
SizedBox(width: 8),
Text('Merge'),
],
),
onTap: () {
Future.delayed(
const Duration(milliseconds: 100),
() => _mergeDeck(deck),
);
},
),
PopupMenuItem(
child: const Row(
children: [

Loading…
Cancel
Save

Powered by TurnKey Linux.