Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 78 additions & 10 deletions lib/providers/badge_slot_provider..dart
Original file line number Diff line number Diff line change
@@ -1,27 +1,95 @@
import 'package:flutter/material.dart';

extension FirstOrNullExtension<E> on Iterable<E> {
E? get firstOrNull => isEmpty ? null : first;
}
Comment on lines +3 to +5
Copy link

Copilot AI Oct 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The FirstOrNullExtension is defined but never used in the codebase. Consider removing it to reduce code clutter, or use it if it was intended for future functionality.

Copilot uses AI. Check for mistakes.

class BadgeSlotProvider with ChangeNotifier {
final Set<String> _selectedBadges = {};
// Maps selected badge key -> assigned slot number (1..8)
final Map<String, int> _badgeKeyToSlot = {};

// Available slot numbers pool
final Set<int> _availableSlots = {1, 2, 3, 4, 5, 6, 7, 8};

static const int maxSelectedBadges = 8;

Set<String> get selectedBadges => _selectedBadges;
Set<String> get selectedBadges => _badgeKeyToSlot.keys.toSet();

bool isSelected(String badgeKey) => _selectedBadges.contains(badgeKey);
bool isSelected(String badgeKey) => _badgeKeyToSlot.containsKey(badgeKey);

bool get canSelectMore => _selectedBadges.length < maxSelectedBadges;
bool get canSelectMore =>
_badgeKeyToSlot.length < maxSelectedBadges && _availableSlots.isNotEmpty;

int? getSlotForBadge(String badgeKey) => _badgeKeyToSlot[badgeKey];

List<String> getSelectionsOrderedBySlot() {
final entries = _badgeKeyToSlot.entries.toList()
..sort((a, b) => a.value.compareTo(b.value));
return entries.map((e) => e.key).toList();
}

/// ✅ Updated reorderSlots to perform push-down insertion
void reorderSlots(String fromBadgeKey, String toBadgeKey) {
if (!_badgeKeyToSlot.containsKey(fromBadgeKey) ||
!_badgeKeyToSlot.containsKey(toBadgeKey)) return;

final orderedKeys = getSelectionsOrderedBySlot();
orderedKeys.remove(fromBadgeKey);
final toIndex = orderedKeys.indexOf(toBadgeKey);

orderedKeys.insert(toIndex, fromBadgeKey);

_badgeKeyToSlot.clear();
_availableSlots
..clear()
..addAll({1, 2, 3, 4, 5, 6, 7, 8});

for (int i = 0; i < orderedKeys.length; i++) {
final slot = i + 1;
_badgeKeyToSlot[orderedKeys[i]] = slot;
_availableSlots.remove(slot);
}

notifyListeners();
}

void toggleSelection(String badgeKey) {
if (_selectedBadges.contains(badgeKey)) {
_selectedBadges.remove(badgeKey);
notifyListeners();
} else if (_selectedBadges.length < maxSelectedBadges) {
_selectedBadges.add(badgeKey);
if (_badgeKeyToSlot.containsKey(badgeKey)) {
// Unselect: free its slot
final freedSlot = _badgeKeyToSlot.remove(badgeKey);
if (freedSlot != null) {
_availableSlots.add(freedSlot);
}
notifyListeners();
return;
}

if (_badgeKeyToSlot.length >= maxSelectedBadges ||
_availableSlots.isEmpty) {
return; // Cannot select more
}

// Assign smallest available slot
final smallest = _availableSlots.reduce((a, b) => a < b ? a : b);
Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will throw a StateError if _availableSlots is empty (which can happen if 8 badges are already selected). Before calling reduce(), check if the set is empty or use firstOrNull from the extension defined in this file. The check on line 141 may not prevent this if called through other code paths.

Copilot uses AI. Check for mistakes.
_availableSlots.remove(smallest);
_badgeKeyToSlot[badgeKey] = smallest;
notifyListeners();
}

void clearSelections() {
_selectedBadges.clear();
_badgeKeyToSlot.clear();
_availableSlots
..clear()
..addAll({1, 2, 3, 4, 5, 6, 7, 8});
notifyListeners();
}

bool canTransfer(String badgeKey) {
final slot = _badgeKeyToSlot[badgeKey];
return slot != null && slot <= 8;
}

List<String> getTransferableBadges() {
return getSelectionsOrderedBySlot().take(8).toList();
}
}
5 changes: 3 additions & 2 deletions lib/view/save_badge_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,9 @@ class _SaveBadgeScreenState extends State<SaveBadgeScreen> {
onPressed: selectionProvider
.selectedBadges.isNotEmpty
? () async {
final selectedBadges =
selectionProvider.selectedBadges;
// Use transferable badges (first 8 slots only)
final selectedBadges = selectionProvider
.getTransferableBadges();
List<Message> badgeDataList = [];

for (var badgeKey in selectedBadges) {
Expand Down
Loading
Loading