Skip to main content

Overview

The CollectiblesLogic class manages the collection of discoverable artifacts in the Wonderous app. It tracks the state of each collectible (lost, discovered, or explored), persists this data locally, and integrates with native home widgets to display recently discovered items.

Class Definition

class CollectiblesLogic with ThrottledSaveLoadMixin {
  @override
  String get fileName => 'collectibles.dat';
  
  final List<CollectibleData> all = collectiblesData;
  late final statesById = ValueNotifier<Map<String, int>>({});
}

Properties

all
List<CollectibleData>
Contains all 24 collectible artifacts (3 per wonder). This is a static list loaded from collectiblesData.
statesById
ValueNotifier<Map<String, int>>
A reactive map tracking the current state of each collectible by its ID. States are defined by CollectibleState constants.
discoveredCount
int
Read-only count of collectibles in the “discovered” state.
exploredCount
int
Read-only count of collectibles in the “explored” state.
fileName
String
The filename used for persisting collectible states. Returns 'collectibles.dat'.

Collectible States

Each collectible can be in one of three states:
class CollectibleState {
  static const int lost = 0;       // Not yet found
  static const int discovered = 1; // Found but not viewed in detail
  static const int explored = 2;   // Viewed in detail
}

Methods

init()

Initializes the native widget service for home screen integration.
void init()
Example:
collectiblesLogic.init();

load()

Loads saved collectible states from local storage. Inherited from ThrottledSaveLoadMixin.
Future<void> load()
Example:
await collectiblesLogic.load();
print('Loaded ${collectiblesLogic.discoveredCount} discovered items');

setState()

Updates the state of a specific collectible and triggers auto-save.
void setState(String id, int state)
Parameters:
  • id - The collectible’s unique identifier
  • state - The new state (use CollectibleState constants)
Side Effects:
  • Updates the native home widget when an item is discovered
  • Schedules an auto-save operation
  • Triggers count updates
Example:
// Mark a collectible as discovered
collectiblesLogic.setState('701645', CollectibleState.discovered);

// Mark as explored after user views details
collectiblesLogic.setState('701645', CollectibleState.explored);

fromId()

Retrieves collectible data by its ID.
CollectibleData? fromId(String? id)
Parameters:
  • id - The collectible’s unique identifier (can be null)
Returns: CollectibleData object or null if not found Example:
final collectible = collectiblesLogic.fromId('701645');
if (collectible != null) {
  print('Found: ${collectible.title}');
}

forWonder()

Retrieves all collectibles associated with a specific wonder.
List<CollectibleData> forWonder(WonderType wonder)
Parameters:
  • wonder - The wonder type enum value
Returns: List of 3 collectibles for the specified wonder Example:
final tajCollectibles = collectiblesLogic.forWonder(WonderType.tajMahal);
print('Taj Mahal has ${tajCollectibles.length} collectibles');

for (var item in tajCollectibles) {
  print('- ${item.title}');
}

getFirstDiscoveredOrNull()

Retrieves the first discovered collectible, sorted by wonder order.
CollectibleData? getFirstDiscoveredOrNull()
Returns: The first discovered collectible or null if none are discovered Example:
final firstDiscovered = collectiblesLogic.getFirstDiscoveredOrNull();
if (firstDiscovered != null) {
  print('You discovered: ${firstDiscovered.title}');
}

isLost()

Checks if a specific collectible for a wonder is in the “lost” state.
bool isLost(WonderType wonderType, int i)
Parameters:
  • wonderType - The wonder to check
  • i - The index of the collectible (0-2) for that wonder
Returns: true if the collectible is lost or doesn’t exist Example:
// Check if the first collectible for Taj Mahal is lost
final isLost = collectiblesLogic.isLost(WonderType.tajMahal, 0);
print('First Taj Mahal collectible is ${isLost ? "lost" : "found"}');

reset()

Resets all collectibles to the “lost” state and clears home widget data.
void reset()
Side Effects:
  • Sets all collectibles to CollectibleState.lost
  • Clears native home widget data
  • Triggers auto-save
Example:
collectiblesLogic.reset();
print('Collection reset complete');

CollectibleData Structure

Each collectible contains:
class CollectibleData {
  final String title;        // Display name
  final String iconName;     // Icon identifier
  final String artifactId;   // Met Museum artifact ID
  final WonderType wonder;   // Associated wonder
  late final ImageProvider icon;
  
  String get id => artifactId;
  String get subtitle => wondersLogic.getData(wonder).artifactCulture;
  String get imageUrl => ArtifactData.getSelfHostedImageUrl(id);
  String get imageUrlSmall => ArtifactData.getSelfHostedImageUrlSmall(id);
}

Collectibles by Wonder

Each wonder has 3 associated collectibles:

Chichen Itza

  • Pendant (jewelry)
  • Bird Ornament (jewelry)
  • La Prison, à Chichen-Itza (picture)

Christ the Redeemer

  • Engraved Horn (statue)
  • Fixed fan (jewelry)
  • Handkerchiefs (textile)

Colosseum

  • Glass hexagonal amphoriskos (vase)
  • Bronze plaque of Mithras (statue)
  • Interno del Colosseo (picture)

Great Wall of China

  • Biographies of Lian Po and Lin Xiangru (scroll)
  • Jar with Dragon (vase)
  • Panel with Peonies and Butterfly (textile)

Machu Picchu

  • Eight-Pointed Star Tunic (textile)
  • Camelid figurine (statue)
  • Double Bowl (vase)

Petra

  • Camel and riders (statue)
  • Vessel (vase)
  • Open bowl (vase)

Pyramids of Giza

  • Two papyrus fragments (scroll)
  • Fragmentary Face of King Khafre (statue)
  • Jewelry Elements (jewelry)

Taj Mahal

  • Dagger with Scabbard (jewelry)
  • The House of Bijapur (picture)
  • Panel of Nasta’liq Calligraphy (scroll)

Usage Examples

Basic Setup

// Access the global CollectiblesLogic instance
CollectiblesLogic get collectiblesLogic => GetIt.I.get<CollectiblesLogic>();

// Initialize and load (called during app bootstrap)
collectiblesLogic.init();
await collectiblesLogic.load();

Tracking Discovery Progress

// Get total progress
final total = collectiblesLogic.all.length; // 24
final discovered = collectiblesLogic.discoveredCount;
final explored = collectiblesLogic.exploredCount;
final found = discovered + explored;

print('Progress: $found / $total collectibles found');
print('$discovered awaiting exploration');
print('$explored fully explored');

Discovering a Collectible

// User finds a collectible during gameplay
void discoverCollectible(String artifactId) {
  collectiblesLogic.setState(artifactId, CollectibleState.discovered);
  
  final item = collectiblesLogic.fromId(artifactId);
  print('Discovered: ${item?.title}');
}

Viewing Wonder Collectibles

// Display collectibles for a specific wonder
void showWonderCollectibles(WonderType wonder) {
  final items = collectiblesLogic.forWonder(wonder);
  final states = collectiblesLogic.statesById.value;
  
  for (var item in items) {
    final state = states[item.id] ?? CollectibleState.lost;
    final status = state == CollectibleState.lost ? '🔒 Locked' :
                   state == CollectibleState.discovered ? '✨ New' : '✓ Explored';
    print('$status - ${item.title}');
  }
}

Reactive UI Updates

// Listen to state changes
collectiblesLogic.statesById.addListener(() {
  final discovered = collectiblesLogic.discoveredCount;
  print('Discovered count changed: $discovered');
  // Update UI
});

Checking Completion

// Check if all collectibles for a wonder are found
bool isWonderComplete(WonderType wonder) {
  final items = collectiblesLogic.forWonder(wonder);
  final states = collectiblesLogic.statesById.value;
  
  return items.every((item) {
    final state = states[item.id] ?? CollectibleState.lost;
    return state != CollectibleState.lost;
  });
}

// Check overall completion
bool isCollectionComplete() {
  final found = collectiblesLogic.discoveredCount + 
                collectiblesLogic.exploredCount;
  return found == collectiblesLogic.all.length;
}

Persistence

The class uses ThrottledSaveLoadMixin for automatic persistence:
  • Auto-save: Changes are automatically saved after a short delay
  • File location: collectibles.dat in the app’s data directory
  • Format: JSON map of artifact IDs to state integers
  • Throttling: Multiple rapid changes are batched into a single save operation

Native Widget Integration

When a collectible is discovered, the home widget is updated with:
  • Title: The collectible’s name
  • Subtitle: The artifact’s date
  • Image: A base64-encoded thumbnail
  • Count: Total number of discovered items

Registration

The CollectiblesLogic instance is registered as a singleton using GetIt:
void registerSingletons() {
  GetIt.I.registerLazySingleton<CollectiblesLogic>(() => CollectiblesLogic());
}

Initialization Flow

The CollectiblesLogic initialization is part of the app’s bootstrap process:
Future<void> bootstrap() async {
  // ... previous initialization steps
  
  // Collectibles
  collectiblesLogic.init();
  await collectiblesLogic.load();
  
  // ... remaining initialization
}
  • CollectibleData - Data structure for each collectible
  • CollectibleState - State constants (lost, discovered, explored)
  • WonderType - Enum linking collectibles to wonders
  • ArtifactAPILogic - Fetches detailed artifact data
  • NativeWidgetService - Updates home screen widgets
  • ThrottledSaveLoadMixin - Provides persistence functionality

Build docs developers (and LLMs) love