diff --git a/lib/screens/deck_edit_screen.dart b/lib/screens/deck_edit_screen.dart index 6dadbc8..51cafa9 100644 --- a/lib/screens/deck_edit_screen.dart +++ b/lib/screens/deck_edit_screen.dart @@ -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 { 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 { 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, diff --git a/lib/screens/deck_import_screen.dart b/lib/screens/deck_import_screen.dart index b8e0c39..98a631a 100644 --- a/lib/screens/deck_import_screen.dart +++ b/lib/screens/deck_import_screen.dart @@ -586,7 +586,7 @@ class _DeckImportScreenState extends State { 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), ), diff --git a/lib/screens/deck_list_screen.dart b/lib/screens/deck_list_screen.dart index b87c6a3..9002a84 100644 --- a/lib/screens/deck_list_screen.dart +++ b/lib/screens/deck_list_screen.dart @@ -102,6 +102,68 @@ class _DeckListScreenState extends State { }); } + 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( + 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 { ); }, ), + 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: [