Files
smooth-app/packages/smooth_app/lib/pages/prices/infinite_scroll_manager.dart
monsieurtanuki 800344f2ee feat: 6790 - reuse price tokens (#6793)
Impacted files:
* `background_task_add_other_price.dart`: minor refactoring
* `background_task_add_price.dart`: minor refactoring
* `background_task_price.dart`: now using new method `ProductQuery.getPriceToken` to get the token; never closing the token session at the end
* `infinite_scroll_manager.dart`: minor refactoring
* `prices_proofs_page.dart`: minor refactoring
* `product_query.dart`: new method `getPriceToken` that caches the token
2025-07-21 17:14:25 +02:00

140 lines
3.6 KiB
Dart

import 'package:flutter/material.dart';
import 'package:smooth_app/l10n/app_localizations.dart';
/// A generic abstract class for handling infinite scrolling in lists.
/// [T] is the type of items being displayed.
abstract class InfiniteScrollManager<T> {
/// Creates an [InfiniteScrollManager] with optional initial state.
InfiniteScrollManager({
final List<T>? initialItems,
final int? totalItems,
final int? totalPages,
}) : _items = List<T>.from(initialItems ?? <T>[]),
_currentPage = initialItems != null && initialItems.isNotEmpty
? _initialPage
: 0,
_totalPages = totalPages,
_totalItems = totalItems;
static const int _initialPage = 1;
/// Current items in the list
final List<T> _items;
/// Current page being fetched
int _currentPage;
/// Whether currently loading more items
bool _isLoading = false;
/// Additional pagination information
int? _totalItems;
int? _totalPages;
/// Getter for items
List<T> get items => _items;
/// Getter for current page
int get currentPage => _currentPage;
/// Getter for loading state
bool get isLoading => _isLoading;
/// Getter for total items
int? get totalItems => _totalItems;
/// Getter for total pages
int? get totalPages => _totalPages;
@protected
Future<void> fetchInit(final BuildContext context) async {}
/// Fetches data for a specific page
@protected
Future<void> fetchData(int pageNumber);
/// Displays an item.
Widget buildItem({required BuildContext context, required T item});
/// Update the list with new items and pagination info
@protected
void updateItems({
required List<T>? newItems,
required int? pageNumber,
required int? totalItems,
required int? totalPages,
}) {
if (newItems == null && pageNumber == null) {
return;
}
if (newItems != null) {
_items.addAll(newItems);
}
if (pageNumber != null) {
_currentPage = pageNumber;
}
_totalItems = totalItems ?? _totalItems;
_totalPages = totalPages ?? _totalPages;
}
/// Load initial data only if the list is empty
Future<void> loadInitiallyIfNeeded(BuildContext context) async {
await fetchInit(context);
if (_items.isNotEmpty) {
return;
}
if (context.mounted) {
await _load(context: context, pageNumber: _initialPage);
}
}
bool canLoadMore() {
return !_isLoading && (totalPages == null || currentPage < totalPages!);
}
/// Load more items (next page)
Future<void> loadMore(BuildContext context) async {
if (_totalPages == null || _currentPage >= _totalPages!) {
return;
}
await _load(context: context, pageNumber: _currentPage + 1);
}
/// Internal method to handle loading with error handling
Future<void> _load({
required BuildContext context,
required int pageNumber,
}) async {
if (_isLoading) {
return;
}
_isLoading = true;
try {
await fetchInit(context);
await fetchData(pageNumber);
} catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
AppLocalizations.of(context).prices_error_loading_more_items,
),
),
);
}
} finally {
_isLoading = false;
}
}
/// Returns a formatted item count (e.g., "25 of 100 items")
String formattedItemCount(BuildContext context) {
final AppLocalizations appLocalizations = AppLocalizations.of(context);
return _totalItems != null
? appLocalizations.item_count_with_total(_items.length, _totalItems!)
: appLocalizations.item_count(_items.length);
}
}