Compare commits
2 Commits
e4cd0c99df
...
2ff60fc4ff
| Author | SHA1 | Date | |
|---|---|---|---|
|
2ff60fc4ff
|
|||
|
ea93aa144e
|
@@ -79,43 +79,21 @@ class PostSearchScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
isNoBackground: false,
|
isNoBackground: false,
|
||||||
appBar: isWideScreen(context)
|
appBar: AppBar(
|
||||||
? null
|
title: Text('searchPosts'.tr()),
|
||||||
: AppBar(
|
actions: [
|
||||||
title: Row(
|
if (!isWideScreen(context))
|
||||||
children: [
|
IconButton(
|
||||||
Expanded(
|
icon: Icon(
|
||||||
child: TextField(
|
showFilters.value
|
||||||
controller: searchController,
|
? Icons.filter_alt
|
||||||
decoration: InputDecoration(
|
: Icons.filter_alt_outlined,
|
||||||
hintText: 'search'.tr(),
|
|
||||||
border: InputBorder.none,
|
|
||||||
hintStyle: TextStyle(
|
|
||||||
color: Theme.of(context).appBarTheme.foregroundColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).appBarTheme.foregroundColor,
|
|
||||||
),
|
|
||||||
onChanged: onSearchChanged,
|
|
||||||
onSubmitted: (value) {
|
|
||||||
onSearchChanged(value, skipDebounce: true);
|
|
||||||
},
|
|
||||||
autofocus: true,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(
|
|
||||||
showFilters.value
|
|
||||||
? Icons.filter_alt
|
|
||||||
: Icons.filter_alt_outlined,
|
|
||||||
),
|
|
||||||
onPressed: toggleFilterDisplay,
|
|
||||||
tooltip: 'toggleFilters'.tr(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
onPressed: toggleFilterDisplay,
|
||||||
|
tooltip: 'toggleFilters'.tr(),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
body: Consumer(
|
body: Consumer(
|
||||||
builder: (context, ref, child) {
|
builder: (context, ref, child) {
|
||||||
final searchState = ref.watch(
|
final searchState = ref.watch(
|
||||||
@@ -152,10 +130,7 @@ class PostSearchScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SliverGap(16),
|
const SliverGap(12),
|
||||||
if (showFilters.value && !isWideScreen(context))
|
|
||||||
SliverToBoxAdapter(child: buildFilterPanel()),
|
|
||||||
// Use PaginationList with isSliver=true
|
|
||||||
PaginationList(
|
PaginationList(
|
||||||
provider: postListProvider(
|
provider: postListProvider(
|
||||||
PostListQueryConfig(id: kSearchPostListId),
|
PostListQueryConfig(id: kSearchPostListId),
|
||||||
@@ -246,6 +221,28 @@ class PostSearchScreen extends HookConsumerWidget {
|
|||||||
)
|
)
|
||||||
: CustomScrollView(
|
: CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
|
const SliverGap(4),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
|
child: SearchBar(
|
||||||
|
elevation: WidgetStateProperty.all(4),
|
||||||
|
controller: searchController,
|
||||||
|
hintText: 'search'.tr(),
|
||||||
|
leading: const Icon(Icons.search),
|
||||||
|
padding: WidgetStateProperty.all(
|
||||||
|
const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
),
|
||||||
|
onChanged: onSearchChanged,
|
||||||
|
onSubmitted: (value) {
|
||||||
|
onSearchChanged(value, skipDebounce: true);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
if (showFilters.value)
|
if (showFilters.value)
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Center(
|
child: Center(
|
||||||
@@ -255,7 +252,6 @@ class PostSearchScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Use PaginationList with isSliver=true
|
|
||||||
PaginationList(
|
PaginationList(
|
||||||
provider: postListProvider(
|
provider: postListProvider(
|
||||||
PostListQueryConfig(id: kSearchPostListId),
|
PostListQueryConfig(id: kSearchPostListId),
|
||||||
|
|||||||
@@ -39,70 +39,115 @@ 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 ((data.isLoading || noti.isLoading) && data.value?.isEmpty == true) {
|
if (isSliver) {
|
||||||
final content = List<Widget>.generate(
|
// For slivers, return widgets directly without animation
|
||||||
10,
|
if ((data.isLoading || noti.isLoading) && data.value?.isEmpty == true) {
|
||||||
(_) => Skeletonizer(
|
final content = List<Widget>.generate(
|
||||||
enabled: true,
|
10,
|
||||||
effect: ShimmerEffect(
|
(_) => Skeletonizer(
|
||||||
baseColor: Theme.of(context).colorScheme.surfaceContainerHigh,
|
enabled: true,
|
||||||
highlightColor: Theme.of(
|
effect: ShimmerEffect(
|
||||||
context,
|
baseColor: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
).colorScheme.surfaceContainerHighest,
|
highlightColor: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.surfaceContainerHighest,
|
||||||
|
),
|
||||||
|
containersColor: Theme.of(context).colorScheme.surfaceContainerLow,
|
||||||
|
child: footerSkeletonChild ?? const _DefaultSkeletonChild(),
|
||||||
),
|
),
|
||||||
containersColor: Theme.of(context).colorScheme.surfaceContainerLow,
|
);
|
||||||
child: footerSkeletonChild ?? const _DefaultSkeletonChild(),
|
return SliverList.list(children: content);
|
||||||
),
|
}
|
||||||
|
|
||||||
|
if (data.hasError) {
|
||||||
|
final content = ResponseErrorWidget(
|
||||||
|
error: data.error,
|
||||||
|
onRetry: noti.refresh,
|
||||||
|
);
|
||||||
|
return SliverFillRemaining(child: content);
|
||||||
|
}
|
||||||
|
|
||||||
|
final listView = 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;
|
||||||
|
},
|
||||||
);
|
);
|
||||||
return isSliver
|
|
||||||
? SliverList.list(children: content)
|
return isRefreshable
|
||||||
: ListView(children: content);
|
? ExtendedRefreshIndicator(onRefresh: noti.refresh, child: listView)
|
||||||
|
: listView;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.hasError) {
|
// For non-slivers, use AnimatedSwitcher for smooth transitions
|
||||||
final content = ResponseErrorWidget(
|
Widget buildContent() {
|
||||||
error: data.error,
|
if ((data.isLoading || noti.isLoading) && data.value?.isEmpty == true) {
|
||||||
onRetry: noti.refresh,
|
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 isSliver ? SliverFillRemaining(child: content) : content;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final listView = isSliver
|
return AnimatedSwitcher(
|
||||||
? SuperSliverList.builder(
|
duration: const Duration(milliseconds: 300),
|
||||||
itemCount: (data.value?.length ?? 0) + 1,
|
child: buildContent(),
|
||||||
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,
|
|
||||||
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 isRefreshable
|
|
||||||
? ExtendedRefreshIndicator(onRefresh: noti.refresh, child: listView)
|
|
||||||
: listView;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,44 +175,96 @@ 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 ((data.isLoading || noti.isLoading) && data.value?.isEmpty == true) {
|
if (isSliver) {
|
||||||
final content = List<Widget>.generate(
|
// For slivers, return widgets directly without animation
|
||||||
10,
|
if ((data.isLoading || noti.isLoading) && data.value?.isEmpty == true) {
|
||||||
(_) => Skeletonizer(
|
final content = List<Widget>.generate(
|
||||||
enabled: true,
|
10,
|
||||||
effect: ShimmerEffect(
|
(_) => Skeletonizer(
|
||||||
baseColor: Theme.of(context).colorScheme.surfaceContainerHigh,
|
enabled: true,
|
||||||
highlightColor: Theme.of(
|
effect: ShimmerEffect(
|
||||||
context,
|
baseColor: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
).colorScheme.surfaceContainerHighest,
|
highlightColor: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.surfaceContainerHighest,
|
||||||
|
),
|
||||||
|
containersColor: Theme.of(context).colorScheme.surfaceContainerLow,
|
||||||
|
child: footerSkeletonChild ?? const _DefaultSkeletonChild(),
|
||||||
),
|
),
|
||||||
containersColor: Theme.of(context).colorScheme.surfaceContainerLow,
|
);
|
||||||
child: footerSkeletonChild ?? const _DefaultSkeletonChild(),
|
return SliverList.list(children: content);
|
||||||
),
|
}
|
||||||
|
|
||||||
|
if (data.hasError) {
|
||||||
|
final content = ResponseErrorWidget(
|
||||||
|
error: data.error,
|
||||||
|
onRetry: noti.refresh,
|
||||||
|
);
|
||||||
|
return SliverFillRemaining(child: content);
|
||||||
|
}
|
||||||
|
|
||||||
|
final footer = PaginationListFooter(
|
||||||
|
noti: noti,
|
||||||
|
data: data,
|
||||||
|
skeletonChild: footerSkeletonChild,
|
||||||
);
|
);
|
||||||
return isSliver
|
final content = contentBuilder(data.value ?? [], footer);
|
||||||
? SliverList.list(children: content)
|
|
||||||
: ListView(children: content);
|
return isRefreshable
|
||||||
|
? ExtendedRefreshIndicator(onRefresh: noti.refresh, child: content)
|
||||||
|
: content;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.hasError) {
|
// For non-slivers, use AnimatedSwitcher for smooth transitions
|
||||||
final content = ResponseErrorWidget(
|
Widget buildContent() {
|
||||||
error: data.error,
|
if ((data.isLoading || noti.isLoading) && data.value?.isEmpty == true) {
|
||||||
onRetry: noti.refresh,
|
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 isSliver ? SliverFillRemaining(child: content) : content;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final footer = PaginationListFooter(
|
return AnimatedSwitcher(
|
||||||
noti: noti,
|
duration: const Duration(milliseconds: 300),
|
||||||
data: data,
|
child: buildContent(),
|
||||||
skeletonChild: footerSkeletonChild,
|
|
||||||
);
|
);
|
||||||
final content = contentBuilder(data.value ?? [], footer);
|
|
||||||
|
|
||||||
return isRefreshable
|
|
||||||
? ExtendedRefreshIndicator(onRefresh: noti.refresh, child: content)
|
|
||||||
: content;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user