refactorings and cleanup
This commit is contained in:
@@ -33,7 +33,7 @@ final class BangSearchProvider
|
||||
BangSearch create() => BangSearch();
|
||||
}
|
||||
|
||||
String _$bangSearchHash() => r'7993bba3765d24ca9a7a17eca86ffdbc7c1b5e65';
|
||||
String _$bangSearchHash() => r'feed24edfe703b0697f4a855be9c7359c456b0f2';
|
||||
|
||||
abstract class _$BangSearch extends $StreamNotifier<List<BangData>> {
|
||||
Stream<List<BangData>> build();
|
||||
|
||||
+28
-14
@@ -19,6 +19,8 @@
|
||||
*/
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:fast_equatable/fast_equatable.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:riverpod/experimental/persist.dart';
|
||||
import 'package:riverpod_annotation/experimental/persist.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
@@ -27,14 +29,30 @@ import 'package:weblibre/features/user/data/providers.dart';
|
||||
|
||||
part 'search_module_order.g.dart';
|
||||
|
||||
typedef ModuleOrderEntry = ({SearchModuleType type, bool visible});
|
||||
@JsonSerializable()
|
||||
class ModuleOrderEntry with FastEquatable {
|
||||
final SearchModuleType type;
|
||||
final bool visible;
|
||||
|
||||
ModuleOrderEntry({required this.type, required this.visible});
|
||||
|
||||
factory ModuleOrderEntry.fromJson(Map<String, dynamic> json) =>
|
||||
_$ModuleOrderEntryFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$ModuleOrderEntryToJson(this);
|
||||
|
||||
@override
|
||||
List<Object?> get hashParameters => [type, visible];
|
||||
}
|
||||
|
||||
List<ModuleOrderEntry> _mergeWithDefaults(
|
||||
List<ModuleOrderEntry>? persisted,
|
||||
List<SearchModuleType> defaults,
|
||||
) {
|
||||
if (persisted == null) {
|
||||
return defaults.map((type) => (type: type, visible: true)).toList();
|
||||
return defaults
|
||||
.map((type) => ModuleOrderEntry(type: type, visible: true))
|
||||
.toList();
|
||||
}
|
||||
|
||||
final defaultSet = defaults.toSet();
|
||||
@@ -44,7 +62,7 @@ List<ModuleOrderEntry> _mergeWithDefaults(
|
||||
final persistedTypes = result.map((e) => e.type).toSet();
|
||||
for (final type in defaults) {
|
||||
if (!persistedTypes.contains(type)) {
|
||||
result.add((type: type, visible: true));
|
||||
result.add(ModuleOrderEntry(type: type, visible: true));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -63,7 +81,10 @@ class SearchModuleOrder extends _$SearchModuleOrder {
|
||||
void toggleVisibility(SearchModuleType type) {
|
||||
state = [
|
||||
for (final e in state)
|
||||
if (e.type == type) (type: e.type, visible: !e.visible) else e,
|
||||
if (e.type == type)
|
||||
ModuleOrderEntry(type: e.type, visible: !e.visible)
|
||||
else
|
||||
e,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -72,20 +93,13 @@ class SearchModuleOrder extends _$SearchModuleOrder {
|
||||
persist(
|
||||
ref.watch(riverpodDatabaseStorageProvider),
|
||||
key: group.key,
|
||||
encode: (state) => jsonEncode(
|
||||
state
|
||||
.map((e) => {'type': e.type.name, 'visible': e.visible})
|
||||
.toList(),
|
||||
),
|
||||
encode: (state) => jsonEncode(state.map((e) => e.toJson()).toList()),
|
||||
decode: (encoded) {
|
||||
final decoded = (jsonDecode(encoded) as List<dynamic>)
|
||||
.cast<Map<String, dynamic>>()
|
||||
.map((e) {
|
||||
try {
|
||||
return (
|
||||
type: SearchModuleType.values.byName(e['type']! as String),
|
||||
visible: e['visible']! as bool,
|
||||
);
|
||||
return ModuleOrderEntry.fromJson(e);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
@@ -99,7 +113,7 @@ class SearchModuleOrder extends _$SearchModuleOrder {
|
||||
|
||||
return stateOrNull ??
|
||||
group.defaultModules
|
||||
.map((type) => (type: type, visible: true))
|
||||
.map((type) => ModuleOrderEntry(type: type, visible: true))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
+30
-1
@@ -2,6 +2,35 @@
|
||||
|
||||
part of 'search_module_order.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
ModuleOrderEntry _$ModuleOrderEntryFromJson(Map<String, dynamic> json) =>
|
||||
ModuleOrderEntry(
|
||||
type: $enumDecode(_$SearchModuleTypeEnumMap, json['type']),
|
||||
visible: json['visible'] as bool,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ModuleOrderEntryToJson(ModuleOrderEntry instance) =>
|
||||
<String, dynamic>{
|
||||
'type': _$SearchModuleTypeEnumMap[instance.type]!,
|
||||
'visible': instance.visible,
|
||||
};
|
||||
|
||||
const _$SearchModuleTypeEnumMap = {
|
||||
SearchModuleType.tabs: 'tabs',
|
||||
SearchModuleType.articles: 'articles',
|
||||
SearchModuleType.bookmarks: 'bookmarks',
|
||||
SearchModuleType.history: 'history',
|
||||
SearchModuleType.historyHighlights: 'historyHighlights',
|
||||
SearchModuleType.topSites: 'topSites',
|
||||
SearchModuleType.recentHistory: 'recentHistory',
|
||||
SearchModuleType.recentArticles: 'recentArticles',
|
||||
SearchModuleType.recentTabs: 'recentTabs',
|
||||
SearchModuleType.containers: 'containers',
|
||||
};
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
@@ -58,7 +87,7 @@ final class SearchModuleOrderProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$searchModuleOrderHash() => r'eeea86534497671a12c1383cbf251a8df797c1fc';
|
||||
String _$searchModuleOrderHash() => r'245c933174b1808b2cb27c4c375fac4edd12d727';
|
||||
|
||||
final class SearchModuleOrderFamily extends $Family
|
||||
with
|
||||
|
||||
+6
-3
@@ -31,10 +31,13 @@ class HistoryHighlightsSection extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final highlightsAsync = ref.watch(searchEmptyHistoryHighlightsProvider());
|
||||
final highlights = highlightsAsync.value ?? [];
|
||||
final highlights = ref.watch(
|
||||
searchEmptyHistoryHighlightsProvider().select(
|
||||
(value) => value.value ?? [],
|
||||
),
|
||||
);
|
||||
|
||||
if (highlightsAsync.hasValue && highlights.isEmpty) {
|
||||
if (highlights.isEmpty) {
|
||||
return const SliverToBoxAdapter(child: SizedBox.shrink());
|
||||
}
|
||||
|
||||
|
||||
+6
-3
@@ -37,10 +37,13 @@ class RecentFeedArticlesSection extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final articlesAsync = ref.watch(searchEmptyRecentFeedArticlesProvider());
|
||||
final articles = articlesAsync.value ?? [];
|
||||
final articles = ref.watch(
|
||||
searchEmptyRecentFeedArticlesProvider().select(
|
||||
(value) => value.value ?? [],
|
||||
),
|
||||
);
|
||||
|
||||
if (articlesAsync.hasValue && articles.isEmpty) {
|
||||
if (articles.isEmpty) {
|
||||
return const SliverToBoxAdapter(child: SizedBox.shrink());
|
||||
}
|
||||
|
||||
|
||||
+4
-3
@@ -31,10 +31,11 @@ class RecentHistorySection extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final historyAsync = ref.watch(searchEmptyRecentHistoryProvider());
|
||||
final visits = historyAsync.value ?? [];
|
||||
final visits = ref.watch(
|
||||
searchEmptyRecentHistoryProvider().select((value) => value.value ?? []),
|
||||
);
|
||||
|
||||
if (historyAsync.hasValue && visits.isEmpty) {
|
||||
if (visits.isEmpty) {
|
||||
return const SliverToBoxAdapter(child: SizedBox.shrink());
|
||||
}
|
||||
|
||||
|
||||
+128
-131
@@ -73,12 +73,13 @@ class TopSitesSection extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final topSitesAsync = ref.watch(
|
||||
topSiteListProvider(limit: _topSitesMaxLimit),
|
||||
final topSites = ref.watch(
|
||||
topSiteListProvider(
|
||||
limit: _topSitesMaxLimit,
|
||||
).select((value) => value.value ?? []),
|
||||
);
|
||||
final topSites = topSitesAsync.value ?? [];
|
||||
|
||||
if (topSitesAsync.hasValue && topSites.isEmpty) {
|
||||
if (topSites.isEmpty) {
|
||||
return const SliverToBoxAdapter(child: SizedBox.shrink());
|
||||
}
|
||||
|
||||
@@ -335,7 +336,7 @@ class _ReorderableTopSitesGrid extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _TopSiteGridTile extends StatelessWidget {
|
||||
class _TopSiteGridTile extends StatefulWidget {
|
||||
final TopSiteItem item;
|
||||
final VoidCallback onTap;
|
||||
final VoidCallback? onPin;
|
||||
@@ -355,153 +356,149 @@ class _TopSiteGridTile extends StatelessWidget {
|
||||
static const _iconSize = 40.0;
|
||||
static const _borderRadius = BorderRadius.all(Radius.circular(12.0));
|
||||
|
||||
@override
|
||||
State<_TopSiteGridTile> createState() => _TopSiteGridTileState();
|
||||
}
|
||||
|
||||
class _TopSiteGridTileState extends State<_TopSiteGridTile> {
|
||||
final _menuController = MenuController();
|
||||
|
||||
bool get _hasMenu =>
|
||||
(widget.item.isPersisted &&
|
||||
(widget.onEdit != null || widget.onRemove != null)) ||
|
||||
(!widget.item.isPersisted && widget.onPin != null);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
|
||||
return Material(
|
||||
color: colorScheme.surfaceContainerHigh,
|
||||
borderRadius: _borderRadius,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: InkWell(
|
||||
borderRadius: _borderRadius,
|
||||
onTap: onTap,
|
||||
onLongPress: _hasMenu ? () => _showContextMenu(context) : null,
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8.0,
|
||||
vertical: 10.0,
|
||||
),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final titleStyle = textTheme.bodySmall?.copyWith(
|
||||
color: colorScheme.onSurface,
|
||||
);
|
||||
final lineHeight =
|
||||
(titleStyle?.fontSize ?? 12.0) *
|
||||
(titleStyle?.height ?? 1.2);
|
||||
const textLines = 2;
|
||||
const gap = 6.0;
|
||||
const minIconSize = 18.0;
|
||||
const textHeightPadding = 16.0;
|
||||
final minTextHeight = lineHeight + textHeightPadding;
|
||||
final maxTextHeight =
|
||||
lineHeight * textLines + textHeightPadding;
|
||||
final iconSize = (constraints.maxHeight - minTextHeight - gap)
|
||||
.clamp(minIconSize, _iconSize);
|
||||
return MenuAnchor(
|
||||
controller: _menuController,
|
||||
menuChildren: [
|
||||
if (!widget.item.isPersisted && widget.onPin != null)
|
||||
MenuItemButton(onPressed: widget.onPin, child: const Text('Pin')),
|
||||
if (widget.item.isPersisted && widget.onEdit != null)
|
||||
MenuItemButton(onPressed: widget.onEdit, child: const Text('Edit')),
|
||||
if (widget.item.isPersisted && widget.onRemove != null)
|
||||
MenuItemButton(
|
||||
onPressed: widget.onRemove,
|
||||
child: const Text('Remove'),
|
||||
),
|
||||
],
|
||||
child: Material(
|
||||
color: colorScheme.surfaceContainerHigh,
|
||||
borderRadius: _TopSiteGridTile._borderRadius,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: InkWell(
|
||||
borderRadius: _TopSiteGridTile._borderRadius,
|
||||
onTap: widget.onTap,
|
||||
onLongPress: _hasMenu ? () => _menuController.open() : null,
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8.0,
|
||||
vertical: 10.0,
|
||||
),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final titleStyle = textTheme.bodySmall?.copyWith(
|
||||
color: colorScheme.onSurface,
|
||||
);
|
||||
final lineHeight =
|
||||
(titleStyle?.fontSize ?? 12.0) *
|
||||
(titleStyle?.height ?? 1.2);
|
||||
const textLines = 2;
|
||||
const gap = 6.0;
|
||||
const minIconSize = 18.0;
|
||||
const textHeightPadding = 16.0;
|
||||
final minTextHeight = lineHeight + textHeightPadding;
|
||||
final maxTextHeight =
|
||||
lineHeight * textLines + textHeightPadding;
|
||||
final iconSize =
|
||||
(constraints.maxHeight - minTextHeight - gap).clamp(
|
||||
minIconSize,
|
||||
_TopSiteGridTile._iconSize,
|
||||
);
|
||||
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Align(
|
||||
child: SizedBox.square(
|
||||
dimension: iconSize,
|
||||
child: RepaintBoundary(
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(8.0),
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Align(
|
||||
child: SizedBox.square(
|
||||
dimension: iconSize,
|
||||
child: RepaintBoundary(
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(8.0),
|
||||
),
|
||||
child: UrlIcon([
|
||||
widget.item.url,
|
||||
], iconSize: iconSize),
|
||||
),
|
||||
child: UrlIcon([item.url], iconSize: iconSize),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: gap),
|
||||
Flexible(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxHeight: maxTextHeight),
|
||||
child: Text(
|
||||
item.title,
|
||||
maxLines: textLines,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.center,
|
||||
style: titleStyle,
|
||||
const SizedBox(height: gap),
|
||||
Flexible(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: maxTextHeight,
|
||||
),
|
||||
child: Text(
|
||||
widget.item.title,
|
||||
maxLines: textLines,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.center,
|
||||
style: titleStyle,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
if (item.source == TopSiteSource.pinned)
|
||||
Positioned(
|
||||
top: 4,
|
||||
right: 4,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.primaryContainer,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(10.0)),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(3.0),
|
||||
child: Icon(
|
||||
Icons.push_pin,
|
||||
size: 12,
|
||||
color: colorScheme.onPrimaryContainer,
|
||||
if (widget.item.source == TopSiteSource.pinned)
|
||||
Positioned(
|
||||
top: 4,
|
||||
right: 4,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.primaryContainer,
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(10.0),
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(3.0),
|
||||
child: Icon(
|
||||
Icons.push_pin,
|
||||
size: 12,
|
||||
color: colorScheme.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (showDragHandle)
|
||||
Positioned(
|
||||
top: 2,
|
||||
right: 2,
|
||||
child: Icon(
|
||||
Icons.drag_indicator,
|
||||
size: 16,
|
||||
color: colorScheme.onSurfaceVariant.withValues(alpha: 0.6),
|
||||
if (widget.showDragHandle)
|
||||
Positioned(
|
||||
top: 2,
|
||||
right: 2,
|
||||
child: Icon(
|
||||
Icons.drag_indicator,
|
||||
size: 16,
|
||||
color: colorScheme.onSurfaceVariant.withValues(alpha: 0.6),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
bool get _hasMenu =>
|
||||
(item.isPersisted && (onEdit != null || onRemove != null)) ||
|
||||
(!item.isPersisted && onPin != null);
|
||||
|
||||
Future<void> _showContextMenu(BuildContext context) async {
|
||||
final RenderBox renderBox = context.findRenderObject()! as RenderBox;
|
||||
final position = renderBox.localToGlobal(Offset.zero);
|
||||
final size = renderBox.size;
|
||||
|
||||
final value = await showMenu<String>(
|
||||
context: context,
|
||||
position: RelativeRect.fromLTRB(
|
||||
position.dx,
|
||||
position.dy + size.height,
|
||||
position.dx + size.width,
|
||||
position.dy + size.height,
|
||||
),
|
||||
items: [
|
||||
if (!item.isPersisted && onPin != null)
|
||||
const PopupMenuItem(value: 'pin', child: Text('Pin')),
|
||||
if (item.isPersisted && onEdit != null)
|
||||
const PopupMenuItem(value: 'edit', child: Text('Edit')),
|
||||
if (item.isPersisted && onRemove != null)
|
||||
const PopupMenuItem(value: 'remove', child: Text('Remove')),
|
||||
],
|
||||
);
|
||||
|
||||
switch (value) {
|
||||
case 'pin':
|
||||
onPin?.call();
|
||||
case 'edit':
|
||||
onEdit?.call();
|
||||
case 'remove':
|
||||
onRemove?.call();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _pinItem(
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ final class TopSiteRepositoryProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$topSiteRepositoryHash() => r'8aa231fadabe1e115d92cd37e032f27ba8da55e6';
|
||||
String _$topSiteRepositoryHash() => r'6e954c84ed5916ac0b5a9e251d43cbe1e9ed15c3';
|
||||
|
||||
abstract class _$TopSiteRepository extends $Notifier<void> {
|
||||
void build();
|
||||
|
||||
Reference in New Issue
Block a user