2017-05-26 13:21:47 +00:00
|
|
|
/// Efficiently paginates collections of items in an object-oriented manner.
|
|
|
|
class Paginator<T> {
|
|
|
|
final Map<int, PaginationResult<T>> _cache = {};
|
2021-06-20 12:37:20 +00:00
|
|
|
PaginationResult<T>? _current;
|
2017-05-26 13:21:47 +00:00
|
|
|
int _page = 0;
|
|
|
|
|
|
|
|
/// The collection of items to be paginated.
|
|
|
|
final Iterable<T> _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;
|
|
|
|
|
2021-06-20 12:37:20 +00:00
|
|
|
Paginator(this._items, {this.itemsPerPage = 5, this.useCache = true});
|
2017-05-26 13:21:47 +00:00
|
|
|
|
|
|
|
/// 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.
|
2021-06-20 12:37:20 +00:00
|
|
|
PaginationResult<T>? get current {
|
|
|
|
if (_current != null) {
|
2017-05-26 13:21:47 +00:00
|
|
|
return _current;
|
2021-06-20 12:37:20 +00:00
|
|
|
} else {
|
2017-05-26 13:21:47 +00:00
|
|
|
return _current = _getPage();
|
2021-06-20 12:37:20 +00:00
|
|
|
}
|
2017-05-26 13:21:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
PaginationResult<T> _computePage() {
|
|
|
|
var len = _items.length;
|
2021-06-20 12:37:20 +00:00
|
|
|
//var it = _items.skip(_page * (itemsPerPage ?? 5));
|
|
|
|
var it = _items.skip(_page * (itemsPerPage));
|
2017-05-26 13:21:47 +00:00
|
|
|
var offset = len - it.length;
|
|
|
|
it = it.take(itemsPerPage);
|
|
|
|
var last = _lastPage();
|
|
|
|
// print('cur: $_page, last: $last');
|
2021-06-20 12:37:20 +00:00
|
|
|
return _PaginationResultImpl(it,
|
2017-05-26 13:21:47 +00:00
|
|
|
currentPage: _page + 1,
|
|
|
|
previousPage: _page <= 0 ? -1 : _page,
|
|
|
|
nextPage: _page >= last - 1 ? -1 : _page + 2,
|
2017-05-27 14:23:51 +00:00
|
|
|
startIndex: it.isEmpty ? -1 : offset,
|
2017-05-26 13:21:47 +00:00
|
|
|
endIndex: offset + it.length - 1,
|
2019-02-01 14:39:10 +00:00
|
|
|
itemsPerPage:
|
|
|
|
itemsPerPage < _items.length ? itemsPerPage : _items.length,
|
2017-05-26 13:21:47 +00:00
|
|
|
total: len);
|
|
|
|
}
|
|
|
|
|
|
|
|
PaginationResult<T> _getPage() {
|
2021-06-20 12:37:20 +00:00
|
|
|
if (useCache != false) {
|
2017-05-26 13:21:47 +00:00
|
|
|
return _cache.putIfAbsent(_page, () => _computePage());
|
2021-06-20 12:37:20 +00:00
|
|
|
} else {
|
2017-05-26 13:21:47 +00:00
|
|
|
return _computePage();
|
2021-06-20 12:37:20 +00:00
|
|
|
}
|
2017-05-26 13:21:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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<T> {
|
|
|
|
factory PaginationResult.fromMap(Map<String, dynamic> map) =>
|
2021-06-20 12:37:20 +00:00
|
|
|
_PaginationResultImpl((map['data'] as List).cast<T>(),
|
|
|
|
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?);
|
2017-05-26 13:21:47 +00:00
|
|
|
|
2021-06-20 12:37:20 +00:00
|
|
|
Iterable<T> get data;
|
2017-05-26 13:21:47 +00:00
|
|
|
|
2021-06-20 12:37:20 +00:00
|
|
|
int? get currentPage;
|
2017-05-26 13:21:47 +00:00
|
|
|
|
2021-06-20 12:37:20 +00:00
|
|
|
int? get previousPage;
|
2017-05-26 13:21:47 +00:00
|
|
|
|
2021-06-20 12:37:20 +00:00
|
|
|
int? get nextPage;
|
2017-05-26 13:21:47 +00:00
|
|
|
|
2021-06-20 12:37:20 +00:00
|
|
|
int? get itemsPerPage;
|
2017-05-26 13:21:47 +00:00
|
|
|
|
2021-06-20 12:37:20 +00:00
|
|
|
int? get total;
|
2017-05-26 13:21:47 +00:00
|
|
|
|
2021-06-20 12:37:20 +00:00
|
|
|
int? get startIndex;
|
2017-05-26 13:21:47 +00:00
|
|
|
|
2021-06-20 12:37:20 +00:00
|
|
|
int? get endIndex;
|
2017-05-26 13:21:47 +00:00
|
|
|
|
|
|
|
Map<String, dynamic> toJson();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _PaginationResultImpl<T> implements PaginationResult<T> {
|
|
|
|
final Iterable<T> _data;
|
2021-06-20 12:37:20 +00:00
|
|
|
Iterable<T>? _cachedData;
|
2017-05-26 13:21:47 +00:00
|
|
|
|
|
|
|
@override
|
2021-06-20 12:37:20 +00:00
|
|
|
final int? currentPage;
|
2017-05-26 13:21:47 +00:00
|
|
|
|
|
|
|
_PaginationResultImpl(this._data,
|
|
|
|
{this.currentPage,
|
2019-02-01 14:39:10 +00:00
|
|
|
this.endIndex,
|
|
|
|
this.itemsPerPage,
|
|
|
|
this.nextPage,
|
|
|
|
this.previousPage,
|
|
|
|
this.startIndex,
|
|
|
|
this.total});
|
2017-05-26 13:21:47 +00:00
|
|
|
|
|
|
|
@override
|
2021-06-20 12:37:20 +00:00
|
|
|
Iterable<T> get data => _cachedData ?? (_cachedData = List<T>.from(_data));
|
2017-05-26 13:21:47 +00:00
|
|
|
|
|
|
|
@override
|
2021-06-20 12:37:20 +00:00
|
|
|
final int? endIndex;
|
2017-05-26 13:21:47 +00:00
|
|
|
|
|
|
|
@override
|
2021-06-20 12:37:20 +00:00
|
|
|
final int? itemsPerPage;
|
2017-05-26 13:21:47 +00:00
|
|
|
|
|
|
|
@override
|
2021-06-20 12:37:20 +00:00
|
|
|
final int? nextPage;
|
2017-05-26 13:21:47 +00:00
|
|
|
|
|
|
|
@override
|
2021-06-20 12:37:20 +00:00
|
|
|
final int? previousPage;
|
2017-05-26 13:21:47 +00:00
|
|
|
|
|
|
|
@override
|
2021-06-20 12:37:20 +00:00
|
|
|
final int? startIndex;
|
2017-05-26 13:21:47 +00:00
|
|
|
|
|
|
|
@override
|
2021-06-20 12:37:20 +00:00
|
|
|
final int? total;
|
2017-05-26 13:21:47 +00:00
|
|
|
|
|
|
|
@override
|
|
|
|
Map<String, dynamic> 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
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|