import 'package:presentum/presentum.dart';/// Where presentations can appearenum AppSurface with PresentumSurface { homeTopBanner, profileAlert, popup;}/// How presentations are displayedenum CampaignVariant with PresentumVisualVariant { banner, dialog, inline;}
Surfaces are locations (homeTopBanner, popup). Variants are display
styles (banner, dialog).
import 'dart:async';import 'package:presentum/presentum.dart';import 'package:shared_preferences/shared_preferences.dart';typedef PersistentPresentumStorageKey< S extends PresentumSurface, V extends PresentumVisualVariant> = (String itemId, S surface, V variant);extension type PersistentPresentumStorageKeys< S extends PresentumSurface, V extends PresentumVisualVariant>(PersistentPresentumStorageKey<S, V> key) { String get shownCount => '__shown_${key.$1}_${key.$2.name}_${key.$3.name}_count_key__'; String get lastShown => '__shown_${key.$1}_${key.$2.name}_${key.$3.name}_last_shown_key__'; String get timestamps => '__shown_${key.$1}_${key.$2.name}_${key.$3.name}_timestamps_key__'; String get dismissedAt => '__dismissed_${key.$1}_${key.$2.name}_${key.$3.name}_at_key__'; String get convertedAt => '__converted_${key.$1}_${key.$2.name}_${key.$3.name}_at_key__'; List<String> get allKeys => [ shownCount, lastShown, timestamps, dismissedAt, convertedAt, ];}class PersistentPresentumStorage< S extends PresentumSurface, V extends PresentumVisualVariant> implements PresentumStorage<S, V> { PersistentPresentumStorage({required SharedPreferencesWithCache prefs}) : _prefs = prefs; final SharedPreferencesWithCache _prefs; @override Future<void> clearItem( String itemId, { required S surface, required V variant, }) => Future.wait( PersistentPresentumStorageKeys(( itemId, surface, variant, )).allKeys.map(_prefs.remove), ); @override FutureOr<DateTime?> getLastShown( String itemId, { required S surface, required V variant, }) async { final key = PersistentPresentumStorageKeys(( itemId, surface, variant, )).lastShown; final timestampStr = _prefs.getString(key); return timestampStr != null ? DateTime.parse(timestampStr) : null; } @override FutureOr<void> recordShown( String itemId, { required S surface, required V variant, required DateTime at, }) async { final keys = PersistentPresentumStorageKeys((itemId, surface, variant)); final countKey = keys.shownCount; final lastShownKey = keys.lastShown; final timestampsKey = keys.timestamps; final currentCount = _prefs.getInt(countKey) ?? 0; final currentTimestamps = _prefs.getStringList(timestampsKey) ?? []; await _prefs.setInt(countKey, currentCount + 1); await _prefs.setString(lastShownKey, at.toIso8601String()); await _prefs.setStringList(timestampsKey, [ ...currentTimestamps, at.toIso8601String(), ]); } @override FutureOr<int> getShownCount( String itemId, { required Duration period, required S surface, required V variant, }) async { final keys = PersistentPresentumStorageKeys((itemId, surface, variant)); final timestampsKey = keys.timestamps; final timestampStrings = _prefs.getStringList(timestampsKey) ?? []; final timestamps = timestampStrings.map(DateTime.parse).toList(); final cutoff = DateTime.now().subtract(period); final count = timestamps.where((t) => t.isAfter(cutoff)).length; return count; } @override FutureOr<DateTime?> getDismissedAt( String itemId, { required S surface, required V variant, }) async { final keys = PersistentPresentumStorageKeys((itemId, surface, variant)); final timestampStr = _prefs.getString(keys.dismissedAt); return timestampStr != null ? DateTime.parse(timestampStr) : null; } @override FutureOr<void> recordDismissed( String itemId, { required S surface, required V variant, required DateTime at, }) async { final keys = PersistentPresentumStorageKeys((itemId, surface, variant)); await _prefs.setString(keys.dismissedAt, at.toIso8601String()); } @override FutureOr<void> recordConverted( String itemId, { required S surface, required V variant, required DateTime at, }) async { final keys = PersistentPresentumStorageKeys((itemId, surface, variant)); await _prefs.setString(keys.convertedAt, at.toIso8601String()); }}
This storage implementation is generic and reusable across any Presentum instance. The type parameters <S, V> make it work with any surface and variant enums you define.