merge decks
This commit is contained in:
@@ -1,4 +1,7 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import '../utils/top_snackbar.dart';
|
import '../utils/top_snackbar.dart';
|
||||||
import 'package:practice_engine/practice_engine.dart';
|
import 'package:practice_engine/practice_engine.dart';
|
||||||
import '../services/deck_storage.dart';
|
import '../services/deck_storage.dart';
|
||||||
@@ -149,6 +152,27 @@ class _DeckEditScreenState extends State<DeckEditScreen> {
|
|||||||
Navigator.pop(context, updatedDeck);
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (_deck == null) {
|
if (_deck == null) {
|
||||||
@@ -161,6 +185,11 @@ class _DeckEditScreenState extends State<DeckEditScreen> {
|
|||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('Edit Deck'),
|
title: const Text('Edit Deck'),
|
||||||
actions: [
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.copy),
|
||||||
|
onPressed: _copyQuestionsJson,
|
||||||
|
tooltip: 'Copy questions as JSON',
|
||||||
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.add),
|
icon: const Icon(Icons.add),
|
||||||
onPressed: _addQuestion,
|
onPressed: _addQuestion,
|
||||||
|
|||||||
@@ -586,7 +586,7 @@ class _DeckImportScreenState extends State<DeckImportScreen> {
|
|||||||
child: CircularProgressIndicator(strokeWidth: 2),
|
child: CircularProgressIndicator(strokeWidth: 2),
|
||||||
)
|
)
|
||||||
: const Icon(Icons.merge_type, size: 20),
|
: const Icon(Icons.merge_type, size: 20),
|
||||||
label: Text(_isLoading ? '...' : 'Merge with existing deck'),
|
label: Text(_isLoading ? '...' : 'Merge with deck'),
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
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) {
|
void _cloneDeck(Deck deck) {
|
||||||
// Create a copy of the deck with a new ID and reset progress
|
// Create a copy of the deck with a new ID and reset progress
|
||||||
final clonedDeck = deck.copyWith(
|
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(
|
PopupMenuItem(
|
||||||
child: const Row(
|
child: const Row(
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
Reference in New Issue
Block a user