improved tab & topic handling
This commit is contained in:
@@ -1,19 +1,19 @@
|
||||
import 'package:copy_with_extension/copy_with_extension.dart';
|
||||
import 'package:fast_equatable/fast_equatable.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
import 'package:lensai/core/uuid.dart';
|
||||
import 'package:lensai/domain/entities/web_page_info.dart';
|
||||
import 'package:lensai/features/web_view/domain/entities/abstract/tab.dart';
|
||||
|
||||
part 'web_view_page.g.dart';
|
||||
|
||||
typedef PageHistory = ({bool canGoBack, bool canGoForward});
|
||||
|
||||
@CopyWith()
|
||||
class WebViewPage extends WebPageInfo with FastEquatable implements ITab {
|
||||
//@CopyWithField(immutable: true)
|
||||
final Key key;
|
||||
|
||||
@override
|
||||
//@CopyWithField(immutable: true)
|
||||
@CopyWithField(immutable: true)
|
||||
final String id;
|
||||
|
||||
final InAppWebViewController? controller;
|
||||
@@ -30,7 +30,6 @@ class WebViewPage extends WebPageInfo with FastEquatable implements ITab {
|
||||
final PageHistory pageHistory;
|
||||
|
||||
WebViewPage({
|
||||
required this.key,
|
||||
required this.id,
|
||||
required this.controller,
|
||||
required super.url,
|
||||
@@ -52,15 +51,13 @@ class WebViewPage extends WebPageInfo with FastEquatable implements ITab {
|
||||
super.favicon,
|
||||
this.screenshot,
|
||||
this.pageHistory = (canGoBack: false, canGoForward: false),
|
||||
}) : key = GlobalKey(),
|
||||
id = id ?? uuid.v7();
|
||||
}) : id = id ?? uuid.v7();
|
||||
|
||||
@override
|
||||
bool get cacheHash => true;
|
||||
|
||||
@override
|
||||
List<Object?> get hashParameters => [
|
||||
key,
|
||||
id,
|
||||
controller,
|
||||
super.url,
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'web_view_page.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// CopyWithGenerator
|
||||
// **************************************************************************
|
||||
|
||||
abstract class _$WebViewPageCWProxy {
|
||||
WebViewPage controller(InAppWebViewController? controller);
|
||||
|
||||
WebViewPage url(Uri url);
|
||||
|
||||
WebViewPage sslError(SslError? sslError);
|
||||
|
||||
WebViewPage title(String? title);
|
||||
|
||||
WebViewPage topicId(String? topicId);
|
||||
|
||||
WebViewPage favicon(Favicon? 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({
|
||||
InAppWebViewController? controller,
|
||||
Uri? url,
|
||||
SslError? sslError,
|
||||
String? title,
|
||||
String? topicId,
|
||||
Favicon? 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(InAppWebViewController? controller) =>
|
||||
this(controller: controller);
|
||||
|
||||
@override
|
||||
WebViewPage url(Uri url) => this(url: url);
|
||||
|
||||
@override
|
||||
WebViewPage sslError(SslError? sslError) => this(sslError: sslError);
|
||||
|
||||
@override
|
||||
WebViewPage title(String? title) => this(title: title);
|
||||
|
||||
@override
|
||||
WebViewPage topicId(String? topicId) => this(topicId: topicId);
|
||||
|
||||
@override
|
||||
WebViewPage favicon(Favicon? 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()
|
||||
? _value.controller
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: controller as InAppWebViewController?,
|
||||
url: url == const $CopyWithPlaceholder() || url == null
|
||||
? _value.url
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: url as Uri,
|
||||
sslError: sslError == const $CopyWithPlaceholder()
|
||||
? _value.sslError
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: sslError as SslError?,
|
||||
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 Favicon?,
|
||||
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);
|
||||
}
|
||||
@@ -5,6 +5,8 @@ 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/topics/domain/providers.dart';
|
||||
import 'package:lensai/features/topics/domain/repositories/tab.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:universal_io/io.dart';
|
||||
|
||||
@@ -43,3 +45,12 @@ Future<String> readerabilityScript(ReaderabilityScriptRef ref) {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
List<String> activeTabs(ActiveTabsRef ref) {
|
||||
final topic = ref.watch(selectedTopicProvider);
|
||||
return ref.watch(
|
||||
topicTabRepositoryProvider(topic)
|
||||
.select((value) => value.valueOrNull ?? []),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -37,5 +37,19 @@ final readerabilityScriptProvider = FutureProvider<String>.internal(
|
||||
);
|
||||
|
||||
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,59 +1,179 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:copy_with_extension/copy_with_extension.dart';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/core/logger.dart';
|
||||
import 'package:lensai/features/topics/data/providers.dart';
|
||||
import 'package:lensai/features/topics/domain/providers.dart';
|
||||
import 'package:lensai/features/topics/domain/repositories/tab.dart';
|
||||
import 'package:lensai/features/web_view/domain/entities/consistent_controller.dart';
|
||||
import 'package:lensai/features/web_view/presentation/controllers/readerability.dart';
|
||||
import 'package:lensai/features/web_view/presentation/widgets/web_view.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';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
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 {
|
||||
late Map<String, WebView> _webViewTabs;
|
||||
String? _requestedTab;
|
||||
|
||||
@override
|
||||
WebView? build() {
|
||||
_webViewTabs = ref.watch(webViewRepositoryProvider);
|
||||
return (stateOrNull != null)
|
||||
? _webViewTabs[stateOrNull!.tabId] ?? _webViewTabs.values.lastOrNull
|
||||
: null;
|
||||
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) {
|
||||
state = (id != null) ? _webViewTabs[id] : null;
|
||||
_requestedTab = id;
|
||||
ref.invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class WebViewRepository extends _$WebViewRepository {
|
||||
@Riverpod()
|
||||
class TabState extends _$TabState implements _TabStateCopyProxy {
|
||||
KeepAliveLink? _aliveLink;
|
||||
|
||||
@override
|
||||
Map<String, WebView> build() {
|
||||
return stateOrNull ?? {};
|
||||
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;
|
||||
}
|
||||
|
||||
void _disposeReaderability(String id) {
|
||||
final controller = state[id]?.currentController;
|
||||
if (controller != null) {
|
||||
ref
|
||||
.read(
|
||||
readerabilityControllerProvider(ConsistentController(controller))
|
||||
.notifier,
|
||||
)
|
||||
.dispose();
|
||||
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?),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void addTab(WebView webView) {
|
||||
state = {...state, webView.tabId: webView};
|
||||
}
|
||||
|
||||
void closeTab(String id) {
|
||||
_disposeReaderability(id);
|
||||
state = Map.of(state)..remove(id);
|
||||
}
|
||||
|
||||
void closeAllTabs() {
|
||||
state.keys.forEach(_disposeReaderability);
|
||||
state = {};
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
InAppWebViewController? webViewController(
|
||||
WebViewControllerRef ref,
|
||||
String tabId,
|
||||
) {
|
||||
return ref
|
||||
.watch(
|
||||
tabStateProvider(tabId)
|
||||
.select((value) => ConsistentController(value?.controller)),
|
||||
)
|
||||
.value;
|
||||
}
|
||||
|
||||
@@ -6,13 +6,164 @@ 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'3f1d7460295c6399382881ee56c4ed7f1fd5fe03';
|
||||
r'cf1b766aa84472040b56a7b4d9ce1fd1606ed5e1';
|
||||
|
||||
/// See also [WebViewTabController].
|
||||
@ProviderFor(WebViewTabController)
|
||||
final webViewTabControllerProvider =
|
||||
NotifierProvider<WebViewTabController, WebView?>.internal(
|
||||
AutoDisposeNotifierProvider<WebViewTabController, String?>.internal(
|
||||
WebViewTabController.new,
|
||||
name: r'webViewTabControllerProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
@@ -22,22 +173,146 @@ final webViewTabControllerProvider =
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$WebViewTabController = Notifier<WebView?>;
|
||||
String _$webViewRepositoryHash() => r'dae5e1527ab4ad27f6a882fb63d8e88cd812b20b';
|
||||
typedef _$WebViewTabController = AutoDisposeNotifier<String?>;
|
||||
String _$tabStateHash() => r'9ce6b0d99e5032f534c97482d6140dda4b14fb07';
|
||||
|
||||
/// See also [WebViewRepository].
|
||||
@ProviderFor(WebViewRepository)
|
||||
final webViewRepositoryProvider =
|
||||
NotifierProvider<WebViewRepository, Map<String, WebView>>.internal(
|
||||
WebViewRepository.new,
|
||||
name: r'webViewRepositoryProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$webViewRepositoryHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
abstract class _$TabState extends BuildlessAutoDisposeNotifier<WebViewPage?> {
|
||||
late final String tabId;
|
||||
|
||||
typedef _$WebViewRepository = Notifier<Map<String, WebView>>;
|
||||
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,5 +1,5 @@
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
import 'package:lensai/features/web_view/domain/entities/consistent_controller.dart';
|
||||
import 'package:lensai/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';
|
||||
@@ -8,19 +8,25 @@ part 'readerability.g.dart';
|
||||
|
||||
@Riverpod()
|
||||
class ReaderabilityController extends _$ReaderabilityController {
|
||||
late InAppWebViewController? _controller;
|
||||
late ReaderabilityScriptService _service;
|
||||
late KeepAliveLink _aliveLink;
|
||||
|
||||
@override
|
||||
AsyncValue<({bool readerable, bool applied})> build(
|
||||
ConsistentController controller,
|
||||
String tabId,
|
||||
) {
|
||||
_controller = controller.value;
|
||||
_service =
|
||||
ref.watch(readerabilityScriptServiceProvider(controller).notifier);
|
||||
_service = ref.watch(readerabilityScriptServiceProvider(tabId).notifier);
|
||||
_aliveLink = ref.keepAlive();
|
||||
|
||||
ref.listen(
|
||||
isTabExistingProvider(tabId),
|
||||
(previous, next) {
|
||||
if (next.valueOrNull == false) {
|
||||
_aliveLink.close();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return const AsyncLoading();
|
||||
}
|
||||
|
||||
@@ -43,7 +49,7 @@ class ReaderabilityController extends _$ReaderabilityController {
|
||||
state = const AsyncLoading();
|
||||
state = await AsyncValue.guard(() async {
|
||||
if (applied) {
|
||||
await _controller?.reload();
|
||||
await ref.read(webViewControllerProvider(tabId))?.reload();
|
||||
} else {
|
||||
await _service.applyReaderable();
|
||||
}
|
||||
@@ -55,8 +61,4 @@ class ReaderabilityController extends _$ReaderabilityController {
|
||||
void reset() {
|
||||
state = const AsyncLoading();
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_aliveLink.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'readerability.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$readerabilityControllerHash() =>
|
||||
r'1237e57abce82f71dbc66b6ab46f50dad9e3ddf9';
|
||||
r'a1c8da3a9cf48300089e390a4fcae89862a6ec1a';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
@@ -32,10 +32,10 @@ class _SystemHash {
|
||||
|
||||
abstract class _$ReaderabilityController extends BuildlessAutoDisposeNotifier<
|
||||
AsyncValue<({bool readerable, bool applied})>> {
|
||||
late final ConsistentController controller;
|
||||
late final String tabId;
|
||||
|
||||
AsyncValue<({bool readerable, bool applied})> build(
|
||||
ConsistentController controller,
|
||||
String tabId,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -51,10 +51,10 @@ class ReaderabilityControllerFamily
|
||||
|
||||
/// See also [ReaderabilityController].
|
||||
ReaderabilityControllerProvider call(
|
||||
ConsistentController controller,
|
||||
String tabId,
|
||||
) {
|
||||
return ReaderabilityControllerProvider(
|
||||
controller,
|
||||
tabId,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ class ReaderabilityControllerFamily
|
||||
covariant ReaderabilityControllerProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.controller,
|
||||
provider.tabId,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -87,9 +87,9 @@ class ReaderabilityControllerProvider extends AutoDisposeNotifierProviderImpl<
|
||||
ReaderabilityController, AsyncValue<({bool readerable, bool applied})>> {
|
||||
/// See also [ReaderabilityController].
|
||||
ReaderabilityControllerProvider(
|
||||
ConsistentController controller,
|
||||
String tabId,
|
||||
) : this._internal(
|
||||
() => ReaderabilityController()..controller = controller,
|
||||
() => ReaderabilityController()..tabId = tabId,
|
||||
from: readerabilityControllerProvider,
|
||||
name: r'readerabilityControllerProvider',
|
||||
debugGetCreateSourceHash:
|
||||
@@ -99,7 +99,7 @@ class ReaderabilityControllerProvider extends AutoDisposeNotifierProviderImpl<
|
||||
dependencies: ReaderabilityControllerFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
ReaderabilityControllerFamily._allTransitiveDependencies,
|
||||
controller: controller,
|
||||
tabId: tabId,
|
||||
);
|
||||
|
||||
ReaderabilityControllerProvider._internal(
|
||||
@@ -109,17 +109,17 @@ class ReaderabilityControllerProvider extends AutoDisposeNotifierProviderImpl<
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.controller,
|
||||
required this.tabId,
|
||||
}) : super.internal();
|
||||
|
||||
final ConsistentController controller;
|
||||
final String tabId;
|
||||
|
||||
@override
|
||||
AsyncValue<({bool readerable, bool applied})> runNotifierBuild(
|
||||
covariant ReaderabilityController notifier,
|
||||
) {
|
||||
return notifier.build(
|
||||
controller,
|
||||
tabId,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -128,13 +128,13 @@ class ReaderabilityControllerProvider extends AutoDisposeNotifierProviderImpl<
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: ReaderabilityControllerProvider._internal(
|
||||
() => create()..controller = controller,
|
||||
() => create()..tabId = tabId,
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
controller: controller,
|
||||
tabId: tabId,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -147,14 +147,13 @@ class ReaderabilityControllerProvider extends AutoDisposeNotifierProviderImpl<
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is ReaderabilityControllerProvider &&
|
||||
other.controller == controller;
|
||||
return other is ReaderabilityControllerProvider && other.tabId == tabId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, controller.hashCode);
|
||||
hash = _SystemHash.combine(hash, tabId.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
@@ -162,8 +161,8 @@ class ReaderabilityControllerProvider extends AutoDisposeNotifierProviderImpl<
|
||||
|
||||
mixin ReaderabilityControllerRef on AutoDisposeNotifierProviderRef<
|
||||
AsyncValue<({bool readerable, bool applied})>> {
|
||||
/// The parameter `controller` of this provider.
|
||||
ConsistentController get controller;
|
||||
/// The parameter `tabId` of this provider.
|
||||
String get tabId;
|
||||
}
|
||||
|
||||
class _ReaderabilityControllerProviderElement
|
||||
@@ -173,8 +172,7 @@ class _ReaderabilityControllerProviderElement
|
||||
_ReaderabilityControllerProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
ConsistentController get controller =>
|
||||
(origin as ReaderabilityControllerProvider).controller;
|
||||
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,12 +1,13 @@
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
import 'package:lensai/features/topics/domain/providers.dart';
|
||||
import 'package:lensai/features/topics/domain/repositories/tab.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:lensai/features/web_view/presentation/widgets/web_view.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'switch_new_tab.g.dart';
|
||||
|
||||
@riverpod
|
||||
@Riverpod(keepAlive: true)
|
||||
class SwitchNewTabController extends _$SwitchNewTabController {
|
||||
@override
|
||||
FutureOr<void> build() {}
|
||||
@@ -15,13 +16,15 @@ class SwitchNewTabController extends _$SwitchNewTabController {
|
||||
state = const AsyncLoading();
|
||||
state = await AsyncValue.guard(
|
||||
() async {
|
||||
final topic = ref.read(selectedTopicProvider);
|
||||
final newTab = WebViewPage.create(
|
||||
url: WebUri.uri(url),
|
||||
topicId: topic,
|
||||
);
|
||||
|
||||
ref
|
||||
.read(webViewRepositoryProvider.notifier)
|
||||
.addTab(WebView(tab: newTab));
|
||||
await ref
|
||||
.read(topicTabRepositoryProvider(topic).notifier)
|
||||
.addTab(newTab);
|
||||
|
||||
ref.read(webViewTabControllerProvider.notifier).showTab(newTab.id);
|
||||
},
|
||||
|
||||
@@ -7,12 +7,12 @@ part of 'switch_new_tab.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$switchNewTabControllerHash() =>
|
||||
r'fb831015e4b99d3ae86bd2a88a3c6f4c7004783d';
|
||||
r'7ebfea953aeb81c279ea11254ff0ad558e6a949f';
|
||||
|
||||
/// See also [SwitchNewTabController].
|
||||
@ProviderFor(SwitchNewTabController)
|
||||
final switchNewTabControllerProvider =
|
||||
AutoDisposeAsyncNotifierProvider<SwitchNewTabController, void>.internal(
|
||||
AsyncNotifierProvider<SwitchNewTabController, void>.internal(
|
||||
SwitchNewTabController.new,
|
||||
name: r'switchNewTabControllerProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
@@ -22,6 +22,6 @@ final switchNewTabControllerProvider =
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$SwitchNewTabController = AutoDisposeAsyncNotifier<void>;
|
||||
typedef _$SwitchNewTabController = 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
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
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/entities/consistent_controller.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';
|
||||
@@ -14,8 +14,8 @@ class ReaderabilityScriptService extends _$ReaderabilityScriptService {
|
||||
late bool _enableReadability;
|
||||
|
||||
@override
|
||||
Future<void> build(ConsistentController controller) async {
|
||||
_controller = controller.value;
|
||||
Future<void> build(String tabId) async {
|
||||
_controller = ref.watch(webViewControllerProvider(tabId));
|
||||
_readerabilityScript = ref.watch(readerabilityScriptProvider.future);
|
||||
_enableReadability = ref.watch(
|
||||
settingsRepositoryProvider.select(
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'readerability_script.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$readerabilityScriptServiceHash() =>
|
||||
r'bb38e2934ed124502b70faed11b6921c40d0eec2';
|
||||
r'85f0de50516cadb1f2d1efa8161a8cf45868b9d9';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
@@ -32,10 +32,10 @@ class _SystemHash {
|
||||
|
||||
abstract class _$ReaderabilityScriptService
|
||||
extends BuildlessAutoDisposeAsyncNotifier<void> {
|
||||
late final ConsistentController controller;
|
||||
late final String tabId;
|
||||
|
||||
FutureOr<void> build(
|
||||
ConsistentController controller,
|
||||
String tabId,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -50,10 +50,10 @@ class ReaderabilityScriptServiceFamily extends Family<AsyncValue<void>> {
|
||||
|
||||
/// See also [ReaderabilityScriptService].
|
||||
ReaderabilityScriptServiceProvider call(
|
||||
ConsistentController controller,
|
||||
String tabId,
|
||||
) {
|
||||
return ReaderabilityScriptServiceProvider(
|
||||
controller,
|
||||
tabId,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ class ReaderabilityScriptServiceFamily extends Family<AsyncValue<void>> {
|
||||
covariant ReaderabilityScriptServiceProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.controller,
|
||||
provider.tabId,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -87,9 +87,9 @@ class ReaderabilityScriptServiceProvider
|
||||
void> {
|
||||
/// See also [ReaderabilityScriptService].
|
||||
ReaderabilityScriptServiceProvider(
|
||||
ConsistentController controller,
|
||||
String tabId,
|
||||
) : this._internal(
|
||||
() => ReaderabilityScriptService()..controller = controller,
|
||||
() => ReaderabilityScriptService()..tabId = tabId,
|
||||
from: readerabilityScriptServiceProvider,
|
||||
name: r'readerabilityScriptServiceProvider',
|
||||
debugGetCreateSourceHash:
|
||||
@@ -99,7 +99,7 @@ class ReaderabilityScriptServiceProvider
|
||||
dependencies: ReaderabilityScriptServiceFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
ReaderabilityScriptServiceFamily._allTransitiveDependencies,
|
||||
controller: controller,
|
||||
tabId: tabId,
|
||||
);
|
||||
|
||||
ReaderabilityScriptServiceProvider._internal(
|
||||
@@ -109,17 +109,17 @@ class ReaderabilityScriptServiceProvider
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.controller,
|
||||
required this.tabId,
|
||||
}) : super.internal();
|
||||
|
||||
final ConsistentController controller;
|
||||
final String tabId;
|
||||
|
||||
@override
|
||||
FutureOr<void> runNotifierBuild(
|
||||
covariant ReaderabilityScriptService notifier,
|
||||
) {
|
||||
return notifier.build(
|
||||
controller,
|
||||
tabId,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -128,13 +128,13 @@ class ReaderabilityScriptServiceProvider
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: ReaderabilityScriptServiceProvider._internal(
|
||||
() => create()..controller = controller,
|
||||
() => create()..tabId = tabId,
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
controller: controller,
|
||||
tabId: tabId,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -147,14 +147,13 @@ class ReaderabilityScriptServiceProvider
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is ReaderabilityScriptServiceProvider &&
|
||||
other.controller == controller;
|
||||
return other is ReaderabilityScriptServiceProvider && other.tabId == tabId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, controller.hashCode);
|
||||
hash = _SystemHash.combine(hash, tabId.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
@@ -162,8 +161,8 @@ class ReaderabilityScriptServiceProvider
|
||||
|
||||
mixin ReaderabilityScriptServiceRef
|
||||
on AutoDisposeAsyncNotifierProviderRef<void> {
|
||||
/// The parameter `controller` of this provider.
|
||||
ConsistentController get controller;
|
||||
/// The parameter `tabId` of this provider.
|
||||
String get tabId;
|
||||
}
|
||||
|
||||
class _ReaderabilityScriptServiceProviderElement
|
||||
@@ -172,8 +171,7 @@ class _ReaderabilityScriptServiceProviderElement
|
||||
_ReaderabilityScriptServiceProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
ConsistentController get controller =>
|
||||
(origin as ReaderabilityScriptServiceProvider).controller;
|
||||
String get tabId => (origin as ReaderabilityScriptServiceProvider).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
|
||||
|
||||
@@ -15,46 +15,22 @@ 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/features/web_view/presentation/widgets/favicon.dart';
|
||||
import 'package:lensai/presentation/controllers/website_title.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;
|
||||
import 'package:lensai/utils/uri_parser.dart' as uri_parser;
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
class LoadingWebPageDialog extends HookConsumerWidget {
|
||||
final Uri url;
|
||||
|
||||
final void Function()? onDismiss;
|
||||
|
||||
const LoadingWebPageDialog(this.url, {this.onDismiss});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final pageInfoAsync = ref.watch(pageInfoProvider(url));
|
||||
|
||||
return pageInfoAsync.when(
|
||||
data: (pageInfo) => WebPageDialog(
|
||||
page: pageInfo,
|
||||
onDismiss: onDismiss,
|
||||
),
|
||||
error: (error, stackTrace) => SizedBox.shrink(),
|
||||
loading: () => ModalBarrier(
|
||||
color: Theme.of(context).dialogTheme.barrierColor ?? Colors.black54,
|
||||
onDismiss: onDismiss,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class WebPageDialog extends HookConsumerWidget {
|
||||
final WebPageInfo page;
|
||||
final Uri url;
|
||||
final WebPageInfo? precachedInfo;
|
||||
final InAppWebViewController? webViewController;
|
||||
|
||||
final void Function()? onDismiss;
|
||||
|
||||
const WebPageDialog({
|
||||
required this.page,
|
||||
required this.url,
|
||||
this.precachedInfo,
|
||||
this.webViewController,
|
||||
this.onDismiss,
|
||||
super.key,
|
||||
@@ -71,7 +47,7 @@ class WebPageDialog extends HookConsumerWidget {
|
||||
final availableBangsAsync = ref.watch(
|
||||
bangDataListProvider(
|
||||
filter: (
|
||||
domain: page.url.host,
|
||||
domain: url.host,
|
||||
groups: null,
|
||||
categoryFilter: null,
|
||||
orderMostFrequentFirst: true,
|
||||
@@ -81,8 +57,7 @@ class WebPageDialog extends HookConsumerWidget {
|
||||
final availableBangCount = availableBangsAsync.valueOrNull?.length;
|
||||
|
||||
final formKey = useMemoized(() => GlobalKey<FormState>());
|
||||
final urlTextController =
|
||||
useTextEditingController(text: page.url.toString());
|
||||
final urlTextController = useTextEditingController(text: url.toString());
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
@@ -97,16 +72,7 @@ class WebPageDialog extends HookConsumerWidget {
|
||||
horizontal: 20.0,
|
||||
vertical: 24.0,
|
||||
),
|
||||
title: ListTile(
|
||||
leading: FaviconImage(
|
||||
favicon: page.favicon,
|
||||
url: page.url,
|
||||
size: 24,
|
||||
),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(page.title ?? 'Unknown Title'),
|
||||
subtitle: Text(page.url.authority),
|
||||
),
|
||||
title: WebsiteTitleTile(url, precachedInfo: precachedInfo),
|
||||
children: [
|
||||
SizedBox(
|
||||
//We need this to stretch the dialog, then padding from dialog is applied
|
||||
@@ -164,7 +130,7 @@ class WebPageDialog extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
return SiteSearch(
|
||||
domain: page.url.host,
|
||||
domain: url.host,
|
||||
availableBangs: availableBangs,
|
||||
);
|
||||
},
|
||||
@@ -173,7 +139,7 @@ class WebPageDialog extends HookConsumerWidget {
|
||||
exception: error,
|
||||
),
|
||||
loading: () => SiteSearch(
|
||||
domain: page.url.host,
|
||||
domain: url.host,
|
||||
availableBangs: [
|
||||
BangData(
|
||||
websiteName: 'websiteName',
|
||||
@@ -194,14 +160,14 @@ class WebPageDialog extends HookConsumerWidget {
|
||||
title: const Text('Copy address'),
|
||||
onTap: () async {
|
||||
await Clipboard.setData(
|
||||
ClipboardData(text: page.url.toString()),
|
||||
ClipboardData(text: url.toString()),
|
||||
);
|
||||
onDismiss?.call();
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
onTap: () async {
|
||||
await ui_helper.launchUrlFeedback(context, page.url);
|
||||
await ui_helper.launchUrlFeedback(context, url);
|
||||
},
|
||||
leading: const Icon(Icons.open_in_browser),
|
||||
title: const Text('Launch External'),
|
||||
@@ -212,7 +178,7 @@ class WebPageDialog extends HookConsumerWidget {
|
||||
onTap: () async {
|
||||
await ref
|
||||
.read(switchNewTabControllerProvider.notifier)
|
||||
.add(page.url);
|
||||
.add(url);
|
||||
|
||||
onDismiss?.call();
|
||||
},
|
||||
@@ -221,7 +187,7 @@ class WebPageDialog extends HookConsumerWidget {
|
||||
leading: const Icon(Icons.share),
|
||||
title: const Text('Share link'),
|
||||
onTap: () async {
|
||||
await Share.shareUri(page.url);
|
||||
await Share.shareUri(url);
|
||||
|
||||
onDismiss?.call();
|
||||
},
|
||||
@@ -231,14 +197,14 @@ class WebPageDialog extends HookConsumerWidget {
|
||||
leading: Icon(KagiTool.summarizer.icon),
|
||||
title: const Text('Summarize'),
|
||||
onTap: () async {
|
||||
final url = uri_builder.summarizerUri(
|
||||
document: SharedUrl(page.url),
|
||||
final summarizerUrl = uri_builder.summarizerUri(
|
||||
document: SharedUrl(url),
|
||||
mode: SummarizerMode.keyMoments,
|
||||
);
|
||||
|
||||
await ref
|
||||
.read(switchNewTabControllerProvider.notifier)
|
||||
.add(url);
|
||||
.add(summarizerUrl);
|
||||
|
||||
onDismiss?.call();
|
||||
},
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:copy_with_extension/copy_with_extension.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
@@ -17,9 +15,8 @@ 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/features/web_view/domain/entities/consistent_controller.dart';
|
||||
import 'package:lensai/features/web_view/domain/entities/web_view_page.dart';
|
||||
import 'package:lensai/features/web_view/domain/providers.dart';
|
||||
import 'package:lensai/features/web_view/domain/repositories/web_view.dart';
|
||||
import 'package:lensai/features/web_view/presentation/controllers/readerability.dart';
|
||||
import 'package:lensai/features/web_view/presentation/controllers/switch_new_tab.dart';
|
||||
import 'package:lensai/features/web_view/presentation/widgets/web_page_dialog.dart';
|
||||
@@ -38,124 +35,15 @@ const _webViewSupportedSchemes = [
|
||||
"about",
|
||||
];
|
||||
|
||||
abstract class _WebViewPageCWProxy {
|
||||
WebViewPage controller(InAppWebViewController? controller);
|
||||
|
||||
WebViewPage url(Uri url);
|
||||
|
||||
WebViewPage sslError(SslError? sslError);
|
||||
|
||||
WebViewPage title(String? title);
|
||||
|
||||
WebViewPage topicId(String? topicId);
|
||||
|
||||
WebViewPage favicon(Favicon? favicon);
|
||||
|
||||
WebViewPage screenshot(Uint8List? screenshot);
|
||||
|
||||
WebViewPage pageHistory(({bool canGoBack, bool canGoForward}) pageHistory);
|
||||
|
||||
WebViewPage call({
|
||||
InAppWebViewController? controller,
|
||||
Uri? url,
|
||||
SslError? sslError,
|
||||
String? title,
|
||||
String? topicId,
|
||||
Favicon? favicon,
|
||||
Uint8List? screenshot,
|
||||
({bool canGoBack, bool canGoForward})? pageHistory,
|
||||
});
|
||||
}
|
||||
|
||||
class _WebViewPageCWProxyImpl implements _WebViewPageCWProxy {
|
||||
const _WebViewPageCWProxyImpl(this._value);
|
||||
|
||||
final WebViewPage _value;
|
||||
|
||||
@override
|
||||
WebViewPage controller(InAppWebViewController? controller) =>
|
||||
this(controller: controller);
|
||||
|
||||
@override
|
||||
WebViewPage url(Uri url) => this(url: url);
|
||||
|
||||
@override
|
||||
WebViewPage sslError(SslError? sslError) => this(sslError: sslError);
|
||||
|
||||
@override
|
||||
WebViewPage title(String? title) => this(title: title);
|
||||
|
||||
@override
|
||||
WebViewPage topicId(String? topicId) => this(topicId: topicId);
|
||||
|
||||
@override
|
||||
WebViewPage favicon(Favicon? favicon) => this(favicon: favicon);
|
||||
|
||||
@override
|
||||
WebViewPage screenshot(Uint8List? screenshot) => this(screenshot: screenshot);
|
||||
|
||||
@override
|
||||
WebViewPage pageHistory(({bool canGoBack, bool canGoForward}) pageHistory) =>
|
||||
this(pageHistory: pageHistory);
|
||||
|
||||
@override
|
||||
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(
|
||||
key: _value.key,
|
||||
id: _value.id,
|
||||
controller: controller == const $CopyWithPlaceholder()
|
||||
? _value.controller
|
||||
: controller as InAppWebViewController?,
|
||||
url: url == const $CopyWithPlaceholder() || url == null
|
||||
? _value.url
|
||||
: url as Uri,
|
||||
sslError: sslError == const $CopyWithPlaceholder()
|
||||
? _value.sslError
|
||||
: sslError as SslError?,
|
||||
title: title == const $CopyWithPlaceholder()
|
||||
? _value.title
|
||||
: title as String?,
|
||||
topicId: topicId == const $CopyWithPlaceholder()
|
||||
? _value.topicId
|
||||
: topicId as String?,
|
||||
favicon: favicon == const $CopyWithPlaceholder()
|
||||
? _value.favicon
|
||||
: favicon as Favicon?,
|
||||
screenshot: screenshot == const $CopyWithPlaceholder()
|
||||
? _value.screenshot
|
||||
: screenshot as Uint8List?,
|
||||
pageHistory:
|
||||
pageHistory == const $CopyWithPlaceholder() || pageHistory == null
|
||||
? _value.pageHistory
|
||||
: pageHistory as ({bool canGoBack, bool canGoForward}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class WebView extends StatefulHookConsumerWidget {
|
||||
final String tabId;
|
||||
final URLRequest? initialUrlRequest;
|
||||
|
||||
final ValueNotifier<WebViewPage> _pageNotifier;
|
||||
|
||||
ValueListenable<WebViewPage> get page => _pageNotifier;
|
||||
|
||||
/// Don't cache this value as it depends on current value of a ValueListenable
|
||||
InAppWebViewController? get currentController =>
|
||||
_pageNotifier.value.controller;
|
||||
|
||||
WebView({required WebViewPage tab})
|
||||
: _pageNotifier = ValueNotifier(tab),
|
||||
tabId = tab.id,
|
||||
super(key: tab.key);
|
||||
const WebView({
|
||||
required this.tabId,
|
||||
required super.key,
|
||||
this.initialUrlRequest,
|
||||
});
|
||||
|
||||
@override
|
||||
ConsumerState<ConsumerStatefulWidget> createState() => _WebViewState();
|
||||
@@ -165,13 +53,6 @@ class _WebViewState extends ConsumerState<WebView> {
|
||||
Timer? _onLoadStopDebounce;
|
||||
Timer? _periodicScreenshotUpdate;
|
||||
|
||||
void updatePage(
|
||||
WebViewPage Function(_WebViewPageCWProxyImpl copyWith) update,
|
||||
) {
|
||||
final x = widget._pageNotifier.value =
|
||||
update(_WebViewPageCWProxyImpl(widget._pageNotifier.value));
|
||||
}
|
||||
|
||||
Future<bool> _downloadChat(
|
||||
DownloadStartRequest downloadStartRequest,
|
||||
BuildContext context,
|
||||
@@ -216,25 +97,6 @@ class _WebViewState extends ConsumerState<WebView> {
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<void> _updateScreenshot() async {
|
||||
final screenshot = await widget.page.value.controller
|
||||
?.takeScreenshot(
|
||||
screenshotConfiguration: ScreenshotConfiguration(
|
||||
compressFormat: CompressFormat.JPEG,
|
||||
quality: 20,
|
||||
),
|
||||
)
|
||||
.timeout(
|
||||
const Duration(milliseconds: 1500),
|
||||
onTimeout: () {
|
||||
logger.w('Screenshot timed out');
|
||||
return null;
|
||||
},
|
||||
);
|
||||
|
||||
updatePage((copyWith) => copyWith.screenshot(screenshot));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
super.dispose();
|
||||
@@ -242,9 +104,7 @@ class _WebViewState extends ConsumerState<WebView> {
|
||||
_onLoadStopDebounce?.cancel();
|
||||
_periodicScreenshotUpdate?.cancel();
|
||||
|
||||
widget._pageNotifier.dispose();
|
||||
|
||||
logger.i('Disposed ${widget.key} (${widget.page.value.title})');
|
||||
logger.i('Disposed ${widget.key} ${widget.tabId}');
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -291,7 +151,8 @@ class _WebViewState extends ConsumerState<WebView> {
|
||||
(value.valueOrNull ?? Settings.withDefaults()).enableJavascript,
|
||||
),
|
||||
(previous, next) async {
|
||||
await widget.page.value.controller?.setSettings(
|
||||
final controller = ref.read(webViewControllerProvider(widget.tabId));
|
||||
await controller?.setSettings(
|
||||
settings: initialSettings.copy()..javaScriptEnabled = next,
|
||||
);
|
||||
},
|
||||
@@ -322,20 +183,22 @@ class _WebViewState extends ConsumerState<WebView> {
|
||||
);
|
||||
|
||||
useOnAppLifecycleStateChange((previous, current) async {
|
||||
final controller = ref.read(webViewControllerProvider(widget.tabId));
|
||||
|
||||
switch (current) {
|
||||
case AppLifecycleState.paused:
|
||||
if (platform_util.isAndroid()) {
|
||||
await widget.page.value.controller?.pause();
|
||||
await controller?.pause();
|
||||
}
|
||||
if (platform_util.isAndroid() || platform_util.isIOS()) {
|
||||
await widget.page.value.controller?.pauseTimers();
|
||||
await controller?.pauseTimers();
|
||||
}
|
||||
case AppLifecycleState.resumed:
|
||||
if (platform_util.isAndroid()) {
|
||||
await widget.page.value.controller?.resume();
|
||||
await controller?.resume();
|
||||
}
|
||||
if (platform_util.isAndroid() || platform_util.isIOS()) {
|
||||
await widget.page.value.controller?.resumeTimers();
|
||||
await controller?.resumeTimers();
|
||||
}
|
||||
default:
|
||||
}
|
||||
@@ -344,7 +207,7 @@ class _WebViewState extends ConsumerState<WebView> {
|
||||
return Stack(
|
||||
children: [
|
||||
InAppWebView(
|
||||
initialUrlRequest: URLRequest(url: WebUri.uri(widget.page.value.url)),
|
||||
initialUrlRequest: widget.initialUrlRequest,
|
||||
initialSettings: initialSettings,
|
||||
findInteractionController: findInteractionController,
|
||||
contextMenu: ContextMenu(
|
||||
@@ -353,8 +216,10 @@ class _WebViewState extends ConsumerState<WebView> {
|
||||
id: 1,
|
||||
title: "Search",
|
||||
action: () async {
|
||||
final selectedText =
|
||||
await widget.page.value.controller?.getSelectedText();
|
||||
final controller =
|
||||
ref.read(webViewControllerProvider(widget.tabId));
|
||||
|
||||
final selectedText = await controller?.getSelectedText();
|
||||
|
||||
if (selectedText != null && selectedText.isNotEmpty) {
|
||||
final searchBang =
|
||||
@@ -371,8 +236,10 @@ class _WebViewState extends ConsumerState<WebView> {
|
||||
id: 2,
|
||||
title: "Assistant",
|
||||
action: () async {
|
||||
final selectedText =
|
||||
await widget.page.value.controller?.getSelectedText();
|
||||
final controller =
|
||||
ref.read(webViewControllerProvider(widget.tabId));
|
||||
|
||||
final selectedText = await controller?.getSelectedText();
|
||||
|
||||
if (selectedText != null && selectedText.isNotEmpty) {
|
||||
ref.read(bottomSheetProvider.notifier).show(
|
||||
@@ -391,19 +258,24 @@ class _WebViewState extends ConsumerState<WebView> {
|
||||
await controller.startSafeBrowsing();
|
||||
}
|
||||
|
||||
updatePage((copyWith) => copyWith.controller(controller));
|
||||
await ref
|
||||
.read(tabStateProvider(widget.tabId).notifier)
|
||||
.copyWith(controller: controller);
|
||||
},
|
||||
onReceivedServerTrustAuthRequest: (controller, challenge) async {
|
||||
final sslError = challenge.protectionSpace.sslError;
|
||||
|
||||
if (sslError != null && sslError.code != null) {
|
||||
if (challenge.protectionSpace.host ==
|
||||
await controller.getUrl().then((value) => value?.host)) {
|
||||
updatePage((copyWith) => copyWith.sslError(sslError));
|
||||
final url = await controller.getUrl();
|
||||
if (challenge.protectionSpace.host == url?.host) {
|
||||
await ref
|
||||
.read(tabStateProvider(widget.tabId).notifier)
|
||||
.copyWith(sslError: sslError);
|
||||
|
||||
if (context.mounted) {
|
||||
ui_helper.showErrorMessage(
|
||||
context,
|
||||
'We detected an security issue and did not continue to ${widget.page.value.url.authority}: ${sslError.message}',
|
||||
'We detected an security issue and did not continue to ${url?.authority}: ${sslError.message}',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -414,7 +286,10 @@ class _WebViewState extends ConsumerState<WebView> {
|
||||
);
|
||||
}
|
||||
|
||||
updatePage((copyWith) => copyWith.sslError(null));
|
||||
await ref
|
||||
.read(tabStateProvider(widget.tabId).notifier)
|
||||
.copyWith(sslError: null);
|
||||
|
||||
return ServerTrustAuthResponse(
|
||||
action: ServerTrustAuthResponseAction.PROCEED,
|
||||
);
|
||||
@@ -468,46 +343,48 @@ class _WebViewState extends ConsumerState<WebView> {
|
||||
onProgressChanged: (controller, progress) {
|
||||
webViewProgress.value = progress;
|
||||
},
|
||||
onLoadStart: (controller, url) {
|
||||
onLoadStart: (controller, url) async {
|
||||
final readabilityNotifier = ref.read(
|
||||
readerabilityControllerProvider(
|
||||
ConsistentController(controller),
|
||||
).notifier,
|
||||
readerabilityControllerProvider(widget.tabId).notifier,
|
||||
);
|
||||
|
||||
if (url != null) {
|
||||
updatePage(
|
||||
(copyWith) => copyWith(
|
||||
url: url,
|
||||
// ignore: avoid_redundant_argument_values
|
||||
sslError: null,
|
||||
),
|
||||
);
|
||||
await ref.read(tabStateProvider(widget.tabId).notifier).copyWith(
|
||||
url: url,
|
||||
// ignore: avoid_redundant_argument_values
|
||||
sslError: null,
|
||||
);
|
||||
}
|
||||
|
||||
readabilityNotifier.reset();
|
||||
},
|
||||
onLoadStop: (controller, url) async {
|
||||
if (url != null) {
|
||||
updatePage((copyWith) => copyWith.url(url));
|
||||
await ref
|
||||
.read(tabStateProvider(widget.tabId).notifier)
|
||||
.copyWith(url: url);
|
||||
}
|
||||
|
||||
_onLoadStopDebounce?.cancel();
|
||||
_onLoadStopDebounce =
|
||||
Timer(const Duration(milliseconds: 150), () async {
|
||||
final readabilityNotifier = ref.read(
|
||||
readerabilityControllerProvider(
|
||||
ConsistentController(controller),
|
||||
).notifier,
|
||||
readerabilityControllerProvider(widget.tabId).notifier,
|
||||
);
|
||||
|
||||
await readabilityNotifier.checkReaderable();
|
||||
|
||||
await _updateScreenshot().whenComplete(() {
|
||||
await ref
|
||||
.read(tabStateProvider(widget.tabId).notifier)
|
||||
.updateScreenshot()
|
||||
.whenComplete(() {
|
||||
_periodicScreenshotUpdate?.cancel();
|
||||
_periodicScreenshotUpdate =
|
||||
Timer.periodic(const Duration(seconds: 5), (timer) async {
|
||||
await _updateScreenshot().onError((error, stackTrace) {
|
||||
await ref
|
||||
.read(tabStateProvider(widget.tabId).notifier)
|
||||
.updateScreenshot()
|
||||
.onError((error, stackTrace) {
|
||||
logger.e(error, stackTrace: stackTrace);
|
||||
timer.cancel();
|
||||
});
|
||||
@@ -522,7 +399,9 @@ class _WebViewState extends ConsumerState<WebView> {
|
||||
canGoForward: await controller.canGoForward()
|
||||
);
|
||||
|
||||
updatePage((copyWith) => copyWith.pageHistory(history));
|
||||
await ref
|
||||
.read(tabStateProvider(widget.tabId).notifier)
|
||||
.copyWith(pageHistory: history);
|
||||
}
|
||||
},
|
||||
shouldOverrideUrlLoading: (controller, navigationAction) async {
|
||||
@@ -582,8 +461,8 @@ class _WebViewState extends ConsumerState<WebView> {
|
||||
Uri.tryParse(requestFocusNodeHrefResult?.src ?? '');
|
||||
if (url?.hasScheme == true && url?.hasAuthority == true) {
|
||||
ref.read(overlayDialogProvider.notifier).show(
|
||||
LoadingWebPageDialog(
|
||||
url!,
|
||||
WebPageDialog(
|
||||
url: url!,
|
||||
onDismiss: () {
|
||||
ref.read(overlayDialogProvider.notifier).dismiss();
|
||||
},
|
||||
@@ -592,8 +471,10 @@ class _WebViewState extends ConsumerState<WebView> {
|
||||
}
|
||||
}
|
||||
},
|
||||
onTitleChanged: (controller, title) {
|
||||
updatePage((copyWith) => copyWith.title(title));
|
||||
onTitleChanged: (controller, title) async {
|
||||
await ref
|
||||
.read(tabStateProvider(widget.tabId).notifier)
|
||||
.copyWith(title: title);
|
||||
},
|
||||
onDownloadStartRequest: (controller, downloadStartRequest) async {
|
||||
final handled = switch (downloadStartRequest.mimeType) {
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/features/web_view/domain/entities/abstract/tab.dart';
|
||||
import 'package:lensai/features/web_view/domain/repositories/web_view.dart';
|
||||
import 'package:lensai/features/web_view/presentation/widgets/favicon.dart';
|
||||
|
||||
class WebViewTab extends HookConsumerWidget {
|
||||
class WebViewTab extends StatelessWidget {
|
||||
final ITab tab;
|
||||
final bool isActive;
|
||||
|
||||
final VoidCallback onClose;
|
||||
final VoidCallback? onTap;
|
||||
final VoidCallback? onLongPress;
|
||||
final VoidCallback? onDelete;
|
||||
|
||||
const WebViewTab({
|
||||
required this.tab,
|
||||
required this.isActive,
|
||||
required this.onClose,
|
||||
this.onTap,
|
||||
this.onLongPress,
|
||||
this.onDelete,
|
||||
super.key,
|
||||
});
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
return Container(
|
||||
@@ -33,15 +35,8 @@ class WebViewTab extends HookConsumerWidget {
|
||||
borderRadius: const BorderRadius.all(Radius.circular(16.0)),
|
||||
child: InkWell(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(16.0)),
|
||||
onTap: () {
|
||||
if (!isActive) {
|
||||
//Close first to avoid rebuilds
|
||||
onClose();
|
||||
ref.read(webViewTabControllerProvider.notifier).showTab(tab.id);
|
||||
} else {
|
||||
onClose();
|
||||
}
|
||||
},
|
||||
onTap: onTap,
|
||||
onLongPress: onLongPress,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
@@ -59,11 +54,7 @@ class WebViewTab extends HookConsumerWidget {
|
||||
IconButton(
|
||||
visualDensity:
|
||||
const VisualDensity(horizontal: -4.0, vertical: -4.0),
|
||||
onPressed: () {
|
||||
ref
|
||||
.read(webViewRepositoryProvider.notifier)
|
||||
.closeTab(tab.id);
|
||||
},
|
||||
onPressed: onDelete,
|
||||
icon: const Icon(Icons.close),
|
||||
),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user