everything build but untested & migration not finished

This commit is contained in:
Fabian Freund
2024-10-09 13:27:42 +02:00
parent 37d2c84d7f
commit 2c71eeb670
132 changed files with 3790 additions and 2817 deletions
+6 -6
View File
@@ -8,14 +8,14 @@ import 'package:lensai/features/bangs/presentation/screens/search.dart';
import 'package:lensai/features/chat_archive/presentation/screens/detail.dart';
import 'package:lensai/features/chat_archive/presentation/screens/list.dart';
import 'package:lensai/features/chat_archive/presentation/screens/search.dart';
import 'package:lensai/features/search_browser/presentation/screens/browser.dart';
import 'package:lensai/features/settings/presentation/screens/settings.dart';
import 'package:lensai/features/geckoview/features/browser/screens/browser.dart';
import 'package:lensai/features/geckoview/features/topics/presentation/screens/topic_list.dart';
import 'package:lensai/features/settings/presentation/screens/settings.dart';
part 'routes.g.dart';
@TypedGoRoute<KagiRoute>(
name: 'KagiRoute',
@TypedGoRoute<BrowserRoute>(
name: 'BrowserRoute',
path: '/',
routes: [
TypedGoRoute<AboutRoute>(
@@ -48,10 +48,10 @@ part 'routes.g.dart';
),
],
)
class KagiRoute extends GoRouteData {
class BrowserRoute extends GoRouteData {
@override
Widget build(BuildContext context, GoRouterState state) {
return const KagiScreen();
return const BrowserScreen();
}
}
+6 -6
View File
@@ -7,15 +7,15 @@ part of 'routes.dart';
// **************************************************************************
List<RouteBase> get $appRoutes => [
$kagiRoute,
$browserRoute,
$settingsRoute,
$chatArchiveListRoute,
];
RouteBase get $kagiRoute => GoRouteData.$route(
RouteBase get $browserRoute => GoRouteData.$route(
path: '/',
name: 'KagiRoute',
factory: $KagiRouteExtension._fromState,
name: 'BrowserRoute',
factory: $BrowserRouteExtension._fromState,
routes: [
GoRouteData.$route(
path: 'about',
@@ -54,8 +54,8 @@ RouteBase get $kagiRoute => GoRouteData.$route(
],
);
extension $KagiRouteExtension on KagiRoute {
static KagiRoute _fromState(GoRouterState state) => KagiRoute();
extension $BrowserRouteExtension on BrowserRoute {
static BrowserRoute _fromState(GoRouterState state) => BrowserRoute();
String get location => GoRouteData.$location(
'/',
@@ -6,10 +6,9 @@ import 'package:lensai/features/bangs/data/models/bang.dart';
import 'package:lensai/features/bangs/domain/repositories/sync.dart';
import 'package:lensai/features/content_block/data/models/host.dart';
import 'package:lensai/features/content_block/domain/repositories/sync.dart';
import 'package:lensai/features/search_browser/domain/services/session.dart';
import 'package:lensai/features/kagi/data/services/session.dart';
import 'package:lensai/features/settings/data/models/settings.dart';
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
import 'package:lensai/features/geckoview/features/topics/data/providers.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'app_initialization.g.dart';
@@ -77,15 +76,11 @@ class AppInitializationService extends _$AppInitializationService {
),
);
if (settings.incognitoMode) {
await ref.read(sessionServiceProvider.notifier).clearAllData();
//Delete all unsasigned tabs
await ref.read(tabDatabaseProvider).tabDao.deleteTopicTabs(null);
}
if (settings.kagiSession case final String session) {
if (session.isNotEmpty) {
await ref.read(sessionServiceProvider.notifier).setKagiSession(session);
await ref
.read(kagiSessionServiceProvider.notifier)
.setKagiSession(session);
}
}
}
@@ -7,7 +7,7 @@ part of 'app_initialization.dart';
// **************************************************************************
String _$appInitializationServiceHash() =>
r'eee9c8eadc793fb435af1032ad2099dfd381a034';
r'0d7dc3eb3ed2e17643f4c34e4e172734ed8d09e2';
/// See also [AppInitializationService].
@ProviderFor(AppInitializationService)
@@ -1,7 +1,4 @@
import 'dart:ui';
import 'package:drift/drift.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:lensai/features/bangs/data/database/database.dart';
import 'package:lensai/features/bangs/data/models/bang.dart';
import 'package:lensai/features/bangs/data/models/bang_data.dart';
@@ -4,9 +4,10 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/core/routing/routes.dart';
import 'package:lensai/features/bangs/domain/providers.dart';
import 'package:lensai/features/bangs/presentation/widgets/bang_details.dart';
import 'package:lensai/features/geckoview/features/browser/domain/entities/sheet.dart';
import 'package:lensai/features/geckoview/features/browser/domain/providers.dart';
import 'package:lensai/features/geckoview/features/controllers/bottom_sheet.dart';
import 'package:lensai/features/kagi/data/entities/modes.dart';
import 'package:lensai/features/search_browser/domain/entities/sheet.dart';
import 'package:lensai/features/search_browser/domain/providers.dart';
import 'package:lensai/presentation/widgets/failure_widget.dart';
class BangListScreen extends HookConsumerWidget {
@@ -49,15 +50,16 @@ class BangListScreen extends HookConsumerWidget {
.read(selectedBangTriggerProvider().notifier)
.setTrigger(bang.trigger);
if (ref.read(bottomSheetProvider) is! CreateTabSheet) {
ref.read(bottomSheetProvider.notifier).show(
if (ref.read(bottomSheetControllerProvider)
is! CreateTabSheet) {
ref.read(bottomSheetControllerProvider.notifier).show(
CreateTabSheet(
preferredTool: KagiTool.search,
),
);
}
context.go(KagiRoute().location);
context.go(BrowserRoute().location);
},
);
},
@@ -8,9 +8,10 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/core/routing/routes.dart';
import 'package:lensai/features/bangs/domain/repositories/search.dart';
import 'package:lensai/features/bangs/presentation/widgets/bang_details.dart';
import 'package:lensai/features/geckoview/features/browser/domain/entities/sheet.dart';
import 'package:lensai/features/geckoview/features/browser/domain/providers.dart';
import 'package:lensai/features/geckoview/features/controllers/bottom_sheet.dart';
import 'package:lensai/features/kagi/data/entities/modes.dart';
import 'package:lensai/features/search_browser/domain/entities/sheet.dart';
import 'package:lensai/features/search_browser/domain/providers.dart';
import 'package:lensai/features/settings/data/models/settings.dart';
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
import 'package:lensai/presentation/hooks/listenable_callback.dart';
@@ -77,13 +78,14 @@ class BangSearchScreen extends HookConsumerWidget {
.read(selectedBangTriggerProvider().notifier)
.setTrigger(bang.trigger);
if (ref.read(bottomSheetProvider) is! CreateTabSheet) {
ref.read(bottomSheetProvider.notifier).show(
if (ref.read(bottomSheetControllerProvider)
is! CreateTabSheet) {
ref.read(bottomSheetControllerProvider.notifier).show(
CreateTabSheet(preferredTool: KagiTool.search),
);
}
context.go(KagiRoute().location);
context.go(BrowserRoute().location);
},
);
},
@@ -4,6 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/core/routing/routes.dart';
import 'package:lensai/features/bangs/data/models/bang_data.dart';
import 'package:lensai/features/bangs/presentation/widgets/bang_icon.dart';
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
import 'package:lensai/features/settings/data/models/settings.dart';
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
import 'package:lensai/utils/ui_helper.dart' as ui_helper;
@@ -82,11 +83,11 @@ class BangDetails extends HookConsumerWidget {
await ui_helper.launchUrlFeedback(context, url);
} else {
await ref
.read(switchNewTabControllerProvider.notifier)
.add(url);
.read(tabRepositoryProvider.notifier)
.addTab(url: url);
if (context.mounted) {
context.go(KagiRoute().location);
context.go(BrowserRoute().location);
}
}
},
@@ -5,8 +5,8 @@ import 'package:flutter_material_design_icons/flutter_material_design_icons.dart
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/features/bangs/data/models/bang_data.dart';
import 'package:lensai/features/bangs/presentation/widgets/bang_icon.dart';
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/speech_to_text_button.dart';
import 'package:lensai/features/kagi/domain/repositories/autosuggest.dart';
import 'package:lensai/features/search_browser/presentation/widgets/speech_to_text_button.dart';
import 'package:lensai/features/settings/data/models/settings.dart';
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
import 'package:lensai/presentation/widgets/autocomplete.dart';
@@ -6,7 +6,9 @@ import 'package:lensai/features/bangs/data/models/bang_data.dart';
import 'package:lensai/features/bangs/domain/repositories/data.dart';
import 'package:lensai/features/bangs/presentation/widgets/bang_icon.dart';
import 'package:lensai/features/bangs/presentation/widgets/search_field.dart';
import 'package:lensai/features/search_browser/domain/providers.dart';
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
import 'package:lensai/features/geckoview/features/browser/domain/providers.dart';
import 'package:lensai/features/geckoview/features/controllers/overlay_dialog.dart';
import 'package:lensai/presentation/widgets/selectable_chips.dart';
class SiteSearch extends HookConsumerWidget {
@@ -39,10 +41,10 @@ class SiteSearch extends HookConsumerWidget {
.increaseFrequency(activeBang.trigger);
await ref
.read(switchNewTabControllerProvider.notifier)
.add(activeBang.getUrl(textController.text));
.read(tabRepositoryProvider.notifier)
.addTab(url: activeBang.getUrl(textController.text));
ref.read(overlayDialogProvider.notifier).dismiss();
ref.read(overlayDialogControllerProvider.notifier).dismiss();
}
}
@@ -1,6 +1,5 @@
import 'dart:async';
import 'package:collection/collection.dart';
import 'package:lensai/core/logger.dart';
import 'package:lensai/features/chat_archive/data/database/database.dart';
import 'package:lensai/features/chat_archive/data/providers.dart';
@@ -53,7 +52,7 @@ class ChatArchiveSearchRepository extends _$ChatArchiveSearchRepository {
),
);
return availableChats.whereNotNull();
return availableChats.nonNulls;
}
Future<void> search(
@@ -7,7 +7,7 @@ part of 'search.dart';
// **************************************************************************
String _$chatArchiveSearchRepositoryHash() =>
r'6cb31ab2c4a37561ec17a60dae3bb4ecd3893744';
r'a3decc7975bf49995b20ea578db3c08b69f7c03c';
/// See also [ChatArchiveSearchRepository].
@ProviderFor(ChatArchiveSearchRepository)
@@ -10,6 +10,7 @@ import 'package:lensai/features/chat_archive/data/services/file.dart';
import 'package:lensai/features/chat_archive/domain/entities/chat_entity.dart';
import 'package:lensai/features/chat_archive/domain/repositories/archive.dart';
import 'package:lensai/features/chat_archive/utils/markdown_to_text.dart';
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
import 'package:lensai/features/settings/data/models/settings.dart';
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
import 'package:lensai/presentation/widgets/failure_widget.dart';
@@ -113,11 +114,11 @@ class ChatArchiveDetailScreen extends HookConsumerWidget {
await ui_helper.launchUrlFeedback(context, Uri.parse(href));
} else {
await ref
.read(switchNewTabControllerProvider.notifier)
.add(url);
.read(tabRepositoryProvider.notifier)
.addTab(url: url);
if (context.mounted) {
context.go(KagiRoute().location);
context.go(BrowserRoute().location);
}
}
}
@@ -2,8 +2,8 @@ import 'package:fading_scroll/fading_scroll.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/extensions/date_time.dart';
import 'package:lensai/core/routing/routes.dart';
import 'package:lensai/extensions/date_time.dart';
import 'package:lensai/features/chat_archive/domain/repositories/archive.dart';
import 'package:lensai/presentation/widgets/failure_widget.dart';
import 'package:skeletonizer/skeletonizer.dart';
@@ -6,8 +6,8 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/extensions/date_time.dart';
import 'package:lensai/core/routing/routes.dart';
import 'package:lensai/extensions/date_time.dart';
import 'package:lensai/features/chat_archive/domain/entities/chat_entity.dart';
import 'package:lensai/features/chat_archive/domain/repositories/search.dart';
import 'package:lensai/features/settings/data/models/settings.dart';
@@ -1,6 +1,5 @@
import 'dart:convert';
import 'package:collection/collection.dart';
import 'package:exceptions/exceptions.dart';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
@@ -30,10 +29,7 @@ class HostSourceService extends _$HostSourceService {
);
final matches = hostRegex.allMatches(utf8.decode(args[0]));
return matches
.map((match) => match.group(2))
.whereNotNull()
.toSet();
return matches.map((match) => match.group(2)).nonNulls.toSet();
},
[response.bodyBytes],
);
@@ -6,7 +6,7 @@ part of 'source.dart';
// RiverpodGenerator
// **************************************************************************
String _$hostSourceServiceHash() => r'26ac09fcd34cc37440eb643075984707e0e9bba3';
String _$hostSourceServiceHash() => r'6c1ee7bfde02cc542a4f55db666d49037fda187e';
/// See also [HostSourceService].
@ProviderFor(HostSourceService)
@@ -21,14 +21,14 @@ class BrowserIcon with FastEquatable {
final image =
await tryDecodeImage(bytes).then((image) => image!.toEquatable());
return BrowserIcon._(
return BrowserIcon(
image: image,
dominantColor: dominantColor,
source: source,
);
}
BrowserIcon._({
BrowserIcon({
required this.image,
required this.dominantColor,
required this.source,
@@ -0,0 +1,29 @@
import 'package:fast_equatable/fast_equatable.dart';
class FindResultState with FastEquatable {
final int activeMatchOrdinal;
final int numberOfMatches;
final bool isDoneCounting;
FindResultState({
required this.activeMatchOrdinal,
required this.numberOfMatches,
required this.isDoneCounting,
});
factory FindResultState.$default() => FindResultState(
activeMatchOrdinal: -1,
numberOfMatches: 0,
isDoneCounting: false,
);
@override
List<Object?> get hashParameters => [
activeMatchOrdinal,
numberOfMatches,
isDoneCounting,
];
@override
bool get cacheHash => true;
}
@@ -1,6 +1,10 @@
import 'package:copy_with_extension/copy_with_extension.dart';
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/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';
import 'package:lensai/features/geckoview/domain/entities/readerable_state.dart';
import 'package:lensai/features/geckoview/domain/entities/security_state.dart';
@@ -8,16 +12,28 @@ import 'package:lensai/features/geckoview/domain/entities/security_state.dart';
part 'tab_state.g.dart';
@CopyWith()
class TabState with FastEquatable {
class TabState with FastEquatable implements WebPageInfo {
@CopyWithField(immutable: true)
final String id;
final String? contextId;
@override
final Uri url;
@override
final String title;
final EquatableImage? icon;
@override
BrowserIcon? get favicon => (icon != null)
? BrowserIcon(
image: icon!,
dominantColor: null,
source: IconSource.memory,
)
: null;
final EquatableImage? thumbnail;
final int progress;
@@ -29,6 +45,7 @@ class TabState with FastEquatable {
final SecurityState securityInfoState;
final HistoryState historyState;
final ReaderableState readerableState;
final FindResultState findResultState;
TabState({
required this.id,
@@ -44,6 +61,7 @@ class TabState with FastEquatable {
required this.securityInfoState,
required this.historyState,
required this.readerableState,
required this.findResultState,
});
factory TabState.$default(String tabId) => TabState(
@@ -60,6 +78,7 @@ class TabState with FastEquatable {
securityInfoState: SecurityState.$default(),
historyState: HistoryState.$default(),
readerableState: ReaderableState.$default(),
findResultState: FindResultState.$default(),
);
@override
@@ -80,5 +99,6 @@ class TabState with FastEquatable {
securityInfoState,
historyState,
readerableState,
findResultState,
];
}
@@ -31,6 +31,8 @@ abstract class _$TabStateCWProxy {
TabState readerableState(ReaderableState readerableState);
TabState findResultState(FindResultState findResultState);
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `TabState(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
///
/// Usage
@@ -50,6 +52,7 @@ abstract class _$TabStateCWProxy {
SecurityState? securityInfoState,
HistoryState? historyState,
ReaderableState? readerableState,
FindResultState? findResultState,
});
}
@@ -98,6 +101,10 @@ class _$TabStateCWProxyImpl implements _$TabStateCWProxy {
TabState readerableState(ReaderableState readerableState) =>
this(readerableState: readerableState);
@override
TabState findResultState(FindResultState findResultState) =>
this(findResultState: findResultState);
@override
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `TabState(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
@@ -119,6 +126,7 @@ class _$TabStateCWProxyImpl implements _$TabStateCWProxy {
Object? securityInfoState = const $CopyWithPlaceholder(),
Object? historyState = const $CopyWithPlaceholder(),
Object? readerableState = const $CopyWithPlaceholder(),
Object? findResultState = const $CopyWithPlaceholder(),
}) {
return TabState(
id: _value.id,
@@ -174,6 +182,11 @@ class _$TabStateCWProxyImpl implements _$TabStateCWProxy {
? _value.readerableState
// ignore: cast_nullable_to_non_nullable
: readerableState as ReaderableState,
findResultState: findResultState == const $CopyWithPlaceholder() ||
findResultState == null
? _value.findResultState
// ignore: cast_nullable_to_non_nullable
: findResultState as FindResultState,
);
}
}
@@ -1,8 +1,5 @@
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:lensai/features/geckoview/domain/entities/tab_state.dart';
import 'package:lensai/features/geckoview/domain/repositories/selected_tab.dart';
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
import 'package:lensai/features/geckoview/domain/repositories/tab_session.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_session.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'providers.g.dart';
@@ -18,23 +15,6 @@ GeckoEventService eventService(EventServiceRef ref) {
return service;
}
@Riverpod()
TabState? tabState(TabStateRef ref, String? tabId) {
if (tabId == null) {
return null;
}
return ref.watch(
tabRepositoryProvider.select((tabs) => tabs[tabId]),
);
}
@Riverpod()
TabState? selectedTabState(SelectedTabStateRef ref) {
final tabId = ref.watch(selectedTabControllerProvider);
return ref.watch(tabStateProvider(tabId));
}
@Riverpod()
TabSession selectedTabSessionNotifier(SelectedTabSessionNotifierRef ref) {
return ref.watch(tabSessionProvider(null).notifier);
@@ -20,169 +20,6 @@ final eventServiceProvider = Provider<GeckoEventService>.internal(
);
typedef EventServiceRef = ProviderRef<GeckoEventService>;
String _$tabStateHash() => r'6985f5472625a9cf44c8bbc61e61b7577eb60e71';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
/// See also [tabState].
@ProviderFor(tabState)
const tabStateProvider = TabStateFamily();
/// See also [tabState].
class TabStateFamily extends Family<TabState?> {
/// See also [tabState].
const TabStateFamily();
/// See also [tabState].
TabStateProvider call(
String? tabId,
) {
return TabStateProvider(
tabId,
);
}
@override
TabStateProvider getProviderOverride(
covariant TabStateProvider 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'tabStateProvider';
}
/// See also [tabState].
class TabStateProvider extends AutoDisposeProvider<TabState?> {
/// See also [tabState].
TabStateProvider(
String? tabId,
) : this._internal(
(ref) => tabState(
ref as TabStateRef,
tabId,
),
from: tabStateProvider,
name: r'tabStateProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$tabStateHash,
dependencies: TabStateFamily._dependencies,
allTransitiveDependencies: TabStateFamily._allTransitiveDependencies,
tabId: tabId,
);
TabStateProvider._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(
TabState? Function(TabStateRef provider) create,
) {
return ProviderOverride(
origin: this,
override: TabStateProvider._internal(
(ref) => create(ref as TabStateRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
tabId: tabId,
),
);
}
@override
AutoDisposeProviderElement<TabState?> createElement() {
return _TabStateProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is TabStateProvider && other.tabId == tabId;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, tabId.hashCode);
return _SystemHash.finish(hash);
}
}
mixin TabStateRef on AutoDisposeProviderRef<TabState?> {
/// The parameter `tabId` of this provider.
String? get tabId;
}
class _TabStateProviderElement extends AutoDisposeProviderElement<TabState?>
with TabStateRef {
_TabStateProviderElement(super.provider);
@override
String? get tabId => (origin as TabStateProvider).tabId;
}
String _$selectedTabStateHash() => r'd5240ade9010f5c9f00d7a4f419e1b776f75fac1';
/// See also [selectedTabState].
@ProviderFor(selectedTabState)
final selectedTabStateProvider = AutoDisposeProvider<TabState?>.internal(
selectedTabState,
name: r'selectedTabStateProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$selectedTabStateHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef SelectedTabStateRef = AutoDisposeProviderRef<TabState?>;
String _$selectedTabSessionNotifierHash() =>
r'1b0fe1afa87c0b09fc8407ceb91946fffaef5214';
@@ -1,19 +1,12 @@
import 'dart:async';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:lensai/features/geckoview/domain/providers.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'selected_tab.g.dart';
@Riverpod(keepAlive: true)
class SelectedTabController extends _$SelectedTabController {
final _tabsService = GeckoTabService();
Future<void> selectTab(String tabId) {
return _tabsService.selectTab(tabId: tabId);
}
class SelectedTab extends _$SelectedTab {
@override
String? build() {
final eventSerivce = ref.watch(eventServiceProvider);
@@ -0,0 +1,24 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'selected_tab.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$selectedTabHash() => r'd30f0490c7924b7de30e23f3d4d132311bf336d0';
/// See also [SelectedTab].
@ProviderFor(SelectedTab)
final selectedTabProvider = NotifierProvider<SelectedTab, String?>.internal(
SelectedTab.new,
name: r'selectedTabProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$selectedTabHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$SelectedTab = Notifier<String?>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
@@ -19,6 +19,18 @@ class TabSession extends _$TabSession {
);
}
Future<void> reload() {
return _sessionService.reload();
}
Future<void> goBack() {
return _sessionService.goBack();
}
Future<void> goForward() {
return _sessionService.goForward();
}
@override
void build(String? tabId) {
_sessionService = (tabId != null)
@@ -6,7 +6,7 @@ part of 'tab_session.dart';
// RiverpodGenerator
// **************************************************************************
String _$tabSessionHash() => r'4aab49dcb544cfc9e7a61b498d2ccf2c28ef729b';
String _$tabSessionHash() => r'cdde9c7426178f50dc430284939e215f158e8535';
/// Copied from Dart SDK
class _SystemHash {
@@ -0,0 +1,206 @@
import 'dart:async';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:lensai/extensions/image.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';
import 'package:lensai/features/geckoview/domain/entities/security_state.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/utils/image_helper.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'tab_state.g.dart';
@Riverpod(keepAlive: true)
class TabStates extends _$TabStates {
final _tabsService = GeckoTabService();
void _onTabListChange(List<String> tabs) {
state = {
for (final tabId in tabs) tabId: state[tabId] ?? TabState.$default(tabId),
};
}
void _onTabContentStateChange(TabContentState contentState) {
final current =
state[contentState.id] ?? TabState.$default(contentState.id);
state = {...state}..[contentState.id] = current.copyWith(
contextId: contentState.contextId,
url: Uri.parse(contentState.url),
title: contentState.title,
progress: contentState.progress,
isPrivate: contentState.isPrivate,
isFullScreen: contentState.isFullScreen,
isLoading: contentState.isLoading,
);
}
Future<void> _onIconChange(IconEvent event) async {
final IconEvent(:tabId, :bytes) = event;
final current = state[tabId] ?? TabState.$default(tabId);
final image = (bytes != null)
? (await tryDecodeImage(bytes)
.then((image) async => image?.toEquatable()))
: null;
state = {...state}..[tabId] = current.copyWith.icon(image);
}
Future<void> _onThumbnailChange(ThumbnailEvent event) async {
final ThumbnailEvent(:tabId, :bytes) = event;
final current = state[tabId] ?? TabState.$default(tabId);
final image = (bytes != null)
? (await tryDecodeImage(bytes)
.then((image) async => image?.toEquatable()))
: null;
state = {...state}..[tabId] = current.copyWith.thumbnail(image);
}
void _onSecurityInfoStateChange(SecurityInfoEvent event) {
final SecurityInfoEvent(:tabId, :securityInfo) = event;
final current = state[tabId] ?? TabState.$default(tabId);
state = {...state}..[tabId] = current.copyWith.securityInfoState(
SecurityState(
secure: securityInfo.secure,
host: securityInfo.host,
issuer: securityInfo.issuer,
),
);
}
void _onHistoryStateChange(HistoryEvent event) {
final HistoryEvent(:tabId, :history) = event;
final current = state[tabId] ?? TabState.$default(tabId);
state = {...state}..[tabId] = current.copyWith.historyState(
HistoryState(
items: history.items.nonNulls
.map(
(item) =>
HistoryItem(url: Uri.parse(item.url), title: item.title),
)
.toList(),
currentIndex: history.currentIndex,
canGoBack: history.canGoBack,
canGoForward: history.canGoForward,
),
);
}
void _onReaderableStateChange(ReaderableEvent event) {
final ReaderableEvent(:tabId, :readerable) = event;
final current = state[tabId] ?? TabState.$default(tabId);
state = {...state}..[tabId] = current.copyWith.readerableState(
ReaderableState(
readerable: readerable.readerable,
active: readerable.active,
),
);
}
void _onFindResults(FindResultsEvent event) {
final FindResultsEvent(:tabId, :results) = event;
if (results.isNotEmpty) {
final current = state[tabId] ?? TabState.$default(tabId);
final result = results.last;
state = {...state}..[tabId] = current.copyWith.findResultState(
FindResultState(
activeMatchOrdinal: result.activeMatchOrdinal,
numberOfMatches: result.numberOfMatches,
isDoneCounting: result.isDoneCounting,
),
);
}
}
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,
);
}
Future<void> closeTabs(List<String> tabIds) {
return _tabsService.removeTabs(ids: tabIds);
}
Future<void> closeTab(String tabIds) {
return _tabsService.removeTab(tabId: tabIds);
}
@override
Map<String, TabState> build() {
final eventService = ref.watch(eventServiceProvider);
final subscriptions = [
eventService.tabListEvents.listen(_onTabListChange),
eventService.tabContentEvents.listen(_onTabContentStateChange),
eventService.iconEvents.listen(_onIconChange),
eventService.thumbnailEvents.listen(_onThumbnailChange),
eventService.securityInfoEvents.listen(_onSecurityInfoStateChange),
eventService.historyEvents.listen(_onHistoryStateChange),
eventService.readerableEvents.listen(_onReaderableStateChange),
eventService.findResultsEvent.listen(_onFindResults),
];
ref.onDispose(() {
for (final sub in subscriptions) {
unawaited(sub.cancel());
}
});
return {};
}
}
@Riverpod()
TabState? tabState(TabStateRef ref, String? tabId) {
if (tabId == null) {
return null;
}
return ref.watch(
tabStatesProvider.select((tabs) => tabs[tabId]),
);
}
@Riverpod()
TabState? selectedTabState(SelectedTabStateRef ref) {
final tabId = ref.watch(selectedTabProvider);
return ref.watch(tabStateProvider(tabId));
}
@@ -0,0 +1,188 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'tab_state.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$tabStateHash() => r'9fd7990804643fac180dd4fd50803bc7c9fa2827';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
/// See also [tabState].
@ProviderFor(tabState)
const tabStateProvider = TabStateFamily();
/// See also [tabState].
class TabStateFamily extends Family<TabState?> {
/// See also [tabState].
const TabStateFamily();
/// See also [tabState].
TabStateProvider call(
String? tabId,
) {
return TabStateProvider(
tabId,
);
}
@override
TabStateProvider getProviderOverride(
covariant TabStateProvider 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'tabStateProvider';
}
/// See also [tabState].
class TabStateProvider extends AutoDisposeProvider<TabState?> {
/// See also [tabState].
TabStateProvider(
String? tabId,
) : this._internal(
(ref) => tabState(
ref as TabStateRef,
tabId,
),
from: tabStateProvider,
name: r'tabStateProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$tabStateHash,
dependencies: TabStateFamily._dependencies,
allTransitiveDependencies: TabStateFamily._allTransitiveDependencies,
tabId: tabId,
);
TabStateProvider._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(
TabState? Function(TabStateRef provider) create,
) {
return ProviderOverride(
origin: this,
override: TabStateProvider._internal(
(ref) => create(ref as TabStateRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
tabId: tabId,
),
);
}
@override
AutoDisposeProviderElement<TabState?> createElement() {
return _TabStateProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is TabStateProvider && other.tabId == tabId;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, tabId.hashCode);
return _SystemHash.finish(hash);
}
}
mixin TabStateRef on AutoDisposeProviderRef<TabState?> {
/// The parameter `tabId` of this provider.
String? get tabId;
}
class _TabStateProviderElement extends AutoDisposeProviderElement<TabState?>
with TabStateRef {
_TabStateProviderElement(super.provider);
@override
String? get tabId => (origin as TabStateProvider).tabId;
}
String _$selectedTabStateHash() => r'22172913554e14e67f692eff0777ea015175906e';
/// See also [selectedTabState].
@ProviderFor(selectedTabState)
final selectedTabStateProvider = AutoDisposeProvider<TabState?>.internal(
selectedTabState,
name: r'selectedTabStateProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$selectedTabStateHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef SelectedTabStateRef = AutoDisposeProviderRef<TabState?>;
String _$tabStatesHash() => r'22fd36fe2e443fd2dfb703dc3be72716fbccef6d';
/// See also [TabStates].
@ProviderFor(TabStates)
final tabStatesProvider =
NotifierProvider<TabStates, Map<String, TabState>>.internal(
TabStates.new,
name: r'tabStatesProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$tabStatesHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$TabStates = Notifier<Map<String, TabState>>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
@@ -1,13 +1,6 @@
import 'dart:async';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:lensai/extensions/image.dart';
import 'package:lensai/features/geckoview/domain/entities/history_state.dart';
import 'package:lensai/features/geckoview/domain/entities/readerable_state.dart';
import 'package:lensai/features/geckoview/domain/entities/security_state.dart';
import 'package:lensai/features/geckoview/domain/entities/tab_state.dart';
import 'package:lensai/features/geckoview/domain/providers.dart';
import 'package:lensai/features/geckoview/utils/image_helper.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'tab.g.dart';
@@ -16,100 +9,6 @@ part 'tab.g.dart';
class TabRepository extends _$TabRepository {
final _tabsService = GeckoTabService();
void _onTabListChange(List<String> tabs) {
state = {
for (final tabId in tabs) tabId: state[tabId] ?? TabState.$default(tabId),
};
}
void _onTabContentStateChange(TabContentState contentState) {
final current =
state[contentState.id] ?? TabState.$default(contentState.id);
state = {...state}..[contentState.id] = current.copyWith(
contextId: contentState.contextId,
url: Uri.parse(contentState.url),
title: contentState.title,
progress: contentState.progress,
isPrivate: contentState.isPrivate,
isFullScreen: contentState.isFullScreen,
isLoading: contentState.isLoading,
);
}
Future<void> _onIconChange(IconEvent event) async {
final IconEvent(:tabId, :bytes) = event;
final current = state[tabId] ?? TabState.$default(tabId);
final image = (bytes != null)
? (await tryDecodeImage(bytes)
.then((image) async => image?.toEquatable()))
: null;
state = {...state}..[tabId] = current.copyWith.icon(image);
}
Future<void> _onThumbnailChange(ThumbnailEvent event) async {
final ThumbnailEvent(:tabId, :bytes) = event;
final current = state[tabId] ?? TabState.$default(tabId);
final image = (bytes != null)
? (await tryDecodeImage(bytes)
.then((image) async => image?.toEquatable()))
: null;
state = {...state}..[tabId] = current.copyWith.thumbnail(image);
}
void _onSecurityInfoStateChange(SecurityInfoEvent event) {
final SecurityInfoEvent(:tabId, :securityInfo) = event;
final current = state[tabId] ?? TabState.$default(tabId);
state = {...state}..[tabId] = current.copyWith.securityInfoState(
SecurityState(
secure: securityInfo.secure,
host: securityInfo.host,
issuer: securityInfo.issuer,
),
);
}
void _onHistoryStateChange(HistoryEvent event) {
final HistoryEvent(:tabId, :history) = event;
final current = state[tabId] ?? TabState.$default(tabId);
state = {...state}..[tabId] = current.copyWith.historyState(
HistoryState(
items: history.items.nonNulls
.map(
(item) =>
HistoryItem(url: Uri.parse(item.url), title: item.title),
)
.toList(),
currentIndex: history.currentIndex,
canGoBack: history.canGoBack,
canGoForward: history.canGoForward,
),
);
}
void _onReaderableStateChange(ReaderableEvent event) {
final ReaderableEvent(:tabId, :readerable) = event;
final current = state[tabId] ?? TabState.$default(tabId);
state = {...state}..[tabId] = current.copyWith.readerableState(
ReaderableState(
readerable: readerable.readerable,
active: readerable.active,
),
);
}
Future<String> addTab({
Uri? url,
bool selectTab = true,
@@ -136,30 +35,18 @@ class TabRepository extends _$TabRepository {
);
}
Future<void> selectTab(String tabId) {
return _tabsService.selectTab(tabId: tabId);
}
Future<void> closeTab(String tabId) {
return _tabsService.removeTab(tabId: tabId);
}
Future<void> closeTabs(List<String> tabIds) {
return _tabsService.removeTabs(ids: tabIds);
}
@override
Map<String, TabState> build() {
final eventService = ref.watch(eventServiceProvider);
final subscriptions = [
eventService.tabListEvents.listen(_onTabListChange),
eventService.tabContentEvents.listen(_onTabContentStateChange),
eventService.iconEvents.listen(_onIconChange),
eventService.thumbnailEvents.listen(_onThumbnailChange),
eventService.securityInfoEvents.listen(_onSecurityInfoStateChange),
eventService.historyEvents.listen(_onHistoryStateChange),
eventService.readerableEvents.listen(_onReaderableStateChange),
];
ref.onDispose(() {
for (final sub in subscriptions) {
unawaited(sub.cancel());
}
});
return {};
}
void build() {}
}
@@ -6,12 +6,11 @@ part of 'tab.dart';
// RiverpodGenerator
// **************************************************************************
String _$tabRepositoryHash() => r'64724c0215624753c5c6ff8d6827b890ad9a6f2c';
String _$tabRepositoryHash() => r'0abf38ef42f29015d81ccd11eb5990b741ebc616';
/// See also [TabRepository].
@ProviderFor(TabRepository)
final tabRepositoryProvider =
NotifierProvider<TabRepository, Map<String, TabState>>.internal(
final tabRepositoryProvider = NotifierProvider<TabRepository, void>.internal(
TabRepository.new,
name: r'tabRepositoryProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
@@ -21,6 +20,6 @@ final tabRepositoryProvider =
allTransitiveDependencies: null,
);
typedef _$TabRepository = Notifier<Map<String, TabState>>;
typedef _$TabRepository = 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
@@ -2,12 +2,9 @@
import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:lensai/features/bangs/data/models/bang_data.dart';
import 'package:lensai/features/bangs/domain/repositories/data.dart';
import 'package:lensai/features/kagi/data/entities/modes.dart';
import 'package:lensai/features/search_browser/domain/entities/sheet.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'providers.g.dart';
@@ -3,8 +3,8 @@ import 'dart:async';
import 'package:collection/collection.dart';
import 'package:lensai/data/models/received_parameter.dart';
import 'package:lensai/features/app_widget/domain/services/home_widget.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/search_browser/domain/entities/sheet.dart';
import 'package:lensai/features/share_intent/domain/services/sharing_intent.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:rxdart/rxdart.dart';
@@ -0,0 +1,9 @@
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'engine_settings.g.dart';
@Riverpod(keepAlive: true)
GeckoEngineSettingsService engineSettingsService(EngineSettingsServiceRef ref) {
return GeckoEngineSettingsService();
}
@@ -1,27 +1,27 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'selected_tab.dart';
part of 'engine_settings.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$selectedTabControllerHash() =>
r'b5d79d4d5c2dc08d504e601fa29c8b83d51c9716';
String _$engineSettingsServiceHash() =>
r'9d9abb3601a3782f1f3ef91e31049db40acbf64a';
/// See also [SelectedTabController].
@ProviderFor(SelectedTabController)
final selectedTabControllerProvider =
NotifierProvider<SelectedTabController, String?>.internal(
SelectedTabController.new,
name: r'selectedTabControllerProvider',
/// See also [engineSettingsService].
@ProviderFor(engineSettingsService)
final engineSettingsServiceProvider =
Provider<GeckoEngineSettingsService>.internal(
engineSettingsService,
name: r'engineSettingsServiceProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$selectedTabControllerHash,
: _$engineSettingsServiceHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$SelectedTabController = Notifier<String?>;
typedef EngineSettingsServiceRef = ProviderRef<GeckoEngineSettingsService>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
@@ -3,28 +3,32 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/features/geckoview/domain/entities/tab_state.dart';
import 'package:lensai/features/geckoview/domain/providers.dart';
import 'package:lensai/features/geckoview/features/topics/domain/repositories/topic.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
import 'package:lensai/features/geckoview/features/topics/domain/providers.dart';
import 'package:lensai/features/geckoview/features/topics/domain/repositories/tab_link.dart';
class TabActionDialog extends HookConsumerWidget {
final String tabId;
final TabState initialTab;
final void Function()? onDismiss;
const TabActionDialog({
required this.tabId,
required this.initialTab,
this.onDismiss,
super.key,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final tabState =
ref.watch(tabStateProvider(tabId)) ?? TabState.$default(tabId);
final tabState = ref.watch(tabStateProvider(initialTab.id)) ?? initialTab;
final tabTopicId = ref.watch(
tabTopicIdProvider(initialTab.id).select((value) => value.valueOrNull),
);
final topics =
ref.watch(topicRepositoryProvider.select((value) => value.valueOrNull));
ref.watch(topicsWithCountProvider.select((value) => value.valueOrNull));
final selectedTopic =
topics?.firstWhereOrNull((topic) => topic.id == tabState.topicId);
topics?.firstWhereOrNull((topic) => topic.id == tabTopicId);
final expansionController = useExpansionTileController();
@@ -64,17 +68,15 @@ class TabActionDialog extends HookConsumerWidget {
? Text(selectedTopic.name ?? 'New Topic')
: const Text('Assign a Topic'),
children: topics
?.where((topic) => topic.id != tabState.topicId)
?.where((topic) => topic.id != tabTopicId)
.map(
(topic) => ListTile(
leading: CircleAvatar(backgroundColor: topic.color),
title: Text(topic.name ?? 'New Topic'),
onTap: () async {
await ref
.read(
tabStateProvider(initialTab.id).notifier,
)
.copyWith(topicId: topic.id);
.read(tabLinkRepositoryProvider.notifier)
.assignTab(initialTab.id, topic.id);
expansionController.collapse();
},
@@ -1,20 +1,19 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/data/models/web_page_info.dart';
import 'package:lensai/features/bangs/data/models/bang_data.dart';
import 'package:lensai/features/bangs/domain/providers.dart';
import 'package:lensai/features/bangs/presentation/widgets/site_search.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_session.dart';
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
import 'package:lensai/features/kagi/data/entities/modes.dart';
import 'package:lensai/features/search_browser/utils/url_builder.dart'
as uri_builder;
import 'package:lensai/features/kagi/utils/url_builder.dart' as uri_builder;
import 'package:lensai/features/settings/data/models/settings.dart';
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
import 'package:lensai/features/share_intent/domain/entities/shared_content.dart';
import 'package:lensai/features/web_view/presentation/controllers/switch_new_tab.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;
@@ -84,27 +83,23 @@ class WebPageDialog extends HookConsumerWidget {
enableIMEPersonalizedLearning: !incognitoEnabled,
decoration: InputDecoration(
labelText: 'URL',
suffixIcon: (webViewController != null)
? IconButton(
onPressed: () async {
if (formKey.currentState?.validate() ?? false) {
await webViewController!.loadUrl(
urlRequest: URLRequest(
url: WebUri.uri(
uri_parser.tryParseUrl(
urlTextController.text,
eagerParsing: true,
)!,
),
),
);
suffixIcon: IconButton(
onPressed: () async {
if (formKey.currentState?.validate() ?? false) {
await ref
.read(tabSessionProvider(null).notifier)
.loadUrl(
url: uri_parser.tryParseUrl(
urlTextController.text,
eagerParsing: true,
)!,
);
onDismiss?.call();
}
},
icon: const Icon(Icons.send),
)
: null,
onDismiss?.call();
}
},
icon: const Icon(Icons.send),
),
),
validator: (value) {
if (uri_parser.tryParseUrl(value, eagerParsing: true) !=
@@ -174,9 +169,7 @@ class WebPageDialog extends HookConsumerWidget {
leading: const Icon(MdiIcons.tabPlus),
title: const Text('Open in new tab'),
onTap: () async {
await ref
.read(switchNewTabControllerProvider.notifier)
.add(url);
await ref.read(tabRepositoryProvider.notifier).addTab(url: url);
onDismiss?.call();
},
@@ -201,8 +194,8 @@ class WebPageDialog extends HookConsumerWidget {
);
await ref
.read(switchNewTabControllerProvider.notifier)
.add(summarizerUrl);
.read(tabRepositoryProvider.notifier)
.addTab(url: summarizerUrl);
onDismiss?.call();
},
@@ -3,7 +3,7 @@ import 'package:flutter_markdown/flutter_markdown.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/features/search_browser/presentation/widgets/error_container.dart';
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/error_container.dart';
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
class LandingAction extends HookConsumerWidget {
@@ -4,8 +4,9 @@ import 'package:flutter/services.dart' show rootBundle;
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/domain/services/app_initialization.dart';
import 'package:lensai/features/search_browser/presentation/widgets/error_container.dart';
import 'package:lensai/features/search_browser/presentation/widgets/landing/action.dart';
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/error_container.dart';
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/landing/action.dart';
import 'package:lensai/presentation/hooks/cached_future.dart';
class LandingContent extends HookConsumerWidget {
@@ -99,7 +100,8 @@ class LandingContent extends HookConsumerWidget {
onPressed: () async {
await ref
.read(
appInitializationServiceProvider.notifier)
appInitializationServiceProvider.notifier,
)
.reinitialize();
},
style: OutlinedButton.styleFrom(
@@ -119,8 +121,8 @@ class LandingContent extends HookConsumerWidget {
onTapLink: (text, href, title) async {
if (href != null) {
await ref
.read(switchNewTabControllerProvider.notifier)
.add(Uri.parse(href));
.read(tabRepositoryProvider.notifier)
.addTab(url: Uri.parse(href));
}
},
selectable: true,
@@ -2,11 +2,11 @@ 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/features/geckoview/features/browser/domain/entities/sheet.dart';
import 'package:lensai/features/kagi/data/entities/modes.dart';
import 'package:lensai/features/search_browser/domain/entities/sheet.dart';
import 'package:lensai/features/search_browser/presentation/widgets/tabs/assistant_tab.dart';
import 'package:lensai/features/search_browser/presentation/widgets/tabs/search_tab.dart';
import 'package:lensai/features/search_browser/presentation/widgets/tabs/summarize_tab.dart';
import 'package:lensai/features/kagi/presentation/tabs/assistant_tab.dart';
import 'package:lensai/features/kagi/presentation/tabs/search_tab.dart';
import 'package:lensai/features/kagi/presentation/tabs/summarize_tab.dart';
import 'package:lensai/features/settings/data/models/settings.dart';
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
import 'package:lensai/features/share_intent/domain/entities/shared_content.dart';
@@ -14,12 +14,12 @@ import 'package:lensai/presentation/hooks/sync_page_tab.dart';
typedef OnSubmitUri = void Function(Uri url);
class SharedContentSheet extends HookConsumerWidget {
class CreateTabSheetWidget extends HookConsumerWidget {
final CreateTabSheet parameter;
final OnSubmitUri onSubmit;
final void Function(KagiTool tool)? onActiveToolChanged;
const SharedContentSheet({
const CreateTabSheetWidget({
required this.parameter,
required this.onSubmit,
this.onActiveToolChanged,
@@ -4,10 +4,16 @@ import 'package:fading_scroll/fading_scroll.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/features/geckoview/domain/providers/selected_tab.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
import 'package:lensai/features/geckoview/features/browser/presentation/dialogs/tab_action.dart';
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/tab_preview.dart';
import 'package:lensai/features/geckoview/features/controllers/overlay_dialog.dart';
import 'package:lensai/features/geckoview/features/topics/domain/providers.dart';
import 'package:lensai/features/geckoview/features/topics/domain/providers/selected_topic.dart';
import 'package:lensai/features/geckoview/features/topics/domain/repositories/tab_link.dart';
import 'package:lensai/features/geckoview/features/topics/presentation/widgets/topic_chips.dart';
import 'package:lensai/features/search_browser/presentation/dialogs/tab_action.dart';
class _SliverHeaderDelagate extends SliverPersistentHeaderDelegate {
static const _headerSize = 104.0;
@@ -36,8 +42,8 @@ class _SliverHeaderDelagate extends SliverPersistentHeaderDelegate {
TextButton.icon(
onPressed: () async {
await ref
.read(switchNewTabControllerProvider.notifier)
.add(Uri.https('kagi.com'));
.read(tabRepositoryProvider.notifier)
.addTab(url: Uri.https('kagi.com'));
onClose();
},
@@ -48,8 +54,8 @@ class _SliverHeaderDelagate extends SliverPersistentHeaderDelegate {
onPressed: () async {
final topic = ref.read(selectedTopicProvider);
await ref
.read(topicTabRepositoryProvider(topic).notifier)
.closeAllTabs();
.read(tabLinkRepositoryProvider.notifier)
.closeAllTabs(topic);
onClose();
},
@@ -79,11 +85,11 @@ class _SliverHeaderDelagate extends SliverPersistentHeaderDelegate {
false;
}
class ViewTabsSheet extends HookConsumerWidget {
class ViewTabsSheetWidget extends HookConsumerWidget {
final ScrollController sheetScrollController;
final VoidCallback onClose;
const ViewTabsSheet({
const ViewTabsSheetWidget({
required this.onClose,
required this.sheetScrollController,
super.key,
@@ -127,10 +133,10 @@ class ViewTabsSheet extends HookConsumerWidget {
builder: (context, ref, child) {
final topic = ref.watch(selectedTopicProvider);
final availableTabs = ref.watch(
topicTabRepositoryProvider(topic)
topicTabIdsProvider(topic)
.select((value) => value.valueOrNull ?? []),
);
final activeTab = ref.watch(webViewTabControllerProvider);
final activeTab = ref.watch(selectedTabProvider);
final itemHeight = useMemoized(
() => _calculateItemHeight(
@@ -185,19 +191,18 @@ class ViewTabsSheet extends HookConsumerWidget {
final tab = ref.watch(tabStateProvider(tabId));
return (tab != null)
? WebViewTab(
? TabPreview(
tab: tab,
isActive: tabId == activeTab,
onTap: () {
onTap: () async {
if (tabId != activeTab) {
//Close first to avoid rebuilds
onClose();
ref
await ref
.read(
webViewTabControllerProvider
.notifier,
tabRepositoryProvider.notifier,
)
.showTab(tab.id);
.selectTab(tab.id);
} else {
onClose();
}
@@ -205,14 +210,15 @@ class ViewTabsSheet extends HookConsumerWidget {
onLongPress: () {
ref
.read(
overlayDialogProvider.notifier,
overlayDialogControllerProvider
.notifier,
)
.show(
TabActionDialog(
initialTab: tab,
onDismiss: ref
.read(
overlayDialogProvider
overlayDialogControllerProvider
.notifier,
)
.dismiss,
@@ -224,7 +230,7 @@ class ViewTabsSheet extends HookConsumerWidget {
.read(
tabRepositoryProvider.notifier,
)
.deleteTab(tab.id);
.closeTab(tab.id);
},
)
: const SizedBox.shrink();
@@ -1,16 +1,15 @@
import 'package:flutter/material.dart';
import 'package:lensai/features/web_view/domain/entities/abstract/tab.dart';
import 'package:lensai/features/search_browser/presentation/widgets/favicon.dart';
import 'package:lensai/features/geckoview/domain/entities/tab_state.dart';
class WebViewTab extends StatelessWidget {
final ITab tab;
class TabPreview extends StatelessWidget {
final TabState tab;
final bool isActive;
final VoidCallback? onTap;
final VoidCallback? onLongPress;
final VoidCallback? onDelete;
const WebViewTab({
const TabPreview({
required this.tab,
required this.isActive,
this.onTap,
@@ -46,7 +45,7 @@ class WebViewTab extends StatelessWidget {
padding: const EdgeInsets.only(left: 6.0),
child: Text(
overflow: TextOverflow.ellipsis,
tab.title ?? 'New Tab',
tab.title,
maxLines: 2,
),
),
@@ -64,9 +63,10 @@ class WebViewTab extends StatelessWidget {
const SizedBox(
width: 6.0,
),
FaviconImage(
url: tab.url,
favicon: tab.favicon,
RawImage(
image: tab.icon?.value,
height: 16,
width: 16,
),
const SizedBox(
width: 6.0,
@@ -82,7 +82,7 @@ class WebViewTab extends StatelessWidget {
const SizedBox(
height: 6,
),
if (tab.screenshot != null)
if (tab.thumbnail != null)
Expanded(
child: ClipRRect(
borderRadius: const BorderRadius.only(
@@ -91,9 +91,9 @@ class WebViewTab extends StatelessWidget {
),
child: SizedBox(
width: double.infinity,
child: Image.memory(
child: RawImage(
image: tab.thumbnail!.value,
fit: BoxFit.fitWidth,
tab.screenshot!,
),
),
),
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
class TabsActionButton extends HookConsumerWidget {
final bool isActive;
@@ -16,8 +16,7 @@ class TabsActionButton extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final theme = Theme.of(context);
final tabCount =
ref.watch(tabRepositoryProvider.select((tabs) => tabs.length));
final tabCount = ref.watch(tabStatesProvider.select((tabs) => tabs.length));
return InkWell(
onTap: onTap,
@@ -1,34 +1,39 @@
import 'dart:async';
import 'package:animations/animations.dart';
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:flutter_mozilla_components/flutter_mozilla_components.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/features/geckoview/domain/providers/tab_session.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
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/dialogs/web_page_dialog.dart';
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/app_bar_title.dart';
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/sheets/create_tab.dart';
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/sheets/view_tabs.dart';
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/tabs_action_button.dart';
import 'package:lensai/features/geckoview/features/controllers/bottom_sheet.dart';
import 'package:lensai/features/geckoview/features/controllers/overlay_dialog.dart';
import 'package:lensai/features/geckoview/features/find_in_page/presentation/controllers/find_in_page_visibility.dart';
import 'package:lensai/features/geckoview/features/find_in_page/presentation/widgets/find_in_page.dart';
import 'package:lensai/features/geckoview/features/readerview/presentation/widgets/reader_button.dart';
import 'package:lensai/features/kagi/data/entities/modes.dart';
import 'package:lensai/features/search_browser/domain/entities/sheet.dart';
import 'package:lensai/features/search_browser/domain/providers.dart';
import 'package:lensai/features/search_browser/domain/services/create_tab.dart';
import 'package:lensai/features/search_browser/presentation/controllers/bottom_sheet.dart';
import 'package:lensai/features/search_browser/presentation/controllers/overlay_dialog.dart';
import 'package:lensai/features/search_browser/presentation/widgets/app_bar_title.dart';
import 'package:lensai/features/search_browser/presentation/widgets/landing/content.dart';
import 'package:lensai/features/search_browser/presentation/widgets/sheets/shared_content_sheet.dart';
import 'package:lensai/features/search_browser/presentation/widgets/sheets/view_tabs_sheet.dart';
import 'package:lensai/features/search_browser/presentation/widgets/tabs_action_button.dart';
import 'package:lensai/features/kagi/data/services/session.dart';
import 'package:lensai/features/settings/data/models/settings.dart';
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
import 'package:lensai/presentation/hooks/overlay_portal_controller.dart';
import 'package:lensai/presentation/widgets/animate_gradient_shader.dart';
import 'package:lensai/presentation/widgets/animated_indexed_stack.dart';
import 'package:lensai/utils/ui_helper.dart' as ui_helper;
import 'package:share_plus/share_plus.dart';
import 'package:speech_to_text_google_dialog/speech_to_text_google_dialog.dart';
class KagiScreen extends HookConsumerWidget {
const KagiScreen({super.key});
class BrowserScreen extends HookConsumerWidget {
const BrowserScreen({super.key});
double _realtiveSafeArea(BuildContext context) {
final mediaQuery = MediaQuery.of(context);
@@ -60,7 +65,8 @@ class KagiScreen extends HookConsumerWidget {
),
);
final activeTabId = ref.watch(webViewTabControllerProvider);
final selectedTabId =
ref.watch(selectedTabStateProvider.select((value) => value?.id));
final menuController = useMemoized(() => MenuController());
@@ -81,12 +87,24 @@ class KagiScreen extends HookConsumerWidget {
},
);
ref.listen(
settingsRepositoryProvider
.select((value) => value.valueOrNull?.enableJavascript),
(previous, next) async {
if (next != null) {
await ref.read(engineSettingsServiceProvider).javaScriptEnabled(next);
}
},
);
ref.listen(
settingsRepositoryProvider
.select((value) => value.valueOrNull?.kagiSession),
(previous, next) async {
if (next != null && next.isNotEmpty) {
await ref.read(sessionServiceProvider.notifier).setKagiSession(next);
await ref
.read(kagiSessionServiceProvider.notifier)
.setKagiSession(next);
}
},
);
@@ -98,22 +116,28 @@ class KagiScreen extends HookConsumerWidget {
child: AppBar(
automaticallyImplyLeading: false,
titleSpacing: 8.0,
title: (activeTabId != null)
title: (selectedTabId != null)
? Consumer(
builder: (context, ref, child) {
final page = ref.watch(tabStateProvider(activeTabId));
final tabState = ref.watch(tabStateProvider(selectedTabId));
return (page != null)
return (tabState != null)
? AppBarTitle(
tab: page,
tab: tabState,
onTap: () {
ref.read(overlayDialogProvider.notifier).show(
ref
.read(
overlayDialogControllerProvider.notifier,
)
.show(
WebPageDialog(
url: page.url,
precachedInfo: page,
webViewController: page.controller,
url: tabState.url,
precachedInfo: tabState,
onDismiss: ref
.read(overlayDialogProvider.notifier)
.read(
overlayDialogControllerProvider
.notifier,
)
.dismiss,
),
);
@@ -127,7 +151,7 @@ class KagiScreen extends HookConsumerWidget {
final activeTool = useListenableSelector(
activeKagiTool,
() {
if (displayedSheet is SharedContentSheet) {
if (displayedSheet is CreateTabSheetWidget) {
return null;
}
@@ -146,7 +170,8 @@ class KagiScreen extends HookConsumerWidget {
.read(createTabStreamProvider.notifier)
.createTab(
CreateTabSheet(
preferredTool: KagiTool.search),
preferredTool: KagiTool.search,
),
);
},
icon: Icon(KagiTool.search.icon),
@@ -160,7 +185,8 @@ class KagiScreen extends HookConsumerWidget {
.read(createTabStreamProvider.notifier)
.createTab(
CreateTabSheet(
preferredTool: KagiTool.summarizer),
preferredTool: KagiTool.summarizer,
),
);
},
icon: Icon(KagiTool.summarizer.icon),
@@ -186,93 +212,7 @@ class KagiScreen extends HookConsumerWidget {
},
),
actions: [
if (activeTabId != null)
HookConsumer(
builder: (context, ref, child) {
final colorScheme = Theme.of(context).colorScheme;
final readerabilityState = ref.watch(
readerabilityControllerProvider(activeTabId),
);
final enableReadability = ref.watch(
settingsRepositoryProvider.select(
(value) => (value.valueOrNull ?? Settings.withDefaults())
.enableReadability,
),
);
final isReaderable =
readerabilityState.valueOrNull?.readerable ?? false;
final readerableApplied =
readerabilityState.valueOrNull?.applied ?? false;
final icon = useMemoized(
() => readerableApplied
? Icon(
MdiIcons.bookOpen,
color: Theme.of(context).colorScheme.primary,
)
: const Icon(
MdiIcons.bookOpenOutline,
color: Colors.white,
),
[readerableApplied],
);
return Visibility(
visible: enableReadability,
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 15.0,
horizontal: 8.0,
),
child: readerabilityState.when(
data: (_) => Visibility(
visible: isReaderable == true || readerableApplied,
child: InkWell(
onTap: readerabilityState.isLoading
? null
: () async {
final controller = ref.read(
webViewControllerProvider(activeTabId),
);
if (controller != null) {
final readabilityNotifier = ref.read(
readerabilityControllerProvider(
activeTabId,
).notifier,
);
await readabilityNotifier
.toggleReaderable();
}
},
child: icon,
),
),
error: (error, stackTrace) => SizedBox.shrink(),
loading: () => AnimateGradientShader(
duration: const Duration(milliseconds: 500),
primaryEnd: Alignment.bottomLeft,
secondaryEnd: Alignment.topRight,
primaryColors: [
colorScheme.primary,
colorScheme.primaryContainer,
],
secondaryColors: [
colorScheme.secondary,
colorScheme.secondaryContainer,
],
child: icon,
),
),
),
);
},
),
if (selectedTabId != null) ReaderButton(),
if (quickAction != null)
InkWell(
onTap: () async {
@@ -321,9 +261,11 @@ class KagiScreen extends HookConsumerWidget {
isActive: displayedSheet is ViewTabsSheet,
onTap: () {
if (displayedSheet case ViewTabsSheet()) {
ref.read(bottomSheetProvider.notifier).dismiss();
ref.read(bottomSheetControllerProvider.notifier).dismiss();
} else {
ref.read(bottomSheetProvider.notifier).show(ViewTabsSheet());
ref
.read(bottomSheetControllerProvider.notifier)
.show(ViewTabsSheet());
}
},
),
@@ -410,63 +352,63 @@ class KagiScreen extends HookConsumerWidget {
child: const Text('Search'),
),
const Divider(),
if (activeTabId != null)
if (selectedTabId != null)
MenuItemButton(
onPressed: () async {
final controller =
ref.read(webViewControllerProvider(activeTabId));
final tabState =
ref.read(tabStateProvider(selectedTabId));
final url = await controller?.getUrl();
if (url != null) {
// ignore: use_build_context_synchronously
if (tabState?.url case final Uri url) {
await ui_helper.launchUrlFeedback(context, url);
}
},
leadingIcon: const Icon(Icons.open_in_browser),
child: const Text('Launch External'),
),
if (activeTabId != null)
if (selectedTabId != null)
MenuItemButton(
onPressed: () async {
final controller =
ref.read(webViewControllerProvider(activeTabId));
final tabState =
ref.read(tabStateProvider(selectedTabId));
final url = await controller?.getUrl();
if (url != null) {
if (tabState?.url case final Uri url) {
await Share.shareUri(url);
}
},
leadingIcon: const Icon(Icons.share),
child: const Text('Share'),
),
if (activeTabId != null) const Divider(),
if (activeTabId != null)
if (selectedTabId != null) const Divider(),
if (selectedTabId != null)
MenuItemButton(
onPressed: () async {
ref.read(showFindInPageProvider.notifier).update(true);
onPressed: () {
ref
.read(findInPageVisibilityControllerProvider.notifier)
.show();
},
leadingIcon: const Icon(Icons.search),
child: const Text('Find in page'),
),
if (activeTabId != null) const Divider(),
if (activeTabId != null)
if (selectedTabId != null) const Divider(),
if (selectedTabId != null)
MenuItemButton(
onPressed: () async {
final controller =
ref.read(webViewControllerProvider(activeTabId));
ref.read(tabSessionProvider(selectedTabId).notifier);
await controller?.reload();
await controller.reload();
menuController.close();
},
leadingIcon: const Icon(Icons.refresh),
child: const Text('Reload'),
),
if (activeTabId != null) const Divider(),
if (activeTabId != null)
if (selectedTabId != null) const Divider(),
if (selectedTabId != null)
Consumer(
builder: (context, ref, child) {
final history = ref.watch(
tabStateProvider(activeTabId)
.select((value) => value?.pageHistory),
tabStateProvider(selectedTabId)
.select((value) => value?.historyState),
);
return Row(
@@ -476,12 +418,12 @@ class KagiScreen extends HookConsumerWidget {
onPressed: (history?.canGoBack == true)
? () async {
final controller = ref.read(
webViewControllerProvider(
activeTabId,
),
tabSessionProvider(
selectedTabId,
).notifier,
);
await controller?.goBack();
await controller.goBack();
menuController.close();
}
: null,
@@ -494,12 +436,12 @@ class KagiScreen extends HookConsumerWidget {
onPressed: (history?.canGoForward == true)
? () async {
final controller = ref.read(
webViewControllerProvider(
activeTabId,
),
tabSessionProvider(
selectedTabId,
).notifier,
);
await controller?.goForward();
await controller.goForward();
menuController.close();
}
: null,
@@ -523,42 +465,46 @@ class KagiScreen extends HookConsumerWidget {
child: Listener(
onPointerDown: (displayedSheet != null)
? (_) {
ref.read(bottomSheetProvider.notifier).dismiss();
ref.read(bottomSheetControllerProvider.notifier).dismiss();
}
: null,
child: HookConsumer(
builder: (context, ref, child) {
final webViewWidgets = ref.watch(webViewWidgetCacheProvider);
final activeWebViewStackIndex = (activeTabId != null)
? webViewWidgets
.indexWhere((view) => view.tabId == activeTabId)
: null;
return BackButtonListener(
onBackButtonPressed: () async {
final page = (activeTabId != null)
? ref.read(tabStateProvider(activeTabId))
final tabState = (selectedTabId != null)
? ref.read(tabStateProvider(selectedTabId))
: null;
final tabCount =
ref.read(tabStatesProvider.select((tabs) => tabs.length));
//Don't do anything if a child route is active
if (GoRouterState.of(context).topRoute?.name != 'KagiRoute') {
if (GoRouterState.of(context).topRoute?.path !=
BrowserRoute().location) {
return false;
}
if (displayedSheet != null) {
ref.read(bottomSheetProvider.notifier).dismiss();
ref.read(bottomSheetControllerProvider.notifier).dismiss();
return true;
}
if (displayedOverlayDialog != null) {
ref.read(overlayDialogProvider.notifier).dismiss();
ref
.read(overlayDialogControllerProvider.notifier)
.dismiss();
return true;
}
if (page?.pageHistory.canGoBack == true) {
if (tabState?.historyState.canGoBack == true) {
lastBackButtonPress.value = null;
await page?.controller?.goBack();
final controller = ref.read(
tabSessionProvider(selectedTabId).notifier,
);
await controller.goBack();
return true;
}
@@ -567,10 +513,10 @@ class KagiScreen extends HookConsumerWidget {
const Duration(seconds: 2)) {
lastBackButtonPress.value = null;
if (page != null && webViewWidgets.length > 1) {
if (tabState != null && tabCount > 1) {
await ref
.read(tabRepositoryProvider.notifier)
.deleteTab(page.id);
.read(tabStatesProvider.notifier)
.closeTab(tabState.id);
return true;
} else {
//Mark back as unhandled and navigator will pop
@@ -582,7 +528,7 @@ class KagiScreen extends HookConsumerWidget {
..clearSnackBars()
..showSnackBar(
SnackBar(
content: (webViewWidgets.length > 1)
content: (tabCount > 1)
? const Text(
'Please click BACK again to close current tab',
)
@@ -598,26 +544,52 @@ class KagiScreen extends HookConsumerWidget {
child: Stack(
children: [
SafeArea(
child: AnimatedIndexedStack(
duration: const Duration(milliseconds: 250),
transitionBuilder:
(child, animation, secondaryAnimation) =>
SharedAxisTransition(
animation: animation,
secondaryAnimation: secondaryAnimation,
transitionType: SharedAxisTransitionType.horizontal,
child: child,
),
// key: ValueKey(
// (activeTabId != null)
// ? activeWebViewStackIndex
// : null,
// ),
index: (activeWebViewStackIndex != null &&
activeWebViewStackIndex > -1)
? activeWebViewStackIndex + 1
: 0,
children: [const LandingContent(), ...webViewWidgets],
child: Stack(
children: [
const GeckoView(),
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Consumer(
builder: (context, ref, child) {
final value = ref.watch(
selectedTabStateProvider.select(
(state) => state?.progress ?? 100,
),
);
return Visibility(
visible: value < 100,
child: LinearProgressIndicator(
value: value / 100,
),
);
},
),
),
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Consumer(
builder: (context, ref, child) {
final value = ref.watch(
selectedTabStateProvider.select(
(state) => EdgeInsets.only(
bottom: (state?.progress != null &&
state!.progress < 100)
? 4.0
: 0.0,
),
),
);
return FindInPageWidget(padding: value);
},
),
),
],
),
),
if (displayedSheet != null)
@@ -636,7 +608,7 @@ class KagiScreen extends HookConsumerWidget {
? NotificationListener<DraggableScrollableNotification>(
onNotification: (notification) {
if (notification.extent <= 0.1) {
ref.read(bottomSheetProvider.notifier).dismiss();
ref.read(bottomSheetControllerProvider.notifier).dismiss();
return true;
} else {
ref
@@ -658,10 +630,12 @@ class KagiScreen extends HookConsumerWidget {
topLeft: Radius.circular(28),
topRight: Radius.circular(28),
),
child: ViewTabsSheet(
child: ViewTabsSheetWidget(
sheetScrollController: scrollController,
onClose: () {
ref.read(bottomSheetProvider.notifier).dismiss();
ref
.read(bottomSheetControllerProvider.notifier)
.dismiss();
},
),
);
@@ -676,15 +650,17 @@ class KagiScreen extends HookConsumerWidget {
builder: (context, scrollController) {
return SingleChildScrollView(
controller: scrollController,
child: SharedContentSheet(
child: CreateTabSheetWidget(
key: ValueKey(parameter),
parameter: parameter,
onSubmit: (url) async {
await ref
.read(switchNewTabControllerProvider.notifier)
.add(url);
.read(tabRepositoryProvider.notifier)
.addTab(url: url);
ref.read(bottomSheetProvider.notifier).dismiss();
ref
.read(bottomSheetControllerProvider.notifier)
.dismiss();
},
onActiveToolChanged: (tool) {
activeKagiTool.value = tool;
@@ -1,7 +1,7 @@
import 'dart:async';
import 'package:lensai/features/search_browser/domain/entities/sheet.dart';
import 'package:lensai/features/search_browser/domain/services/create_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:riverpod_annotation/riverpod_annotation.dart';
import 'package:rxdart/rxdart.dart';
@@ -0,0 +1,28 @@
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'find_in_page.g.dart';
@Riverpod()
class FindInPageRepository extends _$FindInPageRepository {
late GeckoFindInPageService _service;
@override
void build(String? tabId) {
_service = (tabId != null)
? GeckoFindInPageService(tabId: tabId)
: GeckoFindInPageService.forActiveTab();
}
Future<void> findAll({required String text}) {
return _service.findAll(text);
}
Future<void> findNext({bool forward = true}) {
return _service.findNext(forward);
}
Future<void> clearMatches() {
return _service.clearMatches();
}
}
@@ -1,13 +1,13 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'readerability_script.dart';
part of 'find_in_page.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$readerabilityScriptServiceHash() =>
r'85f0de50516cadb1f2d1efa8161a8cf45868b9d9';
String _$findInPageRepositoryHash() =>
r'a307a7169cfc559a687483962bd595d808c159bf';
/// Copied from Dart SDK
class _SystemHash {
@@ -30,36 +30,36 @@ class _SystemHash {
}
}
abstract class _$ReaderabilityScriptService
extends BuildlessAutoDisposeAsyncNotifier<void> {
late final String tabId;
abstract class _$FindInPageRepository
extends BuildlessAutoDisposeNotifier<void> {
late final String? tabId;
FutureOr<void> build(
String tabId,
void build(
String? tabId,
);
}
/// See also [ReaderabilityScriptService].
@ProviderFor(ReaderabilityScriptService)
const readerabilityScriptServiceProvider = ReaderabilityScriptServiceFamily();
/// See also [FindInPageRepository].
@ProviderFor(FindInPageRepository)
const findInPageRepositoryProvider = FindInPageRepositoryFamily();
/// See also [ReaderabilityScriptService].
class ReaderabilityScriptServiceFamily extends Family<AsyncValue<void>> {
/// See also [ReaderabilityScriptService].
const ReaderabilityScriptServiceFamily();
/// See also [FindInPageRepository].
class FindInPageRepositoryFamily extends Family<void> {
/// See also [FindInPageRepository].
const FindInPageRepositoryFamily();
/// See also [ReaderabilityScriptService].
ReaderabilityScriptServiceProvider call(
String tabId,
/// See also [FindInPageRepository].
FindInPageRepositoryProvider call(
String? tabId,
) {
return ReaderabilityScriptServiceProvider(
return FindInPageRepositoryProvider(
tabId,
);
}
@override
ReaderabilityScriptServiceProvider getProviderOverride(
covariant ReaderabilityScriptServiceProvider provider,
FindInPageRepositoryProvider getProviderOverride(
covariant FindInPageRepositoryProvider provider,
) {
return call(
provider.tabId,
@@ -78,31 +78,30 @@ class ReaderabilityScriptServiceFamily extends Family<AsyncValue<void>> {
_allTransitiveDependencies;
@override
String? get name => r'readerabilityScriptServiceProvider';
String? get name => r'findInPageRepositoryProvider';
}
/// See also [ReaderabilityScriptService].
class ReaderabilityScriptServiceProvider
extends AutoDisposeAsyncNotifierProviderImpl<ReaderabilityScriptService,
void> {
/// See also [ReaderabilityScriptService].
ReaderabilityScriptServiceProvider(
String tabId,
/// See also [FindInPageRepository].
class FindInPageRepositoryProvider
extends AutoDisposeNotifierProviderImpl<FindInPageRepository, void> {
/// See also [FindInPageRepository].
FindInPageRepositoryProvider(
String? tabId,
) : this._internal(
() => ReaderabilityScriptService()..tabId = tabId,
from: readerabilityScriptServiceProvider,
name: r'readerabilityScriptServiceProvider',
() => FindInPageRepository()..tabId = tabId,
from: findInPageRepositoryProvider,
name: r'findInPageRepositoryProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$readerabilityScriptServiceHash,
dependencies: ReaderabilityScriptServiceFamily._dependencies,
: _$findInPageRepositoryHash,
dependencies: FindInPageRepositoryFamily._dependencies,
allTransitiveDependencies:
ReaderabilityScriptServiceFamily._allTransitiveDependencies,
FindInPageRepositoryFamily._allTransitiveDependencies,
tabId: tabId,
);
ReaderabilityScriptServiceProvider._internal(
FindInPageRepositoryProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
@@ -112,11 +111,11 @@ class ReaderabilityScriptServiceProvider
required this.tabId,
}) : super.internal();
final String tabId;
final String? tabId;
@override
FutureOr<void> runNotifierBuild(
covariant ReaderabilityScriptService notifier,
void runNotifierBuild(
covariant FindInPageRepository notifier,
) {
return notifier.build(
tabId,
@@ -124,10 +123,10 @@ class ReaderabilityScriptServiceProvider
}
@override
Override overrideWith(ReaderabilityScriptService Function() create) {
Override overrideWith(FindInPageRepository Function() create) {
return ProviderOverride(
origin: this,
override: ReaderabilityScriptServiceProvider._internal(
override: FindInPageRepositoryProvider._internal(
() => create()..tabId = tabId,
from: from,
name: null,
@@ -140,14 +139,14 @@ class ReaderabilityScriptServiceProvider
}
@override
AutoDisposeAsyncNotifierProviderElement<ReaderabilityScriptService, void>
AutoDisposeNotifierProviderElement<FindInPageRepository, void>
createElement() {
return _ReaderabilityScriptServiceProviderElement(this);
return _FindInPageRepositoryProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is ReaderabilityScriptServiceProvider && other.tabId == tabId;
return other is FindInPageRepositoryProvider && other.tabId == tabId;
}
@override
@@ -159,19 +158,18 @@ class ReaderabilityScriptServiceProvider
}
}
mixin ReaderabilityScriptServiceRef
on AutoDisposeAsyncNotifierProviderRef<void> {
mixin FindInPageRepositoryRef on AutoDisposeNotifierProviderRef<void> {
/// The parameter `tabId` of this provider.
String get tabId;
String? get tabId;
}
class _ReaderabilityScriptServiceProviderElement
extends AutoDisposeAsyncNotifierProviderElement<ReaderabilityScriptService,
void> with ReaderabilityScriptServiceRef {
_ReaderabilityScriptServiceProviderElement(super.provider);
class _FindInPageRepositoryProviderElement
extends AutoDisposeNotifierProviderElement<FindInPageRepository, void>
with FindInPageRepositoryRef {
_FindInPageRepositoryProviderElement(super.provider);
@override
String get tabId => (origin as ReaderabilityScriptServiceProvider).tabId;
String? get tabId => (origin as FindInPageRepositoryProvider).tabId;
}
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
@@ -0,0 +1,19 @@
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'find_in_page_visibility.g.dart';
@Riverpod()
class FindInPageVisibilityController extends _$FindInPageVisibilityController {
void show() {
state = true;
}
void hide() {
state = false;
}
@override
bool build() {
return false;
}
}
@@ -0,0 +1,27 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'find_in_page_visibility.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$findInPageVisibilityControllerHash() =>
r'365819c6b181ff2cefc022360413a918457e06d7';
/// See also [FindInPageVisibilityController].
@ProviderFor(FindInPageVisibilityController)
final findInPageVisibilityControllerProvider =
AutoDisposeNotifierProvider<FindInPageVisibilityController, bool>.internal(
FindInPageVisibilityController.new,
name: r'findInPageVisibilityControllerProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$findInPageVisibilityControllerHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$FindInPageVisibilityController = AutoDisposeNotifier<bool>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
@@ -0,0 +1,96 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
import 'package:lensai/features/geckoview/features/find_in_page/domain/repositories/find_in_page.dart';
import 'package:lensai/features/geckoview/features/find_in_page/presentation/controllers/find_in_page_visibility.dart';
class FindInPageWidget extends HookConsumerWidget {
final EdgeInsetsGeometry padding;
const FindInPageWidget({this.padding = EdgeInsets.zero});
@override
Widget build(BuildContext context, WidgetRef ref) {
final showFindInPage = ref.watch(findInPageVisibilityControllerProvider);
final findInteractionController =
ref.watch(findInPageRepositoryProvider(null).notifier);
final textController = useTextEditingController();
return Visibility(
visible: showFindInPage,
child: Padding(
padding: padding,
child: Material(
child: Row(
children: [
const SizedBox(width: 8),
Expanded(
child: TextField(
controller: textController,
autofocus: true,
autocorrect: false,
decoration: const InputDecoration.collapsed(
hintText: 'Find in page',
),
keyboardType: TextInputType.text,
onSubmitted: (value) async {
if (value == '') {
await findInteractionController.clearMatches();
} else {
await findInteractionController.findAll(text: value);
}
},
),
),
HookConsumer(
builder: (context, ref, child) {
final searchResult = ref.watch(
selectedTabStateProvider
.select((state) => state?.findResultState),
);
if (searchResult != null) {
if (searchResult.numberOfMatches == 0) {
return const Text('Not found');
}
return Text(
'${searchResult.activeMatchOrdinal + 1} of ${searchResult.numberOfMatches}',
);
}
return const SizedBox.shrink();
},
),
IconButton(
icon: const Icon(Icons.arrow_upward),
onPressed: () async {
await findInteractionController.findNext(forward: false);
},
),
IconButton(
icon: const Icon(Icons.arrow_downward),
onPressed: () async {
await findInteractionController.findNext();
},
),
IconButton(
icon: const Icon(Icons.clear),
onPressed: () async {
ref
.read(findInPageVisibilityControllerProvider.notifier)
.hide();
await findInteractionController.clearMatches();
textController.clear();
},
),
],
),
),
),
);
}
}
@@ -0,0 +1,27 @@
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'readerable.g.dart';
@Riverpod(keepAlive: true)
GeckoReaderableService readerableService(ReaderableServiceRef ref) {
final service = GeckoReaderableService.setUp();
ref.onDispose(() {
service.dispose();
});
return service;
}
@Riverpod()
Stream<bool> appearanceButtonVisibility(AppearanceButtonVisibilityRef ref) {
final service = ref.watch(readerableServiceProvider);
return service.appearanceVisibility;
}
@Riverpod()
Stream<bool> readerButtonVisibility(ReaderButtonVisibilityRef ref) {
final service = ref.watch(readerableServiceProvider);
return service.readerVisibility;
}
@@ -0,0 +1,58 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'readerable.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$readerableServiceHash() => r'aad722c3869b4ce8dbe336fada733b7d102434c5';
/// See also [readerableService].
@ProviderFor(readerableService)
final readerableServiceProvider = Provider<GeckoReaderableService>.internal(
readerableService,
name: r'readerableServiceProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$readerableServiceHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef ReaderableServiceRef = ProviderRef<GeckoReaderableService>;
String _$appearanceButtonVisibilityHash() =>
r'996ec32ee111cfa713230a50fea3ae3a9984df7c';
/// See also [appearanceButtonVisibility].
@ProviderFor(appearanceButtonVisibility)
final appearanceButtonVisibilityProvider =
AutoDisposeStreamProvider<bool>.internal(
appearanceButtonVisibility,
name: r'appearanceButtonVisibilityProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$appearanceButtonVisibilityHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef AppearanceButtonVisibilityRef = AutoDisposeStreamProviderRef<bool>;
String _$readerButtonVisibilityHash() =>
r'd58132aa4e9a5ba311a0b5d99b9f8416e952b1ab';
/// See also [readerButtonVisibility].
@ProviderFor(readerButtonVisibility)
final readerButtonVisibilityProvider = AutoDisposeStreamProvider<bool>.internal(
readerButtonVisibility,
name: r'readerButtonVisibilityProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$readerButtonVisibilityHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef ReaderButtonVisibilityRef = AutoDisposeStreamProviderRef<bool>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
@@ -0,0 +1,26 @@
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:lensai/features/geckoview/domain/providers.dart';
import 'package:lensai/features/geckoview/features/readerview/domain/providers/readerable.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'readerable.g.dart';
@Riverpod(keepAlive: true)
class ReaderableScreenController extends _$ReaderableScreenController {
late GeckoReaderableService _service;
Future<void> toggleReaderView(bool enable) async {
state = const AsyncValue.loading();
final eventChange = ref.read(eventServiceProvider).readerableEvents.first;
final toggle = _service.toggleReaderView(enable);
state = await AsyncValue.guard(() => Future.wait([toggle, eventChange]));
}
@override
FutureOr<void> build() {
_service = ref.watch(readerableServiceProvider);
return null;
}
}
@@ -1,27 +1,27 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'switch_new_tab.dart';
part of 'readerable.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$switchNewTabControllerHash() =>
r'7ebfea953aeb81c279ea11254ff0ad558e6a949f';
String _$readerableScreenControllerHash() =>
r'463c032a6d07936954fead58589101ccb31c2765';
/// See also [SwitchNewTabController].
@ProviderFor(SwitchNewTabController)
final switchNewTabControllerProvider =
AsyncNotifierProvider<SwitchNewTabController, void>.internal(
SwitchNewTabController.new,
name: r'switchNewTabControllerProvider',
/// See also [ReaderableScreenController].
@ProviderFor(ReaderableScreenController)
final readerableScreenControllerProvider =
AsyncNotifierProvider<ReaderableScreenController, void>.internal(
ReaderableScreenController.new,
name: r'readerableScreenControllerProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$switchNewTabControllerHash,
: _$readerableScreenControllerHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$SwitchNewTabController = AsyncNotifier<void>;
typedef _$ReaderableScreenController = AsyncNotifier<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
@@ -0,0 +1,23 @@
import 'package:flutter/material.dart';
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/features/geckoview/features/readerview/domain/providers/readerable.dart';
class ReaderAppearanceButton extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final buttonVisible = ref.watch(
appearanceButtonVisibilityProvider
.select((value) => value.valueOrNull ?? false),
);
return FloatingActionButton(
onPressed: buttonVisible
? () async {
await ref.read(readerableServiceProvider).onAppearanceButtonTap();
}
: null,
child: const Icon(MdiIcons.formatFont),
);
}
}
@@ -0,0 +1,82 @@
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: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/domain/providers/readerable.dart';
import 'package:lensai/features/geckoview/features/readerview/presentation/controllers/readerable.dart';
import 'package:lensai/presentation/widgets/animate_gradient_shader.dart';
class ReaderButton extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
final readerChanging = ref.watch(readerableScreenControllerProvider);
final readerabilityState = ref.watch(
selectedTabStateProvider.select(
(state) => state?.readerableState ?? ReaderableState.$default(),
),
);
final readerButtonVisible = ref.watch(
readerButtonVisibilityProvider
.select((value) => value.valueOrNull ?? false),
);
final icon = useMemoized(
() => readerabilityState.active
? Icon(
MdiIcons.bookOpen,
color: Theme.of(context).colorScheme.primary,
)
: const Icon(
MdiIcons.bookOpenOutline,
color: Colors.white,
),
[readerabilityState.active],
);
return Visibility(
visible: readerButtonVisible,
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 15.0,
horizontal: 8.0,
),
child: readerChanging.when(
data: (_) => Visibility(
visible: readerButtonVisible,
child: InkWell(
onTap: readerChanging.isLoading
? null
: () async {
await ref
.read(readerableScreenControllerProvider.notifier)
.toggleReaderView(!readerabilityState.active);
},
child: icon,
),
),
error: (error, stackTrace) => SizedBox.shrink(),
loading: () => AnimateGradientShader(
duration: const Duration(milliseconds: 500),
primaryEnd: Alignment.bottomLeft,
secondaryEnd: Alignment.topRight,
primaryColors: [
colorScheme.primary,
colorScheme.primaryContainer,
],
secondaryColors: [
colorScheme.secondary,
colorScheme.secondaryContainer,
],
child: icon,
),
),
),
);
}
}
@@ -8,14 +8,22 @@ class TabLinkDao extends DatabaseAccessor<TabDatabase> with _$TabLinkDaoMixin {
TabLinkDao(super.db);
Selectable<String> topicTabIds(String? topicId) {
final query = (selectOnly(db.tabLink)
final query = selectOnly(db.tabLink)
..addColumns([db.tabLink.id])
..where(
(topicId == null)
? db.tabLink.topicId.isNull()
: db.tabLink.topicId.equals(topicId),
)
..orderBy([OrderingTerm.asc(db.tabLink.id)]));
..orderBy([OrderingTerm.asc(db.tabLink.id)]);
return query.map((row) => row.read(db.tabLink.id)!);
}
SingleOrNullSelectable<String> tabTopicId(String tabId) {
final query = selectOnly(db.tabLink)
..addColumns([db.tabLink.topicId])
..where(db.tabLink.id.equals(tabId));
return query.map((row) => row.read(db.tabLink.id)!);
}
@@ -9,42 +9,6 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'providers.g.dart';
@Riverpod(keepAlive: true)
class SelectedTopic extends _$SelectedTopic {
void setTopic(String id) {
state = id;
}
void toggleTopic(String id) {
if (state == id) {
clearTopic();
} else {
setTopic(id);
}
}
void clearTopic() {
state = null;
}
@override
String? build() {
return null;
}
}
@Riverpod()
Stream<TopicData?> selectedTopicData(SelectedTopicDataRef ref) {
final db = ref.watch(tabDatabaseProvider);
final selectedTopic = ref.watch(selectedTopicProvider);
if (selectedTopic != null) {
return db.topicDao.getTopicData(selectedTopic).watchSingleOrNull();
}
return Stream.value(null);
}
@Riverpod()
Future<Color> unusedRandomTopicColor(UnusedRandomTopicColorRef ref) async {
final repository = ref.watch(topicRepositoryProvider.notifier);
@@ -67,7 +31,13 @@ Stream<List<TopicDataWithCount>> topicsWithCount(TopicsWithCountRef ref) {
}
@Riverpod()
Stream<List<String>> topicTabIds(TopicTabIdsRef ref, String topicId) {
Stream<List<String>> topicTabIds(TopicTabIdsRef ref, String? topicId) {
final db = ref.watch(tabDatabaseProvider);
return db.tabLinkDao.topicTabIds(topicId).watch();
}
@Riverpod()
Stream<String?> tabTopicId(TabTopicIdRef ref, String tabId) {
final db = ref.watch(tabDatabaseProvider);
return db.tabLinkDao.tabTopicId(tabId).watchSingleOrNull();
}
@@ -6,22 +6,6 @@ part of 'providers.dart';
// RiverpodGenerator
// **************************************************************************
String _$selectedTopicDataHash() => r'fa450784052498014f592c356bde4ff5ec675281';
/// See also [selectedTopicData].
@ProviderFor(selectedTopicData)
final selectedTopicDataProvider =
AutoDisposeStreamProvider<TopicData?>.internal(
selectedTopicData,
name: r'selectedTopicDataProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$selectedTopicDataHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef SelectedTopicDataRef = AutoDisposeStreamProviderRef<TopicData?>;
String _$unusedRandomTopicColorHash() =>
r'099730b0d987cc37bcae4ef5c999cbebfa67d7da';
@@ -56,7 +40,7 @@ final topicsWithCountProvider =
typedef TopicsWithCountRef
= AutoDisposeStreamProviderRef<List<TopicDataWithCount>>;
String _$topicTabIdsHash() => r'43719c70e01cf9e030d75171405ab9650936ddf8';
String _$topicTabIdsHash() => r'f7ce2633804421a22df1efc42eb4d19d440d9fd3';
/// Copied from Dart SDK
class _SystemHash {
@@ -90,7 +74,7 @@ class TopicTabIdsFamily extends Family<AsyncValue<List<String>>> {
/// See also [topicTabIds].
TopicTabIdsProvider call(
String topicId,
String? topicId,
) {
return TopicTabIdsProvider(
topicId,
@@ -125,7 +109,7 @@ class TopicTabIdsFamily extends Family<AsyncValue<List<String>>> {
class TopicTabIdsProvider extends AutoDisposeStreamProvider<List<String>> {
/// See also [topicTabIds].
TopicTabIdsProvider(
String topicId,
String? topicId,
) : this._internal(
(ref) => topicTabIds(
ref as TopicTabIdsRef,
@@ -153,7 +137,7 @@ class TopicTabIdsProvider extends AutoDisposeStreamProvider<List<String>> {
required this.topicId,
}) : super.internal();
final String topicId;
final String? topicId;
@override
Override overrideWith(
@@ -194,7 +178,7 @@ class TopicTabIdsProvider extends AutoDisposeStreamProvider<List<String>> {
mixin TopicTabIdsRef on AutoDisposeStreamProviderRef<List<String>> {
/// The parameter `topicId` of this provider.
String get topicId;
String? get topicId;
}
class _TopicTabIdsProviderElement
@@ -202,23 +186,135 @@ class _TopicTabIdsProviderElement
_TopicTabIdsProviderElement(super.provider);
@override
String get topicId => (origin as TopicTabIdsProvider).topicId;
String? get topicId => (origin as TopicTabIdsProvider).topicId;
}
String _$selectedTopicHash() => r'f4e1c0620971a0b7501ee9aff0d5b41da3130ba0';
String _$tabTopicIdHash() => r'284164ac3acd9be3ba4e15eba99fc8d97602ee84';
/// See also [SelectedTopic].
@ProviderFor(SelectedTopic)
final selectedTopicProvider = NotifierProvider<SelectedTopic, String?>.internal(
SelectedTopic.new,
name: r'selectedTopicProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$selectedTopicHash,
dependencies: null,
allTransitiveDependencies: null,
);
/// See also [tabTopicId].
@ProviderFor(tabTopicId)
const tabTopicIdProvider = TabTopicIdFamily();
typedef _$SelectedTopic = Notifier<String?>;
/// See also [tabTopicId].
class TabTopicIdFamily extends Family<AsyncValue<String?>> {
/// See also [tabTopicId].
const TabTopicIdFamily();
/// See also [tabTopicId].
TabTopicIdProvider call(
String tabId,
) {
return TabTopicIdProvider(
tabId,
);
}
@override
TabTopicIdProvider getProviderOverride(
covariant TabTopicIdProvider 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'tabTopicIdProvider';
}
/// See also [tabTopicId].
class TabTopicIdProvider extends AutoDisposeStreamProvider<String?> {
/// See also [tabTopicId].
TabTopicIdProvider(
String tabId,
) : this._internal(
(ref) => tabTopicId(
ref as TabTopicIdRef,
tabId,
),
from: tabTopicIdProvider,
name: r'tabTopicIdProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$tabTopicIdHash,
dependencies: TabTopicIdFamily._dependencies,
allTransitiveDependencies:
TabTopicIdFamily._allTransitiveDependencies,
tabId: tabId,
);
TabTopicIdProvider._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(TabTopicIdRef provider) create,
) {
return ProviderOverride(
origin: this,
override: TabTopicIdProvider._internal(
(ref) => create(ref as TabTopicIdRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
tabId: tabId,
),
);
}
@override
AutoDisposeStreamProviderElement<String?> createElement() {
return _TabTopicIdProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is TabTopicIdProvider && other.tabId == tabId;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, tabId.hashCode);
return _SystemHash.finish(hash);
}
}
mixin TabTopicIdRef on AutoDisposeStreamProviderRef<String?> {
/// The parameter `tabId` of this provider.
String get tabId;
}
class _TabTopicIdProviderElement
extends AutoDisposeStreamProviderElement<String?> with TabTopicIdRef {
_TabTopicIdProviderElement(super.provider);
@override
String get tabId => (origin as TabTopicIdProvider).tabId;
}
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
@@ -0,0 +1,41 @@
import 'package:lensai/features/geckoview/features/topics/data/models/topic_data.dart';
import 'package:lensai/features/geckoview/features/topics/data/providers.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'selected_topic.g.dart';
@Riverpod(keepAlive: true)
class SelectedTopic extends _$SelectedTopic {
void setTopic(String id) {
state = id;
}
void toggleTopic(String id) {
if (state == id) {
clearTopic();
} else {
setTopic(id);
}
}
void clearTopic() {
state = null;
}
@override
String? build() {
return null;
}
}
@Riverpod()
Stream<TopicData?> selectedTopicData(SelectedTopicDataRef ref) {
final db = ref.watch(tabDatabaseProvider);
final selectedTopic = ref.watch(selectedTopicProvider);
if (selectedTopic != null) {
return db.topicDao.getTopicData(selectedTopic).watchSingleOrNull();
}
return Stream.value(null);
}
@@ -0,0 +1,41 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'selected_topic.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$selectedTopicDataHash() => r'fa450784052498014f592c356bde4ff5ec675281';
/// See also [selectedTopicData].
@ProviderFor(selectedTopicData)
final selectedTopicDataProvider =
AutoDisposeStreamProvider<TopicData?>.internal(
selectedTopicData,
name: r'selectedTopicDataProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$selectedTopicDataHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef SelectedTopicDataRef = AutoDisposeStreamProviderRef<TopicData?>;
String _$selectedTopicHash() => r'f4e1c0620971a0b7501ee9aff0d5b41da3130ba0';
/// See also [SelectedTopic].
@ProviderFor(SelectedTopic)
final selectedTopicProvider = NotifierProvider<SelectedTopic, String?>.internal(
SelectedTopic.new,
name: r'selectedTopicProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$selectedTopicHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$SelectedTopic = Notifier<String?>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
@@ -1,5 +1,6 @@
import 'package:drift/drift.dart';
import 'package:lensai/features/geckoview/domain/repositories/selected_tab.dart';
import 'package:lensai/features/geckoview/domain/providers/selected_tab.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
import 'package:lensai/features/geckoview/features/topics/data/database/database.dart';
import 'package:lensai/features/geckoview/features/topics/data/providers.dart';
@@ -17,7 +18,7 @@ class TabLinkRepository extends _$TabLinkRepository {
//Sync existing tabs
ref.listen(
tabRepositoryProvider.select((tabs) => tabs.keys.toList()),
tabStatesProvider.select((tabs) => tabs.keys.toList()),
(previous, next) async {
//No empty list to avoid data loss
if (next.isNotEmpty) {
@@ -27,7 +28,7 @@ class TabLinkRepository extends _$TabLinkRepository {
);
ref.listen(
selectedTabControllerProvider,
selectedTabProvider,
(previous, next) async {
if (next != null) {
await _db.tabLinkDao.touchTabLink(next, timestamp: DateTime.now());
@@ -6,7 +6,7 @@ part of 'tab_link.dart';
// RiverpodGenerator
// **************************************************************************
String _$tabLinkRepositoryHash() => r'e85d88fadb8133c3ef1e38f0176694a12a749397';
String _$tabLinkRepositoryHash() => r'c4944c1f6792bd725f889b1d6e12946b1439be11';
/// See also [TabLinkRepository].
@ProviderFor(TabLinkRepository)
@@ -4,6 +4,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/features/geckoview/features/topics/data/models/topic_data.dart';
import 'package:lensai/features/geckoview/features/topics/domain/providers.dart';
import 'package:lensai/features/geckoview/features/topics/domain/providers/selected_topic.dart';
import 'package:lensai/features/geckoview/features/topics/domain/repositories/topic.dart';
import 'package:lensai/features/geckoview/features/topics/presentation/widgets/topic_dialog.dart';
import 'package:skeletonizer/skeletonizer.dart';
@@ -148,7 +149,7 @@ class TopicListScreen extends HookConsumerWidget {
),
body: HookConsumer(
builder: (context, ref, child) {
final topicsAsync = ref.watch(topicRepositoryProvider);
final topicsAsync = ref.watch(topicsWithCountProvider);
final selectedTopic = ref.watch(selectedTopicProvider);
return Skeletonizer(
@@ -3,13 +3,13 @@ import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/core/routing/routes.dart';
import 'package:lensai/features/geckoview/features/topics/domain/providers.dart';
import 'package:lensai/features/geckoview/features/topics/domain/repositories/topic.dart';
import 'package:lensai/features/geckoview/features/topics/domain/providers/selected_topic.dart';
import 'package:lensai/presentation/widgets/selectable_chips.dart';
class TopicChips extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final topicsAsync = ref.watch(topicRepositoryProvider);
final topicsAsync = ref.watch(topicsWithCountProvider);
final selectedTopic = ref
.watch(selectedTopicDataProvider.select((value) => value.valueOrNull));
@@ -18,9 +18,9 @@ bool useWhiteForeground(Color backgroundColor, {double bias = 0.0}) {
// New:
final v = sqrt(
pow(backgroundColor.red, 2) * 0.299 +
pow(backgroundColor.green, 2) * 0.587 +
pow(backgroundColor.blue, 2) * 0.114,
pow(backgroundColor.r, 2) * 0.299 +
pow(backgroundColor.g, 2) * 0.587 +
pow(backgroundColor.b, 2) * 0.114,
).round();
return v < (130 + bias);
}
@@ -3,12 +3,11 @@ 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:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/features/geckoview/features/browser/domain/providers.dart';
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/sheets/create_tab.dart';
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/speech_to_text_button.dart';
import 'package:lensai/features/kagi/data/entities/modes.dart';
import 'package:lensai/features/search_browser/domain/providers.dart';
import 'package:lensai/features/search_browser/presentation/widgets/sheets/shared_content_sheet.dart';
import 'package:lensai/features/search_browser/presentation/widgets/speech_to_text_button.dart';
import 'package:lensai/features/search_browser/utils/url_builder.dart'
as uri_builder;
import 'package:lensai/features/kagi/utils/url_builder.dart' as uri_builder;
import 'package:lensai/features/settings/data/models/settings.dart';
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
import 'package:lensai/features/share_intent/domain/entities/shared_content.dart';
@@ -8,9 +8,9 @@ import 'package:lensai/features/bangs/domain/providers.dart';
import 'package:lensai/features/bangs/domain/repositories/data.dart';
import 'package:lensai/features/bangs/presentation/widgets/bang_icon.dart';
import 'package:lensai/features/bangs/presentation/widgets/search_field.dart';
import 'package:lensai/features/search_browser/domain/providers.dart';
import 'package:lensai/features/search_browser/presentation/controllers/bottom_sheet.dart';
import 'package:lensai/features/search_browser/presentation/widgets/sheets/shared_content_sheet.dart';
import 'package:lensai/features/geckoview/features/browser/domain/providers.dart';
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/sheets/create_tab.dart';
import 'package:lensai/features/geckoview/features/controllers/bottom_sheet.dart';
import 'package:lensai/features/share_intent/domain/entities/shared_content.dart';
import 'package:lensai/presentation/widgets/selectable_chips.dart';
@@ -6,10 +6,9 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
import 'package:flutter_pdf_text/flutter_pdf_text.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/sheets/create_tab.dart';
import 'package:lensai/features/kagi/data/entities/modes.dart';
import 'package:lensai/features/search_browser/presentation/widgets/sheets/shared_content_sheet.dart';
import 'package:lensai/features/search_browser/utils/url_builder.dart'
as uri_builder;
import 'package:lensai/features/kagi/utils/url_builder.dart' as uri_builder;
import 'package:lensai/features/settings/data/models/settings.dart';
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
import 'package:lensai/features/share_intent/domain/entities/shared_content.dart';
@@ -8,7 +8,7 @@ Set<HostSource>? parseHostSources(List<String>? input) => input
(list) =>
HostSource.values.firstWhereOrNull((source) => source.name == list),
)
.whereNotNull()
.nonNulls
.toSet();
ThemeMode? parseThemeMode(int? index) {
@@ -1,10 +0,0 @@
import 'package:drift/drift.dart';
abstract interface class ITab {
String get id;
Uri get url;
String? get title;
String? get topicId;
Favicon? get favicon;
Uint8List? get screenshot;
}
@@ -1,16 +0,0 @@
class ConsistentController {
final InAppWebViewController? value;
const ConsistentController(this.value);
@override
// ignore: avoid_dynamic_calls
int get hashCode => value?.platform.id.hashCode ?? -1;
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
if (other is! ConsistentController) return false;
return hashCode == other.hashCode;
}
}
@@ -1,70 +0,0 @@
import 'package:copy_with_extension/copy_with_extension.dart';
import 'package:fast_equatable/fast_equatable.dart';
import 'package:flutter/foundation.dart';
import 'package:lensai/core/uuid.dart';
import 'package:lensai/data/models/web_page_info.dart';
part 'web_view_page.g.dart';
typedef PageHistory = ({bool canGoBack, bool canGoForward});
@CopyWith()
class WebViewPage extends WebPageInfo with FastEquatable implements ITab {
@override
@CopyWithField(immutable: true)
final String id;
final InAppWebViewController? controller;
// ignore: missing_field_in_equatable_props
final SslError? sslError;
@override
final String? topicId;
@override
final Uint8List? screenshot;
final PageHistory pageHistory;
WebViewPage({
required this.id,
required this.controller,
required super.url,
required this.sslError,
required super.title,
required this.topicId,
required super.favicon,
required this.screenshot,
required this.pageHistory,
});
WebViewPage.create({
String? id,
this.controller,
required super.url,
this.sslError,
super.title,
this.topicId,
super.favicon,
this.screenshot,
this.pageHistory = (canGoBack: false, canGoForward: false),
}) : id = id ?? uuid.v7();
@override
bool get cacheHash => true;
@override
List<Object?> get hashParameters => [
id,
controller,
super.url,
sslError?.toString(),
title,
topicId,
favicon?.toString(),
screenshot,
pageHistory,
];
}
@@ -1,138 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'web_view_page.dart';
// **************************************************************************
// CopyWithGenerator
// **************************************************************************
abstract class _$WebViewPageCWProxy {
WebViewPage controller(InvalidType controller);
WebViewPage url(Uri url);
WebViewPage sslError(InvalidType sslError);
WebViewPage title(String? title);
WebViewPage topicId(String? topicId);
WebViewPage favicon(BrowserIcon? favicon);
WebViewPage screenshot(Uint8List? screenshot);
WebViewPage pageHistory(({bool canGoBack, bool canGoForward}) pageHistory);
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `WebViewPage(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
///
/// Usage
/// ```dart
/// WebViewPage(...).copyWith(id: 12, name: "My name")
/// ````
WebViewPage call({
InvalidType? controller,
Uri? url,
InvalidType? sslError,
String? title,
String? topicId,
BrowserIcon? favicon,
Uint8List? screenshot,
({bool canGoBack, bool canGoForward})? pageHistory,
});
}
/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfWebViewPage.copyWith(...)`. Additionally contains functions for specific fields e.g. `instanceOfWebViewPage.copyWith.fieldName(...)`
class _$WebViewPageCWProxyImpl implements _$WebViewPageCWProxy {
const _$WebViewPageCWProxyImpl(this._value);
final WebViewPage _value;
@override
WebViewPage controller(InvalidType controller) =>
this(controller: controller);
@override
WebViewPage url(Uri url) => this(url: url);
@override
WebViewPage sslError(InvalidType sslError) => this(sslError: sslError);
@override
WebViewPage title(String? title) => this(title: title);
@override
WebViewPage topicId(String? topicId) => this(topicId: topicId);
@override
WebViewPage favicon(BrowserIcon? favicon) => this(favicon: favicon);
@override
WebViewPage screenshot(Uint8List? screenshot) => this(screenshot: screenshot);
@override
WebViewPage pageHistory(({bool canGoBack, bool canGoForward}) pageHistory) =>
this(pageHistory: pageHistory);
@override
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `WebViewPage(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
///
/// Usage
/// ```dart
/// WebViewPage(...).copyWith(id: 12, name: "My name")
/// ````
WebViewPage call({
Object? controller = const $CopyWithPlaceholder(),
Object? url = const $CopyWithPlaceholder(),
Object? sslError = const $CopyWithPlaceholder(),
Object? title = const $CopyWithPlaceholder(),
Object? topicId = const $CopyWithPlaceholder(),
Object? favicon = const $CopyWithPlaceholder(),
Object? screenshot = const $CopyWithPlaceholder(),
Object? pageHistory = const $CopyWithPlaceholder(),
}) {
return WebViewPage(
id: _value.id,
controller:
controller == const $CopyWithPlaceholder() || controller == null
? _value.controller
// ignore: cast_nullable_to_non_nullable
: controller as InvalidType,
url: url == const $CopyWithPlaceholder() || url == null
? _value.url
// ignore: cast_nullable_to_non_nullable
: url as Uri,
sslError: sslError == const $CopyWithPlaceholder() || sslError == null
? _value.sslError
// ignore: cast_nullable_to_non_nullable
: sslError as InvalidType,
title: title == const $CopyWithPlaceholder()
? _value.title
// ignore: cast_nullable_to_non_nullable
: title as String?,
topicId: topicId == const $CopyWithPlaceholder()
? _value.topicId
// ignore: cast_nullable_to_non_nullable
: topicId as String?,
favicon: favicon == const $CopyWithPlaceholder()
? _value.favicon
// ignore: cast_nullable_to_non_nullable
: favicon as BrowserIcon?,
screenshot: screenshot == const $CopyWithPlaceholder()
? _value.screenshot
// ignore: cast_nullable_to_non_nullable
: screenshot as Uint8List?,
pageHistory:
pageHistory == const $CopyWithPlaceholder() || pageHistory == null
? _value.pageHistory
// ignore: cast_nullable_to_non_nullable
: pageHistory as ({bool canGoBack, bool canGoForward}),
);
}
}
extension $WebViewPageCopyWith on WebViewPage {
/// Returns a callable class that can be used as follows: `instanceOfWebViewPage.copyWith(...)` or like so:`instanceOfWebViewPage.copyWith.fieldName(...)`.
// ignore: library_private_types_in_public_api
_$WebViewPageCWProxy get copyWith => _$WebViewPageCWProxyImpl(this);
}
@@ -1,56 +0,0 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:lensai/features/content_block/domain/repositories/host.dart';
import 'package:lensai/features/settings/data/models/settings.dart';
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
import 'package:lensai/features/geckoview/features/topics/domain/providers.dart';
import 'package:lensai/features/geckoview/features/topics/domain/repositories/tab_link.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:universal_io/io.dart';
part 'providers.g.dart';
@Riverpod(keepAlive: true)
Stream<Set<String>?> blockContentHosts(BlockContentHostsRef ref) {
final hostRepository = ref.watch(hostRepositoryProvider.notifier);
final enableContentBlocking = ref.watch(
settingsRepositoryProvider.select(
(value) =>
(value.valueOrNull ?? Settings.withDefaults()).enableContentBlocking,
),
);
final enableHostList = ref.watch(
settingsRepositoryProvider.select(
(value) => (value.valueOrNull ?? Settings.withDefaults()).enableHostList,
),
);
if (!enableContentBlocking || enableHostList.isEmpty) {
return Stream.value(null);
}
return hostRepository
.watchHosts(sources: enableHostList)
.map((hosts) => hosts.toSet());
}
@Riverpod(keepAlive: true)
Future<String> readerabilityScript(ReaderabilityScriptRef ref) {
return rootBundle.load('assets/scripts/readability.min.js.gz').then(
(value) => compute(
(message) => utf8.decode(gzip.decode(message)),
value.buffer.asUint8List(),
),
);
}
@Riverpod()
List<String> activeTabs(ActiveTabsRef ref) {
final topic = ref.watch(selectedTopicProvider);
return ref.watch(
topicTabRepositoryProvider(topic)
.select((value) => value.valueOrNull ?? []),
);
}
@@ -1,55 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'providers.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$blockContentHostsHash() => r'41855093d8376152244ae37f98e66eea7c74aa32';
/// See also [blockContentHosts].
@ProviderFor(blockContentHosts)
final blockContentHostsProvider = StreamProvider<Set<String>?>.internal(
blockContentHosts,
name: r'blockContentHostsProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$blockContentHostsHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef BlockContentHostsRef = StreamProviderRef<Set<String>?>;
String _$readerabilityScriptHash() =>
r'6fdd1aa920bc58c35ef9888158e572e25793870a';
/// See also [readerabilityScript].
@ProviderFor(readerabilityScript)
final readerabilityScriptProvider = FutureProvider<String>.internal(
readerabilityScript,
name: r'readerabilityScriptProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$readerabilityScriptHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef ReaderabilityScriptRef = FutureProviderRef<String>;
String _$activeTabsHash() => r'd1ef2ae32d8707f7cc0902dd031e8302a63f50d4';
/// See also [activeTabs].
@ProviderFor(activeTabs)
final activeTabsProvider = AutoDisposeProvider<List<String>>.internal(
activeTabs,
name: r'activeTabsProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$activeTabsHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef ActiveTabsRef = AutoDisposeProviderRef<List<String>>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
@@ -1,179 +0,0 @@
import 'package:copy_with_extension/copy_with_extension.dart';
import 'package:drift/drift.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/core/logger.dart';
import 'package:lensai/features/geckoview/features/topics/data/providers.dart';
import 'package:lensai/features/geckoview/features/topics/domain/providers.dart';
import 'package:lensai/features/geckoview/features/topics/domain/repositories/tab_link.dart';
import 'package:lensai/features/web_view/domain/entities/consistent_controller.dart';
import 'package:lensai/features/web_view/domain/entities/web_view_page.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'web_view.g.dart';
abstract class _TabStateCopyProxy {
Future<void> copyWith({
InAppWebViewController? controller,
Uri? url,
SslError? sslError,
String? title,
String? topicId,
Favicon? favicon,
Uint8List? screenshot,
({bool canGoBack, bool canGoForward})? pageHistory,
});
}
@Riverpod()
class WebViewTabController extends _$WebViewTabController {
String? _requestedTab;
@override
String? build() {
final webViewTabs = ref.watch(
tabRepositoryProvider.select(
(value) => value.value ?? [],
),
);
if (_requestedTab != null) {
if (webViewTabs.any((tab) => tab.id == _requestedTab)) {
return _requestedTab;
} else if (webViewTabs.isNotEmpty) {
final topic = ref.read(selectedTopicProvider);
return webViewTabs.where((tab) => tab.topicId == topic).lastOrNull?.id;
}
}
return null;
}
void showTab(String? id) {
_requestedTab = id;
ref.invalidateSelf();
}
}
@Riverpod()
class TabState extends _$TabState implements _TabStateCopyProxy {
KeepAliveLink? _aliveLink;
@override
WebViewPage? build(String tabId) {
final tabAsync = ref.watch(tabDataProvider(tabId));
if (tabAsync.hasValue) {
final tab = tabAsync.valueOrNull;
final current = stateOrNull ??
WebViewPage.create(
id: tabId,
url: tab?.url ?? Uri.https('localhost'),
);
if (tab != null) {
_aliveLink ??= ref.keepAlive();
return current.copyWith(
topicId: tab.topicId,
url: tab.url,
title: tab.title,
screenshot: tab.screenshot,
);
} else {
_aliveLink?.close();
_aliveLink = null;
return current;
}
}
return null;
}
Future<void> updateScreenshot() async {
final screenshot = await state?.controller
?.takeScreenshot(
screenshotConfiguration: ScreenshotConfiguration(
compressFormat: CompressFormat.JPEG,
quality: 20,
),
)
.timeout(
const Duration(milliseconds: 1500),
onTimeout: () {
logger.w('Screenshot timed out');
return null;
},
);
await copyWith(screenshot: screenshot);
}
@override
Future<void> copyWith({
Object? controller = const $CopyWithPlaceholder(),
Object? url = const $CopyWithPlaceholder(),
Object? sslError = const $CopyWithPlaceholder(),
Object? title = const $CopyWithPlaceholder(),
Object? topicId = const $CopyWithPlaceholder(),
Object? favicon = const $CopyWithPlaceholder(),
Object? screenshot = const $CopyWithPlaceholder(),
Object? pageHistory = const $CopyWithPlaceholder(),
}) async {
if (state != null) {
final current = state!;
state = WebViewPage(
id: current.id,
controller: controller == const $CopyWithPlaceholder()
? current.controller
: controller as InAppWebViewController?,
url: current.url,
sslError: sslError == const $CopyWithPlaceholder()
? current.sslError
: sslError as SslError?,
title: current.title,
topicId: current.topicId,
favicon: favicon == const $CopyWithPlaceholder()
? current.favicon
: favicon as Favicon?,
screenshot: current.screenshot,
pageHistory:
pageHistory == const $CopyWithPlaceholder() || pageHistory == null
? current.pageHistory
: pageHistory as ({bool canGoBack, bool canGoForward}),
);
await ref.read(tabRepositoryProvider.notifier).updateTab(
current.id,
url: url == const $CopyWithPlaceholder() || url == null
? const Value.absent()
: Value(url as Uri),
title: title == const $CopyWithPlaceholder()
? const Value.absent()
: Value(title as String?),
topicId: topicId == const $CopyWithPlaceholder()
? const Value.absent()
: Value(topicId as String?),
screenshot: screenshot == const $CopyWithPlaceholder()
? const Value.absent()
: Value(screenshot as Uint8List?),
);
}
}
}
@Riverpod()
InAppWebViewController? webViewController(
WebViewControllerRef ref,
String tabId,
) {
return ref
.watch(
tabStateProvider(tabId)
.select((value) => ConsistentController(value?.controller)),
)
.value;
}
@@ -1,318 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'web_view.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$webViewControllerHash() => r'f35fb13d46fcb04cee5676089f0cb8d5ca21e89b';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
/// See also [webViewController].
@ProviderFor(webViewController)
const webViewControllerProvider = WebViewControllerFamily();
/// See also [webViewController].
class WebViewControllerFamily extends Family<InAppWebViewController?> {
/// See also [webViewController].
const WebViewControllerFamily();
/// See also [webViewController].
WebViewControllerProvider call(
String tabId,
) {
return WebViewControllerProvider(
tabId,
);
}
@override
WebViewControllerProvider getProviderOverride(
covariant WebViewControllerProvider 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'webViewControllerProvider';
}
/// See also [webViewController].
class WebViewControllerProvider
extends AutoDisposeProvider<InAppWebViewController?> {
/// See also [webViewController].
WebViewControllerProvider(
String tabId,
) : this._internal(
(ref) => webViewController(
ref as WebViewControllerRef,
tabId,
),
from: webViewControllerProvider,
name: r'webViewControllerProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$webViewControllerHash,
dependencies: WebViewControllerFamily._dependencies,
allTransitiveDependencies:
WebViewControllerFamily._allTransitiveDependencies,
tabId: tabId,
);
WebViewControllerProvider._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(
InAppWebViewController? Function(WebViewControllerRef provider) create,
) {
return ProviderOverride(
origin: this,
override: WebViewControllerProvider._internal(
(ref) => create(ref as WebViewControllerRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
tabId: tabId,
),
);
}
@override
AutoDisposeProviderElement<InAppWebViewController?> createElement() {
return _WebViewControllerProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is WebViewControllerProvider && other.tabId == tabId;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, tabId.hashCode);
return _SystemHash.finish(hash);
}
}
mixin WebViewControllerRef on AutoDisposeProviderRef<InAppWebViewController?> {
/// The parameter `tabId` of this provider.
String get tabId;
}
class _WebViewControllerProviderElement
extends AutoDisposeProviderElement<InAppWebViewController?>
with WebViewControllerRef {
_WebViewControllerProviderElement(super.provider);
@override
String get tabId => (origin as WebViewControllerProvider).tabId;
}
String _$webViewTabControllerHash() =>
r'cf1b766aa84472040b56a7b4d9ce1fd1606ed5e1';
/// See also [WebViewTabController].
@ProviderFor(WebViewTabController)
final webViewTabControllerProvider =
AutoDisposeNotifierProvider<WebViewTabController, String?>.internal(
WebViewTabController.new,
name: r'webViewTabControllerProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$webViewTabControllerHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$WebViewTabController = AutoDisposeNotifier<String?>;
String _$tabStateHash() => r'9ce6b0d99e5032f534c97482d6140dda4b14fb07';
abstract class _$TabState extends BuildlessAutoDisposeNotifier<WebViewPage?> {
late final String tabId;
WebViewPage? build(
String tabId,
);
}
/// See also [TabState].
@ProviderFor(TabState)
const tabStateProvider = TabStateFamily();
/// See also [TabState].
class TabStateFamily extends Family<WebViewPage?> {
/// See also [TabState].
const TabStateFamily();
/// See also [TabState].
TabStateProvider call(
String tabId,
) {
return TabStateProvider(
tabId,
);
}
@override
TabStateProvider getProviderOverride(
covariant TabStateProvider 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'tabStateProvider';
}
/// See also [TabState].
class TabStateProvider
extends AutoDisposeNotifierProviderImpl<TabState, WebViewPage?> {
/// See also [TabState].
TabStateProvider(
String tabId,
) : this._internal(
() => TabState()..tabId = tabId,
from: tabStateProvider,
name: r'tabStateProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$tabStateHash,
dependencies: TabStateFamily._dependencies,
allTransitiveDependencies: TabStateFamily._allTransitiveDependencies,
tabId: tabId,
);
TabStateProvider._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
WebViewPage? runNotifierBuild(
covariant TabState notifier,
) {
return notifier.build(
tabId,
);
}
@override
Override overrideWith(TabState Function() create) {
return ProviderOverride(
origin: this,
override: TabStateProvider._internal(
() => create()..tabId = tabId,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
tabId: tabId,
),
);
}
@override
AutoDisposeNotifierProviderElement<TabState, WebViewPage?> createElement() {
return _TabStateProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is TabStateProvider && other.tabId == tabId;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, tabId.hashCode);
return _SystemHash.finish(hash);
}
}
mixin TabStateRef on AutoDisposeNotifierProviderRef<WebViewPage?> {
/// The parameter `tabId` of this provider.
String get tabId;
}
class _TabStateProviderElement
extends AutoDisposeNotifierProviderElement<TabState, WebViewPage?>
with TabStateRef {
_TabStateProviderElement(super.provider);
@override
String get tabId => (origin as TabStateProvider).tabId;
}
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
@@ -1,64 +0,0 @@
import 'package:lensai/features/geckoview/features/topics/data/providers.dart';
import 'package:lensai/features/web_view/domain/repositories/web_view.dart';
import 'package:lensai/features/web_view/presentation/services/readerability_script.dart';
import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'readerability.g.dart';
@Riverpod()
class ReaderabilityController extends _$ReaderabilityController {
late ReaderabilityScriptService _service;
late KeepAliveLink _aliveLink;
@override
AsyncValue<({bool readerable, bool applied})> build(
String tabId,
) {
_service = ref.watch(readerabilityScriptServiceProvider(tabId).notifier);
_aliveLink = ref.keepAlive();
ref.listen(
isTabExistingProvider(tabId),
(previous, next) {
if (next.valueOrNull == false) {
_aliveLink.close();
}
},
);
return const AsyncLoading();
}
Future<bool> checkReaderable() async {
final applied = state.valueOrNull?.applied ?? false;
state = const AsyncLoading();
final result = await AsyncValue.guard(() async {
return (readerable: await _service.isReaderable(), applied: applied);
});
state = result;
return result.valueOrNull?.readerable ?? false;
}
Future<void> toggleReaderable() async {
final applied = state.valueOrNull?.applied ?? false;
final readerable = state.valueOrNull?.readerable ?? false;
state = const AsyncLoading();
state = await AsyncValue.guard(() async {
if (applied) {
await ref.read(webViewControllerProvider(tabId))?.reload();
} else {
await _service.applyReaderable();
}
return (readerable: readerable, applied: !applied);
});
}
void reset() {
state = const AsyncLoading();
}
}
@@ -1,178 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'readerability.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$readerabilityControllerHash() =>
r'a1c8da3a9cf48300089e390a4fcae89862a6ec1a';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
abstract class _$ReaderabilityController extends BuildlessAutoDisposeNotifier<
AsyncValue<({bool readerable, bool applied})>> {
late final String tabId;
AsyncValue<({bool readerable, bool applied})> build(
String tabId,
);
}
/// See also [ReaderabilityController].
@ProviderFor(ReaderabilityController)
const readerabilityControllerProvider = ReaderabilityControllerFamily();
/// See also [ReaderabilityController].
class ReaderabilityControllerFamily
extends Family<AsyncValue<({bool readerable, bool applied})>> {
/// See also [ReaderabilityController].
const ReaderabilityControllerFamily();
/// See also [ReaderabilityController].
ReaderabilityControllerProvider call(
String tabId,
) {
return ReaderabilityControllerProvider(
tabId,
);
}
@override
ReaderabilityControllerProvider getProviderOverride(
covariant ReaderabilityControllerProvider 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'readerabilityControllerProvider';
}
/// See also [ReaderabilityController].
class ReaderabilityControllerProvider extends AutoDisposeNotifierProviderImpl<
ReaderabilityController, AsyncValue<({bool readerable, bool applied})>> {
/// See also [ReaderabilityController].
ReaderabilityControllerProvider(
String tabId,
) : this._internal(
() => ReaderabilityController()..tabId = tabId,
from: readerabilityControllerProvider,
name: r'readerabilityControllerProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$readerabilityControllerHash,
dependencies: ReaderabilityControllerFamily._dependencies,
allTransitiveDependencies:
ReaderabilityControllerFamily._allTransitiveDependencies,
tabId: tabId,
);
ReaderabilityControllerProvider._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
AsyncValue<({bool readerable, bool applied})> runNotifierBuild(
covariant ReaderabilityController notifier,
) {
return notifier.build(
tabId,
);
}
@override
Override overrideWith(ReaderabilityController Function() create) {
return ProviderOverride(
origin: this,
override: ReaderabilityControllerProvider._internal(
() => create()..tabId = tabId,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
tabId: tabId,
),
);
}
@override
AutoDisposeNotifierProviderElement<ReaderabilityController,
AsyncValue<({bool readerable, bool applied})>> createElement() {
return _ReaderabilityControllerProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is ReaderabilityControllerProvider && other.tabId == tabId;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, tabId.hashCode);
return _SystemHash.finish(hash);
}
}
mixin ReaderabilityControllerRef on AutoDisposeNotifierProviderRef<
AsyncValue<({bool readerable, bool applied})>> {
/// The parameter `tabId` of this provider.
String get tabId;
}
class _ReaderabilityControllerProviderElement
extends AutoDisposeNotifierProviderElement<ReaderabilityController,
AsyncValue<({bool readerable, bool applied})>>
with ReaderabilityControllerRef {
_ReaderabilityControllerProviderElement(super.provider);
@override
String get tabId => (origin as ReaderabilityControllerProvider).tabId;
}
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
@@ -1,32 +0,0 @@
import 'package:lensai/features/geckoview/features/topics/domain/providers.dart';
import 'package:lensai/features/geckoview/features/topics/domain/repositories/tab_link.dart';
import 'package:lensai/features/web_view/domain/entities/web_view_page.dart';
import 'package:lensai/features/web_view/domain/repositories/web_view.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'switch_new_tab.g.dart';
@Riverpod(keepAlive: true)
class SwitchNewTabController extends _$SwitchNewTabController {
@override
FutureOr<void> build() {}
Future<void> add(Uri url) async {
state = const AsyncLoading();
state = await AsyncValue.guard(
() async {
final topic = ref.read(selectedTopicProvider);
final newTab = WebViewPage.create(
url: WebUri.uri(url),
topicId: topic,
);
await ref
.read(topicTabRepositoryProvider(topic).notifier)
.addTab(newTab);
ref.read(webViewTabControllerProvider.notifier).showTab(newTab.id);
},
);
}
}
@@ -1,70 +0,0 @@
import 'package:lensai/features/settings/data/models/settings.dart';
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
import 'package:lensai/features/web_view/domain/providers.dart';
import 'package:lensai/features/web_view/domain/repositories/web_view.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'readerability_script.g.dart';
@Riverpod()
class ReaderabilityScriptService extends _$ReaderabilityScriptService {
late InAppWebViewController? _controller;
late Future<String> _readerabilityScript;
late bool _enableReadability;
@override
Future<void> build(String tabId) async {
_controller = ref.watch(webViewControllerProvider(tabId));
_readerabilityScript = ref.watch(readerabilityScriptProvider.future);
_enableReadability = ref.watch(
settingsRepositoryProvider.select(
(value) =>
(value.valueOrNull ?? Settings.withDefaults()).enableReadability,
),
);
}
Future<void> _injectScript() async {
if (_controller != null) {
await _readerabilityScript
.then((script) => _controller!.evaluateJavascript(source: script));
}
}
Future<void> _ensureScriptInjected() async {
if (_controller != null) {
final injected = await _controller!.evaluateJavascript(
source:
"(typeof window !== 'undefined' && typeof window.isReaderable === 'function')",
) as bool;
if (!injected) {
await _injectScript();
}
}
}
Future<bool> isReaderable() async {
if (_enableReadability) {
await _ensureScriptInjected();
if (_controller != null) {
return await _controller!.evaluateJavascript(
source: 'isReaderable();',
) as bool;
}
}
return false;
}
Future<void> applyReaderable() async {
if (_enableReadability) {
await _ensureScriptInjected();
if (_controller != null) {
await _controller!.evaluateJavascript(source: 'applyReaderable();');
}
}
}
}
File diff suppressed because it is too large Load Diff
@@ -20,10 +20,10 @@ class WebsiteTitleTile extends HookConsumerWidget {
child: pageInfoAsync.when(
data: (info) {
return ListTile(
leading: FaviconImage(
favicon: info.favicon,
url: info.url,
size: 24,
leading: RawImage(
image: info.favicon?.image.value,
height: 24,
width: 24,
),
contentPadding: EdgeInsets.zero,
title: Text(info.title ?? 'Unknown Title'),
@@ -38,10 +38,10 @@ class WebsiteTitleTile extends HookConsumerWidget {
},
loading: () => (precachedInfo != null)
? ListTile(
leading: FaviconImage(
favicon: precachedInfo!.favicon,
url: precachedInfo!.url,
size: 24,
leading: RawImage(
image: precachedInfo!.favicon?.image.value,
height: 24,
width: 24,
),
contentPadding: EdgeInsets.zero,
title: Text(precachedInfo!.title ?? 'Unknown Title'),