import 'dart:async'; import 'dart:math'; import 'package:flutter/widgets.dart'; import 'package:mnemo_cards/main.dart'; import 'package:mnemo_cards_common/mnemo_cards_common.dart'; import 'package:rxdart/rxdart.dart'; import '../../di/locator.dart'; class CardsViewController extends ChangeNotifier { bool _expanded = false; bool _favorite = false; double imageWidth = 0.0; List cards = []; List allCardsOrder = []; List favoriteCardsOrder = []; List get cardsOrder => _favorite ? favoriteCardsOrder : allCardsOrder; final CardPackDto cardPack; final ScrollController _cardsScrollController = ScrollController(keepScrollOffset: true); ScrollController get cardsScrollController => _cardsScrollController; StreamController _streamController = StreamController.broadcast(); Stream get asStream => _streamController.stream.startWith(state).distinct(); Stream get expandedStream => asStream.map((s) => s.expanded).distinct(); Stream get favoriteStream => asStream.map((s) => s.favorite).distinct(); Stream> get cardsStream => asStream.map((s) => s.packCards).distinct((p, n) => (p.length == n.length) && p.indexed.every((p) => n[p.$1].id == p.$2.id)); CardsViewState get state => CardsViewState( expanded: _expanded, favorite: _favorite, packCards: cards, ); List _filterPackCards() => cardPack.cards .where( (card) => !_favorite || locator.favoriteCardsController.isFavoriteCard(card.id), ) .toList(); void setExpanded(bool expanded) { if (cards.isNotEmpty) { _expanded = expanded; notifyListeners(); } } Future _saveCardsOrder() async { if (_favorite) { await globalSharedPreferences.setString( 'sort_fav_${cardPack.id}', favoriteCardsOrder.encode(), ); } else { await globalSharedPreferences.setString( 'sort_${cardPack.id}', allCardsOrder.encode(), ); } } void _loadCardsOrder() { favoriteCardsOrder = globalSharedPreferences .getString( 'sort_fav_${cardPack.id}', ) ?.decodeList() ?? []; allCardsOrder = globalSharedPreferences .getString( 'sort_${cardPack.id}', ) ?.decodeList() ?? []; if (allCardsOrder.isEmpty) { allCardsOrder = cardPack.cards.map((c) => c.id).toList(); } if (favoriteCardsOrder.isEmpty) { favoriteCardsOrder = allCardsOrder.toList(); } } void setCardsOrder(List order) { if (_favorite) { favoriteCardsOrder = order.toList(); } else { allCardsOrder = order.toList(); } _saveCardsOrder(); _updateCards(); } void onOrderChanged(int from, int to) { if (_favorite) { favoriteCardsOrder.move(from, to); } else { allCardsOrder.move(from, to); } _saveCardsOrder(); _updateCards(); } void setFavorite(bool favorite) { _favorite = favorite; _updateCards(); notifyListeners(); } void _addToStream() { _streamController.add(state); } double get offset => _cardsScrollController.offset; int get offsetCards => (_cardsScrollController.offset / imageWidth).floor(); void toggleExpanded() => setExpanded(!_expanded); CardsViewController(this.cardPack) { addListener(_addToStream); _loadCardsOrder(); _updateCards(); } void _updateCards() { cards = _filterPackCards(); cards ..sort((p, n) { final pIndex = cardsOrder.indexOf(p.id); final nIndex = cardsOrder.indexOf(n.id); if (pIndex == -1) { return nIndex == -1 ? p.id.compareTo(n.id) : 1; } else if (nIndex == -1) { return -1; } return pIndex.compareTo(nIndex); }); notifyListeners(); } Future animateTo(int index) { final millis = ((_cardsScrollController.offset / imageWidth - index).abs() * 30.0) .ceil(); return _cardsScrollController.animateTo( imageWidth * index, duration: Duration(milliseconds: max(millis, 100)), curve: Curves.easeInOut, ); } void jumpTo(int index) { return _cardsScrollController.jumpTo( imageWidth * index, ); } } class CardsViewState { final bool expanded; final bool favorite; final List packCards; CardsViewState({ required this.expanded, required this.favorite, required this.packCards, }); @override int get hashCode => expanded.hashCode ^ expanded.hashCode ^ packCards.length; @override bool operator ==(Object other) { return (other is CardsViewState) && expanded == other.expanded && favorite == other.favorite && (packCards.length == other.packCards.length) && packCards.indexed.every((p) => other.packCards[p.$1].id == p.$2.id); } }