/// Efficiently paginates collections of items in an object-oriented manner. class Paginator { final Map> _cache = {}; PaginationResult? _current; int _page = 0; /// The collection of items to be paginated. final Iterable _items; /// The maximum number of items to be shown per page. final int itemsPerPage; /// If `true` (default), then the results of paginations will be saved by page number. /// /// For example, you would only have to paginate page 1 once. Future calls would return a cached version. final bool useCache; Paginator(this._items, {this.itemsPerPage = 5, this.useCache = true}); /// Returns `true` if there are more items at lesser page indices than the current one. bool get canGoBack => _page > 0; /// Returns `true` if there are more items at greater page indices than the current one. bool get canGoForward => _page < _lastPage(); /// The current page index. int get index => _page; /// Returns the greatest possible page number for this collection, given the number of [itemsPerPage]. int get lastPageNumber => _lastPage(); /// The current page number. This is not the same as [index]. /// /// This getter will return user-friendly numbers. The lowest value it will ever return is `1`. int get pageNumber => _page < 1 ? 1 : (_page + 1); /// Fetches the current page. This will be cached until [back] or [next] is called. /// /// If [useCache] is `true` (default), then computations will be cached even after the page changes. PaginationResult? get current { if (_current != null) { return _current; } else { return _current = _getPage(); } } PaginationResult _computePage() { var len = _items.length; //var it = _items.skip(_page * (itemsPerPage ?? 5)); var it = _items.skip(_page * (itemsPerPage)); var offset = len - it.length; it = it.take(itemsPerPage); var last = _lastPage(); // print('cur: $_page, last: $last'); return _PaginationResultImpl(it, currentPage: _page + 1, previousPage: _page <= 0 ? -1 : _page, nextPage: _page >= last - 1 ? -1 : _page + 2, startIndex: it.isEmpty ? -1 : offset, endIndex: offset + it.length - 1, itemsPerPage: itemsPerPage < _items.length ? itemsPerPage : _items.length, total: len); } PaginationResult _getPage() { if (useCache != false) { return _cache.putIfAbsent(_page, () => _computePage()); } else { return _computePage(); } } int _lastPage() { var n = (_items.length / itemsPerPage).round(); // print('items: ${_items.length}'); // print('per page: $itemsPerPage'); // print('quotient: $n'); var remainder = _items.length - (n * itemsPerPage); // print('remainder: $remainder'); return (remainder <= 0) ? n : n + 1; } /// Attempts to go the specified page. If it fails, then it will remain on the current page. /// /// Keep in mind - this just not be a zero-based index, but a one-based page number. The lowest /// allowed value is `1`. void goToPage(int page) { if (page > 0 && page <= _lastPage()) { _page = page - 1; _current = null; } } /// Moves the paginator back one page, if possible. void back() { if (_page > 0) { _page--; _current = null; } } /// Advances the paginator one page, if possible. void next() { if (_page < _lastPage()) { _page++; _current = null; } } } /// Stores the result of a pagination. abstract class PaginationResult { factory PaginationResult.fromMap(Map map) => _PaginationResultImpl((map['data'] as List).cast(), currentPage: map['current_page'] as int?, endIndex: map['end_index'] as int?, itemsPerPage: map['items_per_page'] as int?, nextPage: map['next_page'] as int?, previousPage: map['previous_page'] as int?, startIndex: map['start_index'] as int?, total: map['total'] as int?); Iterable get data; int? get currentPage; int? get previousPage; int? get nextPage; int? get itemsPerPage; int? get total; int? get startIndex; int? get endIndex; Map toJson(); } class _PaginationResultImpl implements PaginationResult { final Iterable _data; Iterable? _cachedData; @override final int? currentPage; _PaginationResultImpl(this._data, {this.currentPage, this.endIndex, this.itemsPerPage, this.nextPage, this.previousPage, this.startIndex, this.total}); @override Iterable get data => _cachedData ?? (_cachedData = List.from(_data)); @override final int? endIndex; @override final int? itemsPerPage; @override final int? nextPage; @override final int? previousPage; @override final int? startIndex; @override final int? total; @override Map toJson() { return { 'total': total, 'items_per_page': itemsPerPage, 'previous_page': previousPage, 'current_page': currentPage, 'next_page': nextPage, 'start_index': startIndex, 'end_index': endIndex, 'data': data }; } }