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
@@ -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,27 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'switch_new_tab.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$switchNewTabControllerHash() =>
r'7ebfea953aeb81c279ea11254ff0ad558e6a949f';
/// See also [SwitchNewTabController].
@ProviderFor(SwitchNewTabController)
final switchNewTabControllerProvider =
AsyncNotifierProvider<SwitchNewTabController, void>.internal(
SwitchNewTabController.new,
name: r'switchNewTabControllerProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$switchNewTabControllerHash,
dependencies: null,
allTransitiveDependencies: null,
);
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,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();');
}
}
}
}
@@ -1,177 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'readerability_script.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$readerabilityScriptServiceHash() =>
r'85f0de50516cadb1f2d1efa8161a8cf45868b9d9';
/// 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 _$ReaderabilityScriptService
extends BuildlessAutoDisposeAsyncNotifier<void> {
late final String tabId;
FutureOr<void> build(
String tabId,
);
}
/// See also [ReaderabilityScriptService].
@ProviderFor(ReaderabilityScriptService)
const readerabilityScriptServiceProvider = ReaderabilityScriptServiceFamily();
/// See also [ReaderabilityScriptService].
class ReaderabilityScriptServiceFamily extends Family<AsyncValue<void>> {
/// See also [ReaderabilityScriptService].
const ReaderabilityScriptServiceFamily();
/// See also [ReaderabilityScriptService].
ReaderabilityScriptServiceProvider call(
String tabId,
) {
return ReaderabilityScriptServiceProvider(
tabId,
);
}
@override
ReaderabilityScriptServiceProvider getProviderOverride(
covariant ReaderabilityScriptServiceProvider 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'readerabilityScriptServiceProvider';
}
/// See also [ReaderabilityScriptService].
class ReaderabilityScriptServiceProvider
extends AutoDisposeAsyncNotifierProviderImpl<ReaderabilityScriptService,
void> {
/// See also [ReaderabilityScriptService].
ReaderabilityScriptServiceProvider(
String tabId,
) : this._internal(
() => ReaderabilityScriptService()..tabId = tabId,
from: readerabilityScriptServiceProvider,
name: r'readerabilityScriptServiceProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$readerabilityScriptServiceHash,
dependencies: ReaderabilityScriptServiceFamily._dependencies,
allTransitiveDependencies:
ReaderabilityScriptServiceFamily._allTransitiveDependencies,
tabId: tabId,
);
ReaderabilityScriptServiceProvider._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
FutureOr<void> runNotifierBuild(
covariant ReaderabilityScriptService notifier,
) {
return notifier.build(
tabId,
);
}
@override
Override overrideWith(ReaderabilityScriptService Function() create) {
return ProviderOverride(
origin: this,
override: ReaderabilityScriptServiceProvider._internal(
() => create()..tabId = tabId,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
tabId: tabId,
),
);
}
@override
AutoDisposeAsyncNotifierProviderElement<ReaderabilityScriptService, void>
createElement() {
return _ReaderabilityScriptServiceProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is ReaderabilityScriptServiceProvider && 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 ReaderabilityScriptServiceRef
on AutoDisposeAsyncNotifierProviderRef<void> {
/// The parameter `tabId` of this provider.
String get tabId;
}
class _ReaderabilityScriptServiceProviderElement
extends AutoDisposeAsyncNotifierProviderElement<ReaderabilityScriptService,
void> with ReaderabilityScriptServiceRef {
_ReaderabilityScriptServiceProviderElement(super.provider);
@override
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
@@ -1,215 +0,0 @@
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/kagi/data/entities/modes.dart';
import 'package:lensai/features/search_browser/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;
import 'package:lensai/utils/uri_parser.dart' as uri_parser;
import 'package:share_plus/share_plus.dart';
class WebPageDialog extends HookConsumerWidget {
final Uri url;
final WebPageInfo? precachedInfo;
final void Function()? onDismiss;
const WebPageDialog({
required this.url,
this.precachedInfo,
this.onDismiss,
super.key,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final incognitoEnabled = ref.watch(
settingsRepositoryProvider.select(
(value) => (value.valueOrNull ?? Settings.withDefaults()).incognitoMode,
),
);
final availableBangsAsync = ref.watch(
bangDataListProvider(
filter: (
domain: url.host,
groups: null,
categoryFilter: null,
orderMostFrequentFirst: true,
),
),
);
final availableBangCount = availableBangsAsync.valueOrNull?.length;
final formKey = useMemoized(() => GlobalKey<FormState>());
final urlTextController = useTextEditingController(text: url.toString());
return Stack(
children: [
ModalBarrier(
color: Theme.of(context).dialogTheme.barrierColor ?? Colors.black54,
onDismiss: onDismiss,
),
SimpleDialog(
titlePadding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 0.0),
contentPadding: EdgeInsets.zero,
insetPadding: const EdgeInsets.symmetric(
horizontal: 20.0,
vertical: 24.0,
),
title: WebsiteTitleTile(url, precachedInfo: precachedInfo),
children: [
SizedBox(
//We need this to stretch the dialog, then padding from dialog is applied
width: double.maxFinite,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Form(
key: formKey,
child: TextFormField(
controller: urlTextController,
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,
)!,
),
),
);
onDismiss?.call();
}
},
icon: const Icon(Icons.send),
)
: null,
),
validator: (value) {
if (uri_parser.tryParseUrl(value, eagerParsing: true) !=
null) {
return null;
}
return 'Invalid URL';
},
),
),
),
),
const Divider(),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: availableBangsAsync.when(
data: (availableBangs) {
if (availableBangs.isEmpty) {
return const SizedBox.shrink();
}
return SiteSearch(
domain: url.host,
availableBangs: availableBangs,
);
},
error: (error, stackTrace) => FailureWidget(
title: 'Could not load bangs',
exception: error,
),
loading: () => SiteSearch(
domain: url.host,
availableBangs: [
BangData(
websiteName: 'websiteName',
domain: 'domain',
trigger: 'trigger',
urlTemplate: 'urlTemplate',
),
],
),
),
),
if (availableBangsAsync.isLoading ||
availableBangCount == null ||
availableBangCount > 0)
const Divider(),
ListTile(
leading: const Icon(MdiIcons.contentCopy),
title: const Text('Copy address'),
onTap: () async {
await Clipboard.setData(
ClipboardData(text: url.toString()),
);
onDismiss?.call();
},
),
ListTile(
onTap: () async {
await ui_helper.launchUrlFeedback(context, url);
},
leading: const Icon(Icons.open_in_browser),
title: const Text('Launch External'),
),
ListTile(
leading: const Icon(MdiIcons.tabPlus),
title: const Text('Open in new tab'),
onTap: () async {
await ref
.read(switchNewTabControllerProvider.notifier)
.add(url);
onDismiss?.call();
},
),
ListTile(
leading: const Icon(Icons.share),
title: const Text('Share link'),
onTap: () async {
await Share.shareUri(url);
onDismiss?.call();
},
),
const Divider(),
ListTile(
leading: Icon(KagiTool.summarizer.icon),
title: const Text('Summarize'),
onTap: () async {
final summarizerUrl = uri_builder.summarizerUri(
document: SharedUrl(url),
mode: SummarizerMode.keyMoments,
);
await ref
.read(switchNewTabControllerProvider.notifier)
.add(summarizerUrl);
onDismiss?.call();
},
),
],
),
],
);
}
}
File diff suppressed because it is too large Load Diff
@@ -1,107 +0,0 @@
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';
class WebViewTab extends StatelessWidget {
final ITab tab;
final bool isActive;
final VoidCallback? onTap;
final VoidCallback? onLongPress;
final VoidCallback? onDelete;
const WebViewTab({
required this.tab,
required this.isActive,
this.onTap,
this.onLongPress,
this.onDelete,
super.key,
});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Container(
decoration: BoxDecoration(
border: Border.all(
color: isActive ? colorScheme.primary : colorScheme.outline,
width: isActive ? 2.0 : 1.0,
),
borderRadius: const BorderRadius.all(Radius.circular(16.0)),
),
child: Material(
color: colorScheme.surfaceContainerHighest,
borderRadius: const BorderRadius.all(Radius.circular(16.0)),
child: InkWell(
borderRadius: const BorderRadius.all(Radius.circular(16.0)),
onTap: onTap,
onLongPress: onLongPress,
child: Column(
children: [
Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.only(left: 6.0),
child: Text(
overflow: TextOverflow.ellipsis,
tab.title ?? 'New Tab',
maxLines: 2,
),
),
),
IconButton(
visualDensity:
const VisualDensity(horizontal: -4.0, vertical: -4.0),
onPressed: onDelete,
icon: const Icon(Icons.close),
),
],
),
Row(
children: [
const SizedBox(
width: 6.0,
),
FaviconImage(
url: tab.url,
favicon: tab.favicon,
),
const SizedBox(
width: 6.0,
),
Expanded(
child: Text(
tab.url.authority,
style: Theme.of(context).textTheme.bodySmall,
),
),
],
),
const SizedBox(
height: 6,
),
if (tab.screenshot != null)
Expanded(
child: ClipRRect(
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(16.0),
bottomRight: Radius.circular(16.0),
),
child: SizedBox(
width: double.infinity,
child: Image.memory(
fit: BoxFit.fitWidth,
tab.screenshot!,
),
),
),
),
],
),
),
),
);
}
}