💫 List loading state switch animation
This commit is contained in:
@@ -39,6 +39,8 @@ class PaginationList<T> extends HookConsumerWidget {
|
|||||||
final data = ref.watch(provider);
|
final data = ref.watch(provider);
|
||||||
final noti = ref.watch(notifier);
|
final noti = ref.watch(notifier);
|
||||||
|
|
||||||
|
if (isSliver) {
|
||||||
|
// For slivers, return widgets directly without animation
|
||||||
if ((data.isLoading || noti.isLoading) && data.value?.isEmpty == true) {
|
if ((data.isLoading || noti.isLoading) && data.value?.isEmpty == true) {
|
||||||
final content = List<Widget>.generate(
|
final content = List<Widget>.generate(
|
||||||
10,
|
10,
|
||||||
@@ -54,9 +56,7 @@ class PaginationList<T> extends HookConsumerWidget {
|
|||||||
child: footerSkeletonChild ?? const _DefaultSkeletonChild(),
|
child: footerSkeletonChild ?? const _DefaultSkeletonChild(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return isSliver
|
return SliverList.list(children: content);
|
||||||
? SliverList.list(children: content)
|
|
||||||
: ListView(children: content);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.hasError) {
|
if (data.hasError) {
|
||||||
@@ -64,27 +64,10 @@ class PaginationList<T> extends HookConsumerWidget {
|
|||||||
error: data.error,
|
error: data.error,
|
||||||
onRetry: noti.refresh,
|
onRetry: noti.refresh,
|
||||||
);
|
);
|
||||||
return isSliver ? SliverFillRemaining(child: content) : content;
|
return SliverFillRemaining(child: content);
|
||||||
}
|
}
|
||||||
|
|
||||||
final listView = isSliver
|
final listView = SuperSliverList.builder(
|
||||||
? SuperSliverList.builder(
|
|
||||||
itemCount: (data.value?.length ?? 0) + 1,
|
|
||||||
itemBuilder: (context, idx) {
|
|
||||||
if (idx == data.value?.length) {
|
|
||||||
return PaginationListFooter(
|
|
||||||
noti: noti,
|
|
||||||
data: data,
|
|
||||||
skeletonChild: footerSkeletonChild,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
final entry = data.value?[idx];
|
|
||||||
if (entry != null) return itemBuilder(context, idx, entry);
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
)
|
|
||||||
: SuperListView.builder(
|
|
||||||
padding: padding,
|
|
||||||
itemCount: (data.value?.length ?? 0) + 1,
|
itemCount: (data.value?.length ?? 0) + 1,
|
||||||
itemBuilder: (context, idx) {
|
itemBuilder: (context, idx) {
|
||||||
if (idx == data.value?.length) {
|
if (idx == data.value?.length) {
|
||||||
@@ -104,6 +87,68 @@ class PaginationList<T> extends HookConsumerWidget {
|
|||||||
? ExtendedRefreshIndicator(onRefresh: noti.refresh, child: listView)
|
? ExtendedRefreshIndicator(onRefresh: noti.refresh, child: listView)
|
||||||
: listView;
|
: listView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For non-slivers, use AnimatedSwitcher for smooth transitions
|
||||||
|
Widget buildContent() {
|
||||||
|
if ((data.isLoading || noti.isLoading) && data.value?.isEmpty == true) {
|
||||||
|
final content = List<Widget>.generate(
|
||||||
|
10,
|
||||||
|
(_) => Skeletonizer(
|
||||||
|
enabled: true,
|
||||||
|
effect: ShimmerEffect(
|
||||||
|
baseColor: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
|
highlightColor: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.surfaceContainerHighest,
|
||||||
|
),
|
||||||
|
containersColor: Theme.of(context).colorScheme.surfaceContainerLow,
|
||||||
|
child: footerSkeletonChild ?? const _DefaultSkeletonChild(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return SizedBox(
|
||||||
|
key: const ValueKey('loading'),
|
||||||
|
child: ListView(children: content),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.hasError) {
|
||||||
|
final content = ResponseErrorWidget(
|
||||||
|
error: data.error,
|
||||||
|
onRetry: noti.refresh,
|
||||||
|
);
|
||||||
|
return SizedBox(key: const ValueKey('error'), child: content);
|
||||||
|
}
|
||||||
|
|
||||||
|
final listView = SuperListView.builder(
|
||||||
|
padding: padding,
|
||||||
|
itemCount: (data.value?.length ?? 0) + 1,
|
||||||
|
itemBuilder: (context, idx) {
|
||||||
|
if (idx == data.value?.length) {
|
||||||
|
return PaginationListFooter(
|
||||||
|
noti: noti,
|
||||||
|
data: data,
|
||||||
|
skeletonChild: footerSkeletonChild,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final entry = data.value?[idx];
|
||||||
|
if (entry != null) return itemBuilder(context, idx, entry);
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return SizedBox(
|
||||||
|
key: const ValueKey('data'),
|
||||||
|
child: isRefreshable
|
||||||
|
? ExtendedRefreshIndicator(onRefresh: noti.refresh, child: listView)
|
||||||
|
: listView,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
child: buildContent(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PaginationWidget<T> extends HookConsumerWidget {
|
class PaginationWidget<T> extends HookConsumerWidget {
|
||||||
@@ -130,6 +175,8 @@ class PaginationWidget<T> extends HookConsumerWidget {
|
|||||||
final data = ref.watch(provider);
|
final data = ref.watch(provider);
|
||||||
final noti = ref.watch(notifier);
|
final noti = ref.watch(notifier);
|
||||||
|
|
||||||
|
if (isSliver) {
|
||||||
|
// For slivers, return widgets directly without animation
|
||||||
if ((data.isLoading || noti.isLoading) && data.value?.isEmpty == true) {
|
if ((data.isLoading || noti.isLoading) && data.value?.isEmpty == true) {
|
||||||
final content = List<Widget>.generate(
|
final content = List<Widget>.generate(
|
||||||
10,
|
10,
|
||||||
@@ -145,9 +192,7 @@ class PaginationWidget<T> extends HookConsumerWidget {
|
|||||||
child: footerSkeletonChild ?? const _DefaultSkeletonChild(),
|
child: footerSkeletonChild ?? const _DefaultSkeletonChild(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return isSliver
|
return SliverList.list(children: content);
|
||||||
? SliverList.list(children: content)
|
|
||||||
: ListView(children: content);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.hasError) {
|
if (data.hasError) {
|
||||||
@@ -155,7 +200,7 @@ class PaginationWidget<T> extends HookConsumerWidget {
|
|||||||
error: data.error,
|
error: data.error,
|
||||||
onRetry: noti.refresh,
|
onRetry: noti.refresh,
|
||||||
);
|
);
|
||||||
return isSliver ? SliverFillRemaining(child: content) : content;
|
return SliverFillRemaining(child: content);
|
||||||
}
|
}
|
||||||
|
|
||||||
final footer = PaginationListFooter(
|
final footer = PaginationListFooter(
|
||||||
@@ -169,6 +214,58 @@ class PaginationWidget<T> extends HookConsumerWidget {
|
|||||||
? ExtendedRefreshIndicator(onRefresh: noti.refresh, child: content)
|
? ExtendedRefreshIndicator(onRefresh: noti.refresh, child: content)
|
||||||
: content;
|
: content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For non-slivers, use AnimatedSwitcher for smooth transitions
|
||||||
|
Widget buildContent() {
|
||||||
|
if ((data.isLoading || noti.isLoading) && data.value?.isEmpty == true) {
|
||||||
|
final content = List<Widget>.generate(
|
||||||
|
10,
|
||||||
|
(_) => Skeletonizer(
|
||||||
|
enabled: true,
|
||||||
|
effect: ShimmerEffect(
|
||||||
|
baseColor: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
|
highlightColor: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.surfaceContainerHighest,
|
||||||
|
),
|
||||||
|
containersColor: Theme.of(context).colorScheme.surfaceContainerLow,
|
||||||
|
child: footerSkeletonChild ?? const _DefaultSkeletonChild(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return SizedBox(
|
||||||
|
key: const ValueKey('loading'),
|
||||||
|
child: ListView(children: content),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.hasError) {
|
||||||
|
final content = ResponseErrorWidget(
|
||||||
|
error: data.error,
|
||||||
|
onRetry: noti.refresh,
|
||||||
|
);
|
||||||
|
return SizedBox(key: const ValueKey('error'), child: content);
|
||||||
|
}
|
||||||
|
|
||||||
|
final footer = PaginationListFooter(
|
||||||
|
noti: noti,
|
||||||
|
data: data,
|
||||||
|
skeletonChild: footerSkeletonChild,
|
||||||
|
);
|
||||||
|
final content = contentBuilder(data.value ?? [], footer);
|
||||||
|
|
||||||
|
return SizedBox(
|
||||||
|
key: const ValueKey('data'),
|
||||||
|
child: isRefreshable
|
||||||
|
? ExtendedRefreshIndicator(onRefresh: noti.refresh, child: content)
|
||||||
|
: content,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
child: buildContent(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PaginationListFooter<T> extends HookConsumerWidget {
|
class PaginationListFooter<T> extends HookConsumerWidget {
|
||||||
|
|||||||
Reference in New Issue
Block a user