|
|
|
|
@ -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: [
|
|
|
|
|
|