intermediate
This commit is contained in:
@@ -4,8 +4,8 @@ FLUTTER_APPLICATION_PATH=/home/fafre/Repos/kagi_bang_bang/app
|
|||||||
COCOAPODS_PARALLEL_CODE_SIGN=true
|
COCOAPODS_PARALLEL_CODE_SIGN=true
|
||||||
FLUTTER_TARGET=lib/main.dart
|
FLUTTER_TARGET=lib/main.dart
|
||||||
FLUTTER_BUILD_DIR=build
|
FLUTTER_BUILD_DIR=build
|
||||||
FLUTTER_BUILD_NAME=0.6.2
|
FLUTTER_BUILD_NAME=0.7.0
|
||||||
FLUTTER_BUILD_NUMBER=14
|
FLUTTER_BUILD_NUMBER=15
|
||||||
EXCLUDED_ARCHS[sdk=iphonesimulator*]=i386
|
EXCLUDED_ARCHS[sdk=iphonesimulator*]=i386
|
||||||
EXCLUDED_ARCHS[sdk=iphoneos*]=armv7
|
EXCLUDED_ARCHS[sdk=iphoneos*]=armv7
|
||||||
DART_OBFUSCATION=false
|
DART_OBFUSCATION=false
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ export "FLUTTER_APPLICATION_PATH=/home/fafre/Repos/kagi_bang_bang/app"
|
|||||||
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
|
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
|
||||||
export "FLUTTER_TARGET=lib/main.dart"
|
export "FLUTTER_TARGET=lib/main.dart"
|
||||||
export "FLUTTER_BUILD_DIR=build"
|
export "FLUTTER_BUILD_DIR=build"
|
||||||
export "FLUTTER_BUILD_NAME=0.6.2"
|
export "FLUTTER_BUILD_NAME=0.7.0"
|
||||||
export "FLUTTER_BUILD_NUMBER=14"
|
export "FLUTTER_BUILD_NUMBER=15"
|
||||||
export "DART_OBFUSCATION=false"
|
export "DART_OBFUSCATION=false"
|
||||||
export "TRACK_WIDGET_CREATION=true"
|
export "TRACK_WIDGET_CREATION=true"
|
||||||
export "TREE_SHAKE_ICONS=false"
|
export "TREE_SHAKE_ICONS=false"
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
import 'package:exceptions/exceptions.dart';
|
|
||||||
import 'package:riverpod/riverpod.dart';
|
|
||||||
|
|
||||||
extension AsyncExtension<T> on Result<T> {
|
|
||||||
AsyncValue<T> toAsyncValue() {
|
|
||||||
return switch (this) {
|
|
||||||
Success(value: final value) => AsyncValue.data(value),
|
|
||||||
// TODO: Handle this case.
|
|
||||||
Failure(
|
|
||||||
error: final error,
|
|
||||||
) =>
|
|
||||||
AsyncError(error.message, error.stackTrace ?? StackTrace.empty),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,7 +10,7 @@ import 'package:lensai/features/chat_archive/presentation/screens/list.dart';
|
|||||||
import 'package:lensai/features/chat_archive/presentation/screens/search.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/search_browser/presentation/screens/browser.dart';
|
||||||
import 'package:lensai/features/settings/presentation/screens/settings.dart';
|
import 'package:lensai/features/settings/presentation/screens/settings.dart';
|
||||||
import 'package:lensai/features/topics/presentation/screens/topic_list.dart';
|
import 'package:lensai/features/geckoview/features/topics/presentation/screens/topic_list.dart';
|
||||||
|
|
||||||
part 'routes.g.dart';
|
part 'routes.g.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import 'package:copy_with_extension/copy_with_extension.dart';
|
||||||
|
import 'package:lensai/features/geckoview/domain/entities/browser_icon.dart';
|
||||||
|
|
||||||
|
part 'web_page_info.g.dart';
|
||||||
|
|
||||||
|
@CopyWith()
|
||||||
|
class WebPageInfo {
|
||||||
|
final Uri url;
|
||||||
|
final String? title;
|
||||||
|
final BrowserIcon? favicon;
|
||||||
|
|
||||||
|
WebPageInfo({
|
||||||
|
required this.url,
|
||||||
|
required this.title,
|
||||||
|
required this.favicon,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'web_page_info.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// CopyWithGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
abstract class _$WebPageInfoCWProxy {
|
||||||
|
WebPageInfo url(Uri url);
|
||||||
|
|
||||||
|
WebPageInfo title(String? title);
|
||||||
|
|
||||||
|
WebPageInfo favicon(BrowserIcon? favicon);
|
||||||
|
|
||||||
|
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `WebPageInfo(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
|
||||||
|
///
|
||||||
|
/// Usage
|
||||||
|
/// ```dart
|
||||||
|
/// WebPageInfo(...).copyWith(id: 12, name: "My name")
|
||||||
|
/// ````
|
||||||
|
WebPageInfo call({
|
||||||
|
Uri? url,
|
||||||
|
String? title,
|
||||||
|
BrowserIcon? favicon,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfWebPageInfo.copyWith(...)`. Additionally contains functions for specific fields e.g. `instanceOfWebPageInfo.copyWith.fieldName(...)`
|
||||||
|
class _$WebPageInfoCWProxyImpl implements _$WebPageInfoCWProxy {
|
||||||
|
const _$WebPageInfoCWProxyImpl(this._value);
|
||||||
|
|
||||||
|
final WebPageInfo _value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
WebPageInfo url(Uri url) => this(url: url);
|
||||||
|
|
||||||
|
@override
|
||||||
|
WebPageInfo title(String? title) => this(title: title);
|
||||||
|
|
||||||
|
@override
|
||||||
|
WebPageInfo favicon(BrowserIcon? favicon) => this(favicon: favicon);
|
||||||
|
|
||||||
|
@override
|
||||||
|
|
||||||
|
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `WebPageInfo(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
|
||||||
|
///
|
||||||
|
/// Usage
|
||||||
|
/// ```dart
|
||||||
|
/// WebPageInfo(...).copyWith(id: 12, name: "My name")
|
||||||
|
/// ````
|
||||||
|
WebPageInfo call({
|
||||||
|
Object? url = const $CopyWithPlaceholder(),
|
||||||
|
Object? title = const $CopyWithPlaceholder(),
|
||||||
|
Object? favicon = const $CopyWithPlaceholder(),
|
||||||
|
}) {
|
||||||
|
return WebPageInfo(
|
||||||
|
url: url == const $CopyWithPlaceholder() || url == null
|
||||||
|
? _value.url
|
||||||
|
// ignore: cast_nullable_to_non_nullable
|
||||||
|
: url as Uri,
|
||||||
|
title: title == const $CopyWithPlaceholder()
|
||||||
|
? _value.title
|
||||||
|
// ignore: cast_nullable_to_non_nullable
|
||||||
|
: title as String?,
|
||||||
|
favicon: favicon == const $CopyWithPlaceholder()
|
||||||
|
? _value.favicon
|
||||||
|
// ignore: cast_nullable_to_non_nullable
|
||||||
|
: favicon as BrowserIcon?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension $WebPageInfoCopyWith on WebPageInfo {
|
||||||
|
/// Returns a callable class that can be used as follows: `instanceOfWebPageInfo.copyWith(...)` or like so:`instanceOfWebPageInfo.copyWith.fieldName(...)`.
|
||||||
|
// ignore: library_private_types_in_public_api
|
||||||
|
_$WebPageInfoCWProxy get copyWith => _$WebPageInfoCWProxyImpl(this);
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:lensai/extensions/image.dart';
|
||||||
|
|
||||||
|
class EquatableImage {
|
||||||
|
final Image value;
|
||||||
|
final String? _imageHash;
|
||||||
|
|
||||||
|
EquatableImage(
|
||||||
|
this.value, {
|
||||||
|
required String? hash,
|
||||||
|
}) : _imageHash = hash;
|
||||||
|
|
||||||
|
static Future<EquatableImage> calculate(Image image) async {
|
||||||
|
final imageHash = await image.calculateHash();
|
||||||
|
|
||||||
|
return EquatableImage(image, hash: imageHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => _imageHash.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is EquatableImage && other._imageHash == _imageHash;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
|
||||||
|
|
||||||
class WebPageInfo {
|
|
||||||
final Uri url;
|
|
||||||
final String? title;
|
|
||||||
final Favicon? favicon;
|
|
||||||
|
|
||||||
WebPageInfo({
|
|
||||||
required this.url,
|
|
||||||
required this.title,
|
|
||||||
required this.favicon,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory WebPageInfo.fromJson(Map<String, dynamic> json) {
|
|
||||||
return WebPageInfo(
|
|
||||||
url: Uri.parse(json['url'] as String),
|
|
||||||
title: json['title'] as String?,
|
|
||||||
favicon: switch (json['favicon']) {
|
|
||||||
final Map<String, dynamic> favicon => Favicon.fromMap(favicon),
|
|
||||||
_ => null
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return {
|
|
||||||
'url': url.toString(),
|
|
||||||
'title': title,
|
|
||||||
'favicon': favicon?.toJson(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,8 +9,7 @@ import 'package:lensai/features/content_block/domain/repositories/sync.dart';
|
|||||||
import 'package:lensai/features/search_browser/domain/services/session.dart';
|
import 'package:lensai/features/search_browser/domain/services/session.dart';
|
||||||
import 'package:lensai/features/settings/data/models/settings.dart';
|
import 'package:lensai/features/settings/data/models/settings.dart';
|
||||||
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
|
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
|
||||||
import 'package:lensai/features/topics/data/providers.dart';
|
import 'package:lensai/features/geckoview/features/topics/data/providers.dart';
|
||||||
import 'package:lensai/features/web_view/domain/providers.dart';
|
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
part 'app_initialization.g.dart';
|
part 'app_initialization.g.dart';
|
||||||
@@ -96,8 +95,6 @@ class AppInitializationService extends _$AppInitializationService {
|
|||||||
final settings = await ref.read(settingsRepositoryProvider.future);
|
final settings = await ref.read(settingsRepositoryProvider.future);
|
||||||
final errors = <ErrorMessage>[];
|
final errors = <ErrorMessage>[];
|
||||||
|
|
||||||
unawaited(ref.read(readerabilityScriptProvider.future));
|
|
||||||
|
|
||||||
await _initPackageInfo();
|
await _initPackageInfo();
|
||||||
|
|
||||||
final bangSyncResults = await _initBangs();
|
final bangSyncResults = await _initBangs();
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ part of 'app_initialization.dart';
|
|||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$appInitializationServiceHash() =>
|
String _$appInitializationServiceHash() =>
|
||||||
r'dadcf205d32efa61eb2a0ff1c9b3d49658b76a56';
|
r'eee9c8eadc793fb435af1032ad2099dfd381a034';
|
||||||
|
|
||||||
/// See also [AppInitializationService].
|
/// See also [AppInitializationService].
|
||||||
@ProviderFor(AppInitializationService)
|
@ProviderFor(AppInitializationService)
|
||||||
|
|||||||
@@ -1,57 +1,169 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:exceptions/exceptions.dart';
|
import 'package:exceptions/exceptions.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||||
|
|
||||||
import 'package:html/dom.dart';
|
import 'package:html/dom.dart';
|
||||||
import 'package:html/parser.dart' as html_parser;
|
import 'package:html/parser.dart' as html_parser;
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:lensai/core/http_error_handler.dart';
|
import 'package:lensai/core/http_error_handler.dart';
|
||||||
import 'package:lensai/domain/entities/web_page_info.dart';
|
import 'package:lensai/data/models/web_page_info.dart';
|
||||||
import 'package:lensai/extensions/web_uri_favicon.dart';
|
import 'package:lensai/features/geckoview/domain/entities/browser_icon.dart';
|
||||||
import 'package:lensai/features/web_view/utils/favicon_helper.dart';
|
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:universal_io/io.dart';
|
import 'package:universal_io/io.dart';
|
||||||
|
|
||||||
part 'generic_website.g.dart';
|
part 'generic_website.g.dart';
|
||||||
|
|
||||||
|
const _typeMap = {
|
||||||
|
"manifest": IconType.manifestIcon,
|
||||||
|
"icon": IconType.favicon,
|
||||||
|
"shortcut icon": IconType.favicon,
|
||||||
|
"fluid-icon": IconType.fluidIcon,
|
||||||
|
"apple-touch-icon": IconType.appleTouchIcon,
|
||||||
|
"image_src": IconType.imageSrc,
|
||||||
|
"apple-touch-icon image_src": IconType.appleTouchIcon,
|
||||||
|
"apple-touch-icon-precomposed": IconType.appleTouchIcon,
|
||||||
|
"og:image": IconType.openGraph,
|
||||||
|
"og:image:url": IconType.openGraph,
|
||||||
|
"og:image:secure_url": IconType.openGraph,
|
||||||
|
"twitter:image": IconType.twitter,
|
||||||
|
"msapplication-TileImage": IconType.microsoftTile,
|
||||||
|
};
|
||||||
|
|
||||||
|
Iterable<ResourceSize> sizesToList(String? sizes) sync* {
|
||||||
|
if (sizes != null) {
|
||||||
|
final splitted =
|
||||||
|
sizes.split(' ').where((size) => size.contains('x')).toList();
|
||||||
|
|
||||||
|
for (final size in splitted) {
|
||||||
|
final dimensions = size.split('x');
|
||||||
|
if (dimensions.length == 2) {
|
||||||
|
final height = int.tryParse(dimensions[0]);
|
||||||
|
final width = int.tryParse(dimensions[1]);
|
||||||
|
|
||||||
|
if (width != null && height != null) {
|
||||||
|
yield ResourceSize(height: height, width: width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Riverpod(keepAlive: true)
|
@Riverpod(keepAlive: true)
|
||||||
class GenericWebsiteService extends _$GenericWebsiteService {
|
class GenericWebsiteService extends _$GenericWebsiteService {
|
||||||
|
final GeckoIconService _iconsService;
|
||||||
|
|
||||||
late http.Client _client;
|
late http.Client _client;
|
||||||
|
|
||||||
final Map<String, bool> _httpsCache;
|
final Map<String, bool> _httpsCache;
|
||||||
|
|
||||||
GenericWebsiteService() : _httpsCache = {};
|
GenericWebsiteService()
|
||||||
|
: _httpsCache = {},
|
||||||
|
_iconsService = GeckoIconService();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void build() {
|
void build() {
|
||||||
_client = http.Client();
|
_client = http.Client();
|
||||||
}
|
}
|
||||||
|
|
||||||
static Iterable<Favicon> _extractFavicons(Uri url, Document document) sync* {
|
static Map<String, dynamic> _serializeResource(Resource resource) {
|
||||||
final links = document.querySelectorAll(
|
return {
|
||||||
'link[rel="icon"], link[rel="shortcut icon"], link[rel="apple-touch-icon"]',
|
'url': resource.url,
|
||||||
|
'type': resource.type,
|
||||||
|
'sizes': resource.sizes.nonNulls.map((s) => [s.height, s.width]).toList(),
|
||||||
|
'mimeType': resource.mimeType,
|
||||||
|
'maskable': resource.maskable,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static Resource _deserializeResource(Map<String, dynamic> resource) {
|
||||||
|
return Resource(
|
||||||
|
url: resource['url'] as String,
|
||||||
|
type: resource['type'] as IconType,
|
||||||
|
mimeType: resource['mimeType'] as String?,
|
||||||
|
sizes: (resource['sizes'] as List<List<int>>)
|
||||||
|
.map((s) => ResourceSize(height: s[0], width: s[1]))
|
||||||
|
.toList(),
|
||||||
|
maskable: resource['maskable'] as bool,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
for (final link in links) {
|
static List<Resource> _extractIcons(Document document) {
|
||||||
final href = link.attributes['href'];
|
final List<Resource> icons = [];
|
||||||
if (href != null) {
|
|
||||||
// Attempt to parse height and width if available
|
void collectLinkIcons(String rel) {
|
||||||
int? height;
|
final links = document.querySelectorAll('link[rel="$rel"]');
|
||||||
int? width;
|
for (final link in links) {
|
||||||
if (link.attributes['sizes'] case final String sizes) {
|
final href = link.attributes['href'];
|
||||||
final dimensions = sizes.split('x');
|
final type = _typeMap[rel];
|
||||||
if (dimensions.length == 2) {
|
final mimeType = link.attributes['type'];
|
||||||
height = int.tryParse(dimensions[0]);
|
if (href != null && type != null) {
|
||||||
width = int.tryParse(dimensions[1]);
|
icons.add(
|
||||||
}
|
Resource(
|
||||||
|
url: href,
|
||||||
|
type: type,
|
||||||
|
sizes: sizesToList(link.attributes['sizes']).toList(),
|
||||||
|
mimeType: (mimeType?.isNotEmpty ?? true) ? null : mimeType,
|
||||||
|
maskable: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
yield Favicon(
|
|
||||||
url: WebUri.uri(url.resolve(href)),
|
|
||||||
rel: link.attributes['rel'],
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void collectMetaPropertyIcons(String property) {
|
||||||
|
final metas = document.querySelectorAll('meta[property="$property"]');
|
||||||
|
for (final meta in metas) {
|
||||||
|
final content = meta.attributes['content'];
|
||||||
|
final type = _typeMap[property];
|
||||||
|
if (content != null && type != null) {
|
||||||
|
icons.add(
|
||||||
|
Resource(
|
||||||
|
type: type,
|
||||||
|
url: content,
|
||||||
|
sizes: [],
|
||||||
|
maskable: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void collectMetaNameIcons(String name) {
|
||||||
|
final metas = document.querySelectorAll('meta[name="$name"]');
|
||||||
|
for (final meta in metas) {
|
||||||
|
final content = meta.attributes['content'];
|
||||||
|
final type = _typeMap[name];
|
||||||
|
if (content != null && type != null) {
|
||||||
|
icons.add(
|
||||||
|
Resource(
|
||||||
|
type: type,
|
||||||
|
url: content,
|
||||||
|
sizes: [],
|
||||||
|
maskable: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
collectLinkIcons("icon");
|
||||||
|
collectLinkIcons("shortcut icon");
|
||||||
|
collectLinkIcons("fluid-icon");
|
||||||
|
collectLinkIcons("apple-touch-icon");
|
||||||
|
collectLinkIcons("image_src");
|
||||||
|
collectLinkIcons("apple-touch-icon image_src");
|
||||||
|
collectLinkIcons("apple-touch-icon-precomposed");
|
||||||
|
|
||||||
|
collectMetaPropertyIcons("og:image");
|
||||||
|
collectMetaPropertyIcons("og:image:url");
|
||||||
|
collectMetaPropertyIcons("og:image:secure_url");
|
||||||
|
|
||||||
|
collectMetaNameIcons("twitter:image");
|
||||||
|
collectMetaNameIcons("msapplication-TileImage");
|
||||||
|
|
||||||
|
return icons;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Result<WebPageInfo>> getInfo(Uri url) async {
|
Future<Result<WebPageInfo>> getInfo(Uri url) async {
|
||||||
@@ -59,50 +171,47 @@ class GenericWebsiteService extends _$GenericWebsiteService {
|
|||||||
() async {
|
() async {
|
||||||
final response =
|
final response =
|
||||||
await _client.get(url).timeout(const Duration(seconds: 10));
|
await _client.get(url).timeout(const Duration(seconds: 10));
|
||||||
return await compute(
|
|
||||||
(args) {
|
final result = await compute(
|
||||||
|
(args) async {
|
||||||
final document = html_parser.parse(args[0]);
|
final document = html_parser.parse(args[0]);
|
||||||
final url = Uri.parse(args[1]);
|
|
||||||
|
|
||||||
final title = document.querySelector('title')?.text;
|
final title = document.querySelector('title')?.text;
|
||||||
final favicon = choseFavicon(_extractFavicons(url, document)) ??
|
final resources = _extractIcons(document);
|
||||||
//In case the icon can not get extracted, we use the resolver
|
|
||||||
//of duckduckgo
|
|
||||||
Favicon(
|
|
||||||
url: WebUri.uri(url.genericFavicon()),
|
|
||||||
);
|
|
||||||
|
|
||||||
return WebPageInfo(url: url, title: title, favicon: favicon)
|
return {
|
||||||
.toJson();
|
'title': title,
|
||||||
|
'resources': resources.map(_serializeResource).toList(),
|
||||||
|
};
|
||||||
},
|
},
|
||||||
[response.body, url.toString()],
|
[response.body],
|
||||||
).then(WebPageInfo.fromJson);
|
);
|
||||||
|
|
||||||
|
final resources = (result['resources']! as List<Map<String, dynamic>>)
|
||||||
|
.map(_deserializeResource)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
final favicon = await getUrlFavicon(url: url, resources: resources);
|
||||||
|
|
||||||
|
return WebPageInfo(
|
||||||
|
url: url,
|
||||||
|
title: result['title'] as String?,
|
||||||
|
favicon: favicon,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
exceptionHandler: handleHttpError,
|
exceptionHandler: handleHttpError,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Result<Uint8List?>> getFaviconBytes(Uri url) {
|
Future<BrowserIcon> getUrlFavicon({
|
||||||
return getInfo(url).then(
|
required Uri url,
|
||||||
(result) => result.flatMapAsync(
|
List<Resource> resources = const [],
|
||||||
(info) async {
|
}) async {
|
||||||
if (info.favicon != null) {
|
final result = await _iconsService.loadIcon(url: url, resources: resources);
|
||||||
return _client
|
return BrowserIcon.fromBytes(
|
||||||
.get(info.favicon!.url)
|
result.image,
|
||||||
.timeout(const Duration(seconds: 10))
|
dominantColor: (result.color != null) ? Color(result.color!) : null,
|
||||||
.then((response) {
|
source: result.source,
|
||||||
if (response.statusCode == 200) {
|
|
||||||
return response.bodyBytes;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
exceptionHandler: handleHttpError,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ part of 'generic_website.dart';
|
|||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$genericWebsiteServiceHash() =>
|
String _$genericWebsiteServiceHash() =>
|
||||||
r'517304ca95e1ef8377d4c1a3bd06fef9c68c92cd';
|
r'0b1d953fe1b30b9dd0396df34e6176424cdaf941';
|
||||||
|
|
||||||
/// See also [GenericWebsiteService].
|
/// See also [GenericWebsiteService].
|
||||||
@ProviderFor(GenericWebsiteService)
|
@ProviderFor(GenericWebsiteService)
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:crypto/crypto.dart';
|
||||||
|
import 'package:lensai/domain/entities/equatable_image.dart';
|
||||||
|
|
||||||
|
extension ImageHash on Image {
|
||||||
|
Future<String?> calculateHash() async {
|
||||||
|
final bytes = await toByteData();
|
||||||
|
if (bytes != null) {
|
||||||
|
final digest = sha1.convert(bytes.buffer.asUint8List());
|
||||||
|
return digest.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<EquatableImage> toEquatable() {
|
||||||
|
return EquatableImage.calculate(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
extension UriFavicon on Uri {
|
|
||||||
Uri genericFavicon() =>
|
|
||||||
Uri.parse('https://icons.duckduckgo.com/ip3/$host.ico');
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'package:home_widget/home_widget.dart';
|
import 'package:home_widget/home_widget.dart';
|
||||||
import 'package:lensai/domain/entities/received_parameter.dart';
|
import 'package:lensai/data/models/received_parameter.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:rxdart/rxdart.dart';
|
import 'package:rxdart/rxdart.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:drift/drift.dart';
|
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/database/database.dart';
|
||||||
import 'package:lensai/features/bangs/data/models/bang.dart';
|
import 'package:lensai/features/bangs/data/models/bang.dart';
|
||||||
import 'package:lensai/features/bangs/data/models/bang_data.dart';
|
import 'package:lensai/features/bangs/data/models/bang_data.dart';
|
||||||
@@ -94,14 +97,17 @@ class BangDao extends DatabaseAccessor<BangDatabase> with _$BangDaoMixin {
|
|||||||
return db.bangQuery(query: db.buildQuery(searchString));
|
return db.bangQuery(query: db.buildQuery(searchString));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> upsertBangIcon(String trigger, Uint8List iconData) {
|
// Future<int> upsertBangIcon(String trigger, BrowserIcon icon) async {
|
||||||
return db.bangIcon.insertOne(
|
// //TODO: swithc to WEBP in future, requires 3rd party
|
||||||
BangIconCompanion.insert(
|
// final compressed = await icon.image.toByteData(format: ImageByteFormat.png);
|
||||||
trigger: trigger,
|
|
||||||
iconData: iconData,
|
// return db.bangIcon.insertOne(
|
||||||
fetchDate: DateTime.now(),
|
// BangIconCompanion.insert(
|
||||||
),
|
// trigger: trigger,
|
||||||
mode: InsertMode.insertOrReplace,
|
// iconData: compressed!.buffer.asUint8List(),
|
||||||
);
|
// fetchDate: DateTime.now(),
|
||||||
}
|
// ),
|
||||||
|
// mode: InsertMode.insertOrReplace,
|
||||||
|
// );
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1021,8 +1021,6 @@ class BangDataView extends ViewInfo<BangDataView, BangData>
|
|||||||
.read(DriftSqlType.int, data['${effectivePrefix}frequency']),
|
.read(DriftSqlType.int, data['${effectivePrefix}frequency']),
|
||||||
lastUsed: attachedDatabase.typeMapping
|
lastUsed: attachedDatabase.typeMapping
|
||||||
.read(DriftSqlType.dateTime, data['${effectivePrefix}last_used']),
|
.read(DriftSqlType.dateTime, data['${effectivePrefix}last_used']),
|
||||||
iconData: attachedDatabase.typeMapping
|
|
||||||
.read(DriftSqlType.blob, data['${effectivePrefix}icon_data']),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1121,7 +1119,6 @@ abstract class _$BangDatabase extends GeneratedDatabase {
|
|||||||
.fromSql(row.readNullable<String>('format')),
|
.fromSql(row.readNullable<String>('format')),
|
||||||
frequency: row.readNullable<int>('frequency'),
|
frequency: row.readNullable<int>('frequency'),
|
||||||
lastUsed: row.readNullable<DateTime>('last_used'),
|
lastUsed: row.readNullable<DateTime>('last_used'),
|
||||||
iconData: row.readNullable<Uint8List>('icon_data'),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:copy_with_extension/copy_with_extension.dart';
|
import 'package:copy_with_extension/copy_with_extension.dart';
|
||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:lensai/features/bangs/data/models/bang.dart';
|
import 'package:lensai/features/bangs/data/models/bang.dart';
|
||||||
|
import 'package:lensai/features/geckoview/domain/entities/browser_icon.dart';
|
||||||
|
|
||||||
part 'bang_data.g.dart';
|
part 'bang_data.g.dart';
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ class BangData extends Bang {
|
|||||||
final int frequency;
|
final int frequency;
|
||||||
final DateTime? lastUsed;
|
final DateTime? lastUsed;
|
||||||
|
|
||||||
final Uint8List? iconData;
|
final BrowserIcon? icon;
|
||||||
|
|
||||||
BangData({
|
BangData({
|
||||||
required super.websiteName,
|
required super.websiteName,
|
||||||
@@ -21,7 +21,7 @@ class BangData extends Bang {
|
|||||||
super.format,
|
super.format,
|
||||||
int? frequency,
|
int? frequency,
|
||||||
this.lastUsed,
|
this.lastUsed,
|
||||||
this.iconData,
|
this.icon,
|
||||||
}) : frequency = frequency ?? 0;
|
}) : frequency = frequency ?? 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -32,6 +32,6 @@ class BangData extends Bang {
|
|||||||
...super.hashParameters,
|
...super.hashParameters,
|
||||||
frequency,
|
frequency,
|
||||||
lastUsed,
|
lastUsed,
|
||||||
iconData,
|
icon,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ abstract class _$BangDataCWProxy {
|
|||||||
|
|
||||||
BangData lastUsed(DateTime? lastUsed);
|
BangData lastUsed(DateTime? lastUsed);
|
||||||
|
|
||||||
BangData iconData(Uint8List? iconData);
|
BangData icon(BrowserIcon? icon);
|
||||||
|
|
||||||
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `BangData(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
|
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `BangData(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
|
||||||
///
|
///
|
||||||
@@ -43,7 +43,7 @@ abstract class _$BangDataCWProxy {
|
|||||||
Set<BangFormat>? format,
|
Set<BangFormat>? format,
|
||||||
int? frequency,
|
int? frequency,
|
||||||
DateTime? lastUsed,
|
DateTime? lastUsed,
|
||||||
Uint8List? iconData,
|
BrowserIcon? icon,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ class _$BangDataCWProxyImpl implements _$BangDataCWProxy {
|
|||||||
BangData lastUsed(DateTime? lastUsed) => this(lastUsed: lastUsed);
|
BangData lastUsed(DateTime? lastUsed) => this(lastUsed: lastUsed);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
BangData iconData(Uint8List? iconData) => this(iconData: iconData);
|
BangData icon(BrowserIcon? icon) => this(icon: icon);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
||||||
@@ -101,7 +101,7 @@ class _$BangDataCWProxyImpl implements _$BangDataCWProxy {
|
|||||||
Object? format = const $CopyWithPlaceholder(),
|
Object? format = const $CopyWithPlaceholder(),
|
||||||
Object? frequency = const $CopyWithPlaceholder(),
|
Object? frequency = const $CopyWithPlaceholder(),
|
||||||
Object? lastUsed = const $CopyWithPlaceholder(),
|
Object? lastUsed = const $CopyWithPlaceholder(),
|
||||||
Object? iconData = const $CopyWithPlaceholder(),
|
Object? icon = const $CopyWithPlaceholder(),
|
||||||
}) {
|
}) {
|
||||||
return BangData(
|
return BangData(
|
||||||
websiteName:
|
websiteName:
|
||||||
@@ -142,10 +142,10 @@ class _$BangDataCWProxyImpl implements _$BangDataCWProxy {
|
|||||||
? _value.lastUsed
|
? _value.lastUsed
|
||||||
// ignore: cast_nullable_to_non_nullable
|
// ignore: cast_nullable_to_non_nullable
|
||||||
: lastUsed as DateTime?,
|
: lastUsed as DateTime?,
|
||||||
iconData: iconData == const $CopyWithPlaceholder()
|
icon: icon == const $CopyWithPlaceholder()
|
||||||
? _value.iconData
|
? _value.icon
|
||||||
// ignore: cast_nullable_to_non_nullable
|
// ignore: cast_nullable_to_non_nullable
|
||||||
: iconData as Uint8List?,
|
: icon as BrowserIcon?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ 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.dart';
|
||||||
import 'package:lensai/features/bangs/data/models/bang_data.dart';
|
import 'package:lensai/features/bangs/data/models/bang_data.dart';
|
||||||
import 'package:lensai/features/bangs/data/providers.dart';
|
import 'package:lensai/features/bangs/data/providers.dart';
|
||||||
import 'package:lensai/utils/image_helper.dart';
|
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
part 'data.g.dart';
|
part 'data.g.dart';
|
||||||
@@ -69,24 +68,16 @@ class BangDataRepository extends _$BangDataRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<Result<BangData>> ensureIconAvailable(BangData bang) async {
|
Future<Result<BangData>> ensureIconAvailable(BangData bang) async {
|
||||||
if (bang.iconData != null) {
|
if (bang.icon != null) {
|
||||||
return Result.success(bang);
|
return Result.success(bang);
|
||||||
}
|
}
|
||||||
|
|
||||||
final result = await ref
|
final icon = await ref
|
||||||
.read(genericWebsiteServiceProvider.notifier)
|
.read(genericWebsiteServiceProvider.notifier)
|
||||||
.getFaviconBytes(Uri.parse(bang.getUrl('').origin));
|
.getUrlFavicon(url: Uri.parse(bang.getUrl('').origin));
|
||||||
|
|
||||||
return result.flatMapAsync(
|
// await _db.bangDao.upsertBangIcon(bang.trigger, icon);
|
||||||
(faviconBytes) async {
|
return Result.success(bang.copyWith.icon(icon));
|
||||||
if (faviconBytes != null && await isImageValid(faviconBytes)) {
|
|
||||||
await _db.bangDao.upsertBangIcon(bang.trigger, faviconBytes);
|
|
||||||
return bang.copyWith.iconData(faviconBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
return bang;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<double> watchIconCacheSize() {
|
Stream<double> watchIconCacheSize() {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ part of 'data.dart';
|
|||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$bangDataRepositoryHash() =>
|
String _$bangDataRepositoryHash() =>
|
||||||
r'ef06babcaa07361e37f5cc62a268eb10aed0fc82';
|
r'abacc6db3a61958cae67a91cbfd312ae87c641b3';
|
||||||
|
|
||||||
/// See also [BangDataRepository].
|
/// See also [BangDataRepository].
|
||||||
@ProviderFor(BangDataRepository)
|
@ProviderFor(BangDataRepository)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:lensai/core/routing/routes.dart';
|
import 'package:lensai/core/routing/routes.dart';
|
||||||
import 'package:lensai/features/bangs/domain/providers.dart';
|
import 'package:lensai/features/bangs/domain/providers.dart';
|
||||||
import 'package:lensai/features/bangs/presentation/widgets/bang_details.dart';
|
import 'package:lensai/features/bangs/presentation/widgets/bang_details.dart';
|
||||||
import 'package:lensai/features/search_browser/domain/entities/modes.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/entities/sheet.dart';
|
||||||
import 'package:lensai/features/search_browser/domain/providers.dart';
|
import 'package:lensai/features/search_browser/domain/providers.dart';
|
||||||
import 'package:lensai/presentation/widgets/failure_widget.dart';
|
import 'package:lensai/presentation/widgets/failure_widget.dart';
|
||||||
@@ -49,9 +49,9 @@ class BangListScreen extends HookConsumerWidget {
|
|||||||
.read(selectedBangTriggerProvider().notifier)
|
.read(selectedBangTriggerProvider().notifier)
|
||||||
.setTrigger(bang.trigger);
|
.setTrigger(bang.trigger);
|
||||||
|
|
||||||
if (ref.read(bottomSheetProvider) is! CreateTab) {
|
if (ref.read(bottomSheetProvider) is! CreateTabSheet) {
|
||||||
ref.read(bottomSheetProvider.notifier).show(
|
ref.read(bottomSheetProvider.notifier).show(
|
||||||
CreateTab(
|
CreateTabSheet(
|
||||||
preferredTool: KagiTool.search,
|
preferredTool: KagiTool.search,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:lensai/core/routing/routes.dart';
|
import 'package:lensai/core/routing/routes.dart';
|
||||||
import 'package:lensai/features/bangs/domain/repositories/search.dart';
|
import 'package:lensai/features/bangs/domain/repositories/search.dart';
|
||||||
import 'package:lensai/features/bangs/presentation/widgets/bang_details.dart';
|
import 'package:lensai/features/bangs/presentation/widgets/bang_details.dart';
|
||||||
import 'package:lensai/features/search_browser/domain/entities/modes.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/entities/sheet.dart';
|
||||||
import 'package:lensai/features/search_browser/domain/providers.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/models/settings.dart';
|
||||||
@@ -77,9 +77,9 @@ class BangSearchScreen extends HookConsumerWidget {
|
|||||||
.read(selectedBangTriggerProvider().notifier)
|
.read(selectedBangTriggerProvider().notifier)
|
||||||
.setTrigger(bang.trigger);
|
.setTrigger(bang.trigger);
|
||||||
|
|
||||||
if (ref.read(bottomSheetProvider) is! CreateTab) {
|
if (ref.read(bottomSheetProvider) is! CreateTabSheet) {
|
||||||
ref.read(bottomSheetProvider.notifier).show(
|
ref.read(bottomSheetProvider.notifier).show(
|
||||||
CreateTab(preferredTool: KagiTool.search),
|
CreateTabSheet(preferredTool: KagiTool.search),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import 'package:lensai/features/bangs/data/models/bang_data.dart';
|
|||||||
import 'package:lensai/features/bangs/presentation/widgets/bang_icon.dart';
|
import 'package:lensai/features/bangs/presentation/widgets/bang_icon.dart';
|
||||||
import 'package:lensai/features/settings/data/models/settings.dart';
|
import 'package:lensai/features/settings/data/models/settings.dart';
|
||||||
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
|
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
|
||||||
import 'package:lensai/features/web_view/presentation/controllers/switch_new_tab.dart';
|
|
||||||
import 'package:lensai/utils/ui_helper.dart' as ui_helper;
|
import 'package:lensai/utils/ui_helper.dart' as ui_helper;
|
||||||
|
|
||||||
class BangDetails extends HookConsumerWidget {
|
class BangDetails extends HookConsumerWidget {
|
||||||
|
|||||||
@@ -20,18 +20,12 @@ class BangIcon extends HookConsumerWidget {
|
|||||||
enabled: bangAsync.isLoading,
|
enabled: bangAsync.isLoading,
|
||||||
child: SizedBox.square(
|
child: SizedBox.square(
|
||||||
dimension: iconSize,
|
dimension: iconSize,
|
||||||
child: (bang.iconData != null)
|
child: (bang.icon != null)
|
||||||
? Image.memory(
|
? RawImage(
|
||||||
bang.iconData!,
|
image: bang.icon?.image.value,
|
||||||
height: iconSize,
|
height: iconSize,
|
||||||
width: iconSize,
|
width: iconSize,
|
||||||
fit: BoxFit.fill,
|
fit: BoxFit.fill,
|
||||||
errorBuilder: (context, error, stackTrace) {
|
|
||||||
return Icon(
|
|
||||||
MdiIcons.web,
|
|
||||||
size: iconSize,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
: Icon(
|
: Icon(
|
||||||
MdiIcons.web,
|
MdiIcons.web,
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ 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/bang_icon.dart';
|
||||||
import 'package:lensai/features/bangs/presentation/widgets/search_field.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/domain/providers.dart';
|
||||||
import 'package:lensai/features/web_view/presentation/controllers/switch_new_tab.dart';
|
|
||||||
import 'package:lensai/presentation/widgets/selectable_chips.dart';
|
import 'package:lensai/presentation/widgets/selectable_chips.dart';
|
||||||
|
|
||||||
class SiteSearch extends HookConsumerWidget {
|
class SiteSearch extends HookConsumerWidget {
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ 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/chat_archive/utils/markdown_to_text.dart';
|
||||||
import 'package:lensai/features/settings/data/models/settings.dart';
|
import 'package:lensai/features/settings/data/models/settings.dart';
|
||||||
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
|
import 'package:lensai/features/settings/data/repositories/settings_repository.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/failure_widget.dart';
|
||||||
import 'package:lensai/utils/ui_helper.dart' as ui_helper;
|
import 'package:lensai/utils/ui_helper.dart' as ui_helper;
|
||||||
import 'package:skeletonizer/skeletonizer.dart';
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
@@ -51,7 +50,7 @@ class ChatArchiveDetailScreen extends HookConsumerWidget {
|
|||||||
if (chatAsync.valueOrNull != null) {
|
if (chatAsync.valueOrNull != null) {
|
||||||
await Clipboard.setData(
|
await Clipboard.setData(
|
||||||
ClipboardData(
|
ClipboardData(
|
||||||
text: await Future.microtask(
|
text: await Future(
|
||||||
() => markdownToText(chatAsync.valueOrNull!),
|
() => markdownToText(chatAsync.valueOrNull!),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import 'package:fading_scroll/fading_scroll.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:lensai/core/extension/date_time.dart';
|
import 'package:lensai/extensions/date_time.dart';
|
||||||
import 'package:lensai/core/routing/routes.dart';
|
import 'package:lensai/core/routing/routes.dart';
|
||||||
import 'package:lensai/features/chat_archive/domain/repositories/archive.dart';
|
import 'package:lensai/features/chat_archive/domain/repositories/archive.dart';
|
||||||
import 'package:lensai/presentation/widgets/failure_widget.dart';
|
import 'package:lensai/presentation/widgets/failure_widget.dart';
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:lensai/core/extension/date_time.dart';
|
import 'package:lensai/extensions/date_time.dart';
|
||||||
import 'package:lensai/core/routing/routes.dart';
|
import 'package:lensai/core/routing/routes.dart';
|
||||||
import 'package:lensai/features/chat_archive/domain/entities/chat_entity.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/chat_archive/domain/repositories/search.dart';
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import 'dart:typed_data';
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:fast_equatable/fast_equatable.dart';
|
||||||
|
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||||
|
import 'package:lensai/domain/entities/equatable_image.dart';
|
||||||
|
import 'package:lensai/extensions/image.dart';
|
||||||
|
import 'package:lensai/features/geckoview/utils/image_helper.dart';
|
||||||
|
|
||||||
|
class BrowserIcon with FastEquatable {
|
||||||
|
final EquatableImage image;
|
||||||
|
|
||||||
|
final Color? dominantColor;
|
||||||
|
final IconSource source;
|
||||||
|
|
||||||
|
static Future<BrowserIcon> fromBytes(
|
||||||
|
Uint8List bytes, {
|
||||||
|
required Color? dominantColor,
|
||||||
|
required IconSource source,
|
||||||
|
}) async {
|
||||||
|
final image =
|
||||||
|
await tryDecodeImage(bytes).then((image) => image!.toEquatable());
|
||||||
|
|
||||||
|
return BrowserIcon._(
|
||||||
|
image: image,
|
||||||
|
dominantColor: dominantColor,
|
||||||
|
source: source,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
BrowserIcon._({
|
||||||
|
required this.image,
|
||||||
|
required this.dominantColor,
|
||||||
|
required this.source,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get cacheHash => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get hashParameters => [
|
||||||
|
image,
|
||||||
|
dominantColor,
|
||||||
|
source,
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import 'package:fast_equatable/fast_equatable.dart';
|
||||||
|
|
||||||
|
class HistoryItem with FastEquatable {
|
||||||
|
final Uri url;
|
||||||
|
final String title;
|
||||||
|
|
||||||
|
HistoryItem({required this.url, required this.title});
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get cacheHash => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get hashParameters => [url, title];
|
||||||
|
}
|
||||||
|
|
||||||
|
class HistoryState with FastEquatable {
|
||||||
|
final List<HistoryItem> items;
|
||||||
|
final int currentIndex;
|
||||||
|
|
||||||
|
final bool canGoBack;
|
||||||
|
final bool canGoForward;
|
||||||
|
|
||||||
|
HistoryState({
|
||||||
|
required this.items,
|
||||||
|
required this.currentIndex,
|
||||||
|
required this.canGoBack,
|
||||||
|
required this.canGoForward,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory HistoryState.$default() => HistoryState(
|
||||||
|
items: const [],
|
||||||
|
currentIndex: 0,
|
||||||
|
canGoBack: false,
|
||||||
|
canGoForward: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get cacheHash => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get hashParameters =>
|
||||||
|
[items, currentIndex, canGoBack, canGoForward];
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import 'package:fast_equatable/fast_equatable.dart';
|
||||||
|
|
||||||
|
class ReaderableState with FastEquatable {
|
||||||
|
final bool readerable;
|
||||||
|
|
||||||
|
final bool active;
|
||||||
|
|
||||||
|
ReaderableState({
|
||||||
|
required this.readerable,
|
||||||
|
required this.active,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory ReaderableState.$default() => ReaderableState(
|
||||||
|
readerable: false,
|
||||||
|
active: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get cacheHash => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get hashParameters => [readerable, active];
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import 'package:fast_equatable/fast_equatable.dart';
|
||||||
|
|
||||||
|
class SecurityState with FastEquatable {
|
||||||
|
final bool secure;
|
||||||
|
final String host;
|
||||||
|
final String issuer;
|
||||||
|
|
||||||
|
SecurityState({
|
||||||
|
required this.secure,
|
||||||
|
required this.host,
|
||||||
|
required this.issuer,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory SecurityState.$default() => SecurityState(
|
||||||
|
secure: false,
|
||||||
|
host: "",
|
||||||
|
issuer: "",
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get cacheHash => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get hashParameters => [secure, host, issuer];
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import 'package:copy_with_extension/copy_with_extension.dart';
|
||||||
|
import 'package:fast_equatable/fast_equatable.dart';
|
||||||
|
import 'package:lensai/domain/entities/equatable_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';
|
||||||
|
|
||||||
|
part 'tab_state.g.dart';
|
||||||
|
|
||||||
|
@CopyWith()
|
||||||
|
class TabState with FastEquatable {
|
||||||
|
@CopyWithField(immutable: true)
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
final String? contextId;
|
||||||
|
|
||||||
|
final Uri url;
|
||||||
|
final String title;
|
||||||
|
|
||||||
|
final EquatableImage? icon;
|
||||||
|
final EquatableImage? thumbnail;
|
||||||
|
|
||||||
|
final int progress;
|
||||||
|
|
||||||
|
final bool isPrivate;
|
||||||
|
final bool isFullScreen;
|
||||||
|
final bool isLoading;
|
||||||
|
|
||||||
|
final SecurityState securityInfoState;
|
||||||
|
final HistoryState historyState;
|
||||||
|
final ReaderableState readerableState;
|
||||||
|
|
||||||
|
TabState({
|
||||||
|
required this.id,
|
||||||
|
required this.contextId,
|
||||||
|
required this.url,
|
||||||
|
required this.title,
|
||||||
|
required this.icon,
|
||||||
|
required this.thumbnail,
|
||||||
|
required this.progress,
|
||||||
|
required this.isPrivate,
|
||||||
|
required this.isFullScreen,
|
||||||
|
required this.isLoading,
|
||||||
|
required this.securityInfoState,
|
||||||
|
required this.historyState,
|
||||||
|
required this.readerableState,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory TabState.$default(String tabId) => TabState(
|
||||||
|
id: tabId,
|
||||||
|
contextId: null,
|
||||||
|
url: Uri.parse('about:blank'),
|
||||||
|
title: "",
|
||||||
|
icon: null,
|
||||||
|
thumbnail: null,
|
||||||
|
progress: 0,
|
||||||
|
isPrivate: false,
|
||||||
|
isFullScreen: false,
|
||||||
|
isLoading: false,
|
||||||
|
securityInfoState: SecurityState.$default(),
|
||||||
|
historyState: HistoryState.$default(),
|
||||||
|
readerableState: ReaderableState.$default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get cacheHash => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get hashParameters => [
|
||||||
|
id,
|
||||||
|
contextId,
|
||||||
|
url,
|
||||||
|
title,
|
||||||
|
icon,
|
||||||
|
thumbnail,
|
||||||
|
progress,
|
||||||
|
isPrivate,
|
||||||
|
isFullScreen,
|
||||||
|
isLoading,
|
||||||
|
securityInfoState,
|
||||||
|
historyState,
|
||||||
|
readerableState,
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'tab_state.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// CopyWithGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
abstract class _$TabStateCWProxy {
|
||||||
|
TabState contextId(String? contextId);
|
||||||
|
|
||||||
|
TabState url(Uri url);
|
||||||
|
|
||||||
|
TabState title(String title);
|
||||||
|
|
||||||
|
TabState icon(EquatableImage? icon);
|
||||||
|
|
||||||
|
TabState thumbnail(EquatableImage? thumbnail);
|
||||||
|
|
||||||
|
TabState progress(int progress);
|
||||||
|
|
||||||
|
TabState isPrivate(bool isPrivate);
|
||||||
|
|
||||||
|
TabState isFullScreen(bool isFullScreen);
|
||||||
|
|
||||||
|
TabState isLoading(bool isLoading);
|
||||||
|
|
||||||
|
TabState securityInfoState(SecurityState securityInfoState);
|
||||||
|
|
||||||
|
TabState historyState(HistoryState historyState);
|
||||||
|
|
||||||
|
TabState readerableState(ReaderableState readerableState);
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
/// ```dart
|
||||||
|
/// TabState(...).copyWith(id: 12, name: "My name")
|
||||||
|
/// ````
|
||||||
|
TabState call({
|
||||||
|
String? contextId,
|
||||||
|
Uri? url,
|
||||||
|
String? title,
|
||||||
|
EquatableImage? icon,
|
||||||
|
EquatableImage? thumbnail,
|
||||||
|
int? progress,
|
||||||
|
bool? isPrivate,
|
||||||
|
bool? isFullScreen,
|
||||||
|
bool? isLoading,
|
||||||
|
SecurityState? securityInfoState,
|
||||||
|
HistoryState? historyState,
|
||||||
|
ReaderableState? readerableState,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfTabState.copyWith(...)`. Additionally contains functions for specific fields e.g. `instanceOfTabState.copyWith.fieldName(...)`
|
||||||
|
class _$TabStateCWProxyImpl implements _$TabStateCWProxy {
|
||||||
|
const _$TabStateCWProxyImpl(this._value);
|
||||||
|
|
||||||
|
final TabState _value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
TabState contextId(String? contextId) => this(contextId: contextId);
|
||||||
|
|
||||||
|
@override
|
||||||
|
TabState url(Uri url) => this(url: url);
|
||||||
|
|
||||||
|
@override
|
||||||
|
TabState title(String title) => this(title: title);
|
||||||
|
|
||||||
|
@override
|
||||||
|
TabState icon(EquatableImage? icon) => this(icon: icon);
|
||||||
|
|
||||||
|
@override
|
||||||
|
TabState thumbnail(EquatableImage? thumbnail) => this(thumbnail: thumbnail);
|
||||||
|
|
||||||
|
@override
|
||||||
|
TabState progress(int progress) => this(progress: progress);
|
||||||
|
|
||||||
|
@override
|
||||||
|
TabState isPrivate(bool isPrivate) => this(isPrivate: isPrivate);
|
||||||
|
|
||||||
|
@override
|
||||||
|
TabState isFullScreen(bool isFullScreen) => this(isFullScreen: isFullScreen);
|
||||||
|
|
||||||
|
@override
|
||||||
|
TabState isLoading(bool isLoading) => this(isLoading: isLoading);
|
||||||
|
|
||||||
|
@override
|
||||||
|
TabState securityInfoState(SecurityState securityInfoState) =>
|
||||||
|
this(securityInfoState: securityInfoState);
|
||||||
|
|
||||||
|
@override
|
||||||
|
TabState historyState(HistoryState historyState) =>
|
||||||
|
this(historyState: historyState);
|
||||||
|
|
||||||
|
@override
|
||||||
|
TabState readerableState(ReaderableState readerableState) =>
|
||||||
|
this(readerableState: readerableState);
|
||||||
|
|
||||||
|
@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.
|
||||||
|
///
|
||||||
|
/// Usage
|
||||||
|
/// ```dart
|
||||||
|
/// TabState(...).copyWith(id: 12, name: "My name")
|
||||||
|
/// ````
|
||||||
|
TabState call({
|
||||||
|
Object? contextId = const $CopyWithPlaceholder(),
|
||||||
|
Object? url = const $CopyWithPlaceholder(),
|
||||||
|
Object? title = const $CopyWithPlaceholder(),
|
||||||
|
Object? icon = const $CopyWithPlaceholder(),
|
||||||
|
Object? thumbnail = const $CopyWithPlaceholder(),
|
||||||
|
Object? progress = const $CopyWithPlaceholder(),
|
||||||
|
Object? isPrivate = const $CopyWithPlaceholder(),
|
||||||
|
Object? isFullScreen = const $CopyWithPlaceholder(),
|
||||||
|
Object? isLoading = const $CopyWithPlaceholder(),
|
||||||
|
Object? securityInfoState = const $CopyWithPlaceholder(),
|
||||||
|
Object? historyState = const $CopyWithPlaceholder(),
|
||||||
|
Object? readerableState = const $CopyWithPlaceholder(),
|
||||||
|
}) {
|
||||||
|
return TabState(
|
||||||
|
id: _value.id,
|
||||||
|
contextId: contextId == const $CopyWithPlaceholder()
|
||||||
|
? _value.contextId
|
||||||
|
// ignore: cast_nullable_to_non_nullable
|
||||||
|
: contextId as String?,
|
||||||
|
url: url == const $CopyWithPlaceholder() || url == null
|
||||||
|
? _value.url
|
||||||
|
// ignore: cast_nullable_to_non_nullable
|
||||||
|
: url as Uri,
|
||||||
|
title: title == const $CopyWithPlaceholder() || title == null
|
||||||
|
? _value.title
|
||||||
|
// ignore: cast_nullable_to_non_nullable
|
||||||
|
: title as String,
|
||||||
|
icon: icon == const $CopyWithPlaceholder()
|
||||||
|
? _value.icon
|
||||||
|
// ignore: cast_nullable_to_non_nullable
|
||||||
|
: icon as EquatableImage?,
|
||||||
|
thumbnail: thumbnail == const $CopyWithPlaceholder()
|
||||||
|
? _value.thumbnail
|
||||||
|
// ignore: cast_nullable_to_non_nullable
|
||||||
|
: thumbnail as EquatableImage?,
|
||||||
|
progress: progress == const $CopyWithPlaceholder() || progress == null
|
||||||
|
? _value.progress
|
||||||
|
// ignore: cast_nullable_to_non_nullable
|
||||||
|
: progress as int,
|
||||||
|
isPrivate: isPrivate == const $CopyWithPlaceholder() || isPrivate == null
|
||||||
|
? _value.isPrivate
|
||||||
|
// ignore: cast_nullable_to_non_nullable
|
||||||
|
: isPrivate as bool,
|
||||||
|
isFullScreen:
|
||||||
|
isFullScreen == const $CopyWithPlaceholder() || isFullScreen == null
|
||||||
|
? _value.isFullScreen
|
||||||
|
// ignore: cast_nullable_to_non_nullable
|
||||||
|
: isFullScreen as bool,
|
||||||
|
isLoading: isLoading == const $CopyWithPlaceholder() || isLoading == null
|
||||||
|
? _value.isLoading
|
||||||
|
// ignore: cast_nullable_to_non_nullable
|
||||||
|
: isLoading as bool,
|
||||||
|
securityInfoState: securityInfoState == const $CopyWithPlaceholder() ||
|
||||||
|
securityInfoState == null
|
||||||
|
? _value.securityInfoState
|
||||||
|
// ignore: cast_nullable_to_non_nullable
|
||||||
|
: securityInfoState as SecurityState,
|
||||||
|
historyState:
|
||||||
|
historyState == const $CopyWithPlaceholder() || historyState == null
|
||||||
|
? _value.historyState
|
||||||
|
// ignore: cast_nullable_to_non_nullable
|
||||||
|
: historyState as HistoryState,
|
||||||
|
readerableState: readerableState == const $CopyWithPlaceholder() ||
|
||||||
|
readerableState == null
|
||||||
|
? _value.readerableState
|
||||||
|
// ignore: cast_nullable_to_non_nullable
|
||||||
|
: readerableState as ReaderableState,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension $TabStateCopyWith on TabState {
|
||||||
|
/// Returns a callable class that can be used as follows: `instanceOfTabState.copyWith(...)` or like so:`instanceOfTabState.copyWith.fieldName(...)`.
|
||||||
|
// ignore: library_private_types_in_public_api
|
||||||
|
_$TabStateCWProxy get copyWith => _$TabStateCWProxyImpl(this);
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
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:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
|
part 'providers.g.dart';
|
||||||
|
|
||||||
|
@Riverpod(keepAlive: true)
|
||||||
|
GeckoEventService eventService(EventServiceRef ref) {
|
||||||
|
final service = GeckoEventService.setUp();
|
||||||
|
|
||||||
|
ref.onDispose(() {
|
||||||
|
service.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'providers.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$eventServiceHash() => r'5aa357fdf0d217677a9a66ecb50417ac18929cad';
|
||||||
|
|
||||||
|
/// See also [eventService].
|
||||||
|
@ProviderFor(eventService)
|
||||||
|
final eventServiceProvider = Provider<GeckoEventService>.internal(
|
||||||
|
eventService,
|
||||||
|
name: r'eventServiceProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product') ? null : _$eventServiceHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
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';
|
||||||
|
|
||||||
|
/// See also [selectedTabSessionNotifier].
|
||||||
|
@ProviderFor(selectedTabSessionNotifier)
|
||||||
|
final selectedTabSessionNotifierProvider =
|
||||||
|
AutoDisposeProvider<TabSession>.internal(
|
||||||
|
selectedTabSessionNotifier,
|
||||||
|
name: r'selectedTabSessionNotifierProvider',
|
||||||
|
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$selectedTabSessionNotifierHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef SelectedTabSessionNotifierRef = AutoDisposeProviderRef<TabSession>;
|
||||||
|
// 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,33 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? build() {
|
||||||
|
final eventSerivce = ref.watch(eventServiceProvider);
|
||||||
|
|
||||||
|
final selectedTabSub = eventSerivce.selectedTabEvents.listen(
|
||||||
|
(tabId) {
|
||||||
|
state = tabId;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
ref.onDispose(() {
|
||||||
|
unawaited(selectedTabSub.cancel());
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'selected_tab.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$selectedTabControllerHash() =>
|
||||||
|
r'b5d79d4d5c2dc08d504e601fa29c8b83d51c9716';
|
||||||
|
|
||||||
|
/// See also [SelectedTabController].
|
||||||
|
@ProviderFor(SelectedTabController)
|
||||||
|
final selectedTabControllerProvider =
|
||||||
|
NotifierProvider<SelectedTabController, String?>.internal(
|
||||||
|
SelectedTabController.new,
|
||||||
|
name: r'selectedTabControllerProvider',
|
||||||
|
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$selectedTabControllerHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef _$SelectedTabController = 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
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
@Riverpod(keepAlive: true)
|
||||||
|
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,
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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 {};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'tab.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$tabRepositoryHash() => r'64724c0215624753c5c6ff8d6827b890ad9a6f2c';
|
||||||
|
|
||||||
|
/// See also [TabRepository].
|
||||||
|
@ProviderFor(TabRepository)
|
||||||
|
final tabRepositoryProvider =
|
||||||
|
NotifierProvider<TabRepository, Map<String, TabState>>.internal(
|
||||||
|
TabRepository.new,
|
||||||
|
name: r'tabRepositoryProvider',
|
||||||
|
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$tabRepositoryHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef _$TabRepository = 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
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
|
part 'tab_session.g.dart';
|
||||||
|
|
||||||
|
@Riverpod()
|
||||||
|
class TabSession extends _$TabSession {
|
||||||
|
late GeckoSessionService _sessionService;
|
||||||
|
|
||||||
|
Future<void> loadUrl({
|
||||||
|
required Uri url,
|
||||||
|
LoadUrlFlags flags = LoadUrlFlags.NONE,
|
||||||
|
Map<String, String>? additionalHeaders,
|
||||||
|
}) {
|
||||||
|
return _sessionService.loadUrl(
|
||||||
|
url: url,
|
||||||
|
flags: flags,
|
||||||
|
additionalHeaders: additionalHeaders,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void build(String? tabId) {
|
||||||
|
_sessionService = (tabId != null)
|
||||||
|
? GeckoSessionService(tabId: tabId)
|
||||||
|
: GeckoSessionService.forActiveTab();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'tab_session.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$tabSessionHash() => r'4aab49dcb544cfc9e7a61b498d2ccf2c28ef729b';
|
||||||
|
|
||||||
|
/// 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 _$TabSession extends BuildlessAutoDisposeNotifier<void> {
|
||||||
|
late final String? tabId;
|
||||||
|
|
||||||
|
void build(
|
||||||
|
String? tabId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [TabSession].
|
||||||
|
@ProviderFor(TabSession)
|
||||||
|
const tabSessionProvider = TabSessionFamily();
|
||||||
|
|
||||||
|
/// See also [TabSession].
|
||||||
|
class TabSessionFamily extends Family<void> {
|
||||||
|
/// See also [TabSession].
|
||||||
|
const TabSessionFamily();
|
||||||
|
|
||||||
|
/// See also [TabSession].
|
||||||
|
TabSessionProvider call(
|
||||||
|
String? tabId,
|
||||||
|
) {
|
||||||
|
return TabSessionProvider(
|
||||||
|
tabId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
TabSessionProvider getProviderOverride(
|
||||||
|
covariant TabSessionProvider 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'tabSessionProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [TabSession].
|
||||||
|
class TabSessionProvider
|
||||||
|
extends AutoDisposeNotifierProviderImpl<TabSession, void> {
|
||||||
|
/// See also [TabSession].
|
||||||
|
TabSessionProvider(
|
||||||
|
String? tabId,
|
||||||
|
) : this._internal(
|
||||||
|
() => TabSession()..tabId = tabId,
|
||||||
|
from: tabSessionProvider,
|
||||||
|
name: r'tabSessionProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$tabSessionHash,
|
||||||
|
dependencies: TabSessionFamily._dependencies,
|
||||||
|
allTransitiveDependencies:
|
||||||
|
TabSessionFamily._allTransitiveDependencies,
|
||||||
|
tabId: tabId,
|
||||||
|
);
|
||||||
|
|
||||||
|
TabSessionProvider._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
|
||||||
|
void runNotifierBuild(
|
||||||
|
covariant TabSession notifier,
|
||||||
|
) {
|
||||||
|
return notifier.build(
|
||||||
|
tabId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Override overrideWith(TabSession Function() create) {
|
||||||
|
return ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
override: TabSessionProvider._internal(
|
||||||
|
() => create()..tabId = tabId,
|
||||||
|
from: from,
|
||||||
|
name: null,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
debugGetCreateSourceHash: null,
|
||||||
|
tabId: tabId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
AutoDisposeNotifierProviderElement<TabSession, void> createElement() {
|
||||||
|
return _TabSessionProviderElement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is TabSessionProvider && 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 TabSessionRef on AutoDisposeNotifierProviderRef<void> {
|
||||||
|
/// The parameter `tabId` of this provider.
|
||||||
|
String? get tabId;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TabSessionProviderElement
|
||||||
|
extends AutoDisposeNotifierProviderElement<TabSession, void>
|
||||||
|
with TabSessionRef {
|
||||||
|
_TabSessionProviderElement(super.provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get tabId => (origin as TabSessionProvider).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,51 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:lensai/features/geckoview/features/topics/data/database/database.dart';
|
||||||
|
|
||||||
|
part 'tab.g.dart';
|
||||||
|
|
||||||
|
@DriftAccessor()
|
||||||
|
class TabLinkDao extends DatabaseAccessor<TabDatabase> with _$TabLinkDaoMixin {
|
||||||
|
TabLinkDao(super.db);
|
||||||
|
|
||||||
|
Selectable<String> topicTabIds(String? topicId) {
|
||||||
|
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)]));
|
||||||
|
|
||||||
|
return query.map((row) => row.read(db.tabLink.id)!);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> upsertTabLink(
|
||||||
|
String tabId, {
|
||||||
|
required DateTime timestamp,
|
||||||
|
Value<String?> topicId = const Value.absent(),
|
||||||
|
}) {
|
||||||
|
return db.tabLink.insertOne(
|
||||||
|
TabLinkCompanion.insert(
|
||||||
|
id: tabId,
|
||||||
|
timestamp: timestamp,
|
||||||
|
topicId: topicId,
|
||||||
|
),
|
||||||
|
mode: InsertMode.insertOrReplace,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> touchTabLink(
|
||||||
|
String id, {
|
||||||
|
required DateTime timestamp,
|
||||||
|
}) {
|
||||||
|
final statement = db.tabLink.update()..where((t) => t.id.equals(id));
|
||||||
|
return statement.write(
|
||||||
|
TabLinkCompanion(timestamp: Value(timestamp)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> syncTabLinks({required List<String> retainTabIds}) {
|
||||||
|
return (db.tabLink.delete()..where((t) => t.id.isNotIn(retainTabIds))).go();
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -3,4 +3,4 @@
|
|||||||
part of 'tab.dart';
|
part of 'tab.dart';
|
||||||
|
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
mixin _$TabDaoMixin on DatabaseAccessor<TabDatabase> {}
|
mixin _$TabLinkDaoMixin on DatabaseAccessor<TabDatabase> {}
|
||||||
+2
-2
@@ -2,8 +2,8 @@ import 'dart:ui';
|
|||||||
|
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:lensai/core/uuid.dart';
|
import 'package:lensai/core/uuid.dart';
|
||||||
import 'package:lensai/features/topics/data/database/database.dart';
|
import 'package:lensai/features/geckoview/features/topics/data/database/database.dart';
|
||||||
import 'package:lensai/features/topics/data/models/topic_data.dart';
|
import 'package:lensai/features/geckoview/features/topics/data/models/topic_data.dart';
|
||||||
|
|
||||||
part 'topic.g.dart';
|
part 'topic.g.dart';
|
||||||
|
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:lensai/features/geckoview/features/topics/data/database/daos/tab.dart';
|
||||||
|
import 'package:lensai/features/geckoview/features/topics/data/database/daos/topic.dart';
|
||||||
|
import 'package:lensai/features/geckoview/features/topics/data/database/drift/converters/color.dart';
|
||||||
|
import 'package:lensai/features/geckoview/features/topics/data/models/topic_data.dart';
|
||||||
|
|
||||||
|
part 'database.g.dart';
|
||||||
|
|
||||||
|
@DriftDatabase(
|
||||||
|
include: {'database.drift'},
|
||||||
|
daos: [TopicDao, TabLinkDao],
|
||||||
|
)
|
||||||
|
class TabDatabase extends _$TabDatabase {
|
||||||
|
@override
|
||||||
|
final int schemaVersion = 2;
|
||||||
|
|
||||||
|
@override
|
||||||
|
MigrationStrategy get migration => MigrationStrategy(
|
||||||
|
onUpgrade: (m, from, to) async {
|
||||||
|
// disable foreign_keys before migrations
|
||||||
|
await customStatement('PRAGMA foreign_keys = OFF');
|
||||||
|
|
||||||
|
if (from < 2) {
|
||||||
|
await transaction(() async {
|
||||||
|
await m.renameTable(tabLink, 'tab');
|
||||||
|
|
||||||
|
await m.dropColumn(tabLink, 'url');
|
||||||
|
await m.dropColumn(tabLink, 'title');
|
||||||
|
await m.dropColumn(tabLink, 'screenshot');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that the schema is valid after migrations
|
||||||
|
if (kDebugMode) {
|
||||||
|
final wrongForeignKeys =
|
||||||
|
await customSelect('PRAGMA foreign_key_check').get();
|
||||||
|
assert(
|
||||||
|
wrongForeignKeys.isEmpty,
|
||||||
|
'${wrongForeignKeys.map((e) => e.data)}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeOpen: (details) async {
|
||||||
|
await customStatement('PRAGMA foreign_keys = ON;');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
TabDatabase(super.e);
|
||||||
|
}
|
||||||
+5
-9
@@ -1,6 +1,5 @@
|
|||||||
import 'package:lensai/features/topics/data/database/drift/converters/color.dart';
|
import 'package:lensai/features/geckoview/features/topics/data/database/drift/converters/color.dart';
|
||||||
import 'package:lensai/features/topics/data/database/drift/converters/uri.dart';
|
import 'package:lensai/features/geckoview/features/topics/data/models/topic_data.dart';
|
||||||
import 'package:lensai/features/topics/data/models/topic_data.dart';
|
|
||||||
|
|
||||||
CREATE TABLE topic (
|
CREATE TABLE topic (
|
||||||
id TEXT PRIMARY KEY NOT NULL,
|
id TEXT PRIMARY KEY NOT NULL,
|
||||||
@@ -8,13 +7,10 @@ CREATE TABLE topic (
|
|||||||
color INTEGER NOT NULL MAPPED BY `const ColorConverter()`
|
color INTEGER NOT NULL MAPPED BY `const ColorConverter()`
|
||||||
) WITH TopicData;
|
) WITH TopicData;
|
||||||
|
|
||||||
CREATE TABLE tab (
|
CREATE TABLE tab_link (
|
||||||
id TEXT PRIMARY KEY NOT NULL,
|
id TEXT PRIMARY KEY NOT NULL,
|
||||||
topic_id TEXT REFERENCES topic (id) ON DELETE CASCADE,
|
topic_id TEXT REFERENCES topic (id) ON DELETE CASCADE,
|
||||||
timestamp DATETIME NOT NULL,
|
timestamp DATETIME NOT NULL
|
||||||
url TEXT NOT NULL MAPPED BY `const UriConverter()`,
|
|
||||||
title TEXT,
|
|
||||||
screenshot BLOB
|
|
||||||
);
|
);
|
||||||
|
|
||||||
topicsWithCount WITH TopicDataWithCount:
|
topicsWithCount WITH TopicDataWithCount:
|
||||||
@@ -27,7 +23,7 @@ topicsWithCount WITH TopicDataWithCount:
|
|||||||
topic_id,
|
topic_id,
|
||||||
COUNT(*) AS tab_count,
|
COUNT(*) AS tab_count,
|
||||||
MAX(timestamp) AS last_updated
|
MAX(timestamp) AS last_updated
|
||||||
FROM tab
|
FROM tab_link
|
||||||
GROUP BY topic_id
|
GROUP BY topic_id
|
||||||
) AS tab_agg ON topic.id = tab_agg.topic_id
|
) AS tab_agg ON topic.id = tab_agg.topic_id
|
||||||
ORDER BY tab_agg.last_updated DESC NULLS FIRST;
|
ORDER BY tab_agg.last_updated DESC NULLS FIRST;
|
||||||
+96
-241
@@ -131,11 +131,11 @@ class TopicCompanion extends UpdateCompanion<TopicData> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Tab extends Table with TableInfo<Tab, TabData> {
|
class TabLink extends Table with TableInfo<TabLink, TabLinkData> {
|
||||||
@override
|
@override
|
||||||
final GeneratedDatabase attachedDatabase;
|
final GeneratedDatabase attachedDatabase;
|
||||||
final String? _alias;
|
final String? _alias;
|
||||||
Tab(this.attachedDatabase, [this._alias]);
|
TabLink(this.attachedDatabase, [this._alias]);
|
||||||
late final GeneratedColumn<String> id = GeneratedColumn<String>(
|
late final GeneratedColumn<String> id = GeneratedColumn<String>(
|
||||||
'id', aliasedName, false,
|
'id', aliasedName, false,
|
||||||
type: DriftSqlType.string,
|
type: DriftSqlType.string,
|
||||||
@@ -151,75 +151,42 @@ class Tab extends Table with TableInfo<Tab, TabData> {
|
|||||||
type: DriftSqlType.dateTime,
|
type: DriftSqlType.dateTime,
|
||||||
requiredDuringInsert: true,
|
requiredDuringInsert: true,
|
||||||
$customConstraints: 'NOT NULL');
|
$customConstraints: 'NOT NULL');
|
||||||
late final GeneratedColumnWithTypeConverter<Uri, String> url =
|
|
||||||
GeneratedColumn<String>('url', aliasedName, false,
|
|
||||||
type: DriftSqlType.string,
|
|
||||||
requiredDuringInsert: true,
|
|
||||||
$customConstraints: 'NOT NULL')
|
|
||||||
.withConverter<Uri>(Tab.$converterurl);
|
|
||||||
late final GeneratedColumn<String> title = GeneratedColumn<String>(
|
|
||||||
'title', aliasedName, true,
|
|
||||||
type: DriftSqlType.string,
|
|
||||||
requiredDuringInsert: false,
|
|
||||||
$customConstraints: '');
|
|
||||||
late final GeneratedColumn<Uint8List> screenshot = GeneratedColumn<Uint8List>(
|
|
||||||
'screenshot', aliasedName, true,
|
|
||||||
type: DriftSqlType.blob,
|
|
||||||
requiredDuringInsert: false,
|
|
||||||
$customConstraints: '');
|
|
||||||
@override
|
@override
|
||||||
List<GeneratedColumn> get $columns =>
|
List<GeneratedColumn> get $columns => [id, topicId, timestamp];
|
||||||
[id, topicId, timestamp, url, title, screenshot];
|
|
||||||
@override
|
@override
|
||||||
String get aliasedName => _alias ?? actualTableName;
|
String get aliasedName => _alias ?? actualTableName;
|
||||||
@override
|
@override
|
||||||
String get actualTableName => $name;
|
String get actualTableName => $name;
|
||||||
static const String $name = 'tab';
|
static const String $name = 'tab_link';
|
||||||
@override
|
@override
|
||||||
Set<GeneratedColumn> get $primaryKey => {id};
|
Set<GeneratedColumn> get $primaryKey => {id};
|
||||||
@override
|
@override
|
||||||
TabData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
TabLinkData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||||
return TabData(
|
return TabLinkData(
|
||||||
id: attachedDatabase.typeMapping
|
id: attachedDatabase.typeMapping
|
||||||
.read(DriftSqlType.string, data['${effectivePrefix}id'])!,
|
.read(DriftSqlType.string, data['${effectivePrefix}id'])!,
|
||||||
topicId: attachedDatabase.typeMapping
|
topicId: attachedDatabase.typeMapping
|
||||||
.read(DriftSqlType.string, data['${effectivePrefix}topic_id']),
|
.read(DriftSqlType.string, data['${effectivePrefix}topic_id']),
|
||||||
timestamp: attachedDatabase.typeMapping
|
timestamp: attachedDatabase.typeMapping
|
||||||
.read(DriftSqlType.dateTime, data['${effectivePrefix}timestamp'])!,
|
.read(DriftSqlType.dateTime, data['${effectivePrefix}timestamp'])!,
|
||||||
url: Tab.$converterurl.fromSql(attachedDatabase.typeMapping
|
|
||||||
.read(DriftSqlType.string, data['${effectivePrefix}url'])!),
|
|
||||||
title: attachedDatabase.typeMapping
|
|
||||||
.read(DriftSqlType.string, data['${effectivePrefix}title']),
|
|
||||||
screenshot: attachedDatabase.typeMapping
|
|
||||||
.read(DriftSqlType.blob, data['${effectivePrefix}screenshot']),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Tab createAlias(String alias) {
|
TabLink createAlias(String alias) {
|
||||||
return Tab(attachedDatabase, alias);
|
return TabLink(attachedDatabase, alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
static TypeConverter<Uri, String> $converterurl = const UriConverter();
|
|
||||||
@override
|
@override
|
||||||
bool get dontWriteConstraints => true;
|
bool get dontWriteConstraints => true;
|
||||||
}
|
}
|
||||||
|
|
||||||
class TabData extends DataClass implements Insertable<TabData> {
|
class TabLinkData extends DataClass implements Insertable<TabLinkData> {
|
||||||
final String id;
|
final String id;
|
||||||
final String? topicId;
|
final String? topicId;
|
||||||
final DateTime timestamp;
|
final DateTime timestamp;
|
||||||
final Uri url;
|
const TabLinkData({required this.id, this.topicId, required this.timestamp});
|
||||||
final String? title;
|
|
||||||
final Uint8List? screenshot;
|
|
||||||
const TabData(
|
|
||||||
{required this.id,
|
|
||||||
this.topicId,
|
|
||||||
required this.timestamp,
|
|
||||||
required this.url,
|
|
||||||
this.title,
|
|
||||||
this.screenshot});
|
|
||||||
@override
|
@override
|
||||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
final map = <String, Expression>{};
|
final map = <String, Expression>{};
|
||||||
@@ -228,28 +195,16 @@ class TabData extends DataClass implements Insertable<TabData> {
|
|||||||
map['topic_id'] = Variable<String>(topicId);
|
map['topic_id'] = Variable<String>(topicId);
|
||||||
}
|
}
|
||||||
map['timestamp'] = Variable<DateTime>(timestamp);
|
map['timestamp'] = Variable<DateTime>(timestamp);
|
||||||
{
|
|
||||||
map['url'] = Variable<String>(Tab.$converterurl.toSql(url));
|
|
||||||
}
|
|
||||||
if (!nullToAbsent || title != null) {
|
|
||||||
map['title'] = Variable<String>(title);
|
|
||||||
}
|
|
||||||
if (!nullToAbsent || screenshot != null) {
|
|
||||||
map['screenshot'] = Variable<Uint8List>(screenshot);
|
|
||||||
}
|
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
factory TabData.fromJson(Map<String, dynamic> json,
|
factory TabLinkData.fromJson(Map<String, dynamic> json,
|
||||||
{ValueSerializer? serializer}) {
|
{ValueSerializer? serializer}) {
|
||||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
return TabData(
|
return TabLinkData(
|
||||||
id: serializer.fromJson<String>(json['id']),
|
id: serializer.fromJson<String>(json['id']),
|
||||||
topicId: serializer.fromJson<String?>(json['topic_id']),
|
topicId: serializer.fromJson<String?>(json['topic_id']),
|
||||||
timestamp: serializer.fromJson<DateTime>(json['timestamp']),
|
timestamp: serializer.fromJson<DateTime>(json['timestamp']),
|
||||||
url: serializer.fromJson<Uri>(json['url']),
|
|
||||||
title: serializer.fromJson<String?>(json['title']),
|
|
||||||
screenshot: serializer.fromJson<Uint8List?>(json['screenshot']),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@override
|
@override
|
||||||
@@ -259,130 +214,88 @@ class TabData extends DataClass implements Insertable<TabData> {
|
|||||||
'id': serializer.toJson<String>(id),
|
'id': serializer.toJson<String>(id),
|
||||||
'topic_id': serializer.toJson<String?>(topicId),
|
'topic_id': serializer.toJson<String?>(topicId),
|
||||||
'timestamp': serializer.toJson<DateTime>(timestamp),
|
'timestamp': serializer.toJson<DateTime>(timestamp),
|
||||||
'url': serializer.toJson<Uri>(url),
|
|
||||||
'title': serializer.toJson<String?>(title),
|
|
||||||
'screenshot': serializer.toJson<Uint8List?>(screenshot),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
TabData copyWith(
|
TabLinkData copyWith(
|
||||||
{String? id,
|
{String? id,
|
||||||
Value<String?> topicId = const Value.absent(),
|
Value<String?> topicId = const Value.absent(),
|
||||||
DateTime? timestamp,
|
DateTime? timestamp}) =>
|
||||||
Uri? url,
|
TabLinkData(
|
||||||
Value<String?> title = const Value.absent(),
|
|
||||||
Value<Uint8List?> screenshot = const Value.absent()}) =>
|
|
||||||
TabData(
|
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
topicId: topicId.present ? topicId.value : this.topicId,
|
topicId: topicId.present ? topicId.value : this.topicId,
|
||||||
timestamp: timestamp ?? this.timestamp,
|
timestamp: timestamp ?? this.timestamp,
|
||||||
url: url ?? this.url,
|
|
||||||
title: title.present ? title.value : this.title,
|
|
||||||
screenshot: screenshot.present ? screenshot.value : this.screenshot,
|
|
||||||
);
|
);
|
||||||
TabData copyWithCompanion(TabCompanion data) {
|
TabLinkData copyWithCompanion(TabLinkCompanion data) {
|
||||||
return TabData(
|
return TabLinkData(
|
||||||
id: data.id.present ? data.id.value : this.id,
|
id: data.id.present ? data.id.value : this.id,
|
||||||
topicId: data.topicId.present ? data.topicId.value : this.topicId,
|
topicId: data.topicId.present ? data.topicId.value : this.topicId,
|
||||||
timestamp: data.timestamp.present ? data.timestamp.value : this.timestamp,
|
timestamp: data.timestamp.present ? data.timestamp.value : this.timestamp,
|
||||||
url: data.url.present ? data.url.value : this.url,
|
|
||||||
title: data.title.present ? data.title.value : this.title,
|
|
||||||
screenshot:
|
|
||||||
data.screenshot.present ? data.screenshot.value : this.screenshot,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return (StringBuffer('TabData(')
|
return (StringBuffer('TabLinkData(')
|
||||||
..write('id: $id, ')
|
..write('id: $id, ')
|
||||||
..write('topicId: $topicId, ')
|
..write('topicId: $topicId, ')
|
||||||
..write('timestamp: $timestamp, ')
|
..write('timestamp: $timestamp')
|
||||||
..write('url: $url, ')
|
|
||||||
..write('title: $title, ')
|
|
||||||
..write('screenshot: $screenshot')
|
|
||||||
..write(')'))
|
..write(')'))
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(
|
int get hashCode => Object.hash(id, topicId, timestamp);
|
||||||
id, topicId, timestamp, url, title, $driftBlobEquality.hash(screenshot));
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
(other is TabData &&
|
(other is TabLinkData &&
|
||||||
other.id == this.id &&
|
other.id == this.id &&
|
||||||
other.topicId == this.topicId &&
|
other.topicId == this.topicId &&
|
||||||
other.timestamp == this.timestamp &&
|
other.timestamp == this.timestamp);
|
||||||
other.url == this.url &&
|
|
||||||
other.title == this.title &&
|
|
||||||
$driftBlobEquality.equals(other.screenshot, this.screenshot));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class TabCompanion extends UpdateCompanion<TabData> {
|
class TabLinkCompanion extends UpdateCompanion<TabLinkData> {
|
||||||
final Value<String> id;
|
final Value<String> id;
|
||||||
final Value<String?> topicId;
|
final Value<String?> topicId;
|
||||||
final Value<DateTime> timestamp;
|
final Value<DateTime> timestamp;
|
||||||
final Value<Uri> url;
|
|
||||||
final Value<String?> title;
|
|
||||||
final Value<Uint8List?> screenshot;
|
|
||||||
final Value<int> rowid;
|
final Value<int> rowid;
|
||||||
const TabCompanion({
|
const TabLinkCompanion({
|
||||||
this.id = const Value.absent(),
|
this.id = const Value.absent(),
|
||||||
this.topicId = const Value.absent(),
|
this.topicId = const Value.absent(),
|
||||||
this.timestamp = const Value.absent(),
|
this.timestamp = const Value.absent(),
|
||||||
this.url = const Value.absent(),
|
|
||||||
this.title = const Value.absent(),
|
|
||||||
this.screenshot = const Value.absent(),
|
|
||||||
this.rowid = const Value.absent(),
|
this.rowid = const Value.absent(),
|
||||||
});
|
});
|
||||||
TabCompanion.insert({
|
TabLinkCompanion.insert({
|
||||||
required String id,
|
required String id,
|
||||||
this.topicId = const Value.absent(),
|
this.topicId = const Value.absent(),
|
||||||
required DateTime timestamp,
|
required DateTime timestamp,
|
||||||
required Uri url,
|
|
||||||
this.title = const Value.absent(),
|
|
||||||
this.screenshot = const Value.absent(),
|
|
||||||
this.rowid = const Value.absent(),
|
this.rowid = const Value.absent(),
|
||||||
}) : id = Value(id),
|
}) : id = Value(id),
|
||||||
timestamp = Value(timestamp),
|
timestamp = Value(timestamp);
|
||||||
url = Value(url);
|
static Insertable<TabLinkData> custom({
|
||||||
static Insertable<TabData> custom({
|
|
||||||
Expression<String>? id,
|
Expression<String>? id,
|
||||||
Expression<String>? topicId,
|
Expression<String>? topicId,
|
||||||
Expression<DateTime>? timestamp,
|
Expression<DateTime>? timestamp,
|
||||||
Expression<String>? url,
|
|
||||||
Expression<String>? title,
|
|
||||||
Expression<Uint8List>? screenshot,
|
|
||||||
Expression<int>? rowid,
|
Expression<int>? rowid,
|
||||||
}) {
|
}) {
|
||||||
return RawValuesInsertable({
|
return RawValuesInsertable({
|
||||||
if (id != null) 'id': id,
|
if (id != null) 'id': id,
|
||||||
if (topicId != null) 'topic_id': topicId,
|
if (topicId != null) 'topic_id': topicId,
|
||||||
if (timestamp != null) 'timestamp': timestamp,
|
if (timestamp != null) 'timestamp': timestamp,
|
||||||
if (url != null) 'url': url,
|
|
||||||
if (title != null) 'title': title,
|
|
||||||
if (screenshot != null) 'screenshot': screenshot,
|
|
||||||
if (rowid != null) 'rowid': rowid,
|
if (rowid != null) 'rowid': rowid,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
TabCompanion copyWith(
|
TabLinkCompanion copyWith(
|
||||||
{Value<String>? id,
|
{Value<String>? id,
|
||||||
Value<String?>? topicId,
|
Value<String?>? topicId,
|
||||||
Value<DateTime>? timestamp,
|
Value<DateTime>? timestamp,
|
||||||
Value<Uri>? url,
|
|
||||||
Value<String?>? title,
|
|
||||||
Value<Uint8List?>? screenshot,
|
|
||||||
Value<int>? rowid}) {
|
Value<int>? rowid}) {
|
||||||
return TabCompanion(
|
return TabLinkCompanion(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
topicId: topicId ?? this.topicId,
|
topicId: topicId ?? this.topicId,
|
||||||
timestamp: timestamp ?? this.timestamp,
|
timestamp: timestamp ?? this.timestamp,
|
||||||
url: url ?? this.url,
|
|
||||||
title: title ?? this.title,
|
|
||||||
screenshot: screenshot ?? this.screenshot,
|
|
||||||
rowid: rowid ?? this.rowid,
|
rowid: rowid ?? this.rowid,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -399,15 +312,6 @@ class TabCompanion extends UpdateCompanion<TabData> {
|
|||||||
if (timestamp.present) {
|
if (timestamp.present) {
|
||||||
map['timestamp'] = Variable<DateTime>(timestamp.value);
|
map['timestamp'] = Variable<DateTime>(timestamp.value);
|
||||||
}
|
}
|
||||||
if (url.present) {
|
|
||||||
map['url'] = Variable<String>(Tab.$converterurl.toSql(url.value));
|
|
||||||
}
|
|
||||||
if (title.present) {
|
|
||||||
map['title'] = Variable<String>(title.value);
|
|
||||||
}
|
|
||||||
if (screenshot.present) {
|
|
||||||
map['screenshot'] = Variable<Uint8List>(screenshot.value);
|
|
||||||
}
|
|
||||||
if (rowid.present) {
|
if (rowid.present) {
|
||||||
map['rowid'] = Variable<int>(rowid.value);
|
map['rowid'] = Variable<int>(rowid.value);
|
||||||
}
|
}
|
||||||
@@ -416,13 +320,10 @@ class TabCompanion extends UpdateCompanion<TabData> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return (StringBuffer('TabCompanion(')
|
return (StringBuffer('TabLinkCompanion(')
|
||||||
..write('id: $id, ')
|
..write('id: $id, ')
|
||||||
..write('topicId: $topicId, ')
|
..write('topicId: $topicId, ')
|
||||||
..write('timestamp: $timestamp, ')
|
..write('timestamp: $timestamp, ')
|
||||||
..write('url: $url, ')
|
|
||||||
..write('title: $title, ')
|
|
||||||
..write('screenshot: $screenshot, ')
|
|
||||||
..write('rowid: $rowid')
|
..write('rowid: $rowid')
|
||||||
..write(')'))
|
..write(')'))
|
||||||
.toString();
|
.toString();
|
||||||
@@ -433,16 +334,16 @@ abstract class _$TabDatabase extends GeneratedDatabase {
|
|||||||
_$TabDatabase(QueryExecutor e) : super(e);
|
_$TabDatabase(QueryExecutor e) : super(e);
|
||||||
$TabDatabaseManager get managers => $TabDatabaseManager(this);
|
$TabDatabaseManager get managers => $TabDatabaseManager(this);
|
||||||
late final Topic topic = Topic(this);
|
late final Topic topic = Topic(this);
|
||||||
late final Tab tab = Tab(this);
|
late final TabLink tabLink = TabLink(this);
|
||||||
late final TopicDao topicDao = TopicDao(this as TabDatabase);
|
late final TopicDao topicDao = TopicDao(this as TabDatabase);
|
||||||
late final TabDao tabDao = TabDao(this as TabDatabase);
|
late final TabLinkDao tabLinkDao = TabLinkDao(this as TabDatabase);
|
||||||
Selectable<TopicDataWithCount> topicsWithCount() {
|
Selectable<TopicDataWithCount> topicsWithCount() {
|
||||||
return customSelect(
|
return customSelect(
|
||||||
'SELECT topic.*, tab_agg.tab_count FROM topic LEFT JOIN (SELECT topic_id, COUNT(*) AS tab_count, MAX(timestamp) AS last_updated FROM tab GROUP BY topic_id) AS tab_agg ON topic.id = tab_agg.topic_id ORDER BY tab_agg.last_updated DESC NULLS FIRST',
|
'SELECT topic.*, tab_agg.tab_count FROM topic LEFT JOIN (SELECT topic_id, COUNT(*) AS tab_count, MAX(timestamp) AS last_updated FROM tab_link GROUP BY topic_id) AS tab_agg ON topic.id = tab_agg.topic_id ORDER BY tab_agg.last_updated DESC NULLS FIRST',
|
||||||
variables: [],
|
variables: [],
|
||||||
readsFrom: {
|
readsFrom: {
|
||||||
topic,
|
topic,
|
||||||
tab,
|
tabLink,
|
||||||
}).map((QueryRow row) => TopicDataWithCount(
|
}).map((QueryRow row) => TopicDataWithCount(
|
||||||
id: row.read<String>('id'),
|
id: row.read<String>('id'),
|
||||||
name: row.readNullable<String>('name'),
|
name: row.readNullable<String>('name'),
|
||||||
@@ -455,7 +356,7 @@ abstract class _$TabDatabase extends GeneratedDatabase {
|
|||||||
Iterable<TableInfo<Table, Object?>> get allTables =>
|
Iterable<TableInfo<Table, Object?>> get allTables =>
|
||||||
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
||||||
@override
|
@override
|
||||||
List<DatabaseSchemaEntity> get allSchemaEntities => [topic, tab];
|
List<DatabaseSchemaEntity> get allSchemaEntities => [topic, tabLink];
|
||||||
@override
|
@override
|
||||||
StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules(
|
StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules(
|
||||||
[
|
[
|
||||||
@@ -463,7 +364,7 @@ abstract class _$TabDatabase extends GeneratedDatabase {
|
|||||||
on: TableUpdateQuery.onTableName('topic',
|
on: TableUpdateQuery.onTableName('topic',
|
||||||
limitUpdateKind: UpdateKind.delete),
|
limitUpdateKind: UpdateKind.delete),
|
||||||
result: [
|
result: [
|
||||||
TableUpdate('tab', kind: UpdateKind.delete),
|
TableUpdate('tab_link', kind: UpdateKind.delete),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -487,16 +388,16 @@ final class $TopicReferences
|
|||||||
extends BaseReferences<_$TabDatabase, Topic, TopicData> {
|
extends BaseReferences<_$TabDatabase, Topic, TopicData> {
|
||||||
$TopicReferences(super.$_db, super.$_table, super.$_typedResult);
|
$TopicReferences(super.$_db, super.$_table, super.$_typedResult);
|
||||||
|
|
||||||
static MultiTypedResultKey<Tab, List<TabData>> _tabRefsTable(
|
static MultiTypedResultKey<TabLink, List<TabLinkData>> _tabLinkRefsTable(
|
||||||
_$TabDatabase db) =>
|
_$TabDatabase db) =>
|
||||||
MultiTypedResultKey.fromTable(db.tab,
|
MultiTypedResultKey.fromTable(db.tabLink,
|
||||||
aliasName: $_aliasNameGenerator(db.topic.id, db.tab.topicId));
|
aliasName: $_aliasNameGenerator(db.topic.id, db.tabLink.topicId));
|
||||||
|
|
||||||
$TabProcessedTableManager get tabRefs {
|
$TabLinkProcessedTableManager get tabLinkRefs {
|
||||||
final manager =
|
final manager = $TabLinkTableManager($_db, $_db.tabLink)
|
||||||
$TabTableManager($_db, $_db.tab).filter((f) => f.topicId.id($_item.id));
|
.filter((f) => f.topicId.id($_item.id));
|
||||||
|
|
||||||
final cache = $_typedResult.readTableOrNull(_tabRefsTable($_db));
|
final cache = $_typedResult.readTableOrNull(_tabLinkRefsTable($_db));
|
||||||
return ProcessedTableManager(
|
return ProcessedTableManager(
|
||||||
manager.$state.copyWith(prefetchedData: cache));
|
manager.$state.copyWith(prefetchedData: cache));
|
||||||
}
|
}
|
||||||
@@ -521,15 +422,16 @@ class $TopicFilterComposer extends FilterComposer<_$TabDatabase, Topic> {
|
|||||||
column,
|
column,
|
||||||
joinBuilders: joinBuilders));
|
joinBuilders: joinBuilders));
|
||||||
|
|
||||||
ComposableFilter tabRefs(ComposableFilter Function($TabFilterComposer f) f) {
|
ComposableFilter tabLinkRefs(
|
||||||
final $TabFilterComposer composer = $state.composerBuilder(
|
ComposableFilter Function($TabLinkFilterComposer f) f) {
|
||||||
|
final $TabLinkFilterComposer composer = $state.composerBuilder(
|
||||||
composer: this,
|
composer: this,
|
||||||
getCurrentColumn: (t) => t.id,
|
getCurrentColumn: (t) => t.id,
|
||||||
referencedTable: $state.db.tab,
|
referencedTable: $state.db.tabLink,
|
||||||
getReferencedColumn: (t) => t.topicId,
|
getReferencedColumn: (t) => t.topicId,
|
||||||
builder: (joinBuilder, parentComposers) => $TabFilterComposer(
|
builder: (joinBuilder, parentComposers) => $TabLinkFilterComposer(
|
||||||
ComposerState(
|
ComposerState(
|
||||||
$state.db, $state.db.tab, joinBuilder, parentComposers)));
|
$state.db, $state.db.tabLink, joinBuilder, parentComposers)));
|
||||||
return f(composer);
|
return f(composer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -562,7 +464,7 @@ class $TopicTableManager extends RootTableManager<
|
|||||||
$TopicUpdateCompanionBuilder,
|
$TopicUpdateCompanionBuilder,
|
||||||
(TopicData, $TopicReferences),
|
(TopicData, $TopicReferences),
|
||||||
TopicData,
|
TopicData,
|
||||||
PrefetchHooks Function({bool tabRefs})> {
|
PrefetchHooks Function({bool tabLinkRefs})> {
|
||||||
$TopicTableManager(_$TabDatabase db, Topic table)
|
$TopicTableManager(_$TabDatabase db, Topic table)
|
||||||
: super(TableManagerState(
|
: super(TableManagerState(
|
||||||
db: db,
|
db: db,
|
||||||
@@ -596,19 +498,19 @@ class $TopicTableManager extends RootTableManager<
|
|||||||
withReferenceMapper: (p0) => p0
|
withReferenceMapper: (p0) => p0
|
||||||
.map((e) => (e.readTable(table), $TopicReferences(db, table, e)))
|
.map((e) => (e.readTable(table), $TopicReferences(db, table, e)))
|
||||||
.toList(),
|
.toList(),
|
||||||
prefetchHooksCallback: ({tabRefs = false}) {
|
prefetchHooksCallback: ({tabLinkRefs = false}) {
|
||||||
return PrefetchHooks(
|
return PrefetchHooks(
|
||||||
db: db,
|
db: db,
|
||||||
explicitlyWatchedTables: [if (tabRefs) db.tab],
|
explicitlyWatchedTables: [if (tabLinkRefs) db.tabLink],
|
||||||
addJoins: null,
|
addJoins: null,
|
||||||
getPrefetchedDataCallback: (items) async {
|
getPrefetchedDataCallback: (items) async {
|
||||||
return [
|
return [
|
||||||
if (tabRefs)
|
if (tabLinkRefs)
|
||||||
await $_getPrefetchedData(
|
await $_getPrefetchedData(
|
||||||
currentTable: table,
|
currentTable: table,
|
||||||
referencedTable: $TopicReferences._tabRefsTable(db),
|
referencedTable: $TopicReferences._tabLinkRefsTable(db),
|
||||||
managerFromTypedResult: (p0) =>
|
managerFromTypedResult: (p0) =>
|
||||||
$TopicReferences(db, table, p0).tabRefs,
|
$TopicReferences(db, table, p0).tabLinkRefs,
|
||||||
referencedItemsForCurrentItem: (item,
|
referencedItemsForCurrentItem: (item,
|
||||||
referencedItems) =>
|
referencedItems) =>
|
||||||
referencedItems.where((e) => e.topicId == item.id),
|
referencedItems.where((e) => e.topicId == item.id),
|
||||||
@@ -630,31 +532,26 @@ typedef $TopicProcessedTableManager = ProcessedTableManager<
|
|||||||
$TopicUpdateCompanionBuilder,
|
$TopicUpdateCompanionBuilder,
|
||||||
(TopicData, $TopicReferences),
|
(TopicData, $TopicReferences),
|
||||||
TopicData,
|
TopicData,
|
||||||
PrefetchHooks Function({bool tabRefs})>;
|
PrefetchHooks Function({bool tabLinkRefs})>;
|
||||||
typedef $TabCreateCompanionBuilder = TabCompanion Function({
|
typedef $TabLinkCreateCompanionBuilder = TabLinkCompanion Function({
|
||||||
required String id,
|
required String id,
|
||||||
Value<String?> topicId,
|
Value<String?> topicId,
|
||||||
required DateTime timestamp,
|
required DateTime timestamp,
|
||||||
required Uri url,
|
|
||||||
Value<String?> title,
|
|
||||||
Value<Uint8List?> screenshot,
|
|
||||||
Value<int> rowid,
|
Value<int> rowid,
|
||||||
});
|
});
|
||||||
typedef $TabUpdateCompanionBuilder = TabCompanion Function({
|
typedef $TabLinkUpdateCompanionBuilder = TabLinkCompanion Function({
|
||||||
Value<String> id,
|
Value<String> id,
|
||||||
Value<String?> topicId,
|
Value<String?> topicId,
|
||||||
Value<DateTime> timestamp,
|
Value<DateTime> timestamp,
|
||||||
Value<Uri> url,
|
|
||||||
Value<String?> title,
|
|
||||||
Value<Uint8List?> screenshot,
|
|
||||||
Value<int> rowid,
|
Value<int> rowid,
|
||||||
});
|
});
|
||||||
|
|
||||||
final class $TabReferences extends BaseReferences<_$TabDatabase, Tab, TabData> {
|
final class $TabLinkReferences
|
||||||
$TabReferences(super.$_db, super.$_table, super.$_typedResult);
|
extends BaseReferences<_$TabDatabase, TabLink, TabLinkData> {
|
||||||
|
$TabLinkReferences(super.$_db, super.$_table, super.$_typedResult);
|
||||||
|
|
||||||
static Topic _topicIdTable(_$TabDatabase db) =>
|
static Topic _topicIdTable(_$TabDatabase db) => db.topic
|
||||||
db.topic.createAlias($_aliasNameGenerator(db.tab.topicId, db.topic.id));
|
.createAlias($_aliasNameGenerator(db.tabLink.topicId, db.topic.id));
|
||||||
|
|
||||||
$TopicProcessedTableManager? get topicId {
|
$TopicProcessedTableManager? get topicId {
|
||||||
if ($_item.topicId == null) return null;
|
if ($_item.topicId == null) return null;
|
||||||
@@ -667,8 +564,8 @@ final class $TabReferences extends BaseReferences<_$TabDatabase, Tab, TabData> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class $TabFilterComposer extends FilterComposer<_$TabDatabase, Tab> {
|
class $TabLinkFilterComposer extends FilterComposer<_$TabDatabase, TabLink> {
|
||||||
$TabFilterComposer(super.$state);
|
$TabLinkFilterComposer(super.$state);
|
||||||
ColumnFilters<String> get id => $state.composableBuilder(
|
ColumnFilters<String> get id => $state.composableBuilder(
|
||||||
column: $state.table.id,
|
column: $state.table.id,
|
||||||
builder: (column, joinBuilders) =>
|
builder: (column, joinBuilders) =>
|
||||||
@@ -679,23 +576,6 @@ class $TabFilterComposer extends FilterComposer<_$TabDatabase, Tab> {
|
|||||||
builder: (column, joinBuilders) =>
|
builder: (column, joinBuilders) =>
|
||||||
ColumnFilters(column, joinBuilders: joinBuilders));
|
ColumnFilters(column, joinBuilders: joinBuilders));
|
||||||
|
|
||||||
ColumnWithTypeConverterFilters<Uri, Uri, String> get url =>
|
|
||||||
$state.composableBuilder(
|
|
||||||
column: $state.table.url,
|
|
||||||
builder: (column, joinBuilders) => ColumnWithTypeConverterFilters(
|
|
||||||
column,
|
|
||||||
joinBuilders: joinBuilders));
|
|
||||||
|
|
||||||
ColumnFilters<String> get title => $state.composableBuilder(
|
|
||||||
column: $state.table.title,
|
|
||||||
builder: (column, joinBuilders) =>
|
|
||||||
ColumnFilters(column, joinBuilders: joinBuilders));
|
|
||||||
|
|
||||||
ColumnFilters<Uint8List> get screenshot => $state.composableBuilder(
|
|
||||||
column: $state.table.screenshot,
|
|
||||||
builder: (column, joinBuilders) =>
|
|
||||||
ColumnFilters(column, joinBuilders: joinBuilders));
|
|
||||||
|
|
||||||
$TopicFilterComposer get topicId {
|
$TopicFilterComposer get topicId {
|
||||||
final $TopicFilterComposer composer = $state.composerBuilder(
|
final $TopicFilterComposer composer = $state.composerBuilder(
|
||||||
composer: this,
|
composer: this,
|
||||||
@@ -709,8 +589,9 @@ class $TabFilterComposer extends FilterComposer<_$TabDatabase, Tab> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class $TabOrderingComposer extends OrderingComposer<_$TabDatabase, Tab> {
|
class $TabLinkOrderingComposer
|
||||||
$TabOrderingComposer(super.$state);
|
extends OrderingComposer<_$TabDatabase, TabLink> {
|
||||||
|
$TabLinkOrderingComposer(super.$state);
|
||||||
ColumnOrderings<String> get id => $state.composableBuilder(
|
ColumnOrderings<String> get id => $state.composableBuilder(
|
||||||
column: $state.table.id,
|
column: $state.table.id,
|
||||||
builder: (column, joinBuilders) =>
|
builder: (column, joinBuilders) =>
|
||||||
@@ -721,21 +602,6 @@ class $TabOrderingComposer extends OrderingComposer<_$TabDatabase, Tab> {
|
|||||||
builder: (column, joinBuilders) =>
|
builder: (column, joinBuilders) =>
|
||||||
ColumnOrderings(column, joinBuilders: joinBuilders));
|
ColumnOrderings(column, joinBuilders: joinBuilders));
|
||||||
|
|
||||||
ColumnOrderings<String> get url => $state.composableBuilder(
|
|
||||||
column: $state.table.url,
|
|
||||||
builder: (column, joinBuilders) =>
|
|
||||||
ColumnOrderings(column, joinBuilders: joinBuilders));
|
|
||||||
|
|
||||||
ColumnOrderings<String> get title => $state.composableBuilder(
|
|
||||||
column: $state.table.title,
|
|
||||||
builder: (column, joinBuilders) =>
|
|
||||||
ColumnOrderings(column, joinBuilders: joinBuilders));
|
|
||||||
|
|
||||||
ColumnOrderings<Uint8List> get screenshot => $state.composableBuilder(
|
|
||||||
column: $state.table.screenshot,
|
|
||||||
builder: (column, joinBuilders) =>
|
|
||||||
ColumnOrderings(column, joinBuilders: joinBuilders));
|
|
||||||
|
|
||||||
$TopicOrderingComposer get topicId {
|
$TopicOrderingComposer get topicId {
|
||||||
final $TopicOrderingComposer composer = $state.composerBuilder(
|
final $TopicOrderingComposer composer = $state.composerBuilder(
|
||||||
composer: this,
|
composer: this,
|
||||||
@@ -749,61 +615,50 @@ class $TabOrderingComposer extends OrderingComposer<_$TabDatabase, Tab> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class $TabTableManager extends RootTableManager<
|
class $TabLinkTableManager extends RootTableManager<
|
||||||
_$TabDatabase,
|
_$TabDatabase,
|
||||||
Tab,
|
TabLink,
|
||||||
TabData,
|
TabLinkData,
|
||||||
$TabFilterComposer,
|
$TabLinkFilterComposer,
|
||||||
$TabOrderingComposer,
|
$TabLinkOrderingComposer,
|
||||||
$TabCreateCompanionBuilder,
|
$TabLinkCreateCompanionBuilder,
|
||||||
$TabUpdateCompanionBuilder,
|
$TabLinkUpdateCompanionBuilder,
|
||||||
(TabData, $TabReferences),
|
(TabLinkData, $TabLinkReferences),
|
||||||
TabData,
|
TabLinkData,
|
||||||
PrefetchHooks Function({bool topicId})> {
|
PrefetchHooks Function({bool topicId})> {
|
||||||
$TabTableManager(_$TabDatabase db, Tab table)
|
$TabLinkTableManager(_$TabDatabase db, TabLink table)
|
||||||
: super(TableManagerState(
|
: super(TableManagerState(
|
||||||
db: db,
|
db: db,
|
||||||
table: table,
|
table: table,
|
||||||
filteringComposer: $TabFilterComposer(ComposerState(db, table)),
|
filteringComposer: $TabLinkFilterComposer(ComposerState(db, table)),
|
||||||
orderingComposer: $TabOrderingComposer(ComposerState(db, table)),
|
orderingComposer: $TabLinkOrderingComposer(ComposerState(db, table)),
|
||||||
updateCompanionCallback: ({
|
updateCompanionCallback: ({
|
||||||
Value<String> id = const Value.absent(),
|
Value<String> id = const Value.absent(),
|
||||||
Value<String?> topicId = const Value.absent(),
|
Value<String?> topicId = const Value.absent(),
|
||||||
Value<DateTime> timestamp = const Value.absent(),
|
Value<DateTime> timestamp = const Value.absent(),
|
||||||
Value<Uri> url = const Value.absent(),
|
|
||||||
Value<String?> title = const Value.absent(),
|
|
||||||
Value<Uint8List?> screenshot = const Value.absent(),
|
|
||||||
Value<int> rowid = const Value.absent(),
|
Value<int> rowid = const Value.absent(),
|
||||||
}) =>
|
}) =>
|
||||||
TabCompanion(
|
TabLinkCompanion(
|
||||||
id: id,
|
id: id,
|
||||||
topicId: topicId,
|
topicId: topicId,
|
||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
url: url,
|
|
||||||
title: title,
|
|
||||||
screenshot: screenshot,
|
|
||||||
rowid: rowid,
|
rowid: rowid,
|
||||||
),
|
),
|
||||||
createCompanionCallback: ({
|
createCompanionCallback: ({
|
||||||
required String id,
|
required String id,
|
||||||
Value<String?> topicId = const Value.absent(),
|
Value<String?> topicId = const Value.absent(),
|
||||||
required DateTime timestamp,
|
required DateTime timestamp,
|
||||||
required Uri url,
|
|
||||||
Value<String?> title = const Value.absent(),
|
|
||||||
Value<Uint8List?> screenshot = const Value.absent(),
|
|
||||||
Value<int> rowid = const Value.absent(),
|
Value<int> rowid = const Value.absent(),
|
||||||
}) =>
|
}) =>
|
||||||
TabCompanion.insert(
|
TabLinkCompanion.insert(
|
||||||
id: id,
|
id: id,
|
||||||
topicId: topicId,
|
topicId: topicId,
|
||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
url: url,
|
|
||||||
title: title,
|
|
||||||
screenshot: screenshot,
|
|
||||||
rowid: rowid,
|
rowid: rowid,
|
||||||
),
|
),
|
||||||
withReferenceMapper: (p0) => p0
|
withReferenceMapper: (p0) => p0
|
||||||
.map((e) => (e.readTable(table), $TabReferences(db, table, e)))
|
.map(
|
||||||
|
(e) => (e.readTable(table), $TabLinkReferences(db, table, e)))
|
||||||
.toList(),
|
.toList(),
|
||||||
prefetchHooksCallback: ({topicId = false}) {
|
prefetchHooksCallback: ({topicId = false}) {
|
||||||
return PrefetchHooks(
|
return PrefetchHooks(
|
||||||
@@ -825,8 +680,8 @@ class $TabTableManager extends RootTableManager<
|
|||||||
state = state.withJoin(
|
state = state.withJoin(
|
||||||
currentTable: table,
|
currentTable: table,
|
||||||
currentColumn: table.topicId,
|
currentColumn: table.topicId,
|
||||||
referencedTable: $TabReferences._topicIdTable(db),
|
referencedTable: $TabLinkReferences._topicIdTable(db),
|
||||||
referencedColumn: $TabReferences._topicIdTable(db).id,
|
referencedColumn: $TabLinkReferences._topicIdTable(db).id,
|
||||||
) as T;
|
) as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -840,21 +695,21 @@ class $TabTableManager extends RootTableManager<
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef $TabProcessedTableManager = ProcessedTableManager<
|
typedef $TabLinkProcessedTableManager = ProcessedTableManager<
|
||||||
_$TabDatabase,
|
_$TabDatabase,
|
||||||
Tab,
|
TabLink,
|
||||||
TabData,
|
TabLinkData,
|
||||||
$TabFilterComposer,
|
$TabLinkFilterComposer,
|
||||||
$TabOrderingComposer,
|
$TabLinkOrderingComposer,
|
||||||
$TabCreateCompanionBuilder,
|
$TabLinkCreateCompanionBuilder,
|
||||||
$TabUpdateCompanionBuilder,
|
$TabLinkUpdateCompanionBuilder,
|
||||||
(TabData, $TabReferences),
|
(TabLinkData, $TabLinkReferences),
|
||||||
TabData,
|
TabLinkData,
|
||||||
PrefetchHooks Function({bool topicId})>;
|
PrefetchHooks Function({bool topicId})>;
|
||||||
|
|
||||||
class $TabDatabaseManager {
|
class $TabDatabaseManager {
|
||||||
final _$TabDatabase _db;
|
final _$TabDatabase _db;
|
||||||
$TabDatabaseManager(this._db);
|
$TabDatabaseManager(this._db);
|
||||||
$TopicTableManager get topic => $TopicTableManager(_db, _db.topic);
|
$TopicTableManager get topic => $TopicTableManager(_db, _db.topic);
|
||||||
$TabTableManager get tab => $TabTableManager(_db, _db.tab);
|
$TabLinkTableManager get tabLink => $TabLinkTableManager(_db, _db.tabLink);
|
||||||
}
|
}
|
||||||
+1
-13
@@ -1,6 +1,6 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:drift/native.dart';
|
import 'package:drift/native.dart';
|
||||||
import 'package:lensai/features/topics/data/database/database.dart';
|
import 'package:lensai/features/geckoview/features/topics/data/database/database.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
import 'package:path_provider/path_provider.dart' as path_provider;
|
import 'package:path_provider/path_provider.dart' as path_provider;
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
@@ -35,15 +35,3 @@ TabDatabase tabDatabase(TabDatabaseRef ref) {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Riverpod()
|
|
||||||
Stream<bool> isTabExisting(IsTabExistingRef ref, String tabId) {
|
|
||||||
final db = ref.watch(tabDatabaseProvider);
|
|
||||||
return db.tabDao.watchTabExisiting(tabId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Riverpod()
|
|
||||||
Stream<TabData?> tabData(TabDataRef ref, String tabId) {
|
|
||||||
final db = ref.watch(tabDatabaseProvider);
|
|
||||||
return db.tabDao.watchTab(tabId);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'providers.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$tabDatabaseHash() => r'b886b089ae5fdb9bdcc0982efe5fcbe9bc66486f';
|
||||||
|
|
||||||
|
/// See also [tabDatabase].
|
||||||
|
@ProviderFor(tabDatabase)
|
||||||
|
final tabDatabaseProvider = Provider<TabDatabase>.internal(
|
||||||
|
tabDatabase,
|
||||||
|
name: r'tabDatabaseProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product') ? null : _$tabDatabaseHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef TabDatabaseRef = ProviderRef<TabDatabase>;
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||||
+16
-4
@@ -1,10 +1,10 @@
|
|||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:lensai/features/topics/data/models/topic_data.dart';
|
import 'package:lensai/features/geckoview/features/topics/data/models/topic_data.dart';
|
||||||
import 'package:lensai/features/topics/data/providers.dart';
|
import 'package:lensai/features/geckoview/features/topics/data/providers.dart';
|
||||||
import 'package:lensai/features/topics/domain/repositories/topic.dart';
|
import 'package:lensai/features/geckoview/features/topics/domain/repositories/topic.dart';
|
||||||
import 'package:lensai/features/topics/utils/color_palette.dart';
|
import 'package:lensai/features/geckoview/features/topics/utils/color_palette.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
part 'providers.g.dart';
|
part 'providers.g.dart';
|
||||||
@@ -59,3 +59,15 @@ Future<Color> unusedRandomTopicColor(UnusedRandomTopicColorRef ref) async {
|
|||||||
|
|
||||||
return randomColor;
|
return randomColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Riverpod()
|
||||||
|
Stream<List<TopicDataWithCount>> topicsWithCount(TopicsWithCountRef ref) {
|
||||||
|
final db = ref.watch(tabDatabaseProvider);
|
||||||
|
return db.topicsWithCount().watch();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Riverpod()
|
||||||
|
Stream<List<String>> topicTabIds(TopicTabIdsRef ref, String topicId) {
|
||||||
|
final db = ref.watch(tabDatabaseProvider);
|
||||||
|
return db.tabLinkDao.topicTabIds(topicId).watch();
|
||||||
|
}
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
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';
|
||||||
|
|
||||||
|
/// See also [unusedRandomTopicColor].
|
||||||
|
@ProviderFor(unusedRandomTopicColor)
|
||||||
|
final unusedRandomTopicColorProvider =
|
||||||
|
AutoDisposeFutureProvider<Color>.internal(
|
||||||
|
unusedRandomTopicColor,
|
||||||
|
name: r'unusedRandomTopicColorProvider',
|
||||||
|
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$unusedRandomTopicColorHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef UnusedRandomTopicColorRef = AutoDisposeFutureProviderRef<Color>;
|
||||||
|
String _$topicsWithCountHash() => r'cd9792b0e0af12cd8796a3f0b76b42171a4200e6';
|
||||||
|
|
||||||
|
/// See also [topicsWithCount].
|
||||||
|
@ProviderFor(topicsWithCount)
|
||||||
|
final topicsWithCountProvider =
|
||||||
|
AutoDisposeStreamProvider<List<TopicDataWithCount>>.internal(
|
||||||
|
topicsWithCount,
|
||||||
|
name: r'topicsWithCountProvider',
|
||||||
|
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$topicsWithCountHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef TopicsWithCountRef
|
||||||
|
= AutoDisposeStreamProviderRef<List<TopicDataWithCount>>;
|
||||||
|
String _$topicTabIdsHash() => r'43719c70e01cf9e030d75171405ab9650936ddf8';
|
||||||
|
|
||||||
|
/// 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 [topicTabIds].
|
||||||
|
@ProviderFor(topicTabIds)
|
||||||
|
const topicTabIdsProvider = TopicTabIdsFamily();
|
||||||
|
|
||||||
|
/// See also [topicTabIds].
|
||||||
|
class TopicTabIdsFamily extends Family<AsyncValue<List<String>>> {
|
||||||
|
/// See also [topicTabIds].
|
||||||
|
const TopicTabIdsFamily();
|
||||||
|
|
||||||
|
/// See also [topicTabIds].
|
||||||
|
TopicTabIdsProvider call(
|
||||||
|
String topicId,
|
||||||
|
) {
|
||||||
|
return TopicTabIdsProvider(
|
||||||
|
topicId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
TopicTabIdsProvider getProviderOverride(
|
||||||
|
covariant TopicTabIdsProvider provider,
|
||||||
|
) {
|
||||||
|
return call(
|
||||||
|
provider.topicId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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'topicTabIdsProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [topicTabIds].
|
||||||
|
class TopicTabIdsProvider extends AutoDisposeStreamProvider<List<String>> {
|
||||||
|
/// See also [topicTabIds].
|
||||||
|
TopicTabIdsProvider(
|
||||||
|
String topicId,
|
||||||
|
) : this._internal(
|
||||||
|
(ref) => topicTabIds(
|
||||||
|
ref as TopicTabIdsRef,
|
||||||
|
topicId,
|
||||||
|
),
|
||||||
|
from: topicTabIdsProvider,
|
||||||
|
name: r'topicTabIdsProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$topicTabIdsHash,
|
||||||
|
dependencies: TopicTabIdsFamily._dependencies,
|
||||||
|
allTransitiveDependencies:
|
||||||
|
TopicTabIdsFamily._allTransitiveDependencies,
|
||||||
|
topicId: topicId,
|
||||||
|
);
|
||||||
|
|
||||||
|
TopicTabIdsProvider._internal(
|
||||||
|
super._createNotifier, {
|
||||||
|
required super.name,
|
||||||
|
required super.dependencies,
|
||||||
|
required super.allTransitiveDependencies,
|
||||||
|
required super.debugGetCreateSourceHash,
|
||||||
|
required super.from,
|
||||||
|
required this.topicId,
|
||||||
|
}) : super.internal();
|
||||||
|
|
||||||
|
final String topicId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Override overrideWith(
|
||||||
|
Stream<List<String>> Function(TopicTabIdsRef provider) create,
|
||||||
|
) {
|
||||||
|
return ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
override: TopicTabIdsProvider._internal(
|
||||||
|
(ref) => create(ref as TopicTabIdsRef),
|
||||||
|
from: from,
|
||||||
|
name: null,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
debugGetCreateSourceHash: null,
|
||||||
|
topicId: topicId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
AutoDisposeStreamProviderElement<List<String>> createElement() {
|
||||||
|
return _TopicTabIdsProviderElement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is TopicTabIdsProvider && other.topicId == topicId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, topicId.hashCode);
|
||||||
|
|
||||||
|
return _SystemHash.finish(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mixin TopicTabIdsRef on AutoDisposeStreamProviderRef<List<String>> {
|
||||||
|
/// The parameter `topicId` of this provider.
|
||||||
|
String get topicId;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TopicTabIdsProviderElement
|
||||||
|
extends AutoDisposeStreamProviderElement<List<String>> with TopicTabIdsRef {
|
||||||
|
_TopicTabIdsProviderElement(super.provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get topicId => (origin as TopicTabIdsProvider).topicId;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
|
part 'selected_topic.g.dart';
|
||||||
|
|
||||||
|
@Riverpod(keepAlive: true)
|
||||||
|
class SelectedTopicRepository extends _$SelectedTopicRepository {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'selected_topic.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$selectedTopicRepositoryHash() =>
|
||||||
|
r'd7ffe4ff5501aff747e5155d74b07974784f26f9';
|
||||||
|
|
||||||
|
/// See also [SelectedTopicRepository].
|
||||||
|
@ProviderFor(SelectedTopicRepository)
|
||||||
|
final selectedTopicRepositoryProvider =
|
||||||
|
NotifierProvider<SelectedTopicRepository, String?>.internal(
|
||||||
|
SelectedTopicRepository.new,
|
||||||
|
name: r'selectedTopicRepositoryProvider',
|
||||||
|
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$selectedTopicRepositoryHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef _$SelectedTopicRepository = 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
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import 'package:drift/drift.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/features/topics/data/database/database.dart';
|
||||||
|
import 'package:lensai/features/geckoview/features/topics/data/providers.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
|
part 'tab_link.g.dart';
|
||||||
|
|
||||||
|
@Riverpod(keepAlive: true)
|
||||||
|
class TabLinkRepository extends _$TabLinkRepository {
|
||||||
|
late TabDatabase _db;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void build() {
|
||||||
|
_db = ref.watch(tabDatabaseProvider);
|
||||||
|
|
||||||
|
//Sync existing tabs
|
||||||
|
ref.listen(
|
||||||
|
tabRepositoryProvider.select((tabs) => tabs.keys.toList()),
|
||||||
|
(previous, next) async {
|
||||||
|
//No empty list to avoid data loss
|
||||||
|
if (next.isNotEmpty) {
|
||||||
|
await _db.tabLinkDao.syncTabLinks(retainTabIds: next);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
ref.listen(
|
||||||
|
selectedTabControllerProvider,
|
||||||
|
(previous, next) async {
|
||||||
|
if (next != null) {
|
||||||
|
await _db.tabLinkDao.touchTabLink(next, timestamp: DateTime.now());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> assignTab(String tabId, String? topicId) {
|
||||||
|
return _db.tabLinkDao.upsertTabLink(
|
||||||
|
tabId,
|
||||||
|
timestamp: DateTime.now(),
|
||||||
|
topicId: Value(topicId),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> closeAllTabs(String? topicId) async {
|
||||||
|
final tabIds = await _db.tabLinkDao.topicTabIds(topicId).get();
|
||||||
|
if (tabIds.isNotEmpty) {
|
||||||
|
await ref.read(tabRepositoryProvider.notifier).closeTabs(tabIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'tab_link.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$tabLinkRepositoryHash() => r'e85d88fadb8133c3ef1e38f0176694a12a749397';
|
||||||
|
|
||||||
|
/// See also [TabLinkRepository].
|
||||||
|
@ProviderFor(TabLinkRepository)
|
||||||
|
final tabLinkRepositoryProvider =
|
||||||
|
NotifierProvider<TabLinkRepository, void>.internal(
|
||||||
|
TabLinkRepository.new,
|
||||||
|
name: r'tabLinkRepositoryProvider',
|
||||||
|
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$tabLinkRepositoryHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef _$TabLinkRepository = 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
|
||||||
+3
-5
@@ -1,8 +1,7 @@
|
|||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:lensai/features/topics/data/database/database.dart';
|
import 'package:lensai/features/geckoview/features/topics/data/database/database.dart';
|
||||||
import 'package:lensai/features/topics/data/models/topic_data.dart';
|
import 'package:lensai/features/geckoview/features/topics/data/providers.dart';
|
||||||
import 'package:lensai/features/topics/data/providers.dart';
|
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
part 'topic.g.dart';
|
part 'topic.g.dart';
|
||||||
@@ -12,9 +11,8 @@ class TopicRepository extends _$TopicRepository {
|
|||||||
late TabDatabase _db;
|
late TabDatabase _db;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<List<TopicDataWithCount>> build() {
|
void build() {
|
||||||
_db = ref.watch(tabDatabaseProvider);
|
_db = ref.watch(tabDatabaseProvider);
|
||||||
return _db.topicsWithCount().watch();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addTopic({required String? name, required Color color}) {
|
Future<void> addTopic({required String? name, required Color color}) {
|
||||||
+3
-3
@@ -6,12 +6,12 @@ part of 'topic.dart';
|
|||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$topicRepositoryHash() => r'3b75c5fb1a081ba6d90e2ccb781469d0aa87c415';
|
String _$topicRepositoryHash() => r'6c456c17af4ea7d7ef1747c92d0442459d061080';
|
||||||
|
|
||||||
/// See also [TopicRepository].
|
/// See also [TopicRepository].
|
||||||
@ProviderFor(TopicRepository)
|
@ProviderFor(TopicRepository)
|
||||||
final topicRepositoryProvider =
|
final topicRepositoryProvider =
|
||||||
StreamNotifierProvider<TopicRepository, List<TopicDataWithCount>>.internal(
|
NotifierProvider<TopicRepository, void>.internal(
|
||||||
TopicRepository.new,
|
TopicRepository.new,
|
||||||
name: r'topicRepositoryProvider',
|
name: r'topicRepositoryProvider',
|
||||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||||
@@ -21,6 +21,6 @@ final topicRepositoryProvider =
|
|||||||
allTransitiveDependencies: null,
|
allTransitiveDependencies: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
typedef _$TopicRepository = StreamNotifier<List<TopicDataWithCount>>;
|
typedef _$TopicRepository = Notifier<void>;
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||||
+4
-4
@@ -2,10 +2,10 @@ import 'package:fading_scroll/fading_scroll.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:lensai/features/topics/data/models/topic_data.dart';
|
import 'package:lensai/features/geckoview/features/topics/data/models/topic_data.dart';
|
||||||
import 'package:lensai/features/topics/domain/providers.dart';
|
import 'package:lensai/features/geckoview/features/topics/domain/providers.dart';
|
||||||
import 'package:lensai/features/topics/domain/repositories/topic.dart';
|
import 'package:lensai/features/geckoview/features/topics/domain/repositories/topic.dart';
|
||||||
import 'package:lensai/features/topics/presentation/widgets/topic_dialog.dart';
|
import 'package:lensai/features/geckoview/features/topics/presentation/widgets/topic_dialog.dart';
|
||||||
import 'package:skeletonizer/skeletonizer.dart';
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
|
|
||||||
class _TopicTile extends HookWidget {
|
class _TopicTile extends HookWidget {
|
||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
import 'package:fading_scroll/fading_scroll.dart';
|
import 'package:fading_scroll/fading_scroll.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:lensai/features/topics/utils/color_palette.dart';
|
import 'package:lensai/features/geckoview/features/topics/utils/color_palette.dart';
|
||||||
|
|
||||||
class MaterialPicker extends StatefulWidget {
|
class MaterialPicker extends StatefulWidget {
|
||||||
const MaterialPicker({
|
const MaterialPicker({
|
||||||
+2
-2
@@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:lensai/core/routing/routes.dart';
|
import 'package:lensai/core/routing/routes.dart';
|
||||||
import 'package:lensai/features/topics/domain/providers.dart';
|
import 'package:lensai/features/geckoview/features/topics/domain/providers.dart';
|
||||||
import 'package:lensai/features/topics/domain/repositories/topic.dart';
|
import 'package:lensai/features/geckoview/features/topics/domain/repositories/topic.dart';
|
||||||
import 'package:lensai/presentation/widgets/selectable_chips.dart';
|
import 'package:lensai/presentation/widgets/selectable_chips.dart';
|
||||||
|
|
||||||
class TopicChips extends HookConsumerWidget {
|
class TopicChips extends HookConsumerWidget {
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:lensai/features/topics/presentation/widgets/material_color_picker.dart';
|
import 'package:lensai/features/geckoview/features/topics/presentation/widgets/material_color_picker.dart';
|
||||||
|
|
||||||
typedef TopicResult = ({String? name, Color color});
|
typedef TopicResult = ({String? name, Color color});
|
||||||
|
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import 'dart:typed_data';
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
Future<Image?> tryDecodeImage(
|
||||||
|
Uint8List bytes, {
|
||||||
|
int? targetWidth,
|
||||||
|
int? targetHeight,
|
||||||
|
bool allowUpscaling = true,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final codec = await instantiateImageCodec(
|
||||||
|
bytes,
|
||||||
|
targetWidth: targetWidth,
|
||||||
|
targetHeight: targetHeight,
|
||||||
|
allowUpscaling: allowUpscaling,
|
||||||
|
);
|
||||||
|
|
||||||
|
final frameInfo = await codec.getNextFrame();
|
||||||
|
final image = frameInfo.image;
|
||||||
|
|
||||||
|
if (image.width > 0) {
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
//swallow
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
|
part 'session.g.dart';
|
||||||
|
|
||||||
|
@Riverpod()
|
||||||
|
class KagiSessionService extends _$KagiSessionService {
|
||||||
|
final _cookieManager = GeckoCookieService();
|
||||||
|
|
||||||
|
Future<void> setKagiSession(String session) async {
|
||||||
|
await _cookieManager.setCookie(
|
||||||
|
url: Uri.https('kagi.com'),
|
||||||
|
name: 'kagi_session',
|
||||||
|
value: session,
|
||||||
|
domain: Uri.https('kagi.com'),
|
||||||
|
httpOnly: true,
|
||||||
|
secure: true,
|
||||||
|
sameSite: CookieSameSiteStatus.lax,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void build() {}
|
||||||
|
}
|
||||||
+10
-9
@@ -6,21 +6,22 @@ part of 'session.dart';
|
|||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$sessionServiceHash() => r'fd2cd03bbf2dd254bb19e541c71a6fb249f5a7bb';
|
String _$kagiSessionServiceHash() =>
|
||||||
|
r'8bed5d74d319ff2f874f42bc8596d02f05347c1f';
|
||||||
|
|
||||||
/// See also [SessionService].
|
/// See also [KagiSessionService].
|
||||||
@ProviderFor(SessionService)
|
@ProviderFor(KagiSessionService)
|
||||||
final sessionServiceProvider =
|
final kagiSessionServiceProvider =
|
||||||
AutoDisposeNotifierProvider<SessionService, void>.internal(
|
AutoDisposeNotifierProvider<KagiSessionService, void>.internal(
|
||||||
SessionService.new,
|
KagiSessionService.new,
|
||||||
name: r'sessionServiceProvider',
|
name: r'kagiSessionServiceProvider',
|
||||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||||
? null
|
? null
|
||||||
: _$sessionServiceHash,
|
: _$kagiSessionServiceHash,
|
||||||
dependencies: null,
|
dependencies: null,
|
||||||
allTransitiveDependencies: null,
|
allTransitiveDependencies: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
typedef _$SessionService = AutoDisposeNotifier<void>;
|
typedef _$KagiSessionService = AutoDisposeNotifier<void>;
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
import 'package:fast_equatable/fast_equatable.dart';
|
import 'package:fast_equatable/fast_equatable.dart';
|
||||||
import 'package:lensai/features/search_browser/domain/entities/modes.dart';
|
import 'package:lensai/features/kagi/data/entities/modes.dart';
|
||||||
|
|
||||||
// ignore: missing_override_of_must_be_overridden
|
// ignore: missing_override_of_must_be_overridden
|
||||||
sealed class Sheet with FastEquatable {}
|
sealed class Sheet with FastEquatable {}
|
||||||
|
|
||||||
class CreateTab extends Sheet {
|
class CreateTabSheet extends Sheet {
|
||||||
final String? content;
|
final String? content;
|
||||||
final KagiTool? preferredTool;
|
final KagiTool? preferredTool;
|
||||||
|
|
||||||
bool get hasParameters => content != null || preferredTool != null;
|
bool get hasParameters => content != null || preferredTool != null;
|
||||||
|
|
||||||
CreateTab({this.content, this.preferredTool});
|
CreateTabSheet({this.content, this.preferredTool});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get cacheHash => true;
|
bool get cacheHash => true;
|
||||||
@@ -19,7 +19,7 @@ class CreateTab extends Sheet {
|
|||||||
List<Object?> get hashParameters => [content, preferredTool];
|
List<Object?> get hashParameters => [content, preferredTool];
|
||||||
}
|
}
|
||||||
|
|
||||||
class ViewTabs extends Sheet {
|
class ViewTabsSheet extends Sheet {
|
||||||
@override
|
@override
|
||||||
bool get cacheHash => true;
|
bool get cacheHash => true;
|
||||||
|
|
||||||
|
|||||||
@@ -2,76 +2,16 @@
|
|||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
|
||||||
import 'package:lensai/features/bangs/data/models/bang_data.dart';
|
import 'package:lensai/features/bangs/data/models/bang_data.dart';
|
||||||
import 'package:lensai/features/bangs/domain/repositories/data.dart';
|
import 'package:lensai/features/bangs/domain/repositories/data.dart';
|
||||||
import 'package:lensai/features/search_browser/domain/entities/modes.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/entities/sheet.dart';
|
||||||
import 'package:lensai/features/search_browser/domain/services/create_tab.dart';
|
|
||||||
import 'package:lensai/features/topics/data/database/database.dart';
|
|
||||||
import 'package:lensai/features/topics/domain/repositories/tab.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';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:rxdart/rxdart.dart';
|
|
||||||
|
|
||||||
part 'providers.g.dart';
|
part 'providers.g.dart';
|
||||||
|
|
||||||
@Riverpod()
|
|
||||||
class OverlayDialog extends _$OverlayDialog {
|
|
||||||
@override
|
|
||||||
Widget? build() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
void show(Widget dialog) {
|
|
||||||
state = dialog;
|
|
||||||
}
|
|
||||||
|
|
||||||
void dismiss() {
|
|
||||||
state = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Riverpod()
|
|
||||||
class BottomSheet extends _$BottomSheet {
|
|
||||||
@override
|
|
||||||
Sheet? build() {
|
|
||||||
//Don't use select here because it will only fire on new != previous
|
|
||||||
return ref.watch(createTabStreamProvider).valueOrNull;
|
|
||||||
}
|
|
||||||
|
|
||||||
void show(Sheet sheet) {
|
|
||||||
state = sheet;
|
|
||||||
}
|
|
||||||
|
|
||||||
void dismiss() {
|
|
||||||
state = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Riverpod(keepAlive: true)
|
|
||||||
class BottomSheetExtend extends _$BottomSheetExtend {
|
|
||||||
late StreamController<double> _extentStreamController;
|
|
||||||
|
|
||||||
void add(double extent) {
|
|
||||||
_extentStreamController.add(extent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Stream<double> build() {
|
|
||||||
_extentStreamController = StreamController();
|
|
||||||
ref.onDispose(() async {
|
|
||||||
await _extentStreamController.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
return _extentStreamController.stream
|
|
||||||
.sampleTime(const Duration(milliseconds: 50));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Riverpod(keepAlive: true)
|
@Riverpod(keepAlive: true)
|
||||||
class SelectedBangTrigger extends _$SelectedBangTrigger {
|
class SelectedBangTrigger extends _$SelectedBangTrigger {
|
||||||
void setTrigger(String trigger) {
|
void setTrigger(String trigger) {
|
||||||
@@ -143,65 +83,3 @@ class ShowFindInPage extends _$ShowFindInPage {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Riverpod()
|
|
||||||
class WebViewWidgetCache extends _$WebViewWidgetCache {
|
|
||||||
final _requestedWidgets = <String>{};
|
|
||||||
|
|
||||||
List<TabData> _availableTabs = [];
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<WebView> build() {
|
|
||||||
ref.listen(
|
|
||||||
tabRepositoryProvider,
|
|
||||||
(previous, next) {
|
|
||||||
if (next.hasValue && next.valueOrNull != null) {
|
|
||||||
_availableTabs = next.valueOrNull!;
|
|
||||||
|
|
||||||
_removeClosedWidgets();
|
|
||||||
_requestedWidgets.removeWhere(_ensureWidgetCached);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
ref.listen(
|
|
||||||
webViewTabControllerProvider,
|
|
||||||
(_, tabId) {
|
|
||||||
if (tabId != null && !_ensureWidgetCached(tabId)) {
|
|
||||||
_requestedWidgets.add(tabId);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return stateOrNull ?? [];
|
|
||||||
}
|
|
||||||
|
|
||||||
void _removeClosedWidgets() {
|
|
||||||
state = [...state]..removeWhere(
|
|
||||||
(element) => !_availableTabs.any((tab) => tab.id == element.tabId),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _ensureWidgetCached(String tabId) {
|
|
||||||
final index = state.indexWhere((tab) => tab.tabId == tabId);
|
|
||||||
if (index < 0) {
|
|
||||||
final tab = _availableTabs.firstWhereOrNull((tab) => tab.id == tabId);
|
|
||||||
if (tab != null) {
|
|
||||||
state = [
|
|
||||||
...state,
|
|
||||||
WebView(
|
|
||||||
key: GlobalKey(),
|
|
||||||
tabId: tabId,
|
|
||||||
initialUrlRequest: URLRequest(url: WebUri.uri(tab.url)),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -156,53 +156,6 @@ class _SelectedBangDataProviderElement
|
|||||||
String? get domain => (origin as SelectedBangDataProvider).domain;
|
String? get domain => (origin as SelectedBangDataProvider).domain;
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$overlayDialogHash() => r'7a0376fff0ba12c70a257a47391e7c049dabd36d';
|
|
||||||
|
|
||||||
/// See also [OverlayDialog].
|
|
||||||
@ProviderFor(OverlayDialog)
|
|
||||||
final overlayDialogProvider =
|
|
||||||
AutoDisposeNotifierProvider<OverlayDialog, Widget?>.internal(
|
|
||||||
OverlayDialog.new,
|
|
||||||
name: r'overlayDialogProvider',
|
|
||||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
|
||||||
? null
|
|
||||||
: _$overlayDialogHash,
|
|
||||||
dependencies: null,
|
|
||||||
allTransitiveDependencies: null,
|
|
||||||
);
|
|
||||||
|
|
||||||
typedef _$OverlayDialog = AutoDisposeNotifier<Widget?>;
|
|
||||||
String _$bottomSheetHash() => r'4040ec50a488b4b12123bedceab6d8620e6abe3d';
|
|
||||||
|
|
||||||
/// See also [BottomSheet].
|
|
||||||
@ProviderFor(BottomSheet)
|
|
||||||
final bottomSheetProvider =
|
|
||||||
AutoDisposeNotifierProvider<BottomSheet, Sheet?>.internal(
|
|
||||||
BottomSheet.new,
|
|
||||||
name: r'bottomSheetProvider',
|
|
||||||
debugGetCreateSourceHash:
|
|
||||||
const bool.fromEnvironment('dart.vm.product') ? null : _$bottomSheetHash,
|
|
||||||
dependencies: null,
|
|
||||||
allTransitiveDependencies: null,
|
|
||||||
);
|
|
||||||
|
|
||||||
typedef _$BottomSheet = AutoDisposeNotifier<Sheet?>;
|
|
||||||
String _$bottomSheetExtendHash() => r'81ae254499d1d7f57309274b0f18747b94d6dda3';
|
|
||||||
|
|
||||||
/// See also [BottomSheetExtend].
|
|
||||||
@ProviderFor(BottomSheetExtend)
|
|
||||||
final bottomSheetExtendProvider =
|
|
||||||
StreamNotifierProvider<BottomSheetExtend, double>.internal(
|
|
||||||
BottomSheetExtend.new,
|
|
||||||
name: r'bottomSheetExtendProvider',
|
|
||||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
|
||||||
? null
|
|
||||||
: _$bottomSheetExtendHash,
|
|
||||||
dependencies: null,
|
|
||||||
allTransitiveDependencies: null,
|
|
||||||
);
|
|
||||||
|
|
||||||
typedef _$BottomSheetExtend = StreamNotifier<double>;
|
|
||||||
String _$selectedBangTriggerHash() =>
|
String _$selectedBangTriggerHash() =>
|
||||||
r'b0e12bc95e93d50d04f1c658230cea9f15ff0385';
|
r'b0e12bc95e93d50d04f1c658230cea9f15ff0385';
|
||||||
|
|
||||||
@@ -411,22 +364,5 @@ final showFindInPageProvider = NotifierProvider<ShowFindInPage, bool>.internal(
|
|||||||
);
|
);
|
||||||
|
|
||||||
typedef _$ShowFindInPage = Notifier<bool>;
|
typedef _$ShowFindInPage = Notifier<bool>;
|
||||||
String _$webViewWidgetCacheHash() =>
|
|
||||||
r'7d6f769e8c314d9dc579d700d36d6d4c944e600f';
|
|
||||||
|
|
||||||
/// See also [WebViewWidgetCache].
|
|
||||||
@ProviderFor(WebViewWidgetCache)
|
|
||||||
final webViewWidgetCacheProvider =
|
|
||||||
AutoDisposeNotifierProvider<WebViewWidgetCache, List<WebView>>.internal(
|
|
||||||
WebViewWidgetCache.new,
|
|
||||||
name: r'webViewWidgetCacheProvider',
|
|
||||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
|
||||||
? null
|
|
||||||
: _$webViewWidgetCacheHash,
|
|
||||||
dependencies: null,
|
|
||||||
allTransitiveDependencies: null,
|
|
||||||
);
|
|
||||||
|
|
||||||
typedef _$WebViewWidgetCache = AutoDisposeNotifier<List<WebView>>;
|
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:lensai/domain/entities/received_parameter.dart';
|
import 'package:lensai/data/models/received_parameter.dart';
|
||||||
import 'package:lensai/features/app_widget/domain/services/home_widget.dart';
|
import 'package:lensai/features/app_widget/domain/services/home_widget.dart';
|
||||||
import 'package:lensai/features/search_browser/domain/entities/modes.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/entities/sheet.dart';
|
||||||
import 'package:lensai/features/share_intent/domain/services/sharing_intent.dart';
|
import 'package:lensai/features/share_intent/domain/services/sharing_intent.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
@@ -11,10 +11,10 @@ import 'package:rxdart/rxdart.dart';
|
|||||||
|
|
||||||
part 'create_tab.g.dart';
|
part 'create_tab.g.dart';
|
||||||
|
|
||||||
final _parameterToCreateTabTransformer =
|
final _parameterToCreateTabSheetTransformer =
|
||||||
StreamTransformer<ReceivedParameter, CreateTab>.fromHandlers(
|
StreamTransformer<ReceivedParameter, CreateTabSheet>.fromHandlers(
|
||||||
handleData: (parameter, sink) {
|
handleData: (parameter, sink) {
|
||||||
final createTab = CreateTab(
|
final createTab = CreateTabSheet(
|
||||||
preferredTool: KagiTool.values
|
preferredTool: KagiTool.values
|
||||||
.firstWhereOrNull((tool) => tool.name == parameter.tool),
|
.firstWhereOrNull((tool) => tool.name == parameter.tool),
|
||||||
content: parameter.content,
|
content: parameter.content,
|
||||||
@@ -28,10 +28,10 @@ final _parameterToCreateTabTransformer =
|
|||||||
|
|
||||||
@Riverpod()
|
@Riverpod()
|
||||||
class CreateTabStream extends _$CreateTabStream {
|
class CreateTabStream extends _$CreateTabStream {
|
||||||
late StreamController<CreateTab> _streamController;
|
late StreamController<CreateTabSheet> _streamController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<CreateTab> build() {
|
Stream<CreateTabSheet> build() {
|
||||||
_streamController = StreamController();
|
_streamController = StreamController();
|
||||||
ref.onDispose(() async {
|
ref.onDispose(() async {
|
||||||
await _streamController.close();
|
await _streamController.close();
|
||||||
@@ -41,13 +41,13 @@ class CreateTabStream extends _$CreateTabStream {
|
|||||||
final appWidgetLaunchStream = ref.watch(appWidgetLaunchStreamProvider);
|
final appWidgetLaunchStream = ref.watch(appWidgetLaunchStreamProvider);
|
||||||
|
|
||||||
return MergeStream([
|
return MergeStream([
|
||||||
sharingItentStream.transform(_parameterToCreateTabTransformer),
|
sharingItentStream.transform(_parameterToCreateTabSheetTransformer),
|
||||||
appWidgetLaunchStream.transform(_parameterToCreateTabTransformer),
|
appWidgetLaunchStream.transform(_parameterToCreateTabSheetTransformer),
|
||||||
_streamController.stream,
|
_streamController.stream,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void createTab(CreateTab parameter) {
|
void createTab(CreateTabSheet parameter) {
|
||||||
_streamController.add(parameter);
|
_streamController.add(parameter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ part of 'create_tab.dart';
|
|||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$createTabStreamHash() => r'3e85020edbebf3ec9927dba6d05af9f06a360a14';
|
String _$createTabStreamHash() => r'05ad914e41e0d346e082e4638afe5fe5a04052f1';
|
||||||
|
|
||||||
/// See also [CreateTabStream].
|
/// See also [CreateTabStream].
|
||||||
@ProviderFor(CreateTabStream)
|
@ProviderFor(CreateTabStream)
|
||||||
final createTabStreamProvider =
|
final createTabStreamProvider =
|
||||||
AutoDisposeStreamNotifierProvider<CreateTabStream, CreateTab>.internal(
|
AutoDisposeStreamNotifierProvider<CreateTabStream, CreateTabSheet>.internal(
|
||||||
CreateTabStream.new,
|
CreateTabStream.new,
|
||||||
name: r'createTabStreamProvider',
|
name: r'createTabStreamProvider',
|
||||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||||
@@ -21,6 +21,6 @@ final createTabStreamProvider =
|
|||||||
allTransitiveDependencies: null,
|
allTransitiveDependencies: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
typedef _$CreateTabStream = AutoDisposeStreamNotifier<CreateTab>;
|
typedef _$CreateTabStream = AutoDisposeStreamNotifier<CreateTabSheet>;
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
||||||
|
|
||||||
part 'session.g.dart';
|
|
||||||
|
|
||||||
@Riverpod()
|
|
||||||
class SessionService extends _$SessionService {
|
|
||||||
final _cookieManager = CookieManager.instance();
|
|
||||||
|
|
||||||
Future<void> setKagiSession(String session) async {
|
|
||||||
await _cookieManager.setCookie(
|
|
||||||
url: WebUri.uri(Uri.https('kagi.com')),
|
|
||||||
name: 'kagi_session',
|
|
||||||
value: session,
|
|
||||||
domain: 'kagi.com',
|
|
||||||
isHttpOnly: true,
|
|
||||||
isSecure: true,
|
|
||||||
sameSite: HTTPCookieSameSitePolicy.LAX,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> clearAllData() async {
|
|
||||||
await _cookieManager.deleteAllCookies();
|
|
||||||
|
|
||||||
final webViewLoaded = Completer<void>();
|
|
||||||
final headlessWebView = HeadlessInAppWebView(
|
|
||||||
initialSettings: InAppWebViewSettings(
|
|
||||||
//Clears all data on (just) Android
|
|
||||||
incognito: true,
|
|
||||||
//Clear other stuff
|
|
||||||
clearCache: true,
|
|
||||||
clearSessionCache: true,
|
|
||||||
),
|
|
||||||
onWebViewCreated: (controller) {
|
|
||||||
//wait until settings are applied for sure
|
|
||||||
webViewLoaded.complete();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
unawaited(headlessWebView.run());
|
|
||||||
|
|
||||||
await webViewLoaded.future.whenComplete(() => headlessWebView..dispose());
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void build() {}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
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:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
import 'package:rxdart/rxdart.dart';
|
||||||
|
|
||||||
|
part 'bottom_sheet.g.dart';
|
||||||
|
|
||||||
|
@Riverpod()
|
||||||
|
class BottomSheetController extends _$BottomSheetController {
|
||||||
|
@override
|
||||||
|
Sheet? build() {
|
||||||
|
//Don't use select here because it will only fire on new != previous
|
||||||
|
return ref.watch(createTabStreamProvider).valueOrNull;
|
||||||
|
}
|
||||||
|
|
||||||
|
void show(Sheet sheet) {
|
||||||
|
state = sheet;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dismiss() {
|
||||||
|
state = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Riverpod(keepAlive: true)
|
||||||
|
class BottomSheetExtend extends _$BottomSheetExtend {
|
||||||
|
late StreamController<double> _extentStreamController;
|
||||||
|
|
||||||
|
void add(double extent) {
|
||||||
|
_extentStreamController.add(extent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<double> build() {
|
||||||
|
_extentStreamController = StreamController();
|
||||||
|
ref.onDispose(() async {
|
||||||
|
await _extentStreamController.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
return _extentStreamController.stream
|
||||||
|
.sampleTime(const Duration(milliseconds: 50));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'bottom_sheet.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$bottomSheetControllerHash() =>
|
||||||
|
r'ae91f911fc35fdc0dfb14149ac2295e58ad13a86';
|
||||||
|
|
||||||
|
/// See also [BottomSheetController].
|
||||||
|
@ProviderFor(BottomSheetController)
|
||||||
|
final bottomSheetControllerProvider =
|
||||||
|
AutoDisposeNotifierProvider<BottomSheetController, Sheet?>.internal(
|
||||||
|
BottomSheetController.new,
|
||||||
|
name: r'bottomSheetControllerProvider',
|
||||||
|
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$bottomSheetControllerHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef _$BottomSheetController = AutoDisposeNotifier<Sheet?>;
|
||||||
|
String _$bottomSheetExtendHash() => r'81ae254499d1d7f57309274b0f18747b94d6dda3';
|
||||||
|
|
||||||
|
/// See also [BottomSheetExtend].
|
||||||
|
@ProviderFor(BottomSheetExtend)
|
||||||
|
final bottomSheetExtendProvider =
|
||||||
|
StreamNotifierProvider<BottomSheetExtend, double>.internal(
|
||||||
|
BottomSheetExtend.new,
|
||||||
|
name: r'bottomSheetExtendProvider',
|
||||||
|
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$bottomSheetExtendHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef _$BottomSheetExtend = StreamNotifier<double>;
|
||||||
|
// 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,20 @@
|
|||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
|
part 'overlay_dialog.g.dart';
|
||||||
|
|
||||||
|
@Riverpod()
|
||||||
|
class OverlayDialogController extends _$OverlayDialogController {
|
||||||
|
@override
|
||||||
|
Widget? build() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void show(Widget dialog) {
|
||||||
|
state = dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dismiss() {
|
||||||
|
state = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'overlay_dialog.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$overlayDialogControllerHash() =>
|
||||||
|
r'53b44d9567bcb5bd9faecbfb8bb1af5aafb369c9';
|
||||||
|
|
||||||
|
/// See also [OverlayDialogController].
|
||||||
|
@ProviderFor(OverlayDialogController)
|
||||||
|
final overlayDialogControllerProvider =
|
||||||
|
AutoDisposeNotifierProvider<OverlayDialogController, Widget?>.internal(
|
||||||
|
OverlayDialogController.new,
|
||||||
|
name: r'overlayDialogControllerProvider',
|
||||||
|
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$overlayDialogControllerHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef _$OverlayDialogController = AutoDisposeNotifier<Widget?>;
|
||||||
|
// 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,25 +2,25 @@ import 'package:collection/collection.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:lensai/features/topics/domain/repositories/topic.dart';
|
import 'package:lensai/features/geckoview/domain/entities/tab_state.dart';
|
||||||
import 'package:lensai/features/web_view/domain/entities/web_view_page.dart';
|
import 'package:lensai/features/geckoview/domain/providers.dart';
|
||||||
import 'package:lensai/features/web_view/domain/repositories/web_view.dart';
|
import 'package:lensai/features/geckoview/features/topics/domain/repositories/topic.dart';
|
||||||
import 'package:lensai/features/web_view/presentation/widgets/favicon.dart';
|
|
||||||
|
|
||||||
class TabActionDialog extends HookConsumerWidget {
|
class TabActionDialog extends HookConsumerWidget {
|
||||||
final WebViewPage initialTab;
|
final String tabId;
|
||||||
|
|
||||||
final void Function()? onDismiss;
|
final void Function()? onDismiss;
|
||||||
|
|
||||||
const TabActionDialog({
|
const TabActionDialog({
|
||||||
required this.initialTab,
|
required this.tabId,
|
||||||
this.onDismiss,
|
this.onDismiss,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final tabState = ref.watch(tabStateProvider(initialTab.id)) ?? initialTab;
|
final tabState =
|
||||||
|
ref.watch(tabStateProvider(tabId)) ?? TabState.$default(tabId);
|
||||||
final topics =
|
final topics =
|
||||||
ref.watch(topicRepositoryProvider.select((value) => value.valueOrNull));
|
ref.watch(topicRepositoryProvider.select((value) => value.valueOrNull));
|
||||||
final selectedTopic =
|
final selectedTopic =
|
||||||
@@ -42,13 +42,13 @@ class TabActionDialog extends HookConsumerWidget {
|
|||||||
vertical: 24.0,
|
vertical: 24.0,
|
||||||
),
|
),
|
||||||
title: ListTile(
|
title: ListTile(
|
||||||
leading: FaviconImage(
|
leading: RawImage(
|
||||||
favicon: tabState.favicon,
|
image: tabState.icon?.value,
|
||||||
url: tabState.url,
|
width: 24,
|
||||||
size: 24,
|
height: 24,
|
||||||
),
|
),
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
title: Text(tabState.title ?? 'Unknown Title'),
|
title: Text(tabState.title),
|
||||||
subtitle: Text(tabState.url.authority),
|
subtitle: Text(tabState.url.authority),
|
||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -7,11 +7,12 @@ import 'package:flutter_material_design_icons/flutter_material_design_icons.dart
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:lensai/core/routing/routes.dart';
|
import 'package:lensai/core/routing/routes.dart';
|
||||||
import 'package:lensai/features/search_browser/domain/entities/modes.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/entities/sheet.dart';
|
||||||
import 'package:lensai/features/search_browser/domain/providers.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/domain/services/create_tab.dart';
|
||||||
import 'package:lensai/features/search_browser/domain/services/session.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/app_bar_title.dart';
|
||||||
import 'package:lensai/features/search_browser/presentation/widgets/landing/content.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/shared_content_sheet.dart';
|
||||||
@@ -19,11 +20,6 @@ import 'package:lensai/features/search_browser/presentation/widgets/sheets/view_
|
|||||||
import 'package:lensai/features/search_browser/presentation/widgets/tabs_action_button.dart';
|
import 'package:lensai/features/search_browser/presentation/widgets/tabs_action_button.dart';
|
||||||
import 'package:lensai/features/settings/data/models/settings.dart';
|
import 'package:lensai/features/settings/data/models/settings.dart';
|
||||||
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
|
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
|
||||||
import 'package:lensai/features/topics/domain/repositories/tab.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';
|
|
||||||
import 'package:lensai/presentation/hooks/overlay_portal_controller.dart';
|
import 'package:lensai/presentation/hooks/overlay_portal_controller.dart';
|
||||||
import 'package:lensai/presentation/widgets/animate_gradient_shader.dart';
|
import 'package:lensai/presentation/widgets/animate_gradient_shader.dart';
|
||||||
import 'package:lensai/presentation/widgets/animated_indexed_stack.dart';
|
import 'package:lensai/presentation/widgets/animated_indexed_stack.dart';
|
||||||
@@ -41,8 +37,8 @@ class KagiScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final displayedSheet = ref.watch(bottomSheetProvider);
|
final displayedSheet = ref.watch(bottomSheetControllerProvider);
|
||||||
final displayedOverlayDialog = ref.watch(overlayDialogProvider);
|
final displayedOverlayDialog = ref.watch(overlayDialogControllerProvider);
|
||||||
|
|
||||||
final showEarlyAccessFeatures = ref.watch(
|
final showEarlyAccessFeatures = ref.watch(
|
||||||
settingsRepositoryProvider.select(
|
settingsRepositoryProvider.select(
|
||||||
@@ -75,7 +71,7 @@ class KagiScreen extends HookConsumerWidget {
|
|||||||
final activeKagiTool = useValueNotifier<KagiTool?>(null, [displayedSheet]);
|
final activeKagiTool = useValueNotifier<KagiTool?>(null, [displayedSheet]);
|
||||||
|
|
||||||
ref.listen(
|
ref.listen(
|
||||||
overlayDialogProvider,
|
overlayDialogControllerProvider,
|
||||||
(previous, next) {
|
(previous, next) {
|
||||||
if (next != null) {
|
if (next != null) {
|
||||||
overlayController.show();
|
overlayController.show();
|
||||||
@@ -109,7 +105,7 @@ class KagiScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
return (page != null)
|
return (page != null)
|
||||||
? AppBarTitle(
|
? AppBarTitle(
|
||||||
page: page,
|
tab: page,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
ref.read(overlayDialogProvider.notifier).show(
|
ref.read(overlayDialogProvider.notifier).show(
|
||||||
WebPageDialog(
|
WebPageDialog(
|
||||||
@@ -149,7 +145,8 @@ class KagiScreen extends HookConsumerWidget {
|
|||||||
ref
|
ref
|
||||||
.read(createTabStreamProvider.notifier)
|
.read(createTabStreamProvider.notifier)
|
||||||
.createTab(
|
.createTab(
|
||||||
CreateTab(preferredTool: KagiTool.search),
|
CreateTabSheet(
|
||||||
|
preferredTool: KagiTool.search),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
icon: Icon(KagiTool.search.icon),
|
icon: Icon(KagiTool.search.icon),
|
||||||
@@ -162,7 +159,8 @@ class KagiScreen extends HookConsumerWidget {
|
|||||||
ref
|
ref
|
||||||
.read(createTabStreamProvider.notifier)
|
.read(createTabStreamProvider.notifier)
|
||||||
.createTab(
|
.createTab(
|
||||||
CreateTab(preferredTool: KagiTool.summarizer),
|
CreateTabSheet(
|
||||||
|
preferredTool: KagiTool.summarizer),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
icon: Icon(KagiTool.summarizer.icon),
|
icon: Icon(KagiTool.summarizer.icon),
|
||||||
@@ -176,7 +174,7 @@ class KagiScreen extends HookConsumerWidget {
|
|||||||
ref
|
ref
|
||||||
.read(createTabStreamProvider.notifier)
|
.read(createTabStreamProvider.notifier)
|
||||||
.createTab(
|
.createTab(
|
||||||
CreateTab(
|
CreateTabSheet(
|
||||||
preferredTool: KagiTool.assistant,
|
preferredTool: KagiTool.assistant,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -278,7 +276,7 @@ class KagiScreen extends HookConsumerWidget {
|
|||||||
if (quickAction != null)
|
if (quickAction != null)
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
var tab = CreateTab(
|
var tab = CreateTabSheet(
|
||||||
preferredTool: quickAction,
|
preferredTool: quickAction,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -303,7 +301,7 @@ class KagiScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tab = CreateTab(
|
tab = CreateTabSheet(
|
||||||
preferredTool: quickAction,
|
preferredTool: quickAction,
|
||||||
content: await completer.future,
|
content: await completer.future,
|
||||||
);
|
);
|
||||||
@@ -320,12 +318,12 @@ class KagiScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
TabsActionButton(
|
TabsActionButton(
|
||||||
isActive: displayedSheet is ViewTabs,
|
isActive: displayedSheet is ViewTabsSheet,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (displayedSheet case ViewTabs()) {
|
if (displayedSheet case ViewTabsSheet()) {
|
||||||
ref.read(bottomSheetProvider.notifier).dismiss();
|
ref.read(bottomSheetProvider.notifier).dismiss();
|
||||||
} else {
|
} else {
|
||||||
ref.read(bottomSheetProvider.notifier).show(ViewTabs());
|
ref.read(bottomSheetProvider.notifier).show(ViewTabsSheet());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -387,7 +385,7 @@ class KagiScreen extends HookConsumerWidget {
|
|||||||
MenuItemButton(
|
MenuItemButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
ref.read(createTabStreamProvider.notifier).createTab(
|
ref.read(createTabStreamProvider.notifier).createTab(
|
||||||
CreateTab(preferredTool: KagiTool.assistant),
|
CreateTabSheet(preferredTool: KagiTool.assistant),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
leadingIcon: Icon(KagiTool.assistant.icon),
|
leadingIcon: Icon(KagiTool.assistant.icon),
|
||||||
@@ -396,7 +394,7 @@ class KagiScreen extends HookConsumerWidget {
|
|||||||
MenuItemButton(
|
MenuItemButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
ref.read(createTabStreamProvider.notifier).createTab(
|
ref.read(createTabStreamProvider.notifier).createTab(
|
||||||
CreateTab(preferredTool: KagiTool.summarizer),
|
CreateTabSheet(preferredTool: KagiTool.summarizer),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
leadingIcon: Icon(KagiTool.summarizer.icon),
|
leadingIcon: Icon(KagiTool.summarizer.icon),
|
||||||
@@ -405,7 +403,7 @@ class KagiScreen extends HookConsumerWidget {
|
|||||||
MenuItemButton(
|
MenuItemButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
ref.read(createTabStreamProvider.notifier).createTab(
|
ref.read(createTabStreamProvider.notifier).createTab(
|
||||||
CreateTab(preferredTool: KagiTool.search),
|
CreateTabSheet(preferredTool: KagiTool.search),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
leadingIcon: Icon(KagiTool.search.icon),
|
leadingIcon: Icon(KagiTool.search.icon),
|
||||||
@@ -649,7 +647,7 @@ class KagiScreen extends HookConsumerWidget {
|
|||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
child: switch (displayedSheet) {
|
child: switch (displayedSheet) {
|
||||||
ViewTabs() => DraggableScrollableSheet(
|
ViewTabsSheet() => DraggableScrollableSheet(
|
||||||
key: ValueKey(displayedSheet),
|
key: ValueKey(displayedSheet),
|
||||||
expand: false,
|
expand: false,
|
||||||
minChildSize: 0.1,
|
minChildSize: 0.1,
|
||||||
@@ -669,7 +667,7 @@ class KagiScreen extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
final CreateTab parameter => DraggableScrollableSheet(
|
final CreateTabSheet parameter => DraggableScrollableSheet(
|
||||||
key: ValueKey(displayedSheet),
|
key: ValueKey(displayedSheet),
|
||||||
expand: false,
|
expand: false,
|
||||||
initialChildSize: 0.8,
|
initialChildSize: 0.8,
|
||||||
|
|||||||
@@ -1,24 +1,23 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||||
import 'package:lensai/features/web_view/domain/entities/web_view_page.dart';
|
import 'package:lensai/features/geckoview/domain/entities/tab_state.dart';
|
||||||
import 'package:lensai/features/web_view/presentation/widgets/favicon.dart';
|
|
||||||
import 'package:text_scroll/text_scroll.dart';
|
import 'package:text_scroll/text_scroll.dart';
|
||||||
|
|
||||||
class AppBarTitle extends StatelessWidget {
|
class AppBarTitle extends StatelessWidget {
|
||||||
final WebViewPage page;
|
final TabState tab;
|
||||||
|
|
||||||
final void Function()? onTap;
|
final void Function()? onTap;
|
||||||
|
|
||||||
const AppBarTitle({required this.page, this.onTap, super.key});
|
const AppBarTitle({required this.tab, this.onTap, super.key});
|
||||||
|
|
||||||
Icon _securityStatusIcon(BuildContext context, WebViewPage page) {
|
Icon _securityStatusIcon(BuildContext context) {
|
||||||
if (page.url.isScheme('http')) {
|
if (tab.url.isScheme('http')) {
|
||||||
return Icon(
|
return Icon(
|
||||||
MdiIcons.lockOff,
|
MdiIcons.lockOff,
|
||||||
color: Theme.of(context).colorScheme.error,
|
color: Theme.of(context).colorScheme.error,
|
||||||
size: 14,
|
size: 14,
|
||||||
);
|
);
|
||||||
} else if (page.sslError != null) {
|
} else if (!tab.securityInfoState.secure) {
|
||||||
return Icon(
|
return Icon(
|
||||||
MdiIcons.lockAlert,
|
MdiIcons.lockAlert,
|
||||||
color: Theme.of(context).colorScheme.errorContainer,
|
color: Theme.of(context).colorScheme.errorContainer,
|
||||||
@@ -39,9 +38,10 @@ class AppBarTitle extends StatelessWidget {
|
|||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
FaviconImage(
|
RawImage(
|
||||||
favicon: page.favicon,
|
image: tab.icon?.value,
|
||||||
url: page.url,
|
height: 16,
|
||||||
|
width: 16,
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 8,
|
width: 8,
|
||||||
@@ -52,8 +52,8 @@ class AppBarTitle extends StatelessWidget {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
TextScroll(
|
TextScroll(
|
||||||
key: ValueKey(page.title),
|
key: ValueKey(tab.title),
|
||||||
page.title ?? 'New Tab',
|
tab.title,
|
||||||
style: theme.textTheme.bodyLarge
|
style: theme.textTheme.bodyLarge
|
||||||
?.copyWith(color: theme.colorScheme.onSurface),
|
?.copyWith(color: theme.colorScheme.onSurface),
|
||||||
// mode: TextScrollMode.bouncing,
|
// mode: TextScrollMode.bouncing,
|
||||||
@@ -68,13 +68,13 @@ class AppBarTitle extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
_securityStatusIcon(context, page),
|
_securityStatusIcon(context),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 4,
|
width: 4,
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
page.url.authority,
|
tab.url.authority,
|
||||||
style: theme.textTheme.bodyMedium
|
style: theme.textTheme.bodyMedium
|
||||||
?.copyWith(color: theme.colorScheme.onSurface),
|
?.copyWith(color: theme.colorScheme.onSurface),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:lensai/domain/services/app_initialization.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/error_container.dart';
|
||||||
import 'package:lensai/features/search_browser/presentation/widgets/landing/action.dart';
|
import 'package:lensai/features/search_browser/presentation/widgets/landing/action.dart';
|
||||||
import 'package:lensai/features/web_view/presentation/controllers/switch_new_tab.dart';
|
|
||||||
import 'package:lensai/presentation/hooks/cached_future.dart';
|
import 'package:lensai/presentation/hooks/cached_future.dart';
|
||||||
|
|
||||||
class LandingContent extends HookConsumerWidget {
|
class LandingContent extends HookConsumerWidget {
|
||||||
|
|||||||
+2
-2
@@ -2,7 +2,7 @@ import 'package:expandable_page_view/expandable_page_view.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:lensai/features/search_browser/domain/entities/modes.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/entities/sheet.dart';
|
||||||
import 'package:lensai/features/search_browser/presentation/widgets/tabs/assistant_tab.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/search_tab.dart';
|
||||||
@@ -15,7 +15,7 @@ import 'package:lensai/presentation/hooks/sync_page_tab.dart';
|
|||||||
typedef OnSubmitUri = void Function(Uri url);
|
typedef OnSubmitUri = void Function(Uri url);
|
||||||
|
|
||||||
class SharedContentSheet extends HookConsumerWidget {
|
class SharedContentSheet extends HookConsumerWidget {
|
||||||
final CreateTab parameter;
|
final CreateTabSheet parameter;
|
||||||
final OnSubmitUri onSubmit;
|
final OnSubmitUri onSubmit;
|
||||||
final void Function(KagiTool tool)? onActiveToolChanged;
|
final void Function(KagiTool tool)? onActiveToolChanged;
|
||||||
|
|
||||||
|
|||||||
@@ -4,14 +4,10 @@ import 'package:fading_scroll/fading_scroll.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:lensai/features/search_browser/domain/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/geckoview/features/topics/presentation/widgets/topic_chips.dart';
|
||||||
import 'package:lensai/features/search_browser/presentation/dialogs/tab_action.dart';
|
import 'package:lensai/features/search_browser/presentation/dialogs/tab_action.dart';
|
||||||
import 'package:lensai/features/topics/domain/providers.dart';
|
|
||||||
import 'package:lensai/features/topics/domain/repositories/tab.dart';
|
|
||||||
import 'package:lensai/features/topics/presentation/widgets/topic_chips.dart';
|
|
||||||
import 'package:lensai/features/web_view/domain/repositories/web_view.dart';
|
|
||||||
import 'package:lensai/features/web_view/presentation/controllers/switch_new_tab.dart';
|
|
||||||
import 'package:lensai/features/web_view/presentation/widgets/web_view_tab.dart';
|
|
||||||
|
|
||||||
class _SliverHeaderDelagate extends SliverPersistentHeaderDelegate {
|
class _SliverHeaderDelagate extends SliverPersistentHeaderDelegate {
|
||||||
static const _headerSize = 104.0;
|
static const _headerSize = 104.0;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:lensai/features/search_browser/domain/entities/modes.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/domain/providers.dart';
|
||||||
import 'package:lensai/features/search_browser/presentation/widgets/sheets/shared_content_sheet.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/presentation/widgets/speech_to_text_button.dart';
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ 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/bang_icon.dart';
|
||||||
import 'package:lensai/features/bangs/presentation/widgets/search_field.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/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/search_browser/presentation/widgets/sheets/shared_content_sheet.dart';
|
||||||
import 'package:lensai/features/share_intent/domain/entities/shared_content.dart';
|
import 'package:lensai/features/share_intent/domain/entities/shared_content.dart';
|
||||||
import 'package:lensai/presentation/widgets/selectable_chips.dart';
|
import 'package:lensai/presentation/widgets/selectable_chips.dart';
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||||
import 'package:flutter_pdf_text/flutter_pdf_text.dart';
|
import 'package:flutter_pdf_text/flutter_pdf_text.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:lensai/features/search_browser/domain/entities/modes.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/presentation/widgets/sheets/shared_content_sheet.dart';
|
||||||
import 'package:lensai/features/search_browser/utils/url_builder.dart'
|
import 'package:lensai/features/search_browser/utils/url_builder.dart'
|
||||||
as uri_builder;
|
as uri_builder;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:lensai/features/web_view/domain/providers.dart';
|
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
|
||||||
|
|
||||||
class TabsActionButton extends HookConsumerWidget {
|
class TabsActionButton extends HookConsumerWidget {
|
||||||
final bool isActive;
|
final bool isActive;
|
||||||
@@ -17,7 +17,7 @@ class TabsActionButton extends HookConsumerWidget {
|
|||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
final tabCount =
|
final tabCount =
|
||||||
ref.watch(activeTabsProvider.select((tabs) => tabs.length));
|
ref.watch(tabRepositoryProvider.select((tabs) => tabs.length));
|
||||||
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import 'package:lensai/features/search_browser/domain/entities/modes.dart';
|
import 'package:lensai/features/kagi/data/entities/modes.dart';
|
||||||
import 'package:lensai/features/share_intent/domain/entities/shared_content.dart';
|
import 'package:lensai/features/share_intent/domain/entities/shared_content.dart';
|
||||||
|
|
||||||
final _baseUrl = Uri.https('kagi.com');
|
final _baseUrl = Uri.https('kagi.com');
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import 'package:copy_with_extension/copy_with_extension.dart';
|
|||||||
import 'package:fast_equatable/fast_equatable.dart';
|
import 'package:fast_equatable/fast_equatable.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:lensai/features/content_block/data/models/host.dart';
|
import 'package:lensai/features/content_block/data/models/host.dart';
|
||||||
import 'package:lensai/features/search_browser/domain/entities/modes.dart';
|
import 'package:lensai/features/kagi/data/entities/modes.dart';
|
||||||
|
|
||||||
part 'settings.g.dart';
|
part 'settings.g.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import 'package:lensai/features/bangs/data/models/bang.dart';
|
|||||||
import 'package:lensai/features/bangs/domain/providers.dart';
|
import 'package:lensai/features/bangs/domain/providers.dart';
|
||||||
import 'package:lensai/features/bangs/domain/repositories/data.dart';
|
import 'package:lensai/features/bangs/domain/repositories/data.dart';
|
||||||
import 'package:lensai/features/content_block/data/models/host.dart';
|
import 'package:lensai/features/content_block/data/models/host.dart';
|
||||||
import 'package:lensai/features/search_browser/domain/entities/modes.dart';
|
import 'package:lensai/features/kagi/data/entities/modes.dart';
|
||||||
import 'package:lensai/features/settings/data/models/settings.dart';
|
import 'package:lensai/features/settings/data/models/settings.dart';
|
||||||
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
|
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
|
||||||
import 'package:lensai/features/settings/presentation/controllers/save_settings.dart';
|
import 'package:lensai/features/settings/presentation/controllers/save_settings.dart';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:lensai/features/content_block/data/models/host.dart';
|
import 'package:lensai/features/content_block/data/models/host.dart';
|
||||||
import 'package:lensai/features/search_browser/domain/entities/modes.dart';
|
import 'package:lensai/features/kagi/data/entities/modes.dart';
|
||||||
|
|
||||||
Set<HostSource>? parseHostSources(List<String>? input) => input
|
Set<HostSource>? parseHostSources(List<String>? input) => input
|
||||||
?.map(
|
?.map(
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import 'package:flutter_pdf_text/flutter_pdf_text.dart';
|
|||||||
import 'package:flutter_sharing_intent/flutter_sharing_intent.dart';
|
import 'package:flutter_sharing_intent/flutter_sharing_intent.dart';
|
||||||
import 'package:flutter_sharing_intent/model/sharing_file.dart';
|
import 'package:flutter_sharing_intent/model/sharing_file.dart';
|
||||||
import 'package:lensai/core/logger.dart';
|
import 'package:lensai/core/logger.dart';
|
||||||
import 'package:lensai/domain/entities/received_parameter.dart';
|
import 'package:lensai/data/models/received_parameter.dart';
|
||||||
import 'package:lensai/features/search_browser/domain/entities/modes.dart';
|
import 'package:lensai/features/kagi/data/entities/modes.dart';
|
||||||
import 'package:mime/mime.dart' as mime;
|
import 'package:mime/mime.dart' as mime;
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:rxdart/rxdart.dart';
|
import 'package:rxdart/rxdart.dart';
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:lensai/features/topics/data/database/database.dart';
|
|
||||||
import 'package:lensai/features/web_view/domain/entities/abstract/tab.dart';
|
|
||||||
|
|
||||||
part 'tab.g.dart';
|
|
||||||
|
|
||||||
@DriftAccessor()
|
|
||||||
class TabDao extends DatabaseAccessor<TabDatabase> with _$TabDaoMixin {
|
|
||||||
TabDao(super.db);
|
|
||||||
|
|
||||||
Stream<List<TabData>> watchTabs() {
|
|
||||||
return (db.tab.select()..orderBy([(u) => OrderingTerm.asc(u.id)])).watch();
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream<TabData?> watchTab(String tabId) {
|
|
||||||
return (db.tab.select()..where((t) => t.id.equals(tabId)))
|
|
||||||
.watchSingleOrNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream<bool> watchTabExisiting(String tabId) {
|
|
||||||
final existsStatement =
|
|
||||||
existsQuery(db.tab.select()..where((t) => t.id.equals(tabId)));
|
|
||||||
|
|
||||||
return selectExpressions([existsStatement])
|
|
||||||
.map((row) => row.read(existsStatement)!)
|
|
||||||
.watchSingle();
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream<List<String>> watchTopicTabs(String? topicId) {
|
|
||||||
final query = (selectOnly(db.tab)
|
|
||||||
..addColumns([db.tab.id])
|
|
||||||
..where(
|
|
||||||
(topicId == null)
|
|
||||||
? db.tab.topicId.isNull()
|
|
||||||
: db.tab.topicId.equals(topicId),
|
|
||||||
)
|
|
||||||
..orderBy([OrderingTerm.asc(db.tab.id)]));
|
|
||||||
|
|
||||||
return query.map((row) => row.read(db.tab.id)!).watch();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> upsertTab(ITab tab, DateTime timestamp) {
|
|
||||||
return db.tab.insertOne(
|
|
||||||
TabCompanion.insert(
|
|
||||||
id: tab.id,
|
|
||||||
timestamp: timestamp,
|
|
||||||
url: tab.url,
|
|
||||||
topicId: Value(tab.topicId),
|
|
||||||
title: Value(tab.title),
|
|
||||||
screenshot: Value(tab.screenshot),
|
|
||||||
),
|
|
||||||
mode: InsertMode.insertOrReplace,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> updateTab(
|
|
||||||
String id, {
|
|
||||||
required DateTime timestamp,
|
|
||||||
Value<Uri> url = const Value.absent(),
|
|
||||||
Value<String?> title = const Value.absent(),
|
|
||||||
Value<String?> topicId = const Value.absent(),
|
|
||||||
Value<Uint8List?> screenshot = const Value.absent(),
|
|
||||||
}) {
|
|
||||||
final statement = db.tab.update()..where((t) => t.id.equals(id));
|
|
||||||
|
|
||||||
return statement.write(
|
|
||||||
TabCompanion(
|
|
||||||
timestamp: Value(timestamp),
|
|
||||||
url: url,
|
|
||||||
title: title,
|
|
||||||
topicId: topicId,
|
|
||||||
screenshot: screenshot,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> deleteTab(String id) {
|
|
||||||
return db.tab.deleteOne(TabCompanion.custom(id: Variable(id)));
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> deleteTopicTabs(String? topicId) {
|
|
||||||
return (db.tab.delete()
|
|
||||||
..where(
|
|
||||||
(t) => (topicId == null)
|
|
||||||
? t.topicId.isNull()
|
|
||||||
: t.topicId.equals(topicId),
|
|
||||||
))
|
|
||||||
.go();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user