library flutter_pagewise;

import 'package:flutter/material.dart';
import 'helpers/grid_helpers.dart';

typedef Widget ItemBuilder<T>(BuildContext context, T entry, int index);
typedef Future<List<T>> PageFuture<T>(int? pageIndex);
typedef Widget ErrorBuilder(BuildContext context, Object? error);
typedef Widget LoadingBuilder(BuildContext context);
typedef Widget NoItemsFoundBuilder(BuildContext context);
typedef Widget RetryBuilder(BuildContext context, RetryCallback retryCallback);
typedef void RetryCallback();
typedef Widget PagewiseBuilder<T>(PagewiseState<T> state);

/// An abstract base class for widgets that fetch their content one page at a
/// time.
///
/// The widget fetches the page when we scroll down to it, and then keeps it in
/// memory
///
/// You can build your own Pagewise widgets by extending this class and
/// returning your builder in the [builder] function which provides you with the
/// Pagewise state. Look [PagewiseListView] and [PagewiseGridView] for examples.
///
/// See also:
///
///  * [PagewiseGridView], a [Pagewise] implementation of [GridView](https://docs.flutter.io/flutter/widgets/GridView-class.html)
///  * [PagewiseListView], a [Pagewise] implementation of [ListView](https://docs.flutter.io/flutter/widgets/ListView-class.html)
abstract class Pagewise<T> extends StatefulWidget {
  /// The number  of entries per page
  final int? pageSize;

  /// Called whenever a new page (or batch) is to be fetched
  ///
  /// It is provided with the page index, and expected to return a [Future](https://api.dartlang.org/stable/1.24.3/dart-async/Future-class.html) that
  /// resolves to a list of entries. Please make sure to return only [pageSize]
  /// or less entries (in the case of the last page) for each page.
  final PageFuture<T>? pageFuture;

  /// Called when loading each page.
  ///
  /// It is expected to return a widget to display while the page is loading.
  /// For example:
  /// ```dart
  /// (BuildContext context) {
  ///   return Text('Loading...');
  /// }
  /// ```
  ///
  /// If not specified, a [CircularProgressIndicator](https://docs.flutter.io/flutter/material/CircularProgressIndicator-class.html) will be shown
  final LoadingBuilder? loadingBuilder;

  /// Called with an error object if an error occurs when loading the page
  ///
  /// It is expected to return a widget to display in place of the page that
  /// failed to load. For example:
  /// ```dart
  /// (BuildContext context, Object error) {
  ///   return Text('Failed to load page: $error');
  /// }
  /// ```
  /// If not specified, a [Text] containing the error will be displayed
  final ErrorBuilder? errorBuilder;

  /// Whether to show a retry button when page fails to load.
  ///
  /// If set to true, [retryBuilder] is called to show appropriate retry button.
  ///
  /// If set to false, [errorBuilder] is called instead to show appropriate
  /// error.
  final bool showRetry;

  /// Called when a page fails to load and [showRetry] is set to true.
  ///
  /// It is expected to return a widget that gives the user the idea that retry
  /// is possible. The builder is provided with a [RetryCallback] that must be
  /// called for the retry to happen.
  ///
  /// For example:
  /// ```dart
  /// (context, retryCallback) {
  ///   return FloatingActionButton(
  ///     onPressed: retryCallback,
  ///     backgroundColor: Colors.red,
  ///     child: Icon(Icons.refresh),
  ///   );
  /// }
  /// ```
  ///
  /// In the code above, when the button is pressed, retryCallback is called,
  /// which will retry to fetch the page.
  ///
  /// If not specified, a simple retry button will be shown
  final RetryBuilder? retryBuilder;

  /// Called when no items are found
  ///
  /// It is expected to return a widget that gives the user the idea that no
  /// items exist in the list
  /// For example:
  ///  ```dart
  ///  (BuildContext context) {
  ///    return Text('No Items Found!');
  ///  }
  ///  ```
  final NoItemsFoundBuilder? noItemsFoundBuilder;

  /// Called to build each entry in the view.
  ///
  /// It is called for each of the entries fetched by [pageFuture] and provided
  /// with the [BuildContext](https://docs.flutter.io/flutter/widgets/BuildContext-class.html) and the entry. It is expected to return the widget
  /// that we want to display for each entry
  ///
  /// For example, the [pageFuture] might return a list that looks like:
  /// ```dart
  ///[
  ///  {
  ///    'name': 'product1',
  ///    'price': 10
  ///  },
  ///  {
  ///    'name': 'product2',
  ///    'price': 15
  ///  },
  ///]
  /// ```
  /// Then itemBuilder will be called twice, once for each entry. We can for
  /// example do:
  /// ```dart
  /// (BuildContext context, dynamic entry) {
  ///   return Text(entry['name'] + ' - ' + entry['price']);
  /// }
  /// ```
  final ItemBuilder<T> itemBuilder;

  /// The actual builder that builds the Pagewise widget. It is called and
  /// provided the PagewiseState. This function is important only for classes
  /// extending Pagewise. See [PagewiseListView] and [PagewiseGridView] for
  /// examples.
  final PagewiseBuilder<T> builder;

  /// The controller that controls the loading of pages.
  ///
  /// You don't have to provide this parameter unless you want to control or
  /// listen to the data that Pagewise fetches. Review the documentation of
  /// [PagewiseLoadController] for more details
  final PagewiseLoadController<T>? pageLoadController;

  /// Creates a pagewise widget.
  ///
  /// This is an abstract class, this constructor should only be called from
  /// constructors of widgets that extend this class
  Pagewise(
      {this.pageSize,
      this.pageFuture,
      Key? key,
      this.pageLoadController,
      this.loadingBuilder,
      this.retryBuilder,
      this.noItemsFoundBuilder,
      this.showRetry: true,
      required this.itemBuilder,
      this.errorBuilder,
      required this.builder})
      : assert(
            (pageLoadController == null && pageSize != null && pageFuture != null) || (pageLoadController != null && pageSize == null && pageFuture == null)),
        assert(showRetry == false || errorBuilder == null, 'Cannot specify showRetry and errorBuilder at the same time'),
        assert(showRetry == true || retryBuilder == null, 'Cannot specify retryBuilder when showRetry is set to false'),
        super(key: key);

  @override
  PagewiseState<T> createState() => PagewiseState<T>();
}

class PagewiseState<T> extends State<Pagewise<T>> {
  PagewiseLoadController<T>? _controller;

  PagewiseLoadController<T>? get _effectiveController => widget.pageLoadController ?? this._controller;

  late VoidCallback _controllerListener;

  @override
  void initState() {
    super.initState();

    if (widget.pageLoadController == null) {
      this._controller = PagewiseLoadController<T>(pageFuture: widget.pageFuture, pageSize: widget.pageSize);
    }

    this._effectiveController!.init();

    this._controllerListener = () {
      setState(() {});
    };

    this._effectiveController!.addListener(this._controllerListener);
  }

  @override
  void dispose() {
    super.dispose();
    this._effectiveController!.removeListener(this._controllerListener);
  }

  @override
  void didUpdateWidget(Pagewise<T> oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.pageLoadController == null && oldWidget.pageLoadController != null) {
      oldWidget.pageLoadController!.removeListener(this._controllerListener);
      this._controller = PagewiseLoadController<T>(pageFuture: oldWidget.pageLoadController!.pageFuture, pageSize: oldWidget.pageLoadController!.pageSize);
      this._effectiveController!.addListener(this._controllerListener);
      this._effectiveController!.init();
    } else if (widget.pageLoadController != null && oldWidget.pageLoadController == null) {
      this._controller!.removeListener(this._controllerListener);
      this._controller = null;
      this._effectiveController!.addListener(this._controllerListener);
      this._effectiveController!.init();
    } else if (widget.pageLoadController != null && (widget.pageLoadController != oldWidget.pageLoadController)) {
      oldWidget.pageLoadController!.removeListener(this._controllerListener);
      this._effectiveController!.addListener(this._controllerListener);
      this._effectiveController!.init();
    }
  }

  int get _itemCount => this._effectiveController!.loadedItems!.length + 1;

  @override
  Widget build(BuildContext context) {
    return widget.builder(this);
  }

  Widget _itemBuilder(BuildContext context, int index) {
    // The total number of widgets, is the number of loaded items, plus the
    // number of items that we appended to make all pages the same size,
    // plus 1 for the loader
    final total = this._effectiveController!.loadedItems!.length + this._effectiveController!._appendedItems.length + 1;

    if (index >= total) return SizedBox.shrink();

    if (index == total - 1) {
      if (this._effectiveController!.noItemsFound) {
        return this._getNoItemsFoundWidget();
      }

      if (this._effectiveController!.error != null) {
        if (widget.showRetry) {
          return this._getRetryWidget();
        } else {
          return this._getErrorWidget(this._effectiveController!.error);
        }
      }

      if (this._effectiveController!.hasMoreItems!) {
        this._effectiveController!.fetchNewPage();
        return this._getLoadingWidget();
      } else {
        return Container();
      }
    } else {
      if (index >= this._effectiveController!.loadedItems!.length) {
        // this means that the function is asking for an element from the
        // appended items, so we return an empty container
        return Container();
      }
      // Otherwise, we return the actual item
      return widget.itemBuilder(context, this._effectiveController!.loadedItems![index], index);
    }
  }

  Widget _getLoadingWidget() {
    return this._getStandardContainer(child: widget.loadingBuilder != null ? widget.loadingBuilder!(context) : CircularProgressIndicator());
  }

  Widget _getNoItemsFoundWidget() {
    return this._getStandardContainer(child: widget.noItemsFoundBuilder != null ? widget.noItemsFoundBuilder!(context) : Container());
  }

  Widget _getErrorWidget(Object? error) {
    return this._getStandardContainer(
        child: widget.errorBuilder != null
            ? widget.errorBuilder!(context, this._effectiveController!.error)
            : Text('Error: $error', style: TextStyle(color: Theme.of(context).disabledColor, fontStyle: FontStyle.italic)));
  }

  Widget _getRetryWidget() {
    var defaultRetryButton = TextButton(
      style: TextButton.styleFrom(
        backgroundColor: Colors.grey[300],
        shape: CircleBorder(),
      ),
      child: Icon(
        Icons.refresh,
        color: Colors.white,
      ),
      onPressed: this._effectiveController!.retry,
    );

    return this
        ._getStandardContainer(child: widget.retryBuilder != null ? widget.retryBuilder!(context, this._effectiveController!.retry) : defaultRetryButton);
  }

  Widget _getStandardContainer({Widget? child}) {
    return Padding(
        padding: const EdgeInsets.symmetric(vertical: 8.0),
        child: Align(
          alignment: Alignment.topCenter,
          child: child,
        ));
  }
}

/// The controller responsible for managing page loading in Pagewise
///
/// You don't have to provide a controller yourself when creating a Pagewise
/// widget. The widget will create one for you. However you might wish to create
/// one yourself in order to achieve some effects.
///
/// Notice though that if you provide a controller yourself, you should provide
/// the [pageFuture] and [pageSize] parameters to the *controller* instead of
/// the widget.
///
/// A possible use case of the controller is to force a reset of the loaded
/// pages using a [RefreshIndicator](https://docs.flutter.io/flutter/material/RefreshIndicator-class.html).
/// you could achieve that as follows:
///
/// ```dart
/// final _pageLoadController = PagewiseLoadController(
///   pageSize: 6,
///   pageFuture: BackendService.getPage
/// );
///
/// @override
/// Widget build(BuildContext context) {
///   return RefreshIndicator(
///     onRefresh: () async {
///       await this._pageLoadController.reset();
///     },
///     child: PagewiseListView(
///         itemBuilder: this._itemBuilder,
///         pageLoadController: this._pageLoadController,
///     ),
///   );
/// }
/// ```
///
/// Another use case for creating the controller yourself is if you want to
/// listen to the state of Pagewise and act accordingly.
/// For example, you might want to show a specific widget when the list is empty
/// In that case, you could do:
/// ```dart
/// final _pageLoadController = PagewiseLoadController(
///   pageSize: 6,
///   pageFuture: BackendService.getPage
/// );
///
/// bool _empty = false;
///
/// @override
/// void initState() {
///   super.initState();
///
///   this._pageLoadController.addListener(() {
///     if (this._pageLoadController.noItemsFound) {
///       setState(() {
///         this._empty = this._pageLoadController.noItemsFound;
///       });
///     }
///   });
/// }
/// ```
///
/// And then in your `build` function you do:
/// ```dart
/// if (this._empty) {
///   return Text('NO ITEMS FOUND');
/// }
/// ```
class PagewiseLoadController<T> extends ChangeNotifier {
  List<T>? _loadedItems;
  late List _appendedItems;
  int _numberOfLoadedPages = 0;
  bool? _hasMoreItems;
  Object? _error;
  late bool _isFetching;

  /// Called whenever a new page (or batch) is to be fetched
  ///
  /// It is provided with the page index, and expected to return a [Future](https://api.dartlang.org/stable/1.24.3/dart-async/Future-class.html) that
  /// resolves to a list of entries. Please make sure to return only [pageSize]
  /// or less entries (in the case of the last page) for each page.
  final PageFuture<T>? pageFuture;

  /// The number  of entries per page
  final int? pageSize;

  /// Creates a PagewiseLoadController.
  ///
  /// You must provide both the [pageFuture] and the [pageSize]
  PagewiseLoadController({required this.pageFuture, required this.pageSize});

  /// The list of items that have already been loaded
  List<T>? get loadedItems => this._loadedItems;

  /// The number of pages that have already been loaded
  int? get numberOfLoadedPages => this._numberOfLoadedPages;

  /// Whether there are still more items to load
  bool? get hasMoreItems => this._hasMoreItems;

  /// The latest error that has been faced when trying to load a page
  Object? get error => this._error;

  /// set to true if no data was found
  bool get noItemsFound => this._loadedItems!.length == 0 && this.hasMoreItems == false;

  /// Called to initialize the controller. Same as [reset]
  void init() {
    this.reset();
  }

  /// Resets all the information of the controller
  void reset() {
    this._appendedItems = [];
    this._loadedItems = [];
    this._numberOfLoadedPages = 0;
    this._hasMoreItems = true;
    this._error = null;
    this._isFetching = false;
    this.notifyListeners();
  }

  /// Fetches a new page by calling [pageFuture]
  Future<void> fetchNewPage() async {
    if (!this._isFetching) {
      this._isFetching = true;

      List<T> page;
      try {
        page = await this.pageFuture!(this._numberOfLoadedPages);
        this._numberOfLoadedPages++;
      } catch (error) {
        this._error = error;
        this._isFetching = false;
        this.notifyListeners();
        return;
      }

      // Get length accounting for possible null Future return. We'l treat a null Future as an empty return
      final int length = (page.length);

      if (length > this.pageSize!) {
        this._isFetching = false;
        throw ('Page length ($length) is greater than the maximum size (${this.pageSize})');
      }

      if (length > 0 && length < this.pageSize!) {
        // This should only happen when loading the last page.
        // In that case, we append the last page with a few items to make its size
        // similar to normal pages. This is useful especially with GridView,
        // because we want the loading to show on a new line on its own
        this._appendedItems = List.generate(this.pageSize! - length, (_) => {});
      }

      if (length == 0) {
        this._hasMoreItems = false;
      } else {
        this._loadedItems!.addAll(page);
      }
      this._isFetching = false;
      notifyListeners();
    }
  }

  /// Attempts to retry in case an error occurred
  void retry() {
    this._error = null;
    this.notifyListeners();
  }

  void removeItem(bool Function(T item) test) {
    this._loadedItems?.removeWhere(test);
    this.notifyListeners();
  }
}

class PagewiseListView<T> extends Pagewise<T> {
  /// Creates a Pagewise ListView.
  ///
  /// All the properties are either those documented for normal [ListViews](https://docs.flutter.io/flutter/widgets/ListView-class.html),
  /// or those inherited from [Pagewise]
  PagewiseListView(
      {Key? key,
      EdgeInsetsGeometry? padding,
      bool? primary,
      bool addSemanticIndexes = true,
      int? semanticChildCount,
      bool shrinkWrap: false,
      ScrollController? controller,
      PagewiseLoadController<T>? pageLoadController,
      double? itemExtent,
      bool addAutomaticKeepAlives: true,
      Axis scrollDirection: Axis.vertical,
      bool addRepaintBoundaries: true,
      double? cacheExtent,
      ScrollPhysics? physics,
      bool reverse: false,
      int? pageSize,
      PageFuture<T>? pageFuture,
      LoadingBuilder? loadingBuilder,
      RetryBuilder? retryBuilder,
      NoItemsFoundBuilder? noItemsFoundBuilder,
      bool showRetry: true,
      required ItemBuilder<T> itemBuilder,
      ErrorBuilder? errorBuilder})
      : super(
            pageSize: pageSize,
            pageFuture: pageFuture,
            pageLoadController: pageLoadController,
            key: key,
            loadingBuilder: loadingBuilder,
            retryBuilder: retryBuilder,
            showRetry: showRetry,
            itemBuilder: itemBuilder,
            errorBuilder: errorBuilder,
            noItemsFoundBuilder: noItemsFoundBuilder,
            builder: (PagewiseState<T> state) {
              return ListView.builder(
                  itemExtent: itemExtent,
                  addAutomaticKeepAlives: addAutomaticKeepAlives,
                  scrollDirection: scrollDirection,
                  addRepaintBoundaries: addRepaintBoundaries,
                  cacheExtent: cacheExtent,
                  physics: physics,
                  reverse: reverse,
                  padding: padding,
                  addSemanticIndexes: addSemanticIndexes,
                  semanticChildCount: semanticChildCount,
                  shrinkWrap: shrinkWrap,
                  primary: primary,
                  controller: controller,
                  itemCount: state._itemCount,
                  itemBuilder: state._itemBuilder);
            });
}

class PagewiseGridView<T> extends Pagewise<T> {
  /// Creates a Pagewise GridView with a crossAxisCount.
  ///
  /// All the properties are either those documented for normal [GridViews](https://docs.flutter.io/flutter/widgets/GridView-class.html)
  /// or those inherited from [Pagewise]
  PagewiseGridView.count(
      {Key? key,
      EdgeInsetsGeometry? padding,
      required int crossAxisCount,
      required double height,
      double crossAxisSpacing = 0.0,
      double mainAxisSpacing = 0.0,
      bool addSemanticIndexes = true,
      int? semanticChildCount,
      bool? primary,
      bool shrinkWrap: false,
      ScrollController? controller,
      PagewiseLoadController<T>? pageLoadController,
      bool addAutomaticKeepAlives: true,
      Axis scrollDirection: Axis.vertical,
      bool addRepaintBoundaries: true,
      double? cacheExtent,
      ScrollPhysics? physics,
      bool reverse: false,
      int? pageSize,
      PageFuture<T>? pageFuture,
      LoadingBuilder? loadingBuilder,
      RetryBuilder? retryBuilder,
      NoItemsFoundBuilder? noItemsFoundBuilder,
      bool showRetry: true,
      required ItemBuilder<T> itemBuilder,
      ErrorBuilder? errorBuilder})
      : super(
            pageSize: pageSize,
            pageFuture: pageFuture,
            pageLoadController: pageLoadController,
            key: key,
            loadingBuilder: loadingBuilder,
            retryBuilder: retryBuilder,
            showRetry: showRetry,
            itemBuilder: itemBuilder,
            errorBuilder: errorBuilder,
            noItemsFoundBuilder: noItemsFoundBuilder,
            builder: (PagewiseState<T> state) {
              return GridView.builder(
                  reverse: reverse,
                  physics: physics,
                  cacheExtent: cacheExtent,
                  addRepaintBoundaries: addRepaintBoundaries,
                  scrollDirection: scrollDirection,
                  addAutomaticKeepAlives: addAutomaticKeepAlives,
                  controller: controller,
                  primary: primary,
                  shrinkWrap: shrinkWrap,
                  padding: padding,
                  addSemanticIndexes: addSemanticIndexes,
                  semanticChildCount: semanticChildCount,
                  gridDelegate: SliverGridDelegateWithFixedCrossAxisCountAndLoading(
                    crossAxisCount: crossAxisCount,
                    // childAspectRatio: childAspectRatio,
                    height: height,
                    crossAxisSpacing: crossAxisSpacing,
                    mainAxisSpacing: mainAxisSpacing,
                    itemCount: state._itemCount,
                  ),
                  itemCount: state._itemCount,
                  itemBuilder: state._itemBuilder);
            });

  /// Creates a Pagewise GridView with a maxCrossAxisExtent.
  ///
  /// All the properties are either those documented for normal [GridViews](https://docs.flutter.io/flutter/widgets/GridView-class.html)
  /// or those inherited from [Pagewise]
  PagewiseGridView.extent(
      {Key? key,
      EdgeInsetsGeometry? padding,
      required double maxCrossAxisExtent,
      double childAspectRatio = 1.0,
      double crossAxisSpacing = 0.0,
      double mainAxisSpacing = 0.0,
      bool addSemanticIndexes = true,
      int? semanticChildCount,
      bool? primary,
      bool shrinkWrap: false,
      ScrollController? controller,
      PagewiseLoadController<T>? pageLoadController,
      bool addAutomaticKeepAlives: true,
      Axis scrollDirection: Axis.vertical,
      bool addRepaintBoundaries: true,
      double? cacheExtent,
      ScrollPhysics? physics,
      bool reverse: false,
      int? pageSize,
      PageFuture<T>? pageFuture,
      LoadingBuilder? loadingBuilder,
      RetryBuilder? retryBuilder,
      NoItemsFoundBuilder? noItemsFoundBuilder,
      bool showRetry: true,
      required ItemBuilder<T> itemBuilder,
      ErrorBuilder? errorBuilder})
      : super(
            pageSize: pageSize,
            pageFuture: pageFuture,
            pageLoadController: pageLoadController,
            key: key,
            loadingBuilder: loadingBuilder,
            retryBuilder: retryBuilder,
            showRetry: showRetry,
            itemBuilder: itemBuilder,
            errorBuilder: errorBuilder,
            noItemsFoundBuilder: noItemsFoundBuilder,
            builder: (PagewiseState<T> state) {
              return GridView.builder(
                  reverse: reverse,
                  physics: physics,
                  cacheExtent: cacheExtent,
                  addRepaintBoundaries: addRepaintBoundaries,
                  scrollDirection: scrollDirection,
                  addAutomaticKeepAlives: addAutomaticKeepAlives,
                  addSemanticIndexes: addSemanticIndexes,
                  semanticChildCount: semanticChildCount,
                  controller: controller,
                  primary: primary,
                  shrinkWrap: shrinkWrap,
                  padding: padding,
                  gridDelegate: SliverGridDelegateWithMaxCrossAxisExtentAndLoading(
                      maxCrossAxisExtent: maxCrossAxisExtent,
                      childAspectRatio: childAspectRatio,
                      crossAxisSpacing: crossAxisSpacing,
                      mainAxisSpacing: mainAxisSpacing,
                      itemCount: state._itemCount),
                  itemCount: state._itemCount,
                  itemBuilder: state._itemBuilder);
            });
}

int _kDefaultSemanticIndexCallback(Widget _, int localIndex) => localIndex;

class PagewiseSliverList<T> extends Pagewise<T> {
  /// Creates a Pagewise SliverList.
  ///
  /// All the properties are either those documented for normal [SliverList](https://docs.flutter.io/flutter/widgets/SliverList-class.html)
  /// or those inherited from [Pagewise]
  PagewiseSliverList(
      {Key? key,
      bool addSemanticIndexes = true,
      bool addAutomaticKeepAlives: true,
      bool addRepaintBoundaries: true,
      SemanticIndexCallback semanticIndexCallback = _kDefaultSemanticIndexCallback,
      int semanticIndexOffset = 0,
      PagewiseLoadController<T>? pageLoadController,
      int? pageSize,
      PageFuture<T>? pageFuture,
      LoadingBuilder? loadingBuilder,
      RetryBuilder? retryBuilder,
      NoItemsFoundBuilder? noItemsFoundBuilder,
      bool showRetry: true,
      required ItemBuilder<T> itemBuilder,
      ErrorBuilder? errorBuilder})
      : super(
            pageSize: pageSize,
            pageFuture: pageFuture,
            pageLoadController: pageLoadController,
            key: key,
            loadingBuilder: loadingBuilder,
            retryBuilder: retryBuilder,
            showRetry: showRetry,
            itemBuilder: itemBuilder,
            errorBuilder: errorBuilder,
            noItemsFoundBuilder: noItemsFoundBuilder,
            builder: (PagewiseState<T> state) {
              return SliverList(
                delegate: SliverChildBuilderDelegate(state._itemBuilder,
                    addAutomaticKeepAlives: addAutomaticKeepAlives,
                    addRepaintBoundaries: addRepaintBoundaries,
                    addSemanticIndexes: addSemanticIndexes,
                    semanticIndexCallback: semanticIndexCallback,
                    semanticIndexOffset: semanticIndexOffset,
                    childCount: state._itemCount),
              );
            });
}

class PagewiseSliverGrid<T> extends Pagewise<T> {
  /// Creates a Pagewise SliverGrid with a crossAxisCount.
  ///
  /// All the properties are either those documented for normal [SliverGrid](https://docs.flutter.io/flutter/widgets/SliverGrid-class.html)
  /// or those inherited from [Pagewise]
  PagewiseSliverGrid.count(
      {Key? key,
      bool addSemanticIndexes = true,
      bool addAutomaticKeepAlives: true,
      bool addRepaintBoundaries: true,
      SemanticIndexCallback semanticIndexCallback = _kDefaultSemanticIndexCallback,
      int semanticIndexOffset = 0,
      required int crossAxisCount,
      required double height,
      double crossAxisSpacing = 0.0,
      double mainAxisSpacing = 0.0,
      PagewiseLoadController<T>? pageLoadController,
      int? pageSize,
      PageFuture<T>? pageFuture,
      LoadingBuilder? loadingBuilder,
      RetryBuilder? retryBuilder,
      NoItemsFoundBuilder? noItemsFoundBuilder,
      bool showRetry: true,
      required ItemBuilder<T> itemBuilder,
      ErrorBuilder? errorBuilder})
      : super(
            pageSize: pageSize,
            pageFuture: pageFuture,
            pageLoadController: pageLoadController,
            key: key,
            loadingBuilder: loadingBuilder,
            retryBuilder: retryBuilder,
            showRetry: showRetry,
            itemBuilder: itemBuilder,
            errorBuilder: errorBuilder,
            noItemsFoundBuilder: noItemsFoundBuilder,
            builder: (PagewiseState<T> state) {
              return SliverGrid(
                delegate: SliverChildBuilderDelegate(state._itemBuilder,
                    addAutomaticKeepAlives: addAutomaticKeepAlives,
                    addRepaintBoundaries: addRepaintBoundaries,
                    addSemanticIndexes: addSemanticIndexes,
                    semanticIndexCallback: semanticIndexCallback,
                    semanticIndexOffset: semanticIndexOffset,
                    childCount: state._itemCount),
                gridDelegate: SliverGridDelegateWithFixedCrossAxisCountAndLoading(
                  crossAxisCount: crossAxisCount,
                  height: height,
                  crossAxisSpacing: crossAxisSpacing,
                  mainAxisSpacing: mainAxisSpacing,
                  itemCount: state._itemCount,
                ),
              );
            });

  /// Creates a Pagewise SliverGrid with a maxCrossAxisExtent.
  ///
  /// All the properties are either those documented for normal [SliverGrid](https://docs.flutter.io/flutter/widgets/SliverGrid-class.html)
  /// or those inherited from [Pagewise]
  PagewiseSliverGrid.extent(
      {Key? key,
      bool addSemanticIndexes = true,
      bool addAutomaticKeepAlives: true,
      bool addRepaintBoundaries: true,
      SemanticIndexCallback semanticIndexCallback = _kDefaultSemanticIndexCallback,
      int semanticIndexOffset = 0,
      required double maxCrossAxisExtent,
      double childAspectRatio = 1.0,
      double crossAxisSpacing = 0.0,
      double mainAxisSpacing = 0.0,
      PagewiseLoadController<T>? pageLoadController,
      int? pageSize,
      PageFuture<T>? pageFuture,
      LoadingBuilder? loadingBuilder,
      RetryBuilder? retryBuilder,
      NoItemsFoundBuilder? noItemsFoundBuilder,
      bool showRetry: true,
      required ItemBuilder<T> itemBuilder,
      ErrorBuilder? errorBuilder})
      : super(
            pageSize: pageSize,
            pageFuture: pageFuture,
            pageLoadController: pageLoadController,
            key: key,
            loadingBuilder: loadingBuilder,
            noItemsFoundBuilder: noItemsFoundBuilder,
            retryBuilder: retryBuilder,
            showRetry: showRetry,
            itemBuilder: itemBuilder,
            errorBuilder: errorBuilder,
            builder: (PagewiseState<T> state) {
              return SliverGrid(
                delegate: SliverChildBuilderDelegate(state._itemBuilder,
                    addAutomaticKeepAlives: addAutomaticKeepAlives,
                    addRepaintBoundaries: addRepaintBoundaries,
                    addSemanticIndexes: addSemanticIndexes,
                    semanticIndexCallback: semanticIndexCallback,
                    semanticIndexOffset: semanticIndexOffset,
                    childCount: state._itemCount),
                gridDelegate: SliverGridDelegateWithMaxCrossAxisExtentAndLoading(
                    maxCrossAxisExtent: maxCrossAxisExtent,
                    childAspectRatio: childAspectRatio,
                    crossAxisSpacing: crossAxisSpacing,
                    mainAxisSpacing: mainAxisSpacing,
                    itemCount: state._itemCount),
              );
            });
}
