Add Supa account and search changes

This commit is contained in:
Fabian Freund
2026-05-22 18:10:22 +02:00
parent 3a19865b2e
commit 51289f1266
374 changed files with 54061 additions and 5013 deletions
@@ -107,6 +107,11 @@ extension DefineFunctions on i7.CommonDatabase {
required String Function(int, String?) lexoRankPrevious,
required String Function(String?, String?) lexoRankReorderAfter,
required String Function(String?, String?) lexoRankReorderBefore,
required int Function() generateContentHash,
required bool Function(String?) urlIndexable,
required String Function(String?) urlCanonical,
required String Function(String?) urlHost,
required String Function(String?) urlPath,
}) {
createFunction(
functionName: 'lexo_rank_next',
@@ -144,5 +149,44 @@ extension DefineFunctions on i7.CommonDatabase {
return lexoRankReorderBefore(arg0, arg1);
},
);
createFunction(
functionName: 'generate_content_hash',
argumentCount: const i7.AllowedArgumentCount(0),
function: (args) {
return generateContentHash();
},
);
createFunction(
functionName: 'url_indexable',
argumentCount: const i7.AllowedArgumentCount(1),
function: (args) {
final arg0 = args[0] as String?;
return urlIndexable(arg0);
},
);
createFunction(
functionName: 'url_canonical',
argumentCount: const i7.AllowedArgumentCount(1),
function: (args) {
final arg0 = args[0] as String?;
return urlCanonical(arg0);
},
);
createFunction(
functionName: 'url_host',
argumentCount: const i7.AllowedArgumentCount(1),
function: (args) {
final arg0 = args[0] as String?;
return urlHost(arg0);
},
);
createFunction(
functionName: 'url_path',
argumentCount: const i7.AllowedArgumentCount(1),
function: (args) {
final arg0 = args[0] as String?;
return urlPath(arg0);
},
);
}
}
@@ -49,33 +49,43 @@ class SmallWebHistoryHeader extends ConsumerWidget {
children: [
Text('Recent Discoveries', style: theme.textTheme.titleSmall),
const Spacer(),
PopupMenuButton<_ClearAction>(
icon: Icon(
Icons.more_vert,
size: 20,
color: colorScheme.onSurfaceVariant,
MenuAnchor(
builder: (context, controller, _) => IconButton(
icon: Icon(
Icons.more_vert,
size: 20,
color: colorScheme.onSurfaceVariant,
),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
style: const ButtonStyle(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
onPressed: () =>
controller.isOpen ? controller.close() : controller.open(),
),
iconSize: 20,
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
style: const ButtonStyle(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
offset: const Offset(0, 36),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
onSelected: (action) async {
switch (action) {
case _ClearAction.clearMode:
await ref
.read(smallWebDatabaseProvider)
.smallWebVisitDao
.deleteVisitsBySourceAndMode(
sourceKind: sourceKind,
mode: mode,
);
case _ClearAction.clearAll:
menuChildren: [
if (modeLabel != null)
MenuItemButton(
leadingIcon: const Icon(Icons.delete_sweep, size: 20),
onPressed: () async {
await ref
.read(smallWebDatabaseProvider)
.smallWebVisitDao
.deleteVisitsBySourceAndMode(
sourceKind: sourceKind,
mode: mode,
);
},
child: Text('Clear $modeLabel'),
),
MenuItemButton(
leadingIcon: Icon(
Icons.delete_forever,
size: 20,
color: colorScheme.error,
),
onPressed: () async {
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
@@ -101,33 +111,10 @@ class SmallWebHistoryHeader extends ConsumerWidget {
.smallWebVisitDao
.deleteAllVisits();
}
}
},
itemBuilder: (context) => [
if (modeLabel != null)
PopupMenuItem(
value: _ClearAction.clearMode,
child: ListTile(
dense: true,
contentPadding: EdgeInsets.zero,
leading: const Icon(Icons.delete_sweep, size: 20),
title: Text('Clear $modeLabel'),
),
),
PopupMenuItem(
value: _ClearAction.clearAll,
child: ListTile(
dense: true,
contentPadding: EdgeInsets.zero,
leading: Icon(
Icons.delete_forever,
size: 20,
color: colorScheme.error,
),
title: Text(
'Clear all discoveries',
style: TextStyle(color: colorScheme.error),
),
},
child: Text(
'Clear all discoveries',
style: TextStyle(color: colorScheme.error),
),
),
],
@@ -137,8 +124,6 @@ class SmallWebHistoryHeader extends ConsumerWidget {
}
}
enum _ClearAction { clearMode, clearAll }
class SmallWebHistoryList extends HookConsumerWidget {
static const _initialCount = 10;
@@ -17,6 +17,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:fading_scroll/fading_scroll.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:skeletonizer/skeletonizer.dart';
@@ -140,46 +141,45 @@ class _SourceKindChip extends StatelessWidget {
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return PopupMenuButton<SmallWebSourceKind>(
initialValue: sourceKind,
onSelected: (kind) {
if (kind != sourceKind) onChanged(kind);
},
offset: const Offset(0, 40),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
itemBuilder: (context) => [
return MenuAnchor(
builder: (context, controller, _) => InkWell(
borderRadius: BorderRadius.circular(8),
onTap: () => controller.isOpen ? controller.close() : controller.open(),
child: Chip(
avatar: Icon(sourceKind.icon, size: 18),
label: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(sourceKind.label),
const SizedBox(width: 2),
const Icon(Icons.arrow_drop_down, size: 18),
],
),
visualDensity: VisualDensity.compact,
),
),
menuChildren: [
for (final kind in SmallWebSourceKind.values)
PopupMenuItem(
value: kind,
child: ListTile(
dense: true,
contentPadding: EdgeInsets.zero,
leading: Icon(kind.icon, size: 20),
title: Text(kind.label),
subtitle: Text(
kind.description,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: colorScheme.onSurfaceVariant,
MenuItemButton(
onPressed: () => onChanged(kind),
leadingIcon: Icon(kind.icon, size: 20),
trailingIcon: kind == sourceKind
? Icon(Icons.check, size: 20, color: colorScheme.primary)
: null,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(kind.label),
Text(
kind.description,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
),
trailing: kind == sourceKind
? Icon(Icons.check, size: 20, color: colorScheme.primary)
: null,
],
),
),
],
child: Chip(
avatar: Icon(sourceKind.icon, size: 18),
label: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(sourceKind.label),
const SizedBox(width: 2),
const Icon(Icons.arrow_drop_down, size: 18),
],
),
visualDensity: VisualDensity.compact,
),
);
}
}
@@ -282,74 +282,80 @@ class _WebCategoriesPanel extends ConsumerWidget {
final categoriesAsync = ref.watch(kagiCategoriesProvider);
return categoriesAsync.when(
data: (kagiCategories) => ListView(
data: (kagiCategories) => FadingScroll(
controller: scrollController,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
children: [
if (session.infoMessage != null) ...[
_InfoMessageCard(message: session.infoMessage!),
const SizedBox(height: 8),
],
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
fadingSize: 25,
builder: (context, controller) {
return ListView(
controller: controller,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
children: [
Text('Refine Category', style: theme.textTheme.titleSmall),
FilterChip(
label: const Text('All'),
selected: session.currentCategory == null,
showCheckmark: false,
onSelected: (_) async {
final notifier = ref.read(
smallWebSessionControllerProvider.notifier,
);
notifier.setCategory(null);
await notifier.discover();
},
if (session.infoMessage != null) ...[
_InfoMessageCard(message: session.infoMessage!),
const SizedBox(height: 8),
],
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Refine Category', style: theme.textTheme.titleSmall),
FilterChip(
label: const Text('All'),
selected: session.currentCategory == null,
showCheckmark: false,
onSelected: (_) async {
final notifier = ref.read(
smallWebSessionControllerProvider.notifier,
);
notifier.setCategory(null);
await notifier.discover();
},
),
],
),
const SizedBox(height: 8),
for (final MapEntry(key: groupName, value: slugs)
in kagiCategories.groups.entries) ...[
_SectionHeader(title: groupName),
const SizedBox(height: 6),
_CategoryGrid(
categories: kagiCategories.categories,
slugs: slugs,
currentCategory: session.currentCategory,
onCategorySelected: (slug) async {
final notifier = ref.read(
smallWebSessionControllerProvider.notifier,
);
notifier.setCategory(
session.currentCategory == slug ? null : slug,
);
await notifier.discover();
},
),
const SizedBox(height: 10),
],
SmallWebAttributionCard(
data: SmallWebAttributionData.forSelection(
sourceKind: session.sourceKind,
mode: session.mode,
),
onOpenUri: (uri) => openSmallWebAttributionUri(context, uri),
compact: true,
),
const SizedBox(height: 8),
const _DiscoverButton(),
const SizedBox(height: 12),
SmallWebHistoryHeader(
sourceKind: session.sourceKind,
mode: session.mode,
),
const SizedBox(height: 4),
SmallWebHistoryList(
sourceKind: session.sourceKind,
mode: session.mode,
),
],
),
const SizedBox(height: 8),
for (final MapEntry(key: groupName, value: slugs)
in kagiCategories.groups.entries) ...[
_SectionHeader(title: groupName),
const SizedBox(height: 6),
_CategoryGrid(
categories: kagiCategories.categories,
slugs: slugs,
currentCategory: session.currentCategory,
onCategorySelected: (slug) async {
final notifier = ref.read(
smallWebSessionControllerProvider.notifier,
);
notifier.setCategory(
session.currentCategory == slug ? null : slug,
);
await notifier.discover();
},
),
const SizedBox(height: 10),
],
SmallWebAttributionCard(
data: SmallWebAttributionData.forSelection(
sourceKind: session.sourceKind,
mode: session.mode,
),
onOpenUri: (uri) => openSmallWebAttributionUri(context, uri),
compact: true,
),
const SizedBox(height: 8),
const _DiscoverButton(),
const SizedBox(height: 12),
SmallWebHistoryHeader(
sourceKind: session.sourceKind,
mode: session.mode,
),
const SizedBox(height: 4),
SmallWebHistoryList(
sourceKind: session.sourceKind,
mode: session.mode,
),
],
);
},
),
loading: () => const Center(child: CircularProgressIndicator()),
error: (_, _) => const SizedBox.shrink(),
@@ -394,58 +400,71 @@ class _ModeContextPanel extends ConsumerWidget {
KagiSmallWebMode.web => (Icons.language, ''),
};
return ListView(
return FadingScroll(
controller: scrollController,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
children: [
if (session.infoMessage != null) ...[
_InfoMessageCard(message: session.infoMessage!),
const SizedBox(height: 8),
],
const SizedBox(height: 16),
Center(
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: colorScheme.primaryContainer,
shape: BoxShape.circle,
fadingSize: 25,
builder: (context, controller) {
return ListView(
controller: controller,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
children: [
if (session.infoMessage != null) ...[
_InfoMessageCard(message: session.infoMessage!),
const SizedBox(height: 8),
],
const SizedBox(height: 16),
Center(
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: colorScheme.primaryContainer,
shape: BoxShape.circle,
),
child: Icon(
icon,
size: 36,
color: colorScheme.onPrimaryContainer,
),
),
),
child: Icon(icon, size: 36, color: colorScheme.onPrimaryContainer),
),
),
const SizedBox(height: 12),
Text(
'Searching ${mode.label}',
textAlign: TextAlign.center,
style: theme.textTheme.titleMedium,
),
const SizedBox(height: 8),
Text(
description,
textAlign: TextAlign.center,
style: theme.textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 16),
SmallWebAttributionCard(
data: SmallWebAttributionData.forSelection(
sourceKind: session.sourceKind,
mode: session.mode,
),
onOpenUri: (uri) => openSmallWebAttributionUri(context, uri),
compact: true,
),
const SizedBox(height: 8),
const _DiscoverButton(),
const SizedBox(height: 12),
SmallWebHistoryHeader(
sourceKind: session.sourceKind,
mode: session.mode,
),
const SizedBox(height: 4),
SmallWebHistoryList(sourceKind: session.sourceKind, mode: session.mode),
],
const SizedBox(height: 12),
Text(
'Searching ${mode.label}',
textAlign: TextAlign.center,
style: theme.textTheme.titleMedium,
),
const SizedBox(height: 8),
Text(
description,
textAlign: TextAlign.center,
style: theme.textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 16),
SmallWebAttributionCard(
data: SmallWebAttributionData.forSelection(
sourceKind: session.sourceKind,
mode: session.mode,
),
onOpenUri: (uri) => openSmallWebAttributionUri(context, uri),
compact: true,
),
const SizedBox(height: 8),
const _DiscoverButton(),
const SizedBox(height: 12),
SmallWebHistoryHeader(
sourceKind: session.sourceKind,
mode: session.mode,
),
const SizedBox(height: 4),
SmallWebHistoryList(
sourceKind: session.sourceKind,
mode: session.mode,
),
],
);
},
);
}
}
@@ -462,44 +481,53 @@ class _DefaultContentPanel extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
return ListView(
return FadingScroll(
controller: scrollController,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
children: [
if (session.infoMessage != null) ...[
_InfoMessageCard(message: session.infoMessage!),
const SizedBox(height: 8),
],
SmallWebAttributionCard(
data: SmallWebAttributionData.forSelection(
sourceKind: session.sourceKind,
mode: session.mode,
),
onOpenUri: (uri) => openSmallWebAttributionUri(context, uri),
),
const SizedBox(height: 12),
if (session.sourceKind == SmallWebSourceKind.wander) ...[
_WanderConsoleCard(currentConsoleUrl: session.currentConsoleUrl),
const SizedBox(height: 8),
FilledButton.tonalIcon(
onPressed: () async {
Navigator.of(context).pop();
await showWanderConsoleSheet(context);
},
icon: const Icon(Icons.dns, size: 18),
label: const Text('Browse Consoles'),
),
const SizedBox(height: 12),
],
const _DiscoverButton(),
const SizedBox(height: 12),
SmallWebHistoryHeader(
sourceKind: session.sourceKind,
mode: session.mode,
),
const SizedBox(height: 4),
SmallWebHistoryList(sourceKind: session.sourceKind, mode: session.mode),
],
fadingSize: 25,
builder: (context, controller) {
return ListView(
controller: controller,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
children: [
if (session.infoMessage != null) ...[
_InfoMessageCard(message: session.infoMessage!),
const SizedBox(height: 8),
],
SmallWebAttributionCard(
data: SmallWebAttributionData.forSelection(
sourceKind: session.sourceKind,
mode: session.mode,
),
onOpenUri: (uri) => openSmallWebAttributionUri(context, uri),
),
const SizedBox(height: 12),
if (session.sourceKind == SmallWebSourceKind.wander) ...[
_WanderConsoleCard(currentConsoleUrl: session.currentConsoleUrl),
const SizedBox(height: 8),
FilledButton.tonalIcon(
onPressed: () async {
Navigator.of(context).pop();
await showWanderConsoleSheet(context);
},
icon: const Icon(Icons.dns, size: 18),
label: const Text('Browse Consoles'),
),
const SizedBox(height: 12),
],
const _DiscoverButton(),
const SizedBox(height: 12),
SmallWebHistoryHeader(
sourceKind: session.sourceKind,
mode: session.mode,
),
const SizedBox(height: 4),
SmallWebHistoryList(
sourceKind: session.sourceKind,
mode: session.mode,
),
],
);
},
);
}
}
@@ -736,16 +764,25 @@ class _SmallWebMenuLoading extends StatelessWidget {
),
const Divider(height: 1),
Expanded(
child: ListView(
child: FadingScroll(
controller: scrollController,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
children: const [
Bone.text(words: 2),
SizedBox(height: 8),
_SkeletonHistoryItem(),
_SkeletonHistoryItem(),
_SkeletonHistoryItem(),
],
fadingSize: 25,
builder: (context, controller) {
return ListView(
controller: controller,
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
children: const [
Bone.text(words: 2),
SizedBox(height: 8),
_SkeletonHistoryItem(),
_SkeletonHistoryItem(),
_SkeletonHistoryItem(),
],
);
},
),
),
],
@@ -798,19 +835,25 @@ class _SmallWebMenuError extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView(
return FadingScroll(
controller: scrollController,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
children: [
SizedBox(
height: 240,
child: FailureWidget(
title: 'Small Web unavailable',
exception: error,
onRetry: onRetry,
),
),
],
fadingSize: 25,
builder: (context, controller) {
return ListView(
controller: controller,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
children: [
SizedBox(
height: 240,
child: FailureWidget(
title: 'Small Web unavailable',
exception: error,
onRetry: onRetry,
),
),
],
);
},
);
}
}
@@ -17,10 +17,12 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:fading_scroll/fading_scroll.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/features/small_web/data/models/kagi_small_web_mode.dart';
import 'package:weblibre/features/small_web/domain/providers.dart';
import 'package:weblibre/presentation/widgets/inline_count_badge.dart';
class SmallWebModeChips extends ConsumerWidget {
final KagiSmallWebMode? currentMode;
@@ -34,16 +36,6 @@ class SmallWebModeChips extends ConsumerWidget {
required this.onModeSelected,
});
static String _formatCount(int count) {
if (count >= 1000) {
final k = count / 1000;
return k == k.roundToDouble()
? '${k.round()}k'
: '${k.toStringAsFixed(1)}k';
}
return count.toString();
}
@override
Widget build(BuildContext context, WidgetRef ref) {
final countsAsync = ref.watch(smallWebAllModeItemCountsProvider);
@@ -52,53 +44,46 @@ class SmallWebModeChips extends ConsumerWidget {
return SizedBox(
height: 48,
child: ListView.builder(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 16),
itemCount: KagiSmallWebMode.values.length,
itemBuilder: (context, index) {
final mode = KagiSmallWebMode.values[index];
final isSelected = currentMode == mode;
final count = counts[mode];
return Padding(
padding: const EdgeInsets.only(right: 8, top: 4),
child: ChoiceChip(
avatar: Icon(mode.icon, size: 18),
label: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(mode.label),
if (count != null && count > 0) ...[
const SizedBox(width: 6),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 6,
vertical: 1,
),
decoration: BoxDecoration(
color: isSelected
? colorScheme.primary.withValues(alpha: 0.15)
: colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(10),
),
child: Text(
_formatCount(count),
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.bold,
color: isSelected
child: FadingScroll(
fadingSize: 15,
builder: (context, controller) {
return ListView.builder(
controller: controller,
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 16),
itemCount: KagiSmallWebMode.values.length,
itemBuilder: (context, index) {
final mode = KagiSmallWebMode.values[index];
final isSelected = currentMode == mode;
final count = counts[mode];
return Padding(
padding: const EdgeInsets.only(right: 8, top: 4),
child: ChoiceChip(
avatar: Icon(mode.icon, size: 18),
label: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(mode.label),
if (count != null && count > 0) ...[
const SizedBox(width: 6),
InlineCountBadge(
count: count,
backgroundColor: isSelected
? colorScheme.primary.withValues(alpha: 0.15)
: colorScheme.surfaceContainerHighest,
foregroundColor: isSelected
? colorScheme.onPrimaryContainer
: colorScheme.onSurfaceVariant,
),
),
),
],
],
),
selected: isSelected,
showCheckmark: false,
onSelected: isLoading ? null : (_) => onModeSelected(mode),
),
],
],
),
selected: isSelected,
showCheckmark: false,
onSelected: isLoading ? null : (_) => onModeSelected(mode),
),
);
},
);
},
),
@@ -17,6 +17,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:fading_scroll/fading_scroll.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -263,18 +264,24 @@ class _WanderConsoleSheetLoading extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Skeletonizer(
child: ListView(
child: FadingScroll(
controller: scrollController,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
children: const [
TextField(
decoration: InputDecoration(hintText: 'Filter consoles...'),
),
SizedBox(height: 12),
_SkeletonConsoleTile(),
_SkeletonConsoleTile(),
_SkeletonConsoleTile(),
],
fadingSize: 25,
builder: (context, controller) {
return ListView(
controller: controller,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
children: const [
TextField(
decoration: InputDecoration(hintText: 'Filter consoles...'),
),
SizedBox(height: 12),
_SkeletonConsoleTile(),
_SkeletonConsoleTile(),
_SkeletonConsoleTile(),
],
);
},
),
);
}
@@ -323,19 +330,25 @@ class _WanderConsoleSheetError extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView(
return FadingScroll(
controller: scrollController,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
children: [
SizedBox(
height: 240,
child: FailureWidget(
title: 'Could not load Small Web session',
exception: error,
onRetry: onRetry,
),
),
],
fadingSize: 25,
builder: (context, controller) {
return ListView(
controller: controller,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
children: [
SizedBox(
height: 240,
child: FailureWidget(
title: 'Could not load Small Web session',
exception: error,
onRetry: onRetry,
),
),
],
);
},
);
}
}
@@ -381,17 +394,23 @@ class _LinkedConsoleList extends ConsumerWidget {
),
);
}
return ListView.builder(
return FadingScroll(
controller: scrollController,
padding: const EdgeInsets.only(top: 4, bottom: 56),
itemCount: filtered.length,
itemBuilder: (context, index) {
final console = filtered[index];
return _ConsoleListTile(
url: console.url,
pageCount: console.pageCount,
selectedConsoleUrl: selectedConsoleUrl,
isLoading: isLoading,
fadingSize: 25,
builder: (context, controller) {
return ListView.builder(
controller: controller,
padding: const EdgeInsets.only(top: 4, bottom: 56),
itemCount: filtered.length,
itemBuilder: (context, index) {
final console = filtered[index];
return _ConsoleListTile(
url: console.url,
pageCount: console.pageCount,
selectedConsoleUrl: selectedConsoleUrl,
isLoading: isLoading,
);
},
);
},
);
@@ -430,17 +449,23 @@ class _AllConsoleList extends ConsumerWidget {
),
);
}
return ListView.builder(
return FadingScroll(
controller: scrollController,
padding: const EdgeInsets.only(top: 4, bottom: 56),
itemCount: consoles.length,
itemBuilder: (context, index) {
final console = consoles[index];
return _ConsoleListTile(
url: console.url,
pageCount: console.pageCount,
selectedConsoleUrl: selectedConsoleUrl,
isLoading: isLoading,
fadingSize: 25,
builder: (context, controller) {
return ListView.builder(
controller: controller,
padding: const EdgeInsets.only(top: 4, bottom: 56),
itemCount: consoles.length,
itemBuilder: (context, index) {
final console = consoles[index];
return _ConsoleListTile(
url: console.url,
pageCount: console.pageCount,
selectedConsoleUrl: selectedConsoleUrl,
isLoading: isLoading,
);
},
);
},
);