This commit is contained in:
Fabian Freund
2025-02-03 15:09:04 +01:00
parent 36acd46318
commit 025ecaf2e3
132 changed files with 4431 additions and 1644 deletions
@@ -3,6 +3,7 @@ import 'package:fast_equatable/fast_equatable.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:lensai/data/models/web_page_info.dart';
import 'package:lensai/domain/entities/equatable_image.dart';
import 'package:lensai/extensions/nullable.dart';
import 'package:lensai/features/geckoview/domain/entities/browser_icon.dart';
import 'package:lensai/features/geckoview/domain/entities/find_result_state.dart';
import 'package:lensai/features/geckoview/domain/entities/history_state.dart';
@@ -24,13 +25,13 @@ class TabState extends WebPageInfo with FastEquatable {
final EquatableImage? icon;
@override
BrowserIcon? get favicon => (icon != null)
? BrowserIcon(
image: icon!,
BrowserIcon? get favicon => icon.mapNotNull(
(icon) => BrowserIcon(
image: icon,
dominantColor: null,
source: IconSource.memory,
)
: null;
),
);
final EquatableImage? thumbnail;
@@ -2,6 +2,7 @@ import 'dart:async';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:lensai/extensions/image.dart';
import 'package:lensai/extensions/nullable.dart';
import 'package:lensai/features/geckoview/domain/entities/find_result_state.dart';
import 'package:lensai/features/geckoview/domain/entities/history_state.dart';
import 'package:lensai/features/geckoview/domain/entities/readerable_state.dart';
@@ -39,10 +40,10 @@ class TabStates extends _$TabStates {
Future<void> _onIconChange(IconChangeEvent event) async {
final IconChangeEvent(:tabId, :bytes) = event;
final image = (bytes != null)
? (await tryDecodeImage(bytes)
.then((image) async => image?.toEquatable()))
: null;
final image = await bytes.mapNotNull(
(bytes) =>
tryDecodeImage(bytes).then((image) async => image?.toEquatable()),
);
final current = state[tabId] ?? TabState.$default(tabId);
state = {...state}..[tabId] = current.copyWith.icon(image);
@@ -51,10 +52,10 @@ class TabStates extends _$TabStates {
Future<void> _onThumbnailChange(ThumbnailEvent event) async {
final ThumbnailEvent(:tabId, :bytes) = event;
final image = (bytes != null)
? (await tryDecodeImage(bytes)
.then((image) async => image?.toEquatable()))
: null;
final image = await bytes.mapNotNull(
(bytes) =>
tryDecodeImage(bytes).then((image) async => image?.toEquatable()),
);
final current = state[tabId] ?? TabState.$default(tabId);
state = {...state}..[tabId] = current.copyWith.thumbnail(image);
@@ -173,7 +173,7 @@ final selectedTabStateProvider = AutoDisposeProvider<TabState?>.internal(
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef SelectedTabStateRef = AutoDisposeProviderRef<TabState?>;
String _$tabStatesHash() => r'77f07f330dfac8dc8d28d18737203c7c122f3b74';
String _$tabStatesHash() => r'296d85ec016b22236b5830fce6b01cda81fbc02d';
/// See also [TabStates].
@ProviderFor(TabStates)
@@ -4,6 +4,7 @@ import 'dart:ui';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:lensai/domain/entities/equatable_image.dart';
import 'package:lensai/extensions/image.dart';
import 'package:lensai/extensions/nullable.dart';
import 'package:lensai/features/geckoview/domain/entities/web_extension_state.dart';
import 'package:lensai/features/geckoview/domain/providers.dart';
import 'package:lensai/features/geckoview/utils/image_helper.dart';
@@ -33,12 +34,10 @@ class WebExtensionsState extends _$WebExtensionsState {
title: data.title,
enabled: data.enabled ?? current.enabled,
badgeText: data.badgeText,
badgeTextColor: (data.badgeTextColor != null)
? Color(data.badgeTextColor!)
: null,
badgeBackgroundColor: (data.badgeBackgroundColor != null)
? Color(data.badgeBackgroundColor!)
: null,
badgeTextColor:
data.badgeTextColor.mapNotNull((color) => Color(color)),
badgeBackgroundColor:
data.badgeBackgroundColor.mapNotNull((color) => Color(color)),
);
} else {
if (state.containsKey(extensionId)) {
@@ -7,7 +7,7 @@ part of 'web_extensions_state.dart';
// **************************************************************************
String _$webExtensionsStateHash() =>
r'bbaba8d27980046275858d2707bc66c6e349a537';
r'4e6dc46e49ff353e1c3cc4616008685c0e21501d';
/// Copied from Dart SDK
class _SystemHash {
@@ -2,14 +2,15 @@ import 'dart:async';
import 'package:drift/drift.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:lensai/extensions/nullable.dart';
import 'package:lensai/features/geckoview/domain/entities/tab_state.dart';
import 'package:lensai/features/geckoview/domain/providers.dart';
import 'package:lensai/features/geckoview/domain/providers/selected_tab.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_list.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
import 'package:lensai/features/geckoview/features/tabs/data/database/database.dart';
import 'package:lensai/features/geckoview/features/tabs/data/providers.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/providers/selected_container.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/container.dart';
import 'package:lensai/utils/debouncer.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -19,31 +20,59 @@ part 'tab.g.dart';
class TabRepository extends _$TabRepository {
final _tabsService = GeckoTabService();
late TabDatabase _db;
Future<String> addTab({
Uri? url,
bool selectTab = true,
bool startLoading = true,
String? parentId,
LoadUrlFlags flags = LoadUrlFlags.NONE,
String? contextId,
Source source = Internal.newTab,
bool private = false,
HistoryMetadataKey? historyMetadata,
Map<String, String>? additionalHeaders,
}) {
return _tabsService.addTab(
url: url,
selectTab: selectTab,
startLoading: startLoading,
parentId: parentId,
flags: flags,
contextId: contextId,
source: source,
private: private,
historyMetadata: historyMetadata,
additionalHeaders: additionalHeaders,
}) async {
final selectedContainer =
await ref.read(selectedContainerProvider.notifier).fetchData();
return ref.read(tabDatabaseProvider).tabDao.upsertContainerTabTransactional(
() {
return _tabsService.addTab(
url: url,
selectTab: selectTab,
startLoading: startLoading,
parentId: parentId,
flags: flags,
contextId: selectedContainer?.metadata.contextualIdentity,
source: source,
private: private,
historyMetadata: historyMetadata,
additionalHeaders: additionalHeaders,
);
},
containerId: Value(selectedContainer?.id),
);
}
Future<String> duplicateTab({
required String? selectTabId,
String? containerId,
bool selectTab = true,
}) async {
final containerData = await containerId.mapNotNull(
(containerId) => ref
.read(containerRepositoryProvider.notifier)
.getContainerData(containerId),
);
return ref.read(tabDatabaseProvider).tabDao.upsertContainerTabTransactional(
() {
return _tabsService.duplicateTab(
selectTabId: selectTabId,
newContextId: containerData?.metadata.contextualIdentity,
selectNewTab: selectTab,
);
},
containerId: Value(containerData?.id),
);
}
@@ -67,18 +96,19 @@ class TabRepository extends _$TabRepository {
final tabStateDebouncer = Debouncer(const Duration(seconds: 3));
Map<String, TabState>? debounceStartValue;
_db = ref.watch(tabDatabaseProvider);
final db = ref.watch(tabDatabaseProvider);
final tabAddedSub = eventSerivce.tabAddedStream.listen(
(tabId) async {
final containerId = ref.read(selectedContainerProvider);
await _db.tabDao.upsertTab(tabId, containerId: Value(containerId));
await db.tabDao
.upsertUnassignedTab(tabId, containerId: Value(containerId));
},
);
final tabContentSub =
tabContentService.tabContentStream.listen((content) async {
await _db.tabDao.updateTabContent(
await db.tabDao.updateTabContent(
content.tabId,
isProbablyReaderable: content.isProbablyReaderable,
extractedContentMarkdown: content.extractedContentMarkdown,
@@ -92,7 +122,7 @@ class TabRepository extends _$TabRepository {
selectedTabProvider,
(previous, tabId) async {
if (tabId != null) {
await _db.tabDao.touchTab(tabId, timestamp: DateTime.now());
await db.tabDao.touchTab(tabId, timestamp: DateTime.now());
}
},
);
@@ -104,7 +134,7 @@ class TabRepository extends _$TabRepository {
final syncTabs = next.isNotEmpty || (previous?.isNotEmpty ?? false);
if (syncTabs) {
await _db.tabDao.syncTabs(retainTabIds: next);
await db.tabDao.syncTabs(retainTabIds: next);
}
},
);
@@ -120,7 +150,7 @@ class TabRepository extends _$TabRepository {
}
tabStateDebouncer.eventOccured(() async {
await _db.tabDao.updateTabs(debounceStartValue, next);
await db.tabDao.updateTabs(debounceStartValue, next);
});
},
);
@@ -6,7 +6,7 @@ part of 'tab.dart';
// RiverpodGenerator
// **************************************************************************
String _$tabRepositoryHash() => r'b9caf89fe372b79f37c4a3ebff800ccc2d9289d2';
String _$tabRepositoryHash() => r'85af29fa18ea5a4a67fbffbb9478c801dafddac2';
/// See also [TabRepository].
@ProviderFor(TabRepository)
@@ -0,0 +1,34 @@
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:lensai/features/user/data/models/general_settings.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'delete_browser_data.g.dart';
@Riverpod()
class DeleteBrowserDataService extends _$DeleteBrowserDataService {
final _service = GeckoDeleteBrowserDataService();
Future<void> deleteData(Set<DeleteBrowsingDataType>? types) async {
if (types != null) {
for (final type in types) {
switch (type) {
case DeleteBrowsingDataType.tabs:
await _service.deleteTabs();
case DeleteBrowsingDataType.history:
await _service.deleteBrowsingHistory();
case DeleteBrowsingDataType.cookies:
await _service.deleteCookiesAndSiteData();
case DeleteBrowsingDataType.cache:
await _service.deleteCachedFiles();
case DeleteBrowsingDataType.permissions:
await _service.deleteSitePermissions();
case DeleteBrowsingDataType.downloads:
await _service.deleteDownloads();
}
}
}
}
@override
void build() {}
}
@@ -0,0 +1,27 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'delete_browser_data.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$deleteBrowserDataServiceHash() =>
r'f5fdbac17c14693fceac913591750ca9285b496c';
/// See also [DeleteBrowserDataService].
@ProviderFor(DeleteBrowserDataService)
final deleteBrowserDataServiceProvider =
AutoDisposeNotifierProvider<DeleteBrowserDataService, void>.internal(
DeleteBrowserDataService.new,
name: r'deleteBrowserDataServiceProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$deleteBrowserDataServiceHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$DeleteBrowserDataService = AutoDisposeNotifier<void>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
@@ -1,10 +1,90 @@
import 'package:flutter/material.dart' show ThemeMode;
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:riverpod/riverpod.dart';
import 'package:lensai/features/user/domain/repositories/engine_settings.dart';
import 'package:lensai/features/user/domain/repositories/general_settings.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'engine_settings.g.dart';
@Riverpod(keepAlive: true)
GeckoEngineSettingsService engineSettingsService(Ref ref) {
return GeckoEngineSettingsService();
class EngineSettingsReplicationService
extends _$EngineSettingsReplicationService {
final _service = GeckoEngineSettingsService();
@override
void build() {
var initialSettingsSent = false;
ref.listen(
generalSettingsRepositoryProvider
.select((settings) => settings.themeMode),
(previous, next) async {
final theme = switch (next) {
ThemeMode.system => ColorScheme.system,
ThemeMode.light => ColorScheme.light,
ThemeMode.dark => ColorScheme.dark,
};
await _service.preferredColorScheme(theme);
},
);
ref.listen(
engineSettingsRepositoryProvider,
(previous, next) async {
if (initialSettingsSent && previous != null) {
if (previous.javascriptEnabled != next.javascriptEnabled) {
await _service.javascriptEnabled(next.javascriptEnabled);
}
if (previous.trackingProtectionPolicy !=
next.trackingProtectionPolicy) {
await _service
.trackingProtectionPolicy(next.trackingProtectionPolicy);
}
if (previous.httpsOnlyMode != next.httpsOnlyMode) {
await _service.httpsOnlyMode(next.httpsOnlyMode);
}
if (previous.globalPrivacyControlEnabled !=
next.globalPrivacyControlEnabled) {
await _service
.globalPrivacyControlEnabled(next.globalPrivacyControlEnabled);
}
if (previous.preferredColorScheme != next.preferredColorScheme) {
await _service.preferredColorScheme(next.preferredColorScheme);
}
if (previous.cookieBannerHandlingMode !=
next.cookieBannerHandlingMode) {
await _service
.cookieBannerHandlingMode(next.cookieBannerHandlingMode);
}
if (previous.cookieBannerHandlingModePrivateBrowsing !=
next.cookieBannerHandlingModePrivateBrowsing) {
await _service.cookieBannerHandlingModePrivateBrowsing(
next.cookieBannerHandlingModePrivateBrowsing,
);
}
if (previous.cookieBannerHandlingGlobalRules !=
next.cookieBannerHandlingGlobalRules) {
await _service.cookieBannerHandlingGlobalRules(
next.cookieBannerHandlingGlobalRules,
);
}
if (previous.cookieBannerHandlingGlobalRulesSubFrames !=
next.cookieBannerHandlingGlobalRulesSubFrames) {
await _service.cookieBannerHandlingGlobalRulesSubFrames(
next.cookieBannerHandlingGlobalRulesSubFrames,
);
}
if (previous.webContentIsolationStrategy !=
next.webContentIsolationStrategy) {
await _service
.webContentIsolationStrategy(next.webContentIsolationStrategy);
}
} else {
await _service.setDefaultSettings(next);
initialSettingsSent = true;
}
},
);
}
}
@@ -6,24 +6,22 @@ part of 'engine_settings.dart';
// RiverpodGenerator
// **************************************************************************
String _$engineSettingsServiceHash() =>
r'051d7b6f65eb5dcb07405b4af1bdadd8dd74fdc0';
String _$engineSettingsReplicationServiceHash() =>
r'0729260eeb34b58470622ac9a3a6abb257730a61';
/// See also [engineSettingsService].
@ProviderFor(engineSettingsService)
final engineSettingsServiceProvider =
Provider<GeckoEngineSettingsService>.internal(
engineSettingsService,
name: r'engineSettingsServiceProvider',
/// See also [EngineSettingsReplicationService].
@ProviderFor(EngineSettingsReplicationService)
final engineSettingsReplicationServiceProvider =
NotifierProvider<EngineSettingsReplicationService, void>.internal(
EngineSettingsReplicationService.new,
name: r'engineSettingsReplicationServiceProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$engineSettingsServiceHash,
: _$engineSettingsReplicationServiceHash,
dependencies: null,
allTransitiveDependencies: null,
);
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef EngineSettingsServiceRef = ProviderRef<GeckoEngineSettingsService>;
typedef _$EngineSettingsReplicationService = Notifier<void>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
@@ -1,103 +1,104 @@
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/data/models/equatable_iterable.dart';
import 'package:lensai/features/geckoview/domain/entities/tab_state.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/providers.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/tab.dart';
// import 'package:collection/collection.dart';
// import 'package:flutter/material.dart';
// import 'package:flutter_hooks/flutter_hooks.dart';
// import 'package:hooks_riverpod/hooks_riverpod.dart';
// import 'package:lensai/data/models/equatable_iterable.dart';
// import 'package:lensai/extensions/nullable.dart';
// import 'package:lensai/features/geckoview/domain/entities/tab_state.dart';
// import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
// import 'package:lensai/features/geckoview/features/tabs/domain/providers.dart';
// import 'package:lensai/features/geckoview/features/tabs/domain/repositories/tab.dart';
class TabActionDialog extends HookConsumerWidget {
final TabState initialTab;
// class TabActionDialog extends HookConsumerWidget {
// final TabState initialTab;
final void Function()? onDismiss;
// final void Function()? onDismiss;
const TabActionDialog({
required this.initialTab,
this.onDismiss,
super.key,
});
// const TabActionDialog({
// required this.initialTab,
// this.onDismiss,
// super.key,
// });
@override
Widget build(BuildContext context, WidgetRef ref) {
final tabState = ref.watch(tabStateProvider(initialTab.id)) ?? initialTab;
final tabContainerId = ref.watch(
tabContainerIdProvider(initialTab.id)
.select((value) => value.valueOrNull),
);
// @override
// Widget build(BuildContext context, WidgetRef ref) {
// final tabState = ref.watch(tabStateProvider(initialTab.id)) ?? initialTab;
// final tabContainerId = ref.watch(
// tabContainerIdProvider(initialTab.id)
// .select((value) => value.valueOrNull),
// );
final containers = ref
.watch(
containersWithCountProvider.select(
(value) => EquatableCollection(value.valueOrNull, immutable: true),
),
)
.collection;
// final containers = ref
// .watch(
// containersWithCountProvider.select(
// (value) => EquatableCollection(value.valueOrNull, immutable: true),
// ),
// )
// .collection;
final selectedContainer = containers
?.firstWhereOrNull((container) => container.id == tabContainerId);
// final selectedContainer = containers
// ?.firstWhereOrNull((container) => container.id == tabContainerId);
final expansionController = useExpansionTileController();
// final expansionController = useExpansionTileController();
return Stack(
children: [
ModalBarrier(
color: Theme.of(context).dialogTheme.barrierColor ?? Colors.black54,
onDismiss: onDismiss,
),
SimpleDialog(
titlePadding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 0.0),
contentPadding: EdgeInsets.zero,
insetPadding: const EdgeInsets.symmetric(
horizontal: 20.0,
vertical: 24.0,
),
title: ListTile(
leading: RawImage(
image: tabState.icon?.value,
width: 24,
height: 24,
),
contentPadding: EdgeInsets.zero,
title: Text(tabState.title),
subtitle: Text(tabState.url.authority),
),
children: [
SizedBox(
//We need this to stretch the dialog, then padding from dialog is applied
width: double.maxFinite,
child: ExpansionTile(
controller: expansionController,
leading: (selectedContainer != null)
? CircleAvatar(backgroundColor: selectedContainer.color)
: null,
title: (selectedContainer != null)
? Text(selectedContainer.name ?? 'New Container')
: const Text('Assign a Container'),
children: containers
?.where((container) => container.id != tabContainerId)
.map(
(container) => ListTile(
leading:
CircleAvatar(backgroundColor: container.color),
title: Text(container.name ?? 'New Container'),
onTap: () async {
await ref
.read(tabDataRepositoryProvider.notifier)
.assignContainer(initialTab.id, container.id);
// return Stack(
// children: [
// ModalBarrier(
// color: Theme.of(context).dialogTheme.barrierColor ?? Colors.black54,
// onDismiss: onDismiss,
// ),
// SimpleDialog(
// titlePadding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 0.0),
// contentPadding: EdgeInsets.zero,
// insetPadding: const EdgeInsets.symmetric(
// horizontal: 20.0,
// vertical: 24.0,
// ),
// title: ListTile(
// leading: RawImage(
// image: tabState.icon?.value,
// width: 24,
// height: 24,
// ),
// contentPadding: EdgeInsets.zero,
// title: Text(tabState.title),
// subtitle: Text(tabState.url.authority),
// ),
// children: [
// SizedBox(
// //We need this to stretch the dialog, then padding from dialog is applied
// width: double.maxFinite,
// child: ExpansionTile(
// controller: expansionController,
// leading: selectedContainer?.color.mapNotNull(
// (color) => CircleAvatar(backgroundColor: color),
// ),
// title: (selectedContainer != null)
// ? Text(selectedContainer.name ?? 'New Container')
// : const Text('Assign a Container'),
// children: containers
// ?.where((container) => container.id != tabContainerId)
// .map(
// (container) => ListTile(
// leading:
// CircleAvatar(backgroundColor: container.color),
// title: Text(container.name ?? 'New Container'),
// onTap: () async {
// await ref
// .read(tabDataRepositoryProvider.notifier)
// .assignContainer(initialTab.id, container.id);
expansionController.collapse();
},
),
)
.toList() ??
[],
),
),
],
),
],
);
}
}
// expansionController.collapse();
// },
// ),
// )
// .toList() ??
// [],
// ),
// ),
// ],
// ),
// ],
// );
// }
// }
@@ -22,7 +22,7 @@ import 'package:lensai/features/geckoview/features/browser/domain/entities/sheet
import 'package:lensai/features/kagi/data/entities/modes.dart';
import 'package:lensai/features/kagi/utils/url_builder.dart' as uri_builder;
import 'package:lensai/features/share_intent/domain/entities/shared_content.dart';
import 'package:lensai/features/user/domain/repositories/settings.dart';
import 'package:lensai/features/user/domain/providers.dart';
import 'package:lensai/presentation/widgets/failure_widget.dart';
import 'package:lensai/presentation/widgets/website_title_tile.dart';
import 'package:lensai/utils/ui_helper.dart' as ui_helper;
@@ -40,9 +40,7 @@ class WebPageDialog extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final incognitoEnabled = ref.watch(
settingsRepositoryProvider.select((value) => value.incognitoMode),
);
final incognitoEnabled = ref.watch(incognitoModeEnabledProvider);
final availableBangsAsync = ref.watch(
bangDataListProvider(
@@ -17,7 +17,6 @@ import 'package:lensai/features/geckoview/domain/providers/web_extensions_state.
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
import 'package:lensai/features/geckoview/features/browser/domain/entities/sheet.dart';
import 'package:lensai/features/geckoview/features/browser/domain/services/create_tab.dart';
import 'package:lensai/features/geckoview/features/browser/domain/services/engine_settings.dart';
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/app_bar_title.dart';
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/browser_view.dart';
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/draggable_scrollable_header.dart';
@@ -31,7 +30,6 @@ import 'package:lensai/features/geckoview/features/readerview/presentation/widge
import 'package:lensai/features/geckoview/features/readerview/presentation/widgets/reader_button.dart';
import 'package:lensai/features/geckoview/features/tabs/features/chat/presentation/widgets/tab_qa_chat.dart';
import 'package:lensai/features/kagi/data/entities/modes.dart';
import 'package:lensai/features/user/domain/repositories/settings.dart';
import 'package:lensai/presentation/hooks/draggable_scrollable_controller.dart';
import 'package:lensai/presentation/hooks/menu_controller.dart';
import 'package:lensai/presentation/hooks/overlay_portal_controller.dart';
@@ -77,15 +75,8 @@ class BrowserScreen extends HookConsumerWidget {
},
);
ref.listen(
settingsRepositoryProvider.select((value) => value.enableJavascript),
(previous, next) async {
await ref.read(engineSettingsServiceProvider).javaScriptEnabled(next);
},
);
// ref.listen(
// settingsRepositoryProvider
// generalSettingsRepositoryProvider
// .select((value) => value.valueOrNull?.kagiSession),
// (previous, next) async {
// if (next != null && next.isNotEmpty) {
@@ -598,7 +589,7 @@ class BrowserScreen extends HookConsumerWidget {
MenuItemButton(
onPressed: () async {
await context
.push(BrowserHardeningRoute().location);
.push(WebEngineHardeningRoute().location);
},
leadingIcon: const Icon(Icons.info),
child: const Text('Pref'),
@@ -648,7 +639,8 @@ class BrowserScreen extends HookConsumerWidget {
: null;
final tabCount = ref.read(
tabListProvider.select((tabs) => tabs.length));
tabListProvider.select((tabs) => tabs.length),
);
//Don't do anything if a child route is active
if (GoRouterState.of(context).topRoute?.path !=
@@ -8,13 +8,21 @@ import 'package:lensai/features/geckoview/domain/providers.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
import 'package:lensai/features/geckoview/domain/providers/web_extensions_state.dart';
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
import 'package:lensai/features/geckoview/features/browser/domain/services/delete_browser_data.dart';
import 'package:lensai/features/geckoview/features/browser/domain/services/engine_settings.dart';
import 'package:lensai/features/geckoview/features/tabs/features/vector_store/domain/repositories/document.dart';
import 'package:lensai/features/user/domain/repositories/cache.dart';
import 'package:lensai/features/user/domain/repositories/general_settings.dart';
import 'package:lensai/features/user/domain/services/local_authentication.dart';
class BrowserView extends StatefulHookConsumerWidget {
final Duration screenshotPeriod;
final FutureOr<void> Function()? postInitializationStep;
const BrowserView({this.screenshotPeriod = const Duration(seconds: 10)});
const BrowserView({
this.screenshotPeriod = const Duration(seconds: 10),
this.postInitializationStep,
});
@override
ConsumerState<ConsumerStatefulWidget> createState() => _BrowserViewState();
@@ -63,6 +71,15 @@ class _BrowserViewState extends ConsumerState<BrowserView>
return GeckoView(
preInitializationStep: () async {
await ref
.read(generalSettingsRepositoryProvider.notifier)
.fetch()
.then((settings) {
ref
.read(deleteBrowserDataServiceProvider.notifier)
.deleteData(settings.deleteBrowsingDataOnQuit);
});
await ref
.read(eventServiceProvider)
.viewReadyStateEvents
@@ -77,6 +94,9 @@ class _BrowserViewState extends ConsumerState<BrowserView>
},
);
},
postInitializationStep: () async {
await widget.postInitializationStep?.call();
},
);
}
@@ -109,6 +129,11 @@ class _BrowserViewState extends ConsumerState<BrowserView>
cacheRepositoryProvider,
(previous, next) {},
);
ref.listenManual(
engineSettingsReplicationServiceProvider,
(previous, next) {},
);
}
@override
@@ -124,6 +149,10 @@ class _BrowserViewState extends ConsumerState<BrowserView>
_periodicScreenshotUpdate?.cancel();
_timerPaused = true;
}
ref
.read(localAuthenticationServiceProvider.notifier)
.evictCacheOnBackground();
case AppLifecycleState.resumed:
if (_timerPaused) {
_periodicScreenshotUpdate =
@@ -13,7 +13,7 @@ class LandingAction extends HookConsumerWidget {
final sessionTokenAvailable = false;
// ref.watch(
// settingsRepositoryProvider.select(
// generalSettingsRepositoryProvider.select(
// (value) => value.valueOrNull?.kagiSession?.isNotEmpty ?? false,
// ),
// );
@@ -2,6 +2,7 @@ import 'package:expandable_page_view/expandable_page_view.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/extensions/nullable.dart';
import 'package:lensai/features/geckoview/features/browser/domain/entities/sheet.dart';
import 'package:lensai/features/kagi/data/entities/modes.dart';
import 'package:lensai/features/kagi/presentation/tabs/summarize_tab.dart';
@@ -24,9 +25,8 @@ class CreateTabSheetWidget extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final sharedContent = useMemoized(
() => (parameter.content != null)
? SharedContent.parse(parameter.content!)
: null,
() => parameter.content
.mapNotNull((content) => SharedContent.parse(content)),
[parameter],
);
@@ -56,15 +56,15 @@ class _TabDraggable extends HookConsumerWidget {
onClose();
}
},
onDoubleTap: () {
ref.read(overlayDialogControllerProvider.notifier).show(
TabActionDialog(
initialTab: tab,
onDismiss:
ref.read(overlayDialogControllerProvider.notifier).dismiss,
),
);
},
// onDoubleTap: () {
// ref.read(overlayDialogControllerProvider.notifier).show(
// TabActionDialog(
// initialTab: tab,
// onDismiss:
// ref.read(overlayDialogControllerProvider.notifier).dismiss,
// ),
// );
// },
onDelete: () async {
await ref.read(tabRepositoryProvider.notifier).closeTab(tab.id);
},
@@ -178,8 +178,8 @@ class _Tab extends HookConsumerWidget {
return ContainerChips(
selectedContainer: selectedContainer,
onSelected: (container) {
ref
onSelected: (container) async {
await ref
.read(selectedContainerProvider.notifier)
.setContainerId(container.id);
},
@@ -40,7 +40,7 @@ class PreferenceSetting with FastEquatable {
bool get isActive => value == actualValue;
final String title;
final String? title;
final String? description;
final bool requireUserOptIn;
@@ -73,7 +73,7 @@ extension $PreferenceSettingGroupCopyWith on PreferenceSettingGroup {
abstract class _$PreferenceSettingCWProxy {
PreferenceSetting value(Object value);
PreferenceSetting title(String title);
PreferenceSetting title(String? title);
PreferenceSetting description(String? description);
@@ -91,7 +91,7 @@ abstract class _$PreferenceSettingCWProxy {
/// ````
PreferenceSetting call({
Object value,
String title,
String? title,
String? description,
Object? actualValue,
bool requireUserOptIn,
@@ -109,7 +109,7 @@ class _$PreferenceSettingCWProxyImpl implements _$PreferenceSettingCWProxy {
PreferenceSetting value(Object value) => this(value: value);
@override
PreferenceSetting title(String title) => this(title: title);
PreferenceSetting title(String? title) => this(title: title);
@override
PreferenceSetting description(String? description) =>
@@ -151,7 +151,7 @@ class _$PreferenceSettingCWProxyImpl implements _$PreferenceSettingCWProxy {
title: title == const $CopyWithPlaceholder()
? _value.title
// ignore: cast_nullable_to_non_nullable
: title as String,
: title as String?,
description: description == const $CopyWithPlaceholder()
? _value.description
// ignore: cast_nullable_to_non_nullable
@@ -186,7 +186,7 @@ extension $PreferenceSettingCopyWith on PreferenceSetting {
PreferenceSetting _$PreferenceSettingFromJson(Map<String, dynamic> json) =>
PreferenceSetting(
value: json['value'] as Object,
title: json['title'] as String,
title: json['title'] as String?,
description: json['description'] as String?,
requireUserOptIn: json['requireUserOptIn'] as bool? ?? false,
shouldBeDefault: json['shouldBeDefault'] as bool? ?? false,
@@ -5,6 +5,7 @@ import 'package:collection/collection.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:lensai/features/geckoview/features/preferences/data/models/preference_setting.dart';
import 'package:lensai/features/geckoview/features/tabs/utils/setting_groups_serializer.dart';
import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:rxdart/rxdart.dart';
@@ -12,40 +13,29 @@ import 'package:rxdart/rxdart.dart';
part 'preference_settings.g.dart';
@Riverpod(keepAlive: true)
Future<Map<String, PreferenceSettingGroup>> _preferenceSettingGroups(
Ref ref,
) async {
final content = await rootBundle
Future<Map<String, dynamic>> _preferenceSettingContent(Ref ref) async {
return await rootBundle
.loadString('assets/preferences/settings.json')
.then(jsonDecode) as Map<String, dynamic>;
}
final userContent = content['user'] as Map<String, dynamic>;
return userContent.map(
(key, value) => MapEntry(
key,
PreferenceSettingGroup(
// ignore: avoid_dynamic_calls
description: value['description'] as String?,
// ignore: avoid_dynamic_calls
settings: (value['preferences'] as Map<String, dynamic>).map(
(key, value) => MapEntry(
key,
PreferenceSetting.fromJson(value as Map<String, dynamic>),
),
),
),
),
);
@Riverpod(keepAlive: true)
Future<Map<String, PreferenceSettingGroup>> _preferenceSettingGroups(
Ref ref,
PreferencePartition partition,
) async {
final content = await ref.read(_preferenceSettingContentProvider.future);
return deserializePreferenceSettingGroups(partition, content);
}
@Riverpod(keepAlive: true)
Future<PreferenceSettingGroup> _preferenceSettingGroup(
Ref ref,
PreferencePartition partition,
String groupName,
) {
return ref.watch(
_preferenceSettingGroupsProvider.selectAsync(
_preferenceSettingGroupsProvider(partition).selectAsync(
(groups) =>
groups[groupName] ?? (throw Exception('Unknown setting group')),
),
@@ -90,13 +80,16 @@ class _PreferenceRepository extends _$PreferenceRepository {
}
@Riverpod()
class PreferenceSettingsGeneralRepository
extends _$PreferenceSettingsGeneralRepository {
late Map<String, PreferenceSettingGroup> _statelessGroups;
class UnifiedPreferenceSettingsRepository
extends _$UnifiedPreferenceSettingsRepository {
Map<String, PreferenceSettingGroup>? _statelessGroups;
Future<void> apply() async {
_statelessGroups =
await ref.read(_preferenceSettingGroupsProvider(partition).future);
final prefs = {
for (final group in _statelessGroups.values)
for (final group in _statelessGroups!.values)
...Map.fromEntries(
group.settings.entries
.where((e) => !e.value.requireUserOptIn)
@@ -108,7 +101,10 @@ class PreferenceSettingsGeneralRepository
}
Future<void> reset() async {
final prefs = _statelessGroups.values
_statelessGroups =
await ref.read(_preferenceSettingGroupsProvider(partition).future);
final prefs = _statelessGroups!.values
.map((group) => group.settings.keys)
.flattened
.toList();
@@ -117,13 +113,16 @@ class PreferenceSettingsGeneralRepository
}
@override
Stream<Map<String, PreferenceSettingGroup>> build() async* {
_statelessGroups = await ref.watch(_preferenceSettingGroupsProvider.future);
Stream<Map<String, PreferenceSettingGroup>> build(
PreferencePartition partition,
) async* {
_statelessGroups =
await ref.watch(_preferenceSettingGroupsProvider(partition).future);
final prefStream = ref.watch(_preferenceRepositoryProvider);
yield* prefStream.map(
(prefs) => _statelessGroups.map(
(prefs) => _statelessGroups!.map(
(groupName, group) => MapEntry(
groupName,
group.copyWith.settings(
@@ -143,18 +142,21 @@ class PreferenceSettingsGeneralRepository
@Riverpod()
class PreferenceSettingsGroupRepository
extends _$PreferenceSettingsGroupRepository {
late PreferenceSettingGroup _statelessSettingGroup;
PreferenceSettingGroup? _statelessSettingGroup;
Future<void> apply({List<String>? filter}) async {
_statelessSettingGroup ??= await ref
.read(_preferenceSettingGroupProvider(partition, groupName).future);
final prefs = Map.fromEntries(
filter?.map(
(e) => MapEntry(
e,
_statelessSettingGroup.settings[e]?.value ??
_statelessSettingGroup!.settings[e]?.value ??
(throw Exception('Preference not part of group')),
),
) ??
_statelessSettingGroup.settings.entries
_statelessSettingGroup!.settings.entries
.where((e) => !e.value.requireUserOptIn)
.map((e) => MapEntry(e.key, e.value.value)),
);
@@ -163,28 +165,34 @@ class PreferenceSettingsGroupRepository
}
Future<void> reset({List<String>? filter}) async {
_statelessSettingGroup ??= await ref
.read(_preferenceSettingGroupProvider(partition, groupName).future);
if (filter != null &&
filter.any(
(value) => !_statelessSettingGroup.settings.containsKey(value),
(value) => !_statelessSettingGroup!.settings.containsKey(value),
)) {
throw Exception('Preference not part of group');
}
await ref
.read(_preferenceRepositoryProvider.notifier)
.resetPrefs(filter ?? _statelessSettingGroup.settings.keys.toList());
.resetPrefs(filter ?? _statelessSettingGroup!.settings.keys.toList());
}
@override
Stream<PreferenceSettingGroup> build(String groupName) async* {
_statelessSettingGroup =
await ref.watch(_preferenceSettingGroupProvider(groupName).future);
Stream<PreferenceSettingGroup> build(
PreferencePartition partition,
String groupName,
) async* {
_statelessSettingGroup = await ref
.watch(_preferenceSettingGroupProvider(partition, groupName).future);
final prefStream = ref.watch(_preferenceRepositoryProvider);
yield* prefStream.map(
(prefs) => _statelessSettingGroup.copyWith.settings(
_statelessSettingGroup.settings.map(
(prefs) => _statelessSettingGroup!.copyWith.settings(
_statelessSettingGroup!.settings.map(
(key, value) => MapEntry(key, value.copyWith.actualValue(prefs[key])),
),
),
@@ -6,28 +6,27 @@ part of 'preference_settings.dart';
// RiverpodGenerator
// **************************************************************************
String _$preferenceSettingGroupsHash() =>
r'fa9286248cc71c88c888a518441721dc99b7e0c2';
String _$preferenceSettingContentHash() =>
r'a8a61fdf8800cb7adb365c100409ddce09c574b1';
/// See also [_preferenceSettingGroups].
@ProviderFor(_preferenceSettingGroups)
final _preferenceSettingGroupsProvider =
FutureProvider<Map<String, PreferenceSettingGroup>>.internal(
_preferenceSettingGroups,
name: r'_preferenceSettingGroupsProvider',
/// See also [_preferenceSettingContent].
@ProviderFor(_preferenceSettingContent)
final _preferenceSettingContentProvider =
FutureProvider<Map<String, dynamic>>.internal(
_preferenceSettingContent,
name: r'_preferenceSettingContentProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$preferenceSettingGroupsHash,
: _$preferenceSettingContentHash,
dependencies: null,
allTransitiveDependencies: null,
);
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef _PreferenceSettingGroupsRef
= FutureProviderRef<Map<String, PreferenceSettingGroup>>;
String _$preferenceSettingGroupHash() =>
r'960169ab9f6985a5b73a63663e671a8eac7cd776';
typedef _PreferenceSettingContentRef = FutureProviderRef<Map<String, dynamic>>;
String _$preferenceSettingGroupsHash() =>
r'd267dacf82a11568c9cf0cc864f434db35f93394';
/// Copied from Dart SDK
class _SystemHash {
@@ -50,6 +49,145 @@ class _SystemHash {
}
}
/// See also [_preferenceSettingGroups].
@ProviderFor(_preferenceSettingGroups)
const _preferenceSettingGroupsProvider = _PreferenceSettingGroupsFamily();
/// See also [_preferenceSettingGroups].
class _PreferenceSettingGroupsFamily
extends Family<AsyncValue<Map<String, PreferenceSettingGroup>>> {
/// See also [_preferenceSettingGroups].
const _PreferenceSettingGroupsFamily();
/// See also [_preferenceSettingGroups].
_PreferenceSettingGroupsProvider call(
PreferencePartition partition,
) {
return _PreferenceSettingGroupsProvider(
partition,
);
}
@override
_PreferenceSettingGroupsProvider getProviderOverride(
covariant _PreferenceSettingGroupsProvider provider,
) {
return call(
provider.partition,
);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'_preferenceSettingGroupsProvider';
}
/// See also [_preferenceSettingGroups].
class _PreferenceSettingGroupsProvider
extends FutureProvider<Map<String, PreferenceSettingGroup>> {
/// See also [_preferenceSettingGroups].
_PreferenceSettingGroupsProvider(
PreferencePartition partition,
) : this._internal(
(ref) => _preferenceSettingGroups(
ref as _PreferenceSettingGroupsRef,
partition,
),
from: _preferenceSettingGroupsProvider,
name: r'_preferenceSettingGroupsProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$preferenceSettingGroupsHash,
dependencies: _PreferenceSettingGroupsFamily._dependencies,
allTransitiveDependencies:
_PreferenceSettingGroupsFamily._allTransitiveDependencies,
partition: partition,
);
_PreferenceSettingGroupsProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.partition,
}) : super.internal();
final PreferencePartition partition;
@override
Override overrideWith(
FutureOr<Map<String, PreferenceSettingGroup>> Function(
_PreferenceSettingGroupsRef provider)
create,
) {
return ProviderOverride(
origin: this,
override: _PreferenceSettingGroupsProvider._internal(
(ref) => create(ref as _PreferenceSettingGroupsRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
partition: partition,
),
);
}
@override
FutureProviderElement<Map<String, PreferenceSettingGroup>> createElement() {
return _PreferenceSettingGroupsProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is _PreferenceSettingGroupsProvider &&
other.partition == partition;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, partition.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin _PreferenceSettingGroupsRef
on FutureProviderRef<Map<String, PreferenceSettingGroup>> {
/// The parameter `partition` of this provider.
PreferencePartition get partition;
}
class _PreferenceSettingGroupsProviderElement
extends FutureProviderElement<Map<String, PreferenceSettingGroup>>
with _PreferenceSettingGroupsRef {
_PreferenceSettingGroupsProviderElement(super.provider);
@override
PreferencePartition get partition =>
(origin as _PreferenceSettingGroupsProvider).partition;
}
String _$preferenceSettingGroupHash() =>
r'64d80a37928d7e5aea7d3d567dde8ade7292cd5f';
/// See also [_preferenceSettingGroup].
@ProviderFor(_preferenceSettingGroup)
const _preferenceSettingGroupProvider = _PreferenceSettingGroupFamily();
@@ -62,9 +200,11 @@ class _PreferenceSettingGroupFamily
/// See also [_preferenceSettingGroup].
_PreferenceSettingGroupProvider call(
PreferencePartition partition,
String groupName,
) {
return _PreferenceSettingGroupProvider(
partition,
groupName,
);
}
@@ -74,6 +214,7 @@ class _PreferenceSettingGroupFamily
covariant _PreferenceSettingGroupProvider provider,
) {
return call(
provider.partition,
provider.groupName,
);
}
@@ -98,10 +239,12 @@ class _PreferenceSettingGroupProvider
extends FutureProvider<PreferenceSettingGroup> {
/// See also [_preferenceSettingGroup].
_PreferenceSettingGroupProvider(
PreferencePartition partition,
String groupName,
) : this._internal(
(ref) => _preferenceSettingGroup(
ref as _PreferenceSettingGroupRef,
partition,
groupName,
),
from: _preferenceSettingGroupProvider,
@@ -113,6 +256,7 @@ class _PreferenceSettingGroupProvider
dependencies: _PreferenceSettingGroupFamily._dependencies,
allTransitiveDependencies:
_PreferenceSettingGroupFamily._allTransitiveDependencies,
partition: partition,
groupName: groupName,
);
@@ -123,9 +267,11 @@ class _PreferenceSettingGroupProvider
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.partition,
required this.groupName,
}) : super.internal();
final PreferencePartition partition;
final String groupName;
@override
@@ -143,6 +289,7 @@ class _PreferenceSettingGroupProvider
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
partition: partition,
groupName: groupName,
),
);
@@ -156,12 +303,14 @@ class _PreferenceSettingGroupProvider
@override
bool operator ==(Object other) {
return other is _PreferenceSettingGroupProvider &&
other.partition == partition &&
other.groupName == groupName;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, partition.hashCode);
hash = _SystemHash.combine(hash, groupName.hashCode);
return _SystemHash.finish(hash);
@@ -171,6 +320,9 @@ class _PreferenceSettingGroupProvider
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin _PreferenceSettingGroupRef on FutureProviderRef<PreferenceSettingGroup> {
/// The parameter `partition` of this provider.
PreferencePartition get partition;
/// The parameter `groupName` of this provider.
String get groupName;
}
@@ -180,6 +332,9 @@ class _PreferenceSettingGroupProviderElement
with _PreferenceSettingGroupRef {
_PreferenceSettingGroupProviderElement(super.provider);
@override
PreferencePartition get partition =>
(origin as _PreferenceSettingGroupProvider).partition;
@override
String get groupName => (origin as _PreferenceSettingGroupProvider).groupName;
}
@@ -202,33 +357,174 @@ final _preferenceRepositoryProvider = AutoDisposeNotifierProvider<
typedef _$PreferenceRepository
= AutoDisposeNotifier<Raw<Stream<Map<String, Object>>>>;
String _$preferenceSettingsGeneralRepositoryHash() =>
r'24a70de8284107bb43778e1014abe41bd805987a';
String _$unifiedPreferenceSettingsRepositoryHash() =>
r'35aa16ffa7aa1453c8331ed7470ef85a68d27178';
/// See also [PreferenceSettingsGeneralRepository].
@ProviderFor(PreferenceSettingsGeneralRepository)
final preferenceSettingsGeneralRepositoryProvider =
AutoDisposeStreamNotifierProvider<PreferenceSettingsGeneralRepository,
Map<String, PreferenceSettingGroup>>.internal(
PreferenceSettingsGeneralRepository.new,
name: r'preferenceSettingsGeneralRepositoryProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$preferenceSettingsGeneralRepositoryHash,
dependencies: null,
allTransitiveDependencies: null,
);
abstract class _$UnifiedPreferenceSettingsRepository
extends BuildlessAutoDisposeStreamNotifier<
Map<String, PreferenceSettingGroup>> {
late final PreferencePartition partition;
Stream<Map<String, PreferenceSettingGroup>> build(
PreferencePartition partition,
);
}
/// See also [UnifiedPreferenceSettingsRepository].
@ProviderFor(UnifiedPreferenceSettingsRepository)
const unifiedPreferenceSettingsRepositoryProvider =
UnifiedPreferenceSettingsRepositoryFamily();
/// See also [UnifiedPreferenceSettingsRepository].
class UnifiedPreferenceSettingsRepositoryFamily
extends Family<AsyncValue<Map<String, PreferenceSettingGroup>>> {
/// See also [UnifiedPreferenceSettingsRepository].
const UnifiedPreferenceSettingsRepositoryFamily();
/// See also [UnifiedPreferenceSettingsRepository].
UnifiedPreferenceSettingsRepositoryProvider call(
PreferencePartition partition,
) {
return UnifiedPreferenceSettingsRepositoryProvider(
partition,
);
}
@override
UnifiedPreferenceSettingsRepositoryProvider getProviderOverride(
covariant UnifiedPreferenceSettingsRepositoryProvider provider,
) {
return call(
provider.partition,
);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'unifiedPreferenceSettingsRepositoryProvider';
}
/// See also [UnifiedPreferenceSettingsRepository].
class UnifiedPreferenceSettingsRepositoryProvider
extends AutoDisposeStreamNotifierProviderImpl<
UnifiedPreferenceSettingsRepository,
Map<String, PreferenceSettingGroup>> {
/// See also [UnifiedPreferenceSettingsRepository].
UnifiedPreferenceSettingsRepositoryProvider(
PreferencePartition partition,
) : this._internal(
() => UnifiedPreferenceSettingsRepository()..partition = partition,
from: unifiedPreferenceSettingsRepositoryProvider,
name: r'unifiedPreferenceSettingsRepositoryProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$unifiedPreferenceSettingsRepositoryHash,
dependencies: UnifiedPreferenceSettingsRepositoryFamily._dependencies,
allTransitiveDependencies: UnifiedPreferenceSettingsRepositoryFamily
._allTransitiveDependencies,
partition: partition,
);
UnifiedPreferenceSettingsRepositoryProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.partition,
}) : super.internal();
final PreferencePartition partition;
@override
Stream<Map<String, PreferenceSettingGroup>> runNotifierBuild(
covariant UnifiedPreferenceSettingsRepository notifier,
) {
return notifier.build(
partition,
);
}
@override
Override overrideWith(UnifiedPreferenceSettingsRepository Function() create) {
return ProviderOverride(
origin: this,
override: UnifiedPreferenceSettingsRepositoryProvider._internal(
() => create()..partition = partition,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
partition: partition,
),
);
}
@override
AutoDisposeStreamNotifierProviderElement<UnifiedPreferenceSettingsRepository,
Map<String, PreferenceSettingGroup>> createElement() {
return _UnifiedPreferenceSettingsRepositoryProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is UnifiedPreferenceSettingsRepositoryProvider &&
other.partition == partition;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, partition.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin UnifiedPreferenceSettingsRepositoryRef
on AutoDisposeStreamNotifierProviderRef<
Map<String, PreferenceSettingGroup>> {
/// The parameter `partition` of this provider.
PreferencePartition get partition;
}
class _UnifiedPreferenceSettingsRepositoryProviderElement
extends AutoDisposeStreamNotifierProviderElement<
UnifiedPreferenceSettingsRepository,
Map<String, PreferenceSettingGroup>>
with UnifiedPreferenceSettingsRepositoryRef {
_UnifiedPreferenceSettingsRepositoryProviderElement(super.provider);
@override
PreferencePartition get partition =>
(origin as UnifiedPreferenceSettingsRepositoryProvider).partition;
}
typedef _$PreferenceSettingsGeneralRepository
= AutoDisposeStreamNotifier<Map<String, PreferenceSettingGroup>>;
String _$preferenceSettingsGroupRepositoryHash() =>
r'023794294c5639ac6c6941f87a4ed91e1551fbc9';
r'60f416ffe1cde4d535fb5b643a80aead20836bae';
abstract class _$PreferenceSettingsGroupRepository
extends BuildlessAutoDisposeStreamNotifier<PreferenceSettingGroup> {
late final PreferencePartition partition;
late final String groupName;
Stream<PreferenceSettingGroup> build(
PreferencePartition partition,
String groupName,
);
}
@@ -246,9 +542,11 @@ class PreferenceSettingsGroupRepositoryFamily
/// See also [PreferenceSettingsGroupRepository].
PreferenceSettingsGroupRepositoryProvider call(
PreferencePartition partition,
String groupName,
) {
return PreferenceSettingsGroupRepositoryProvider(
partition,
groupName,
);
}
@@ -258,6 +556,7 @@ class PreferenceSettingsGroupRepositoryFamily
covariant PreferenceSettingsGroupRepositoryProvider provider,
) {
return call(
provider.partition,
provider.groupName,
);
}
@@ -283,9 +582,12 @@ class PreferenceSettingsGroupRepositoryProvider
PreferenceSettingsGroupRepository, PreferenceSettingGroup> {
/// See also [PreferenceSettingsGroupRepository].
PreferenceSettingsGroupRepositoryProvider(
PreferencePartition partition,
String groupName,
) : this._internal(
() => PreferenceSettingsGroupRepository()..groupName = groupName,
() => PreferenceSettingsGroupRepository()
..partition = partition
..groupName = groupName,
from: preferenceSettingsGroupRepositoryProvider,
name: r'preferenceSettingsGroupRepositoryProvider',
debugGetCreateSourceHash:
@@ -295,6 +597,7 @@ class PreferenceSettingsGroupRepositoryProvider
dependencies: PreferenceSettingsGroupRepositoryFamily._dependencies,
allTransitiveDependencies: PreferenceSettingsGroupRepositoryFamily
._allTransitiveDependencies,
partition: partition,
groupName: groupName,
);
@@ -305,9 +608,11 @@ class PreferenceSettingsGroupRepositoryProvider
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.partition,
required this.groupName,
}) : super.internal();
final PreferencePartition partition;
final String groupName;
@override
@@ -315,6 +620,7 @@ class PreferenceSettingsGroupRepositoryProvider
covariant PreferenceSettingsGroupRepository notifier,
) {
return notifier.build(
partition,
groupName,
);
}
@@ -324,12 +630,15 @@ class PreferenceSettingsGroupRepositoryProvider
return ProviderOverride(
origin: this,
override: PreferenceSettingsGroupRepositoryProvider._internal(
() => create()..groupName = groupName,
() => create()
..partition = partition
..groupName = groupName,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
partition: partition,
groupName: groupName,
),
);
@@ -344,12 +653,14 @@ class PreferenceSettingsGroupRepositoryProvider
@override
bool operator ==(Object other) {
return other is PreferenceSettingsGroupRepositoryProvider &&
other.partition == partition &&
other.groupName == groupName;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, partition.hashCode);
hash = _SystemHash.combine(hash, groupName.hashCode);
return _SystemHash.finish(hash);
@@ -360,6 +671,9 @@ class PreferenceSettingsGroupRepositoryProvider
// ignore: unused_element
mixin PreferenceSettingsGroupRepositoryRef
on AutoDisposeStreamNotifierProviderRef<PreferenceSettingGroup> {
/// The parameter `partition` of this provider.
PreferencePartition get partition;
/// The parameter `groupName` of this provider.
String get groupName;
}
@@ -370,6 +684,9 @@ class _PreferenceSettingsGroupRepositoryProviderElement
PreferenceSettingGroup> with PreferenceSettingsGroupRepositoryRef {
_PreferenceSettingsGroupRepositoryProviderElement(super.provider);
@override
PreferencePartition get partition =>
(origin as PreferenceSettingsGroupRepositoryProvider).partition;
@override
String get groupName =>
(origin as PreferenceSettingsGroupRepositoryProvider).groupName;
@@ -5,7 +5,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/features/geckoview/domain/entities/readerable_state.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
import 'package:lensai/features/geckoview/features/readerview/presentation/controllers/readerable.dart';
import 'package:lensai/features/user/domain/repositories/settings.dart';
import 'package:lensai/features/user/domain/repositories/general_settings.dart';
import 'package:lensai/presentation/widgets/animate_gradient_shader.dart';
class ReaderButton extends HookConsumerWidget {
@@ -16,7 +16,8 @@ class ReaderButton extends HookConsumerWidget {
final readerChanging = ref.watch(readerableScreenControllerProvider);
final enableReadability = ref.watch(
settingsRepositoryProvider.select((value) => value.enableReadability),
generalSettingsRepositoryProvider
.select((value) => value.enableReadability),
);
final readerabilityState = ref.watch(
@@ -0,0 +1,20 @@
import 'dart:convert';
import 'package:drift/drift.dart';
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
class ContainerMetadataConverter
implements TypeConverter<ContainerMetadata, String> {
const ContainerMetadataConverter();
@override
ContainerMetadata fromSql(String fromDb) {
final json = jsonDecode(fromDb) as Map<String, dynamic>;
return ContainerMetadata.fromJson(json);
}
@override
String toSql(ContainerMetadata value) {
return jsonEncode(value.toJson());
}
}
@@ -1,7 +1,6 @@
import 'dart:ui';
import 'package:drift/drift.dart';
import 'package:lensai/core/uuid.dart';
import 'package:lensai/features/geckoview/features/tabs/data/database/database.dart';
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
@@ -12,26 +11,24 @@ class ContainerDao extends DatabaseAccessor<TabDatabase>
with _$ContainerDaoMixin {
ContainerDao(super.db);
Future<void> addContainer({String? name, required Color color}) {
Future<void> addContainer(ContainerData container) {
return db.container.insertOne(
ContainerCompanion.insert(
id: uuid.v7(),
name: Value(name),
color: color,
id: container.id,
name: Value(container.name),
color: container.color,
metadata: Value(container.metadata),
),
);
}
Future<void> replaceContainer(
String id, {
required String? name,
required Color color,
}) {
Future<void> replaceContainer(ContainerData container) {
return db.container.replaceOne(
ContainerCompanion(
id: Value(id),
name: Value(name),
color: Value(color),
id: Value(container.id),
name: Value(container.name),
color: Value(container.color),
metadata: Value(container.metadata),
),
);
}
@@ -41,7 +41,7 @@ class TabDao extends DatabaseAccessor<TabDatabase> with _$TabDaoMixin {
return query.map((row) => row.read(db.tab.id)!);
}
SingleSelectable<String?> tabContainerId(String tabId) {
SingleOrNullSelectable<String?> tabContainerId(String tabId) {
final query = selectOnly(db.tab)
..addColumns([db.tab.containerId])
..where(db.tab.id.equals(tabId));
@@ -49,7 +49,41 @@ class TabDao extends DatabaseAccessor<TabDatabase> with _$TabDaoMixin {
return query.map((row) => row.read(db.tab.containerId));
}
Future<String> upsertTab(
Future<String> upsertContainerTabTransactional(
Future<String> Function() createTab, {
Value<String?> containerId = const Value.absent(),
Value<String?> orderKey = const Value.absent(),
}) {
return db.transaction(() async {
final tabId = await createTab();
final currentOrderKey = orderKey.value ??
await db.containerDao
.generateLeadingOrderKey(containerId.value)
.getSingle();
await db.tab.insertOne(
TabCompanion.insert(
id: tabId,
timestamp: DateTime.now(),
containerId: containerId,
orderKey: currentOrderKey,
),
onConflict: DoUpdate(
(old) => TabCompanion.custom(
containerId:
(containerId.present) ? Variable(containerId.value) : null,
orderKey: (orderKey.present) ? Variable(orderKey.value) : null,
),
),
);
return tabId;
});
}
//Upsert an tab only if there is no container assigned yet
Future<String> upsertUnassignedTab(
String tabId, {
Value<String?> containerId = const Value.absent(),
Value<String?> orderKey = const Value.absent(),
@@ -73,6 +107,7 @@ class TabDao extends DatabaseAccessor<TabDatabase> with _$TabDaoMixin {
(containerId.present) ? Variable(containerId.value) : null,
orderKey: (orderKey.present) ? Variable(orderKey.value) : null,
),
where: (old) => old.containerId.isNull(),
),
);
@@ -1,7 +1,7 @@
import 'package:drift/drift.dart';
import 'package:flutter/widgets.dart' show Color, IconData;
import 'package:flutter/widgets.dart' show Color;
import 'package:lensai/data/database/converters/color.dart';
import 'package:lensai/data/database/converters/icon_data.dart';
import 'package:lensai/features/geckoview/features/tabs/data/database/converters/container_metadata_converter.dart';
import 'package:lensai/features/geckoview/features/tabs/data/database/daos/container.dart';
import 'package:lensai/features/geckoview/features/tabs/data/database/daos/tab.dart';
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
@@ -1,14 +1,13 @@
import 'package:lensai/data/database/converters/color.dart';
import 'package:lensai/data/database/converters/icon_data.dart';
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
import 'package:lensai/features/geckoview/features/tabs/data/models/tab_query_result.dart';
import 'package:lensai/features/geckoview/features/tabs/data/database/converters/container_metadata_converter.dart';
CREATE TABLE container (
id TEXT PRIMARY KEY NOT NULL,
contextual_identity TEXT,
name TEXT,
color INTEGER NOT NULL MAPPED BY `const ColorConverter()`,
icon TEXT MAPPED BY `const IconDataTypeConverter()`
metadata TEXT MAPPED BY `const ContainerMetadataConverter()`
) WITH ContainerData;
CREATE TABLE tab (
@@ -79,7 +78,7 @@ containersWithCount WITH ContainerDataWithCount:
FROM tab
GROUP BY container_id
) AS tab_agg ON container.id = tab_agg.container_id
ORDER BY tab_agg.last_updated DESC NULLS FIRST;
ORDER BY tab_agg.last_updated DESC NULLS LAST;
leadingOrderKey(:container_id AS TEXT OR NULL, :bucket AS INTEGER):
SELECT lexo_rank_previous(
@@ -13,11 +13,6 @@ class Container extends Table with TableInfo<Container, ContainerData> {
type: DriftSqlType.string,
requiredDuringInsert: true,
$customConstraints: 'PRIMARY KEY NOT NULL');
late final GeneratedColumn<String> contextualIdentity =
GeneratedColumn<String>('contextual_identity', aliasedName, true,
type: DriftSqlType.string,
requiredDuringInsert: false,
$customConstraints: '');
late final GeneratedColumn<String> name = GeneratedColumn<String>(
'name', aliasedName, true,
type: DriftSqlType.string,
@@ -29,15 +24,14 @@ class Container extends Table with TableInfo<Container, ContainerData> {
requiredDuringInsert: true,
$customConstraints: 'NOT NULL')
.withConverter<Color>(Container.$convertercolor);
late final GeneratedColumnWithTypeConverter<IconData?, String> icon =
GeneratedColumn<String>('icon', aliasedName, true,
late final GeneratedColumnWithTypeConverter<ContainerMetadata?, String>
metadata = GeneratedColumn<String>('metadata', aliasedName, true,
type: DriftSqlType.string,
requiredDuringInsert: false,
$customConstraints: '')
.withConverter<IconData?>(Container.$convertericonn);
.withConverter<ContainerMetadata?>(Container.$convertermetadatan);
@override
List<GeneratedColumn> get $columns =>
[id, contextualIdentity, name, color, icon];
List<GeneratedColumn> get $columns => [id, name, color, metadata];
@override
String get aliasedName => _alias ?? actualTableName;
@override
@@ -51,14 +45,13 @@ class Container extends Table with TableInfo<Container, ContainerData> {
return ContainerData(
id: attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}id'])!,
contextualIdentity: attachedDatabase.typeMapping.read(
DriftSqlType.string, data['${effectivePrefix}contextual_identity']),
name: attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}name']),
color: Container.$convertercolor.fromSql(attachedDatabase.typeMapping
.read(DriftSqlType.int, data['${effectivePrefix}color'])!),
icon: Container.$convertericonn.fromSql(attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}icon'])),
metadata: Container.$convertermetadatan.fromSql(attachedDatabase
.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}metadata'])),
);
}
@@ -68,69 +61,62 @@ class Container extends Table with TableInfo<Container, ContainerData> {
}
static TypeConverter<Color, int> $convertercolor = const ColorConverter();
static TypeConverter<IconData, String> $convertericon =
const IconDataTypeConverter();
static TypeConverter<IconData?, String?> $convertericonn =
NullAwareTypeConverter.wrap($convertericon);
static TypeConverter<ContainerMetadata, String> $convertermetadata =
const ContainerMetadataConverter();
static TypeConverter<ContainerMetadata?, String?> $convertermetadatan =
NullAwareTypeConverter.wrap($convertermetadata);
@override
bool get dontWriteConstraints => true;
}
class ContainerCompanion extends UpdateCompanion<ContainerData> {
final Value<String> id;
final Value<String?> contextualIdentity;
final Value<String?> name;
final Value<Color> color;
final Value<IconData?> icon;
final Value<ContainerMetadata?> metadata;
final Value<int> rowid;
const ContainerCompanion({
this.id = const Value.absent(),
this.contextualIdentity = const Value.absent(),
this.name = const Value.absent(),
this.color = const Value.absent(),
this.icon = const Value.absent(),
this.metadata = const Value.absent(),
this.rowid = const Value.absent(),
});
ContainerCompanion.insert({
required String id,
this.contextualIdentity = const Value.absent(),
this.name = const Value.absent(),
required Color color,
this.icon = const Value.absent(),
this.metadata = const Value.absent(),
this.rowid = const Value.absent(),
}) : id = Value(id),
color = Value(color);
static Insertable<ContainerData> custom({
Expression<String>? id,
Expression<String>? contextualIdentity,
Expression<String>? name,
Expression<int>? color,
Expression<String>? icon,
Expression<String>? metadata,
Expression<int>? rowid,
}) {
return RawValuesInsertable({
if (id != null) 'id': id,
if (contextualIdentity != null) 'contextual_identity': contextualIdentity,
if (name != null) 'name': name,
if (color != null) 'color': color,
if (icon != null) 'icon': icon,
if (metadata != null) 'metadata': metadata,
if (rowid != null) 'rowid': rowid,
});
}
ContainerCompanion copyWith(
{Value<String>? id,
Value<String?>? contextualIdentity,
Value<String?>? name,
Value<Color>? color,
Value<IconData?>? icon,
Value<ContainerMetadata?>? metadata,
Value<int>? rowid}) {
return ContainerCompanion(
id: id ?? this.id,
contextualIdentity: contextualIdentity ?? this.contextualIdentity,
name: name ?? this.name,
color: color ?? this.color,
icon: icon ?? this.icon,
metadata: metadata ?? this.metadata,
rowid: rowid ?? this.rowid,
);
}
@@ -141,9 +127,6 @@ class ContainerCompanion extends UpdateCompanion<ContainerData> {
if (id.present) {
map['id'] = Variable<String>(id.value);
}
if (contextualIdentity.present) {
map['contextual_identity'] = Variable<String>(contextualIdentity.value);
}
if (name.present) {
map['name'] = Variable<String>(name.value);
}
@@ -151,9 +134,9 @@ class ContainerCompanion extends UpdateCompanion<ContainerData> {
map['color'] =
Variable<int>(Container.$convertercolor.toSql(color.value));
}
if (icon.present) {
map['icon'] =
Variable<String>(Container.$convertericonn.toSql(icon.value));
if (metadata.present) {
map['metadata'] =
Variable<String>(Container.$convertermetadatan.toSql(metadata.value));
}
if (rowid.present) {
map['rowid'] = Variable<int>(rowid.value);
@@ -165,10 +148,9 @@ class ContainerCompanion extends UpdateCompanion<ContainerData> {
String toString() {
return (StringBuffer('ContainerCompanion(')
..write('id: $id, ')
..write('contextualIdentity: $contextualIdentity, ')
..write('name: $name, ')
..write('color: $color, ')
..write('icon: $icon, ')
..write('metadata: $metadata, ')
..write('rowid: $rowid')
..write(')'))
.toString();
@@ -1597,18 +1579,18 @@ abstract class _$TabDatabase extends GeneratedDatabase {
Selectable<ContainerDataWithCount> containersWithCount() {
return customSelect(
'SELECT container.*, tab_agg.tab_count FROM container LEFT JOIN (SELECT container_id, COUNT(*) AS tab_count, MAX(timestamp) AS last_updated FROM tab GROUP BY container_id) AS tab_agg ON container.id = tab_agg.container_id ORDER BY tab_agg.last_updated DESC NULLS FIRST',
'SELECT container.*, tab_agg.tab_count FROM container LEFT JOIN (SELECT container_id, COUNT(*) AS tab_count, MAX(timestamp) AS last_updated FROM tab GROUP BY container_id) AS tab_agg ON container.id = tab_agg.container_id ORDER BY tab_agg.last_updated DESC NULLS LAST',
variables: [],
readsFrom: {
container,
tab,
}).map((QueryRow row) => ContainerDataWithCount(
id: row.read<String>('id'),
contextualIdentity: row.readNullable<String>('contextual_identity'),
name: row.readNullable<String>('name'),
color: Container.$convertercolor.fromSql(row.read<int>('color')),
icon: NullAwareTypeConverter.wrapFromSql(
Container.$convertericon, row.readNullable<String>('icon')),
metadata: NullAwareTypeConverter.wrapFromSql(
Container.$convertermetadata,
row.readNullable<String>('metadata')),
tabCount: row.readNullable<int>('tab_count'),
));
}
@@ -1819,18 +1801,16 @@ abstract class _$TabDatabase extends GeneratedDatabase {
typedef $ContainerCreateCompanionBuilder = ContainerCompanion Function({
required String id,
Value<String?> contextualIdentity,
Value<String?> name,
required Color color,
Value<IconData?> icon,
Value<ContainerMetadata?> metadata,
Value<int> rowid,
});
typedef $ContainerUpdateCompanionBuilder = ContainerCompanion Function({
Value<String> id,
Value<String?> contextualIdentity,
Value<String?> name,
Value<Color> color,
Value<IconData?> icon,
Value<ContainerMetadata?> metadata,
Value<int> rowid,
});
@@ -1864,10 +1844,6 @@ class $ContainerFilterComposer extends Composer<_$TabDatabase, Container> {
ColumnFilters<String> get id => $composableBuilder(
column: $table.id, builder: (column) => ColumnFilters(column));
ColumnFilters<String> get contextualIdentity => $composableBuilder(
column: $table.contextualIdentity,
builder: (column) => ColumnFilters(column));
ColumnFilters<String> get name => $composableBuilder(
column: $table.name, builder: (column) => ColumnFilters(column));
@@ -1876,9 +1852,9 @@ class $ContainerFilterComposer extends Composer<_$TabDatabase, Container> {
column: $table.color,
builder: (column) => ColumnWithTypeConverterFilters(column));
ColumnWithTypeConverterFilters<IconData?, IconData, String> get icon =>
$composableBuilder(
column: $table.icon,
ColumnWithTypeConverterFilters<ContainerMetadata?, ContainerMetadata, String>
get metadata => $composableBuilder(
column: $table.metadata,
builder: (column) => ColumnWithTypeConverterFilters(column));
Expression<bool> tabRefs(Expression<bool> Function($TabFilterComposer f) f) {
@@ -1913,18 +1889,14 @@ class $ContainerOrderingComposer extends Composer<_$TabDatabase, Container> {
ColumnOrderings<String> get id => $composableBuilder(
column: $table.id, builder: (column) => ColumnOrderings(column));
ColumnOrderings<String> get contextualIdentity => $composableBuilder(
column: $table.contextualIdentity,
builder: (column) => ColumnOrderings(column));
ColumnOrderings<String> get name => $composableBuilder(
column: $table.name, builder: (column) => ColumnOrderings(column));
ColumnOrderings<int> get color => $composableBuilder(
column: $table.color, builder: (column) => ColumnOrderings(column));
ColumnOrderings<String> get icon => $composableBuilder(
column: $table.icon, builder: (column) => ColumnOrderings(column));
ColumnOrderings<String> get metadata => $composableBuilder(
column: $table.metadata, builder: (column) => ColumnOrderings(column));
}
class $ContainerAnnotationComposer extends Composer<_$TabDatabase, Container> {
@@ -1938,17 +1910,14 @@ class $ContainerAnnotationComposer extends Composer<_$TabDatabase, Container> {
GeneratedColumn<String> get id =>
$composableBuilder(column: $table.id, builder: (column) => column);
GeneratedColumn<String> get contextualIdentity => $composableBuilder(
column: $table.contextualIdentity, builder: (column) => column);
GeneratedColumn<String> get name =>
$composableBuilder(column: $table.name, builder: (column) => column);
GeneratedColumnWithTypeConverter<Color, int> get color =>
$composableBuilder(column: $table.color, builder: (column) => column);
GeneratedColumnWithTypeConverter<IconData?, String> get icon =>
$composableBuilder(column: $table.icon, builder: (column) => column);
GeneratedColumnWithTypeConverter<ContainerMetadata?, String> get metadata =>
$composableBuilder(column: $table.metadata, builder: (column) => column);
Expression<T> tabRefs<T extends Object>(
Expression<T> Function($TabAnnotationComposer a) f) {
@@ -1996,34 +1965,30 @@ class $ContainerTableManager extends RootTableManager<
$ContainerAnnotationComposer($db: db, $table: table),
updateCompanionCallback: ({
Value<String> id = const Value.absent(),
Value<String?> contextualIdentity = const Value.absent(),
Value<String?> name = const Value.absent(),
Value<Color> color = const Value.absent(),
Value<IconData?> icon = const Value.absent(),
Value<ContainerMetadata?> metadata = const Value.absent(),
Value<int> rowid = const Value.absent(),
}) =>
ContainerCompanion(
id: id,
contextualIdentity: contextualIdentity,
name: name,
color: color,
icon: icon,
metadata: metadata,
rowid: rowid,
),
createCompanionCallback: ({
required String id,
Value<String?> contextualIdentity = const Value.absent(),
Value<String?> name = const Value.absent(),
required Color color,
Value<IconData?> icon = const Value.absent(),
Value<ContainerMetadata?> metadata = const Value.absent(),
Value<int> rowid = const Value.absent(),
}) =>
ContainerCompanion.insert(
id: id,
contextualIdentity: contextualIdentity,
name: name,
color: color,
icon: icon,
metadata: metadata,
rowid: rowid,
),
withReferenceMapper: (p0) => p0
@@ -1,20 +1,103 @@
import 'package:copy_with_extension/copy_with_extension.dart';
import 'package:fast_equatable/fast_equatable.dart';
import 'package:flutter/widgets.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:lensai/data/database/converters/icon_data.dart';
part 'container_data.g.dart';
@CopyWith()
@JsonSerializable()
class ContainerAuthSettings with FastEquatable {
final bool authenticationRequired;
final bool lockOnAppBackground;
final Duration? lockTimeout;
ContainerAuthSettings({
required this.authenticationRequired,
required this.lockOnAppBackground,
required this.lockTimeout,
});
ContainerAuthSettings.withDefaults({
bool? authenticationRequired,
bool? lockOnAppBackground,
Duration? lockTimeout,
}) : this(
authenticationRequired: authenticationRequired ?? false,
lockOnAppBackground: lockOnAppBackground ?? false,
lockTimeout: lockTimeout,
);
factory ContainerAuthSettings.fromJson(Map<String, dynamic> json) =>
_$ContainerAuthSettingsFromJson(json);
Map<String, dynamic> toJson() => _$ContainerAuthSettingsToJson(this);
@override
List<Object?> get hashParameters => [
authenticationRequired,
lockOnAppBackground,
lockTimeout,
];
@override
bool get cacheHash => true;
}
@CopyWith()
@JsonSerializable(constructor: 'withDefaults')
class ContainerMetadata with FastEquatable {
@IconDataJsonConverter()
final IconData? iconData;
final String? contextualIdentity;
final ContainerAuthSettings authSettings;
ContainerMetadata({
required this.iconData,
required this.contextualIdentity,
required this.authSettings,
});
ContainerMetadata.withDefaults({
IconData? iconData,
String? contextualIdentity,
ContainerAuthSettings? authSettings,
}) : this(
iconData: iconData,
contextualIdentity: contextualIdentity,
authSettings: authSettings ?? ContainerAuthSettings.withDefaults(),
);
factory ContainerMetadata.fromJson(Map<String, dynamic> json) =>
_$ContainerMetadataFromJson(json);
Map<String, dynamic> toJson() => _$ContainerMetadataToJson(this);
@override
List<Object?> get hashParameters => [
iconData,
contextualIdentity,
authSettings,
];
@override
bool get cacheHash => true;
}
@CopyWith()
class ContainerData with FastEquatable {
final String id;
final String? contextualIdentity;
final String? name;
final Color color;
final IconData? icon;
final ContainerMetadata metadata;
ContainerData({
required this.id,
this.contextualIdentity,
this.name,
required this.color,
this.icon,
});
ContainerMetadata? metadata,
}) : metadata = metadata ?? ContainerMetadata.withDefaults();
@override
bool get cacheHash => true;
@@ -22,10 +105,9 @@ class ContainerData with FastEquatable {
@override
List<Object?> get hashParameters => [
id,
contextualIdentity,
name,
color,
icon,
metadata,
];
}
@@ -34,10 +116,9 @@ class ContainerDataWithCount extends ContainerData {
ContainerDataWithCount({
required super.id,
super.contextualIdentity,
super.name,
required super.color,
super.icon,
super.metadata,
required this.tabCount,
});
@@ -0,0 +1,294 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'container_data.dart';
// **************************************************************************
// CopyWithGenerator
// **************************************************************************
abstract class _$ContainerAuthSettingsCWProxy {
ContainerAuthSettings authenticationRequired(bool authenticationRequired);
ContainerAuthSettings lockOnAppBackground(bool lockOnAppBackground);
ContainerAuthSettings lockTimeout(Duration? lockTimeout);
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `ContainerAuthSettings(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
///
/// Usage
/// ```dart
/// ContainerAuthSettings(...).copyWith(id: 12, name: "My name")
/// ````
ContainerAuthSettings call({
bool authenticationRequired,
bool lockOnAppBackground,
Duration? lockTimeout,
});
}
/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfContainerAuthSettings.copyWith(...)`. Additionally contains functions for specific fields e.g. `instanceOfContainerAuthSettings.copyWith.fieldName(...)`
class _$ContainerAuthSettingsCWProxyImpl
implements _$ContainerAuthSettingsCWProxy {
const _$ContainerAuthSettingsCWProxyImpl(this._value);
final ContainerAuthSettings _value;
@override
ContainerAuthSettings authenticationRequired(bool authenticationRequired) =>
this(authenticationRequired: authenticationRequired);
@override
ContainerAuthSettings lockOnAppBackground(bool lockOnAppBackground) =>
this(lockOnAppBackground: lockOnAppBackground);
@override
ContainerAuthSettings lockTimeout(Duration? lockTimeout) =>
this(lockTimeout: lockTimeout);
@override
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `ContainerAuthSettings(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
///
/// Usage
/// ```dart
/// ContainerAuthSettings(...).copyWith(id: 12, name: "My name")
/// ````
ContainerAuthSettings call({
Object? authenticationRequired = const $CopyWithPlaceholder(),
Object? lockOnAppBackground = const $CopyWithPlaceholder(),
Object? lockTimeout = const $CopyWithPlaceholder(),
}) {
return ContainerAuthSettings(
authenticationRequired:
authenticationRequired == const $CopyWithPlaceholder()
? _value.authenticationRequired
// ignore: cast_nullable_to_non_nullable
: authenticationRequired as bool,
lockOnAppBackground: lockOnAppBackground == const $CopyWithPlaceholder()
? _value.lockOnAppBackground
// ignore: cast_nullable_to_non_nullable
: lockOnAppBackground as bool,
lockTimeout: lockTimeout == const $CopyWithPlaceholder()
? _value.lockTimeout
// ignore: cast_nullable_to_non_nullable
: lockTimeout as Duration?,
);
}
}
extension $ContainerAuthSettingsCopyWith on ContainerAuthSettings {
/// Returns a callable class that can be used as follows: `instanceOfContainerAuthSettings.copyWith(...)` or like so:`instanceOfContainerAuthSettings.copyWith.fieldName(...)`.
// ignore: library_private_types_in_public_api
_$ContainerAuthSettingsCWProxy get copyWith =>
_$ContainerAuthSettingsCWProxyImpl(this);
}
abstract class _$ContainerMetadataCWProxy {
ContainerMetadata iconData(IconData? iconData);
ContainerMetadata contextualIdentity(String? contextualIdentity);
ContainerMetadata authSettings(ContainerAuthSettings authSettings);
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `ContainerMetadata(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
///
/// Usage
/// ```dart
/// ContainerMetadata(...).copyWith(id: 12, name: "My name")
/// ````
ContainerMetadata call({
IconData? iconData,
String? contextualIdentity,
ContainerAuthSettings authSettings,
});
}
/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfContainerMetadata.copyWith(...)`. Additionally contains functions for specific fields e.g. `instanceOfContainerMetadata.copyWith.fieldName(...)`
class _$ContainerMetadataCWProxyImpl implements _$ContainerMetadataCWProxy {
const _$ContainerMetadataCWProxyImpl(this._value);
final ContainerMetadata _value;
@override
ContainerMetadata iconData(IconData? iconData) => this(iconData: iconData);
@override
ContainerMetadata contextualIdentity(String? contextualIdentity) =>
this(contextualIdentity: contextualIdentity);
@override
ContainerMetadata authSettings(ContainerAuthSettings authSettings) =>
this(authSettings: authSettings);
@override
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `ContainerMetadata(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
///
/// Usage
/// ```dart
/// ContainerMetadata(...).copyWith(id: 12, name: "My name")
/// ````
ContainerMetadata call({
Object? iconData = const $CopyWithPlaceholder(),
Object? contextualIdentity = const $CopyWithPlaceholder(),
Object? authSettings = const $CopyWithPlaceholder(),
}) {
return ContainerMetadata(
iconData: iconData == const $CopyWithPlaceholder()
? _value.iconData
// ignore: cast_nullable_to_non_nullable
: iconData as IconData?,
contextualIdentity: contextualIdentity == const $CopyWithPlaceholder()
? _value.contextualIdentity
// ignore: cast_nullable_to_non_nullable
: contextualIdentity as String?,
authSettings: authSettings == const $CopyWithPlaceholder()
? _value.authSettings
// ignore: cast_nullable_to_non_nullable
: authSettings as ContainerAuthSettings,
);
}
}
extension $ContainerMetadataCopyWith on ContainerMetadata {
/// Returns a callable class that can be used as follows: `instanceOfContainerMetadata.copyWith(...)` or like so:`instanceOfContainerMetadata.copyWith.fieldName(...)`.
// ignore: library_private_types_in_public_api
_$ContainerMetadataCWProxy get copyWith =>
_$ContainerMetadataCWProxyImpl(this);
}
abstract class _$ContainerDataCWProxy {
ContainerData id(String id);
ContainerData name(String? name);
ContainerData color(Color color);
ContainerData metadata(ContainerMetadata? metadata);
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `ContainerData(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
///
/// Usage
/// ```dart
/// ContainerData(...).copyWith(id: 12, name: "My name")
/// ````
ContainerData call({
String id,
String? name,
Color color,
ContainerMetadata? metadata,
});
}
/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfContainerData.copyWith(...)`. Additionally contains functions for specific fields e.g. `instanceOfContainerData.copyWith.fieldName(...)`
class _$ContainerDataCWProxyImpl implements _$ContainerDataCWProxy {
const _$ContainerDataCWProxyImpl(this._value);
final ContainerData _value;
@override
ContainerData id(String id) => this(id: id);
@override
ContainerData name(String? name) => this(name: name);
@override
ContainerData color(Color color) => this(color: color);
@override
ContainerData metadata(ContainerMetadata? metadata) =>
this(metadata: metadata);
@override
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `ContainerData(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
///
/// Usage
/// ```dart
/// ContainerData(...).copyWith(id: 12, name: "My name")
/// ````
ContainerData call({
Object? id = const $CopyWithPlaceholder(),
Object? name = const $CopyWithPlaceholder(),
Object? color = const $CopyWithPlaceholder(),
Object? metadata = const $CopyWithPlaceholder(),
}) {
return ContainerData(
id: id == const $CopyWithPlaceholder()
? _value.id
// ignore: cast_nullable_to_non_nullable
: id as String,
name: name == const $CopyWithPlaceholder()
? _value.name
// ignore: cast_nullable_to_non_nullable
: name as String?,
color: color == const $CopyWithPlaceholder()
? _value.color
// ignore: cast_nullable_to_non_nullable
: color as Color,
metadata: metadata == const $CopyWithPlaceholder()
? _value.metadata
// ignore: cast_nullable_to_non_nullable
: metadata as ContainerMetadata?,
);
}
}
extension $ContainerDataCopyWith on ContainerData {
/// Returns a callable class that can be used as follows: `instanceOfContainerData.copyWith(...)` or like so:`instanceOfContainerData.copyWith.fieldName(...)`.
// ignore: library_private_types_in_public_api
_$ContainerDataCWProxy get copyWith => _$ContainerDataCWProxyImpl(this);
}
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
ContainerAuthSettings _$ContainerAuthSettingsFromJson(
Map<String, dynamic> json) =>
ContainerAuthSettings(
authenticationRequired: json['authenticationRequired'] as bool,
lockOnAppBackground: json['lockOnAppBackground'] as bool,
lockTimeout: json['lockTimeout'] == null
? null
: Duration(microseconds: (json['lockTimeout'] as num).toInt()),
);
Map<String, dynamic> _$ContainerAuthSettingsToJson(
ContainerAuthSettings instance) =>
<String, dynamic>{
'authenticationRequired': instance.authenticationRequired,
'lockOnAppBackground': instance.lockOnAppBackground,
'lockTimeout': instance.lockTimeout?.inMicroseconds,
};
ContainerMetadata _$ContainerMetadataFromJson(Map<String, dynamic> json) =>
ContainerMetadata.withDefaults(
iconData: _$JsonConverterFromJson<Map<String, dynamic>, IconData>(
json['iconData'], const IconDataJsonConverter().fromJson),
contextualIdentity: json['contextualIdentity'] as String?,
authSettings: json['authSettings'] == null
? null
: ContainerAuthSettings.fromJson(
json['authSettings'] as Map<String, dynamic>),
);
Map<String, dynamic> _$ContainerMetadataToJson(ContainerMetadata instance) =>
<String, dynamic>{
'iconData': _$JsonConverterToJson<Map<String, dynamic>, IconData>(
instance.iconData, const IconDataJsonConverter().toJson),
'contextualIdentity': instance.contextualIdentity,
'authSettings': instance.authSettings,
};
Value? _$JsonConverterFromJson<Json, Value>(
Object? json,
Value? Function(Json json) fromJson,
) =>
json == null ? null : fromJson(json as Json);
Json? _$JsonConverterToJson<Json, Value>(
Value? value,
Json? Function(Value value) toJson,
) =>
value == null ? null : toJson(value);
@@ -58,12 +58,6 @@ AsyncValue<List<ContainerDataWithCount>> filteredContainersWithCount(
);
}
@Riverpod()
Stream<String?> tabContainerId(Ref ref, String tabId) {
final db = ref.watch(tabDatabaseProvider);
return db.tabDao.tabContainerId(tabId).watchSingle();
}
@Riverpod()
Stream<List<String>> containerTabIds(
Ref ref,
@@ -206,136 +206,6 @@ class _FilteredContainersWithCountProviderElement
(origin as FilteredContainersWithCountProvider).searchText;
}
String _$tabContainerIdHash() => r'726ddd000d23935a2c8ae39c7957412fab16016d';
/// See also [tabContainerId].
@ProviderFor(tabContainerId)
const tabContainerIdProvider = TabContainerIdFamily();
/// See also [tabContainerId].
class TabContainerIdFamily extends Family<AsyncValue<String?>> {
/// See also [tabContainerId].
const TabContainerIdFamily();
/// See also [tabContainerId].
TabContainerIdProvider call(
String tabId,
) {
return TabContainerIdProvider(
tabId,
);
}
@override
TabContainerIdProvider getProviderOverride(
covariant TabContainerIdProvider provider,
) {
return call(
provider.tabId,
);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'tabContainerIdProvider';
}
/// See also [tabContainerId].
class TabContainerIdProvider extends AutoDisposeStreamProvider<String?> {
/// See also [tabContainerId].
TabContainerIdProvider(
String tabId,
) : this._internal(
(ref) => tabContainerId(
ref as TabContainerIdRef,
tabId,
),
from: tabContainerIdProvider,
name: r'tabContainerIdProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$tabContainerIdHash,
dependencies: TabContainerIdFamily._dependencies,
allTransitiveDependencies:
TabContainerIdFamily._allTransitiveDependencies,
tabId: tabId,
);
TabContainerIdProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.tabId,
}) : super.internal();
final String tabId;
@override
Override overrideWith(
Stream<String?> Function(TabContainerIdRef provider) create,
) {
return ProviderOverride(
origin: this,
override: TabContainerIdProvider._internal(
(ref) => create(ref as TabContainerIdRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
tabId: tabId,
),
);
}
@override
AutoDisposeStreamProviderElement<String?> createElement() {
return _TabContainerIdProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is TabContainerIdProvider && other.tabId == tabId;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, tabId.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin TabContainerIdRef on AutoDisposeStreamProviderRef<String?> {
/// The parameter `tabId` of this provider.
String get tabId;
}
class _TabContainerIdProviderElement
extends AutoDisposeStreamProviderElement<String?> with TabContainerIdRef {
_TabContainerIdProviderElement(super.provider);
@override
String get tabId => (origin as TabContainerIdProvider).tabId;
}
String _$containerTabIdsHash() => r'7545a6c500b1832bf81c0838e257dfe5e051463d';
/// See also [containerTabIds].
@@ -1,5 +1,8 @@
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
import 'package:lensai/features/geckoview/features/tabs/data/providers.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/providers.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/container.dart';
import 'package:lensai/features/user/domain/services/local_authentication.dart';
import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -7,15 +10,47 @@ part 'selected_container.g.dart';
@Riverpod(keepAlive: true)
class SelectedContainer extends _$SelectedContainer {
void setContainerId(String id) {
state = id;
Future<ContainerData?> fetchData() async {
if (state != null) {
return ref
.read(containerRepositoryProvider.notifier)
.getContainerData(state!);
}
return null;
}
void toggleContainer(String id) {
Future<void> setContainerId(String id) async {
final container = await ref
.read(containerRepositoryProvider.notifier)
.getContainerData(id);
if (container != null) {
if (container.metadata.authSettings.authenticationRequired) {
final authResult = await ref
.read(localAuthenticationServiceProvider.notifier)
.authenticate(
authKey: 'container_access::${container.id}',
localizedReason:
'Require authentication for container ${container.name ?? 'New Container'}',
settings: container.metadata.authSettings,
useAuthCache: true,
);
if (authResult) {
state = id;
}
} else {
state = id;
}
}
}
Future<void> toggleContainer(String id) async {
if (state == id) {
clearContainer();
} else {
setContainerId(id);
await setContainerId(id);
}
}
@@ -25,6 +60,17 @@ class SelectedContainer extends _$SelectedContainer {
@override
String? build() {
ref.listen(
containersWithCountProvider,
(previous, next) {
if (state != null && next.valueOrNull != null) {
if (!next.value!.any((container) => container.id == state)) {
clearContainer();
}
}
},
);
return null;
}
}
@@ -25,7 +25,7 @@ final selectedContainerDataProvider =
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef SelectedContainerDataRef = AutoDisposeStreamProviderRef<ContainerData?>;
String _$selectedContainerHash() => r'e38e86db5bd0584af9561156c26ceaea3d23aebf';
String _$selectedContainerHash() => r'753f30646c4992981ec538dcfd33fa23aa1f673d';
/// See also [SelectedContainer].
@ProviderFor(SelectedContainer)
@@ -1,54 +1,72 @@
import 'dart:ui';
import 'package:lensai/features/geckoview/features/tabs/data/database/database.dart';
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
import 'package:lensai/features/geckoview/features/tabs/data/providers.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/tab.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'container.g.dart';
@Riverpod(keepAlive: true)
class ContainerRepository extends _$ContainerRepository {
late TabDatabase _db;
@override
void build() {
_db = ref.watch(tabDatabaseProvider);
Future<void> addContainer(ContainerData container) {
return ref.read(tabDatabaseProvider).containerDao.addContainer(container);
}
Future<void> addContainer({required String? name, required Color color}) {
return _db.containerDao.addContainer(name: name, color: color);
Future<void> replaceContainer(ContainerData container) {
return ref
.read(tabDatabaseProvider)
.containerDao
.replaceContainer(container);
}
Future<void> replaceContainer({
required String id,
required String? name,
required Color color,
}) {
return _db.containerDao.replaceContainer(id, name: name, color: color);
Future<ContainerData?> getContainerData(String id) async {
return ref
.read(tabDatabaseProvider)
.containerDao
.getContainerData(id)
.getSingleOrNull();
}
Future<void> deleteContainer(String id) {
return _db.containerDao.deleteContainer(id);
Future<void> deleteContainer(String id) async {
await ref.read(tabDataRepositoryProvider.notifier).closeAllTabs(id);
return ref.read(tabDatabaseProvider).containerDao.deleteContainer(id);
}
Future<Set<Color>> getDistinctColors() {
return _db.containerDao
return ref
.read(tabDatabaseProvider)
.containerDao
.getDistinctColors()
.get()
.then((colors) => colors.toSet());
}
Future<String> getLeadingOrderKey(String? containerId) {
return _db.containerDao.generateLeadingOrderKey(containerId).getSingle();
return ref
.read(tabDatabaseProvider)
.containerDao
.generateLeadingOrderKey(containerId)
.getSingle();
}
Future<String> getTrailingOrderKey(String? containerId) {
return _db.containerDao.generateTrailingOrderKey(containerId).getSingle();
return ref
.read(tabDatabaseProvider)
.containerDao
.generateTrailingOrderKey(containerId)
.getSingle();
}
Future<String> getOrderKeyAfterTab(String tabId, String? containerId) {
return _db.containerDao
return ref
.read(tabDatabaseProvider)
.containerDao
.generateOrderKeyAfterTabId(containerId, tabId)
.getSingle();
}
@override
void build() {}
}
@@ -7,7 +7,7 @@ part of 'container.dart';
// **************************************************************************
String _$containerRepositoryHash() =>
r'89ad15861fb213d6a3fa51232fd0ff9943f51e92';
r'b965883f2279b6f0148592bbac5ff40a60798f51';
/// See also [ContainerRepository].
@ProviderFor(ContainerRepository)
@@ -1,5 +1,4 @@
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
import 'package:lensai/features/geckoview/features/tabs/data/database/database.dart';
import 'package:lensai/features/geckoview/features/tabs/data/providers.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -7,35 +6,40 @@ part 'tab.g.dart';
@Riverpod(keepAlive: true)
class TabDataRepository extends _$TabDataRepository {
late TabDatabase _db;
@override
void build() {
_db = ref.watch(tabDatabaseProvider);
}
void build() {}
Future<void> assignContainer(String tabId, String? containerId) {
return _db.tabDao.assignContainer(
tabId,
containerId: containerId,
);
return ref.read(tabDatabaseProvider).tabDao.assignContainer(
tabId,
containerId: containerId,
);
}
Future<void> assignOrderKey(String tabId, String orderKey) {
return _db.tabDao.assignOrderKey(
tabId,
orderKey: orderKey,
);
return ref.read(tabDatabaseProvider).tabDao.assignOrderKey(
tabId,
orderKey: orderKey,
);
}
Future<void> closeAllTabs(String? containerId) async {
final tabIds = await _db.tabDao.containerTabIds(containerId).get();
final tabIds = await ref
.read(tabDatabaseProvider)
.tabDao
.containerTabIds(containerId)
.get();
if (tabIds.isNotEmpty) {
await ref.read(tabRepositoryProvider.notifier).closeTabs(tabIds);
}
}
Future<String?> containerTabId(String tabId) {
return _db.tabDao.tabContainerId(tabId).getSingle();
return ref
.read(tabDatabaseProvider)
.tabDao
.tabContainerId(tabId)
.getSingleOrNull();
}
}
@@ -6,7 +6,7 @@ part of 'tab.dart';
// RiverpodGenerator
// **************************************************************************
String _$tabDataRepositoryHash() => r'c067bbbf319cd95ad869655740fdf49d13159b94';
String _$tabDataRepositoryHash() => r'30ae87fbcb37facd565cb28de605814cd7db4ce8';
/// See also [TabDataRepository].
@ProviderFor(TabDataRepository)
@@ -1,6 +1,5 @@
import 'dart:async';
import 'package:lensai/features/geckoview/features/tabs/data/database/database.dart';
import 'package:lensai/features/geckoview/features/tabs/data/models/tab_query_result.dart';
import 'package:lensai/features/geckoview/features/tabs/data/providers.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -9,8 +8,6 @@ part 'tab_search.g.dart';
@Riverpod()
class TabSearchRepository extends _$TabSearchRepository {
late TabDatabase _db;
Future<void> addQuery(
String input, {
int snippetLength = 120,
@@ -20,7 +17,9 @@ class TabSearchRepository extends _$TabSearchRepository {
}) async {
if (input.isNotEmpty) {
state = await AsyncValue.guard(
() => _db.tabDao
() => ref
.read(tabDatabaseProvider)
.tabDao
.queryTabs(
matchPrefix: matchPrefix,
matchSuffix: matchSuffix,
@@ -37,8 +36,6 @@ class TabSearchRepository extends _$TabSearchRepository {
@override
Future<List<TabQueryResult>?> build() {
_db = ref.watch(tabDatabaseProvider);
return Future.value();
}
}
@@ -7,7 +7,7 @@ part of 'tab_search.dart';
// **************************************************************************
String _$tabSearchRepositoryHash() =>
r'9bd42f89bcc3a8d94add093076737dd57383bae2';
r'57d130f50aa3feb7a31d27f9a5b54234e0e7064f';
/// See also [TabSearchRepository].
@ProviderFor(TabSearchRepository)
@@ -6,6 +6,7 @@ import 'package:langchain/langchain.dart' as langchain;
import 'package:langchain/langchain.dart';
import 'package:langchain_openai/langchain_openai.dart';
import 'package:lensai/core/models.dart';
import 'package:lensai/extensions/nullable.dart';
import 'package:lensai/features/geckoview/domain/providers.dart';
import 'package:lensai/features/geckoview/features/tabs/data/providers.dart';
import 'package:lensai/features/geckoview/features/tabs/features/vector_store/data/database/daos/vector.dart';
@@ -41,21 +42,21 @@ class DocumentRepository extends _$DocumentRepository {
chunkOverlap: chunkOverlap,
);
final splittedWithSource = (splitted != null)
? (
mainDocumentId: splitted.mainDocumentId,
parts: splitted.parts
.map(
(part) => part.copyWith(
metadata: {
...part.metadata,
'source': part.id,
},
),
)
.toList()
)
: null;
final splittedWithSource = splitted.mapNotNull(
(splitted) => (
mainDocumentId: splitted.mainDocumentId,
parts: splitted.parts
.map(
(part) => part.copyWith(
metadata: {
...part.metadata,
'source': part.id,
},
),
)
.toList()
),
);
await _vectorDao.insertDocuments(
splittedWithSource?.parts ?? [doc],
@@ -7,7 +7,7 @@ part of 'document.dart';
// **************************************************************************
String _$documentRepositoryHash() =>
r'0c93116e0f9887d452b7c493dd4da45e5bf0c2d4';
r'60e5a0c889d9659c8e0e254c59225c2041a0cb56';
/// See also [DocumentRepository].
@ProviderFor(DocumentRepository)
@@ -0,0 +1,299 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/core/uuid.dart';
import 'package:lensai/extensions/nullable.dart';
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/container.dart';
import 'package:lensai/features/geckoview/features/tabs/presentation/widgets/color_picker_dialog.dart';
import 'package:lensai/features/user/domain/services/local_authentication.dart';
enum _DialogMode { create, edit }
const _timeoutOptions = <DropdownMenuItem<Duration?>>[
DropdownMenuItem(
child: Text('Immediately'),
),
DropdownMenuItem(
value: Duration(minutes: 1),
child: Text('1 minute'),
),
DropdownMenuItem(
value: Duration(minutes: 5),
child: Text('5 minutes'),
),
DropdownMenuItem(
value: Duration(minutes: 15),
child: Text('15 minutes'),
),
DropdownMenuItem(
value: Duration(hours: 1),
child: Text('1 hour'),
),
];
class ContainerEditScreen extends HookConsumerWidget {
final _DialogMode _mode;
final ContainerData initialContainer;
const ContainerEditScreen._({
required _DialogMode mode,
required this.initialContainer,
}) : _mode = mode;
factory ContainerEditScreen.create({
required ContainerData initialContainer,
}) {
return ContainerEditScreen._(
mode: _DialogMode.create,
initialContainer: initialContainer,
);
}
factory ContainerEditScreen.edit({required ContainerData initialContainer}) {
return ContainerEditScreen._(
mode: _DialogMode.edit,
initialContainer: initialContainer,
);
}
@override
Widget build(BuildContext context, WidgetRef ref) {
final selectedColor = useState(initialContainer.color);
final contextualIdentity =
useState(initialContainer.metadata.contextualIdentity);
final authSettings = useState(initialContainer.metadata.authSettings);
final textController =
useTextEditingController(text: initialContainer.name);
return Scaffold(
appBar: AppBar(
title: Text(
switch (_mode) {
_DialogMode.create => 'New Container',
_DialogMode.edit => 'Edit Container',
},
),
actions: [
IconButton(
onPressed: () async {
final name = textController.text.trim();
final container = initialContainer.copyWith(
name: name.isNotEmpty ? name : null,
color: selectedColor.value,
metadata: initialContainer.metadata.mapNotNull(
(metadata) => metadata.copyWith(
contextualIdentity: contextualIdentity.value,
authSettings: authSettings.value,
),
) ??
ContainerMetadata(
iconData: null,
contextualIdentity: contextualIdentity.value,
authSettings: authSettings.value,
),
);
//Check for permissions, when auth is set or getting set
if (initialContainer
.metadata.authSettings.authenticationRequired ||
container.metadata.authSettings.authenticationRequired) {
final authResult = await ref
.read(localAuthenticationServiceProvider.notifier)
.authenticate(
authKey: 'container_access::${container.id}',
localizedReason:
'Require authentication for container ${container.name ?? 'New Container'}',
);
if (!authResult) {
return;
}
}
switch (_mode) {
case _DialogMode.create:
await ref
.read(containerRepositoryProvider.notifier)
.addContainer(container);
case _DialogMode.edit:
await ref
.read(containerRepositoryProvider.notifier)
.replaceContainer(container);
}
if (context.mounted) {
context.pop();
}
},
icon: const Icon(Icons.check),
),
],
),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
children: [
Expanded(
child: ListView(
children: [
TextField(
decoration: InputDecoration(
prefixIcon: Padding(
padding: const EdgeInsets.all(10.0),
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
height: 24,
width: 24,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: selectedColor.value,
),
),
),
label: const Text('Name'),
),
controller: textController,
),
TextButton.icon(
label: const Text('Select Color'),
icon: const Icon(Icons.colorize),
onPressed: () async {
final color = await showDialog<Color?>(
context: context,
builder: (context) =>
ColorPickerDialog(selectedColor.value),
);
if (color != null) {
selectedColor.value = color;
}
},
),
SwitchListTile.adaptive(
value: contextualIdentity.value != null,
title: const Text('Cookie Isolation'),
secondary: const Icon(MdiIcons.cookieLock),
contentPadding: EdgeInsets.zero,
onChanged: (_mode == _DialogMode.create)
? (value) {
contextualIdentity.value = value
? initialContainer
.metadata.contextualIdentity ??
uuid.v4()
: null;
}
: null,
),
SwitchListTile.adaptive(
value: authSettings.value.authenticationRequired,
title: const Text('Require Authentication'),
secondary: const Icon(MdiIcons.fingerprint),
contentPadding: EdgeInsets.zero,
onChanged: (value) {
authSettings.value = authSettings.value.copyWith
.authenticationRequired(value);
},
),
if (authSettings.value.authenticationRequired)
CheckboxListTile.adaptive(
value: authSettings.value.lockOnAppBackground,
title: const Text(
'Auto-lock on background',
),
controlAffinity: ListTileControlAffinity.leading,
onChanged: (value) {
authSettings.value = authSettings.value.copyWith
.lockOnAppBackground(value!);
},
),
if (authSettings.value.authenticationRequired)
CheckboxListTile.adaptive(
value: authSettings.value.lockTimeout != null,
title: const Text(
'Timeout',
),
controlAffinity: ListTileControlAffinity.leading,
onChanged: (value) {
final newValue =
value! ? _timeoutOptions[1].value : null;
authSettings.value =
authSettings.value.copyWith.lockTimeout(newValue);
},
secondary: DropdownButton(
value: authSettings.value.lockTimeout,
items: _timeoutOptions,
onChanged: (value) {
authSettings.value =
authSettings.value.copyWith.lockTimeout(value);
},
),
),
],
),
),
if (_mode == _DialogMode.edit)
SizedBox(
width: double.infinity,
child: OutlinedButton.icon(
style: OutlinedButton.styleFrom(
side: BorderSide(
color: Theme.of(context).colorScheme.error,
),
foregroundColor: Theme.of(context).colorScheme.error,
iconColor: Theme.of(context).colorScheme.error,
),
label: const Text('Delete'),
icon: const Icon(Icons.delete),
onPressed: () async {
final result = await showDialog<bool?>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Delete Container'),
content: const Text(
'Are you sure you want to delete this container and close all attached tabs?',
),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.pop(context, false);
},
child: const Text('Cancel'),
),
TextButton(
onPressed: () {
Navigator.pop(context, true);
},
child: const Text('Delete'),
),
],
);
},
);
if (result == true) {
await ref
.read(containerRepositoryProvider.notifier)
.deleteContainer(initialContainer.id);
if (context.mounted) {
context.pop();
}
}
},
),
),
],
),
),
),
);
}
}
@@ -1,119 +1,17 @@
import 'package:fading_scroll/fading_scroll.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/core/routing/routes.dart';
import 'package:lensai/core/uuid.dart';
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/providers.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/providers/selected_container.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/container.dart';
import 'package:lensai/features/geckoview/features/tabs/presentation/widgets/container_dialog.dart';
import 'package:lensai/features/geckoview/features/tabs/presentation/widgets/container_list_tile.dart';
import 'package:skeletonizer/skeletonizer.dart';
class _ContainerTile extends HookWidget {
final ContainerData container;
final bool isSelected;
final void Function(ContainerResult edited) onEdit;
final void Function() onDelete;
final void Function() onTap;
const _ContainerTile(
this.container, {
required this.isSelected,
required this.onEdit,
required this.onDelete,
required this.onTap,
super.key,
});
@override
Widget build(BuildContext context) {
final menuController = useMemoized(() => MenuController());
return ListTile(
selected: isSelected,
leading: CircleAvatar(backgroundColor: container.color),
title: Text(container.name ?? 'New Container'),
trailing: MenuAnchor(
controller: menuController,
builder: (context, controller, child) {
return Padding(
padding: const EdgeInsets.only(right: 4.0),
child: InkWell(
onTap: () {
if (controller.isOpen) {
controller.close();
} else {
controller.open();
}
},
child: const Padding(
padding: EdgeInsets.symmetric(vertical: 15.0, horizontal: 8.0),
child: Icon(Icons.more_vert),
),
),
);
},
menuChildren: [
MenuItemButton(
onPressed: () async {
final result = await showDialog<ContainerResult?>(
context: context,
builder: (context) => ContainerDialog.edit(
name: container.name,
initialColor: container.color,
),
);
if (result != null) {
onEdit(result);
}
},
leadingIcon: const Icon(Icons.edit),
child: const Text('Edit'),
),
MenuItemButton(
onPressed: () async {
final result = await showDialog<bool?>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Delete Container'),
content: const Text(
'Are you sure you want to delete this container and close all attached tabs?',
),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.pop(context, false);
},
child: const Text('Cancel'),
),
TextButton(
onPressed: () {
Navigator.pop(context, true);
},
child: const Text('Delete'),
),
],
);
},
);
if (result == true) {
onDelete();
}
},
leadingIcon: const Icon(Icons.delete),
child: const Text('Delete'),
),
],
),
onTap: onTap,
);
}
}
class ContainerListScreen extends HookConsumerWidget {
const ContainerListScreen();
@@ -139,29 +37,36 @@ class ContainerListScreen extends HookConsumerWidget {
itemCount: containers.length,
itemBuilder: (context, index) {
final container = containers[index];
return _ContainerTile(
container,
return Slidable(
key: ValueKey(container.id),
isSelected: container.id == selectedContainer,
onEdit: (edited) async {
await ref
.read(containerRepositoryProvider.notifier)
.replaceContainer(
id: container.id,
name: edited.name,
color: edited.color,
);
},
onDelete: () async {
await ref
.read(containerRepositoryProvider.notifier)
.deleteContainer(container.id);
},
onTap: () {
ref
.read(selectedContainerProvider.notifier)
.toggleContainer(container.id);
},
endActionPane: ActionPane(
motion: const ScrollMotion(),
children: [
SlidableAction(
onPressed: (context) async {
await ref
.read(containerRepositoryProvider.notifier)
.deleteContainer(container.id);
},
backgroundColor:
Theme.of(context).colorScheme.errorContainer,
foregroundColor: Theme.of(context)
.colorScheme
.onErrorContainer,
icon: Icons.delete,
label: 'Delete',
),
],
),
child: ContainerListTile(
container,
isSelected: container.id == selectedContainer,
onTap: () async {
await ref
.read(selectedContainerProvider.notifier)
.toggleContainer(container.id);
},
),
);
},
);
@@ -170,12 +75,9 @@ class ContainerListScreen extends HookConsumerWidget {
error: (error, stackTrace) => SizedBox.shrink(),
loading: () => ListView.builder(
itemCount: 3,
itemBuilder: (context, index) => _ContainerTile(
itemBuilder: (context, index) => ContainerListTile(
ContainerData(id: 'null', color: Colors.transparent),
isSelected: false,
onEdit: (_) {},
onDelete: () {},
onTap: () {},
),
),
),
@@ -188,17 +90,15 @@ class ContainerListScreen extends HookConsumerWidget {
await ref.read(unusedRandomContainerColorProvider.future);
if (context.mounted) {
final result = await showDialog<ContainerResult?>(
context: context,
builder: (context) => ContainerDialog.create(
initialColor: initialColor,
),
final result = await context.push<ContainerData?>(
ContainerCreateRoute().location,
extra: ContainerData(id: uuid.v7(), color: initialColor),
);
if (result != null) {
await ref
.read(containerRepositoryProvider.notifier)
.addContainer(name: result.name, color: result.color);
.addContainer(result);
}
}
},
@@ -0,0 +1,48 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:lensai/features/geckoview/features/tabs/presentation/widgets/material_color_picker.dart';
class ColorPickerDialog extends HookWidget {
final Color initialColor;
const ColorPickerDialog(this.initialColor);
@override
Widget build(BuildContext context) {
final selectedColor = useState<Color>(initialColor);
return AlertDialog(
titlePadding: const EdgeInsets.fromLTRB(24.0, 24.0, 24.0, 16.0),
contentPadding: const EdgeInsets.only(
left: 20.0,
right: 20.0,
bottom: 24.0,
),
insetPadding: const EdgeInsets.symmetric(
horizontal: 20.0,
vertical: 24.0,
),
title: const Text('Select Color'),
content: MaterialPicker(
pickerColor: selectedColor.value,
onColorChanged: (value) {
selectedColor.value = value;
},
),
actions: [
TextButton(
onPressed: () {
Navigator.pop<Color?>(context);
},
child: const Text('Cancel'),
),
TextButton(
onPressed: () {
Navigator.pop<Color?>(context, selectedColor.value);
},
child: const Text('Select'),
),
],
);
}
}
@@ -5,8 +5,11 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/core/providers.dart';
import 'package:lensai/core/routing/routes.dart';
import 'package:lensai/data/models/drag_data.dart';
import 'package:lensai/extensions/nullable.dart';
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/providers.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/container.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/tab.dart';
import 'package:lensai/presentation/hooks/overlay_portal_controller.dart';
import 'package:lensai/presentation/widgets/selectable_chips.dart';
@@ -90,12 +93,37 @@ class ContainerChips extends HookConsumerWidget {
overlayController.hide();
},
onAcceptWithDetails: (details) async {
await ref
final containerId = await ref
.read(tabDataRepositoryProvider.notifier)
.assignContainer(
details.data.tabId,
container.id,
);
.containerTabId(details.data.tabId);
final containerData =
await containerId.mapNotNull(
(containerId) => ref
.read(containerRepositoryProvider.notifier)
.getContainerData(containerId),
);
if (container.metadata.contextualIdentity ==
containerData?.metadata.contextualIdentity) {
await ref
.read(tabDataRepositoryProvider.notifier)
.assignContainer(
details.data.tabId,
container.id,
);
} else {
await ref
.read(tabRepositoryProvider.notifier)
.duplicateTab(
selectTabId: details.data.tabId,
containerId: container.id,
);
await ref
.read(tabRepositoryProvider.notifier)
.closeTab(details.data.tabId);
}
dragTargetTabId.value = null;
overlayController.hide();
@@ -1,124 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:lensai/features/geckoview/features/tabs/presentation/widgets/material_color_picker.dart';
typedef ContainerResult = ({String? name, Color color});
enum _DialogMode { create, edit }
class ContainerDialog extends HookWidget {
final _DialogMode _mode;
final String? initialName;
final Color initialColor;
const ContainerDialog._({
required _DialogMode mode,
required this.initialColor,
this.initialName,
}) : _mode = mode;
factory ContainerDialog.create({required Color initialColor}) {
return ContainerDialog._(
mode: _DialogMode.create,
initialColor: initialColor,
);
}
factory ContainerDialog.edit({
required String? name,
required Color initialColor,
}) {
return ContainerDialog._(
mode: _DialogMode.edit,
initialColor: initialColor,
initialName: name,
);
}
@override
Widget build(BuildContext context) {
final selectedColor = useState<Color>(initialColor);
final textController = useTextEditingController(text: initialName);
return SimpleDialog(
titlePadding: const EdgeInsets.fromLTRB(24.0, 24.0, 24.0, 16.0),
contentPadding: const EdgeInsets.only(
left: 20.0,
right: 20.0,
bottom: 24.0,
),
insetPadding: const EdgeInsets.symmetric(
horizontal: 20.0,
vertical: 24.0,
),
title: Text(
switch (_mode) {
_DialogMode.create => 'New Container',
_DialogMode.edit => 'Edit Container',
},
),
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: TextField(
decoration: InputDecoration(
prefixIcon: Padding(
padding: const EdgeInsets.all(10.0),
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
height: 24,
width: 24,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: selectedColor.value,
),
),
),
label: const Text('Name'),
),
controller: textController,
),
),
const SizedBox(height: 16),
MaterialPicker(
pickerColor: selectedColor.value,
onColorChanged: (value) {
selectedColor.value = value;
},
),
const SizedBox(height: 24),
OverflowBar(
alignment: MainAxisAlignment.end,
spacing: 8.0,
children: [
TextButton(
onPressed: () {
Navigator.pop<ContainerResult?>(context);
},
child: const Text('Cancel'),
),
TextButton(
onPressed: () {
final name = textController.text.trim();
Navigator.pop<ContainerResult?>(
context,
(
name: name.isNotEmpty ? name : null,
color: selectedColor.value,
),
);
},
child: Text(
switch (_mode) {
_DialogMode.create => 'Add',
_DialogMode.edit => 'Edit',
},
),
),
],
),
],
);
}
}
@@ -0,0 +1,42 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart';
import 'package:lensai/core/routing/routes.dart';
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
class ContainerListTile extends HookWidget {
final ContainerData container;
final bool isSelected;
final void Function()? onTap;
const ContainerListTile(
this.container, {
required this.isSelected,
this.onTap,
super.key,
});
@override
Widget build(BuildContext context) {
return ListTileTheme(
selectedColor: Theme.of(context).colorScheme.onPrimaryContainer,
selectedTileColor: Theme.of(context).colorScheme.primaryContainer,
child: ListTile(
selected: isSelected,
leading: CircleAvatar(backgroundColor: container.color),
title: Text(container.name ?? 'New Container'),
trailing: IconButton(
onPressed: () async {
await context.push(
ContainerEditRoute().location,
extra: container,
);
},
icon: const Icon(Icons.chevron_right),
),
onTap: onTap,
),
);
}
}
@@ -0,0 +1,34 @@
import 'package:lensai/features/geckoview/features/preferences/data/models/preference_setting.dart';
enum PreferencePartition {
user('user'),
system('system');
final String key;
const PreferencePartition(this.key);
}
Map<String, PreferenceSettingGroup> deserializePreferenceSettingGroups(
PreferencePartition partition,
Map<String, dynamic> content,
) {
final parititonedContent = content[partition.key] as Map<String, dynamic>;
return parititonedContent.map(
(key, value) => MapEntry(
key,
PreferenceSettingGroup(
// ignore: avoid_dynamic_calls
description: value['description'] as String?,
// ignore: avoid_dynamic_calls
settings: (value['preferences'] as Map<String, dynamic>).map(
(key, value) => MapEntry(
key,
PreferenceSetting.fromJson(value as Map<String, dynamic>),
),
),
),
),
);
}