-
Notifications
You must be signed in to change notification settings - Fork 2
Add new widget LoadablePaginatedListBothDirectionView #41
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
braginsm
wants to merge
1
commit into
Dash-Kit:master
Choose a base branch
from
braginsm:Add-new-widget-LoadablePaginatedListBothDirectionView
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
179 changes: 179 additions & 0 deletions
179
lib/src/loadable/loadable_list_both_direction_view.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,179 @@ | ||
| import 'package:dash_kit_core/src/loadable/loadable_list_view.dart'; | ||
| import 'package:dash_kit_core/src/loadable/pagination_state.dart'; | ||
| import 'package:dash_kit_core/src/states/operation_state.dart'; | ||
| import 'package:dash_kit_core/src/utils/store_list.dart'; | ||
| import 'package:flutter/material.dart'; | ||
|
|
||
| class LoadableListBothDirectionView<T extends StoreListItem> | ||
| extends StatefulWidget { | ||
| const LoadableListBothDirectionView({ | ||
| required this.headerViewModel, | ||
| required this.footerViewModel, | ||
| required this.emptyStateWidget, | ||
| required this.errorWidget, | ||
| required this.startWidget, | ||
| this.anchor = 0.5, | ||
| this.scrollPhysics = const AlwaysScrollableScrollPhysics(), | ||
| this.onChangeContentOffset, | ||
| this.cacheExtent, | ||
| this.shrinkWrap = false, | ||
| this.scrollDirection = Axis.vertical, | ||
| this.reverse = false, | ||
| this.progressIndicator = const CircularProgressIndicator(), | ||
| this.padding, | ||
| Key? key, | ||
| }) : super(key: key); | ||
|
|
||
| final LoadableListViewModel<T> headerViewModel; | ||
| final LoadableListViewModel<T> footerViewModel; | ||
| final double anchor; | ||
| final ScrollPhysics scrollPhysics; | ||
| final void Function(double offset)? onChangeContentOffset; | ||
| final double? cacheExtent; | ||
| final bool shrinkWrap; | ||
| final Axis scrollDirection; | ||
| final bool reverse; | ||
| final Widget progressIndicator; | ||
| final Widget emptyStateWidget; | ||
| final Widget errorWidget; | ||
| final Widget? startWidget; | ||
| final EdgeInsets? padding; | ||
|
|
||
| @override | ||
| State<StatefulWidget> createState() => | ||
| LoadableListBothDirectionViewState<T>(); | ||
| } | ||
|
|
||
| class LoadableListBothDirectionViewState<T extends StoreListItem> | ||
| extends State<LoadableListBothDirectionView> { | ||
| final ScrollController scrollController = ScrollController(); | ||
|
|
||
| LoadableListViewModel<T> get headerViewModel => | ||
| widget.headerViewModel as LoadableListViewModel<T>; | ||
| LoadableListViewModel<T> get footerViewModel => | ||
| widget.footerViewModel as LoadableListViewModel<T>; | ||
|
|
||
| @override | ||
| void initState() { | ||
| super.initState(); | ||
| if (headerViewModel.loadListRequestState.isIdle) { | ||
| headerViewModel.loadList?.call(); | ||
| } | ||
|
|
||
| if (footerViewModel.loadListRequestState.isIdle) { | ||
| footerViewModel.loadList?.call(); | ||
| } | ||
|
|
||
| scrollController.addListener(_onScrollChanged); | ||
| } | ||
|
|
||
| @override | ||
| Widget build(BuildContext context) { | ||
| final centerKey = UniqueKey(); | ||
| final headerState = headerViewModel.getPaginationState(); | ||
| final footerState = footerViewModel.getPaginationState(); | ||
|
|
||
| if (headerState == PaginationState.loading || | ||
| footerState == PaginationState.loading) { | ||
| return _ProgressSate( | ||
| progressIndicator: widget.progressIndicator, | ||
| padding: widget.padding, | ||
| ); | ||
| } | ||
|
|
||
| if (headerState == PaginationState.empty && | ||
| footerState == PaginationState.empty) { | ||
| return widget.emptyStateWidget; | ||
| } | ||
|
|
||
| if (headerState == PaginationState.error || | ||
| footerState == PaginationState.error) { | ||
| return widget.errorWidget; | ||
| } | ||
|
|
||
| return CustomScrollView( | ||
| key: widget.key, | ||
| center: centerKey, | ||
| anchor: widget.anchor, | ||
| shrinkWrap: widget.shrinkWrap, | ||
| physics: widget.scrollPhysics, | ||
| controller: scrollController, | ||
| cacheExtent: widget.cacheExtent, | ||
| scrollDirection: widget.scrollDirection, | ||
| reverse: widget.reverse, | ||
| slivers: [ | ||
| SliverPadding( | ||
| padding: widget.padding ?? EdgeInsets.zero, | ||
| sliver: SliverList( | ||
| delegate: SliverChildBuilderDelegate( | ||
| (context, index) => buildListItem(index, headerViewModel), | ||
| childCount: headerViewModel.itemsCount, | ||
| ), | ||
| ), | ||
| ), | ||
| if (headerViewModel.endListWidget != null) | ||
| SliverToBoxAdapter(child: footerViewModel.endListWidget), | ||
| SliverToBoxAdapter( | ||
| key: centerKey, | ||
| child: widget.startWidget ?? const SizedBox.shrink(), | ||
| ), | ||
| SliverPadding( | ||
| padding: widget.padding ?? EdgeInsets.zero, | ||
| sliver: SliverList( | ||
| delegate: SliverChildBuilderDelegate( | ||
| (context, index) => buildListItem(index, footerViewModel), | ||
| childCount: footerViewModel.itemsCount, | ||
| ), | ||
| ), | ||
| ), | ||
| if (footerViewModel.endListWidget != null) | ||
| SliverToBoxAdapter(child: footerViewModel.endListWidget), | ||
| ], | ||
| ); | ||
| } | ||
|
|
||
| @override | ||
| void dispose() { | ||
| super.dispose(); | ||
| scrollController.removeListener(_onScrollChanged); | ||
| scrollController.dispose(); | ||
| } | ||
|
|
||
| void _onScrollChanged() { | ||
| widget.onChangeContentOffset?.call(scrollController.position.pixels); | ||
| } | ||
|
|
||
| Widget buildListItem(int index, LoadableListViewModel model) => | ||
| _ListItem(index: index, model: model); | ||
| } | ||
|
|
||
| class _ProgressSate extends StatelessWidget { | ||
| const _ProgressSate({required this.progressIndicator, this.padding}); | ||
|
|
||
| final Widget progressIndicator; | ||
| final EdgeInsetsGeometry? padding; | ||
|
|
||
| @override | ||
| Widget build(BuildContext context) { | ||
| return Container( | ||
| padding: padding, | ||
| alignment: Alignment.center, | ||
| child: progressIndicator, | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| class _ListItem extends StatelessWidget { | ||
| const _ListItem({required this.index, required this.model}); | ||
|
|
||
| final int index; | ||
| final LoadableListViewModel model; | ||
|
|
||
| @override | ||
| Widget build(BuildContext context) => Column( | ||
| children: [ | ||
| model.itemBuilder(index), | ||
| if (index != model.itemsCount - 1) model.itemSeparator(index), | ||
| ], | ||
| ); | ||
| } |
169 changes: 169 additions & 0 deletions
169
lib/src/loadable/loadable_paginated_list_both_direction_view.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,169 @@ | ||
| import 'package:dash_kit_core/dash_kit_core.dart'; | ||
| import 'package:dash_kit_core/src/loadable/loadable_list_both_direction_view.dart'; | ||
| import 'package:flutter/material.dart'; | ||
|
|
||
| class LoadablePaginatedListBothDirectionView<T extends StoreListItem> | ||
| extends LoadableListBothDirectionView<T> { | ||
| const LoadablePaginatedListBothDirectionView({ | ||
| required LoadablePaginatedListViewModel<T> headerViewModel, | ||
| required LoadablePaginatedListViewModel<T> footerViewModel, | ||
| required Widget emptyStateWidget, | ||
| required Widget errorWidget, | ||
| Key? key, | ||
| ScrollPhysics scrollPhysics = const AlwaysScrollableScrollPhysics(), | ||
| double? cacheExtent, | ||
| bool shrinkWrap = false, | ||
| Axis scrollDirection = Axis.vertical, | ||
| bool reverse = false, | ||
| void Function(double offset)? onChangeContentOffset, | ||
| Widget progressIndicator = const CircularProgressIndicator(), | ||
| Widget? startWidget, | ||
| double anchor = 0.5, | ||
| }) : super( | ||
| key: key, | ||
| headerViewModel: headerViewModel, | ||
| footerViewModel: footerViewModel, | ||
| scrollPhysics: scrollPhysics, | ||
| onChangeContentOffset: onChangeContentOffset, | ||
| cacheExtent: cacheExtent, | ||
| shrinkWrap: shrinkWrap, | ||
| scrollDirection: scrollDirection, | ||
| reverse: reverse, | ||
| progressIndicator: progressIndicator, | ||
| emptyStateWidget: emptyStateWidget, | ||
| errorWidget: errorWidget, | ||
| startWidget: startWidget, | ||
| anchor: anchor, | ||
| ); | ||
|
|
||
| @override | ||
| State<StatefulWidget> createState() => | ||
| LoadablePaginatedListBothDirectionState<T>(); | ||
| } | ||
|
|
||
| class LoadablePaginatedListBothDirectionState<T extends StoreListItem> | ||
| extends LoadableListBothDirectionViewState<T> { | ||
| @override | ||
| LoadablePaginatedListViewModel<T> get headerViewModel => | ||
| widget.headerViewModel as LoadablePaginatedListViewModel<T>; | ||
| @override | ||
| LoadablePaginatedListViewModel<T> get footerViewModel => | ||
| widget.footerViewModel as LoadablePaginatedListViewModel<T>; | ||
|
|
||
| @override | ||
| void initState() { | ||
| super.initState(); | ||
|
|
||
| scrollController.addListener(_onScrollChanged); | ||
| } | ||
|
|
||
| @override | ||
| Widget buildListItem(int index, LoadableListViewModel model) { | ||
| return index == model.itemsCount - 1 | ||
| ? _LastItem( | ||
| paginatedModel: model as LoadablePaginatedListViewModel, | ||
| scrollController: scrollController, | ||
| progressIndicator: widget.progressIndicator, | ||
| cacheExtent: widget.cacheExtent, | ||
| ) | ||
| : super.buildListItem(index, model); | ||
| } | ||
|
|
||
| @override | ||
| void dispose() { | ||
| super.dispose(); | ||
| scrollController.removeListener(_onScrollChanged); | ||
| } | ||
|
|
||
| void _onScrollChanged() { | ||
| final canLoadHeader = (headerViewModel.loadPageRequestState.isSucceed || | ||
| headerViewModel.loadPageRequestState.isIdle) && | ||
| !headerViewModel.paginatedList.isAllItemsLoaded; | ||
| final canLoadFooter = (footerViewModel.loadPageRequestState.isSucceed || | ||
| footerViewModel.loadPageRequestState.isIdle) && | ||
| !footerViewModel.paginatedList.isAllItemsLoaded; | ||
| final maxScrollExtent = | ||
| scrollController.position.maxScrollExtent - (widget.cacheExtent ?? 0); | ||
| final minScrollExtent = | ||
| scrollController.position.minScrollExtent + (widget.cacheExtent ?? 0); | ||
|
|
||
| if (scrollController.position.pixels >= maxScrollExtent && canLoadFooter) { | ||
| footerViewModel.loadPage?.call(); | ||
| } | ||
| if (scrollController.position.pixels <= minScrollExtent && canLoadHeader) { | ||
| headerViewModel.loadPage?.call(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| class _LastItem extends StatelessWidget { | ||
| const _LastItem({ | ||
| required this.paginatedModel, | ||
| required this.scrollController, | ||
| required this.progressIndicator, | ||
| this.cacheExtent, | ||
| }); | ||
|
|
||
| final LoadablePaginatedListViewModel paginatedModel; | ||
| final ScrollController scrollController; | ||
| final Widget progressIndicator; | ||
| final double? cacheExtent; | ||
|
|
||
| @override | ||
| Widget build(BuildContext context) { | ||
| switch (paginatedModel.getPaginationState()) { | ||
| case PaginationState.loadingPage: | ||
| return _ProgressPage( | ||
| scrollController: scrollController, | ||
| progressIndicator: progressIndicator, | ||
| cacheExtent: cacheExtent, | ||
| ); | ||
|
|
||
| case PaginationState.errorLoadingPage: | ||
| return paginatedModel.errorWidget; | ||
|
|
||
| case PaginationState.succeedLoadingPage: | ||
| if (paginatedModel.paginatedList.isAllItemsLoaded) { | ||
| return paginatedModel.endListWidget ?? const SizedBox.shrink(); | ||
| } | ||
|
|
||
| return const SizedBox.shrink(); | ||
|
|
||
| default: | ||
| return const SizedBox.shrink(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| class _ProgressPage extends StatelessWidget { | ||
| const _ProgressPage({ | ||
| required this.progressIndicator, | ||
| required this.scrollController, | ||
| this.cacheExtent, | ||
| }); | ||
|
|
||
| final Widget progressIndicator; | ||
| final ScrollController scrollController; | ||
| final double? cacheExtent; | ||
|
|
||
| @override | ||
| Widget build(BuildContext context) { | ||
| { | ||
| WidgetsBinding.instance.addPostFrameCallback((_) => cacheExtent == null | ||
| ? scrollController.animateTo( | ||
| scrollController.offset > 0 | ||
| ? scrollController.position.maxScrollExtent | ||
| : scrollController.position.minScrollExtent, | ||
| duration: const Duration(milliseconds: 100), | ||
| curve: Curves.linear, | ||
| ) | ||
| : null); | ||
|
|
||
| return Container( | ||
| padding: const EdgeInsets.all(8), | ||
| margin: const EdgeInsets.only(top: 8), | ||
| child: Center(child: progressIndicator), | ||
| ); | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be in initState