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
|
||||
FLUTTER_TARGET=lib/main.dart
|
||||
FLUTTER_BUILD_DIR=build
|
||||
FLUTTER_BUILD_NAME=0.6.2
|
||||
FLUTTER_BUILD_NUMBER=14
|
||||
FLUTTER_BUILD_NAME=0.7.0
|
||||
FLUTTER_BUILD_NUMBER=15
|
||||
EXCLUDED_ARCHS[sdk=iphonesimulator*]=i386
|
||||
EXCLUDED_ARCHS[sdk=iphoneos*]=armv7
|
||||
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 "FLUTTER_TARGET=lib/main.dart"
|
||||
export "FLUTTER_BUILD_DIR=build"
|
||||
export "FLUTTER_BUILD_NAME=0.6.2"
|
||||
export "FLUTTER_BUILD_NUMBER=14"
|
||||
export "FLUTTER_BUILD_NAME=0.7.0"
|
||||
export "FLUTTER_BUILD_NUMBER=15"
|
||||
export "DART_OBFUSCATION=false"
|
||||
export "TRACK_WIDGET_CREATION=true"
|
||||
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/search_browser/presentation/screens/browser.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';
|
||||
|
||||
|
||||
@@ -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/settings/data/models/settings.dart';
|
||||
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
|
||||
import 'package:lensai/features/topics/data/providers.dart';
|
||||
import 'package:lensai/features/web_view/domain/providers.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/data/providers.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'app_initialization.g.dart';
|
||||
@@ -96,8 +95,6 @@ class AppInitializationService extends _$AppInitializationService {
|
||||
final settings = await ref.read(settingsRepositoryProvider.future);
|
||||
final errors = <ErrorMessage>[];
|
||||
|
||||
unawaited(ref.read(readerabilityScriptProvider.future));
|
||||
|
||||
await _initPackageInfo();
|
||||
|
||||
final bangSyncResults = await _initBangs();
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'app_initialization.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$appInitializationServiceHash() =>
|
||||
r'dadcf205d32efa61eb2a0ff1c9b3d49658b76a56';
|
||||
r'eee9c8eadc793fb435af1032ad2099dfd381a034';
|
||||
|
||||
/// See also [AppInitializationService].
|
||||
@ProviderFor(AppInitializationService)
|
||||
|
||||
@@ -1,57 +1,169 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:exceptions/exceptions.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/parser.dart' as html_parser;
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:lensai/core/http_error_handler.dart';
|
||||
import 'package:lensai/domain/entities/web_page_info.dart';
|
||||
import 'package:lensai/extensions/web_uri_favicon.dart';
|
||||
import 'package:lensai/features/web_view/utils/favicon_helper.dart';
|
||||
import 'package:lensai/data/models/web_page_info.dart';
|
||||
import 'package:lensai/features/geckoview/domain/entities/browser_icon.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:universal_io/io.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)
|
||||
class GenericWebsiteService extends _$GenericWebsiteService {
|
||||
final GeckoIconService _iconsService;
|
||||
|
||||
late http.Client _client;
|
||||
|
||||
final Map<String, bool> _httpsCache;
|
||||
|
||||
GenericWebsiteService() : _httpsCache = {};
|
||||
GenericWebsiteService()
|
||||
: _httpsCache = {},
|
||||
_iconsService = GeckoIconService();
|
||||
|
||||
@override
|
||||
void build() {
|
||||
_client = http.Client();
|
||||
}
|
||||
|
||||
static Iterable<Favicon> _extractFavicons(Uri url, Document document) sync* {
|
||||
final links = document.querySelectorAll(
|
||||
'link[rel="icon"], link[rel="shortcut icon"], link[rel="apple-touch-icon"]',
|
||||
static Map<String, dynamic> _serializeResource(Resource resource) {
|
||||
return {
|
||||
'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) {
|
||||
final href = link.attributes['href'];
|
||||
if (href != null) {
|
||||
// Attempt to parse height and width if available
|
||||
int? height;
|
||||
int? width;
|
||||
if (link.attributes['sizes'] case final String sizes) {
|
||||
final dimensions = sizes.split('x');
|
||||
if (dimensions.length == 2) {
|
||||
height = int.tryParse(dimensions[0]);
|
||||
width = int.tryParse(dimensions[1]);
|
||||
}
|
||||
static List<Resource> _extractIcons(Document document) {
|
||||
final List<Resource> icons = [];
|
||||
|
||||
void collectLinkIcons(String rel) {
|
||||
final links = document.querySelectorAll('link[rel="$rel"]');
|
||||
for (final link in links) {
|
||||
final href = link.attributes['href'];
|
||||
final type = _typeMap[rel];
|
||||
final mimeType = link.attributes['type'];
|
||||
if (href != null && type != null) {
|
||||
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 {
|
||||
@@ -59,50 +171,47 @@ class GenericWebsiteService extends _$GenericWebsiteService {
|
||||
() async {
|
||||
final response =
|
||||
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 url = Uri.parse(args[1]);
|
||||
|
||||
final title = document.querySelector('title')?.text;
|
||||
final favicon = choseFavicon(_extractFavicons(url, document)) ??
|
||||
//In case the icon can not get extracted, we use the resolver
|
||||
//of duckduckgo
|
||||
Favicon(
|
||||
url: WebUri.uri(url.genericFavicon()),
|
||||
);
|
||||
final resources = _extractIcons(document);
|
||||
|
||||
return WebPageInfo(url: url, title: title, favicon: favicon)
|
||||
.toJson();
|
||||
return {
|
||||
'title': title,
|
||||
'resources': resources.map(_serializeResource).toList(),
|
||||
};
|
||||
},
|
||||
[response.body, url.toString()],
|
||||
).then(WebPageInfo.fromJson);
|
||||
[response.body],
|
||||
);
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
Future<Result<Uint8List?>> getFaviconBytes(Uri url) {
|
||||
return getInfo(url).then(
|
||||
(result) => result.flatMapAsync(
|
||||
(info) async {
|
||||
if (info.favicon != null) {
|
||||
return _client
|
||||
.get(info.favicon!.url)
|
||||
.timeout(const Duration(seconds: 10))
|
||||
.then((response) {
|
||||
if (response.statusCode == 200) {
|
||||
return response.bodyBytes;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
exceptionHandler: handleHttpError,
|
||||
),
|
||||
Future<BrowserIcon> getUrlFavicon({
|
||||
required Uri url,
|
||||
List<Resource> resources = const [],
|
||||
}) async {
|
||||
final result = await _iconsService.loadIcon(url: url, resources: resources);
|
||||
return BrowserIcon.fromBytes(
|
||||
result.image,
|
||||
dominantColor: (result.color != null) ? Color(result.color!) : null,
|
||||
source: result.source,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'generic_website.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$genericWebsiteServiceHash() =>
|
||||
r'517304ca95e1ef8377d4c1a3bd06fef9c68c92cd';
|
||||
r'0b1d953fe1b30b9dd0396df34e6176424cdaf941';
|
||||
|
||||
/// See also [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:lensai/domain/entities/received_parameter.dart';
|
||||
import 'package:lensai/data/models/received_parameter.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||
import 'package:lensai/features/bangs/data/database/database.dart';
|
||||
import 'package:lensai/features/bangs/data/models/bang.dart';
|
||||
import 'package:lensai/features/bangs/data/models/bang_data.dart';
|
||||
@@ -94,14 +97,17 @@ class BangDao extends DatabaseAccessor<BangDatabase> with _$BangDaoMixin {
|
||||
return db.bangQuery(query: db.buildQuery(searchString));
|
||||
}
|
||||
|
||||
Future<int> upsertBangIcon(String trigger, Uint8List iconData) {
|
||||
return db.bangIcon.insertOne(
|
||||
BangIconCompanion.insert(
|
||||
trigger: trigger,
|
||||
iconData: iconData,
|
||||
fetchDate: DateTime.now(),
|
||||
),
|
||||
mode: InsertMode.insertOrReplace,
|
||||
);
|
||||
}
|
||||
// Future<int> upsertBangIcon(String trigger, BrowserIcon icon) async {
|
||||
// //TODO: swithc to WEBP in future, requires 3rd party
|
||||
// final compressed = await icon.image.toByteData(format: ImageByteFormat.png);
|
||||
|
||||
// return db.bangIcon.insertOne(
|
||||
// BangIconCompanion.insert(
|
||||
// trigger: trigger,
|
||||
// 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']),
|
||||
lastUsed: attachedDatabase.typeMapping
|
||||
.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')),
|
||||
frequency: row.readNullable<int>('frequency'),
|
||||
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:drift/drift.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';
|
||||
|
||||
@@ -9,7 +9,7 @@ class BangData extends Bang {
|
||||
final int frequency;
|
||||
final DateTime? lastUsed;
|
||||
|
||||
final Uint8List? iconData;
|
||||
final BrowserIcon? icon;
|
||||
|
||||
BangData({
|
||||
required super.websiteName,
|
||||
@@ -21,7 +21,7 @@ class BangData extends Bang {
|
||||
super.format,
|
||||
int? frequency,
|
||||
this.lastUsed,
|
||||
this.iconData,
|
||||
this.icon,
|
||||
}) : frequency = frequency ?? 0;
|
||||
|
||||
@override
|
||||
@@ -32,6 +32,6 @@ class BangData extends Bang {
|
||||
...super.hashParameters,
|
||||
frequency,
|
||||
lastUsed,
|
||||
iconData,
|
||||
icon,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ abstract class _$BangDataCWProxy {
|
||||
|
||||
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.
|
||||
///
|
||||
@@ -43,7 +43,7 @@ abstract class _$BangDataCWProxy {
|
||||
Set<BangFormat>? format,
|
||||
int? frequency,
|
||||
DateTime? lastUsed,
|
||||
Uint8List? iconData,
|
||||
BrowserIcon? icon,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ class _$BangDataCWProxyImpl implements _$BangDataCWProxy {
|
||||
BangData lastUsed(DateTime? lastUsed) => this(lastUsed: lastUsed);
|
||||
|
||||
@override
|
||||
BangData iconData(Uint8List? iconData) => this(iconData: iconData);
|
||||
BangData icon(BrowserIcon? icon) => this(icon: icon);
|
||||
|
||||
@override
|
||||
|
||||
@@ -101,7 +101,7 @@ class _$BangDataCWProxyImpl implements _$BangDataCWProxy {
|
||||
Object? format = const $CopyWithPlaceholder(),
|
||||
Object? frequency = const $CopyWithPlaceholder(),
|
||||
Object? lastUsed = const $CopyWithPlaceholder(),
|
||||
Object? iconData = const $CopyWithPlaceholder(),
|
||||
Object? icon = const $CopyWithPlaceholder(),
|
||||
}) {
|
||||
return BangData(
|
||||
websiteName:
|
||||
@@ -142,10 +142,10 @@ class _$BangDataCWProxyImpl implements _$BangDataCWProxy {
|
||||
? _value.lastUsed
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: lastUsed as DateTime?,
|
||||
iconData: iconData == const $CopyWithPlaceholder()
|
||||
? _value.iconData
|
||||
icon: icon == const $CopyWithPlaceholder()
|
||||
? _value.icon
|
||||
// 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_data.dart';
|
||||
import 'package:lensai/features/bangs/data/providers.dart';
|
||||
import 'package:lensai/utils/image_helper.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'data.g.dart';
|
||||
@@ -69,24 +68,16 @@ class BangDataRepository extends _$BangDataRepository {
|
||||
}
|
||||
|
||||
Future<Result<BangData>> ensureIconAvailable(BangData bang) async {
|
||||
if (bang.iconData != null) {
|
||||
if (bang.icon != null) {
|
||||
return Result.success(bang);
|
||||
}
|
||||
|
||||
final result = await ref
|
||||
final icon = await ref
|
||||
.read(genericWebsiteServiceProvider.notifier)
|
||||
.getFaviconBytes(Uri.parse(bang.getUrl('').origin));
|
||||
.getUrlFavicon(url: Uri.parse(bang.getUrl('').origin));
|
||||
|
||||
return result.flatMapAsync(
|
||||
(faviconBytes) async {
|
||||
if (faviconBytes != null && await isImageValid(faviconBytes)) {
|
||||
await _db.bangDao.upsertBangIcon(bang.trigger, faviconBytes);
|
||||
return bang.copyWith.iconData(faviconBytes);
|
||||
}
|
||||
|
||||
return bang;
|
||||
},
|
||||
);
|
||||
// await _db.bangDao.upsertBangIcon(bang.trigger, icon);
|
||||
return Result.success(bang.copyWith.icon(icon));
|
||||
}
|
||||
|
||||
Stream<double> watchIconCacheSize() {
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'data.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$bangDataRepositoryHash() =>
|
||||
r'ef06babcaa07361e37f5cc62a268eb10aed0fc82';
|
||||
r'abacc6db3a61958cae67a91cbfd312ae87c641b3';
|
||||
|
||||
/// See also [BangDataRepository].
|
||||
@ProviderFor(BangDataRepository)
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/core/routing/routes.dart';
|
||||
import 'package:lensai/features/bangs/domain/providers.dart';
|
||||
import 'package:lensai/features/bangs/presentation/widgets/bang_details.dart';
|
||||
import 'package:lensai/features/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/providers.dart';
|
||||
import 'package:lensai/presentation/widgets/failure_widget.dart';
|
||||
@@ -49,9 +49,9 @@ class BangListScreen extends HookConsumerWidget {
|
||||
.read(selectedBangTriggerProvider().notifier)
|
||||
.setTrigger(bang.trigger);
|
||||
|
||||
if (ref.read(bottomSheetProvider) is! CreateTab) {
|
||||
if (ref.read(bottomSheetProvider) is! CreateTabSheet) {
|
||||
ref.read(bottomSheetProvider.notifier).show(
|
||||
CreateTab(
|
||||
CreateTabSheet(
|
||||
preferredTool: KagiTool.search,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -8,7 +8,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/core/routing/routes.dart';
|
||||
import 'package:lensai/features/bangs/domain/repositories/search.dart';
|
||||
import 'package:lensai/features/bangs/presentation/widgets/bang_details.dart';
|
||||
import 'package:lensai/features/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/providers.dart';
|
||||
import 'package:lensai/features/settings/data/models/settings.dart';
|
||||
@@ -77,9 +77,9 @@ class BangSearchScreen extends HookConsumerWidget {
|
||||
.read(selectedBangTriggerProvider().notifier)
|
||||
.setTrigger(bang.trigger);
|
||||
|
||||
if (ref.read(bottomSheetProvider) is! CreateTab) {
|
||||
if (ref.read(bottomSheetProvider) is! CreateTabSheet) {
|
||||
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/settings/data/models/settings.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;
|
||||
|
||||
class BangDetails extends HookConsumerWidget {
|
||||
|
||||
@@ -20,18 +20,12 @@ class BangIcon extends HookConsumerWidget {
|
||||
enabled: bangAsync.isLoading,
|
||||
child: SizedBox.square(
|
||||
dimension: iconSize,
|
||||
child: (bang.iconData != null)
|
||||
? Image.memory(
|
||||
bang.iconData!,
|
||||
child: (bang.icon != null)
|
||||
? RawImage(
|
||||
image: bang.icon?.image.value,
|
||||
height: iconSize,
|
||||
width: iconSize,
|
||||
fit: BoxFit.fill,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
return Icon(
|
||||
MdiIcons.web,
|
||||
size: iconSize,
|
||||
);
|
||||
},
|
||||
)
|
||||
: Icon(
|
||||
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/search_field.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';
|
||||
|
||||
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/settings/data/models/settings.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/utils/ui_helper.dart' as ui_helper;
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
@@ -51,7 +50,7 @@ class ChatArchiveDetailScreen extends HookConsumerWidget {
|
||||
if (chatAsync.valueOrNull != null) {
|
||||
await Clipboard.setData(
|
||||
ClipboardData(
|
||||
text: await Future.microtask(
|
||||
text: await Future(
|
||||
() => markdownToText(chatAsync.valueOrNull!),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'package:fading_scroll/fading_scroll.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/core/extension/date_time.dart';
|
||||
import 'package:lensai/extensions/date_time.dart';
|
||||
import 'package:lensai/core/routing/routes.dart';
|
||||
import 'package:lensai/features/chat_archive/domain/repositories/archive.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:go_router/go_router.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/features/chat_archive/domain/entities/chat_entity.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';
|
||||
|
||||
// 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:lensai/core/uuid.dart';
|
||||
import 'package:lensai/features/topics/data/database/database.dart';
|
||||
import 'package:lensai/features/topics/data/models/topic_data.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/data/database/database.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/data/models/topic_data.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/topics/data/database/drift/converters/uri.dart';
|
||||
import 'package:lensai/features/topics/data/models/topic_data.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';
|
||||
|
||||
CREATE TABLE topic (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
@@ -8,13 +7,10 @@ CREATE TABLE topic (
|
||||
color INTEGER NOT NULL MAPPED BY `const ColorConverter()`
|
||||
) WITH TopicData;
|
||||
|
||||
CREATE TABLE tab (
|
||||
CREATE TABLE tab_link (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
topic_id TEXT REFERENCES topic (id) ON DELETE CASCADE,
|
||||
timestamp DATETIME NOT NULL,
|
||||
url TEXT NOT NULL MAPPED BY `const UriConverter()`,
|
||||
title TEXT,
|
||||
screenshot BLOB
|
||||
timestamp DATETIME NOT NULL
|
||||
);
|
||||
|
||||
topicsWithCount WITH TopicDataWithCount:
|
||||
@@ -27,7 +23,7 @@ topicsWithCount WITH TopicDataWithCount:
|
||||
topic_id,
|
||||
COUNT(*) AS tab_count,
|
||||
MAX(timestamp) AS last_updated
|
||||
FROM tab
|
||||
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;
|
||||
+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
|
||||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
Tab(this.attachedDatabase, [this._alias]);
|
||||
TabLink(this.attachedDatabase, [this._alias]);
|
||||
late final GeneratedColumn<String> id = GeneratedColumn<String>(
|
||||
'id', aliasedName, false,
|
||||
type: DriftSqlType.string,
|
||||
@@ -151,75 +151,42 @@ class Tab extends Table with TableInfo<Tab, TabData> {
|
||||
type: DriftSqlType.dateTime,
|
||||
requiredDuringInsert: true,
|
||||
$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
|
||||
List<GeneratedColumn> get $columns =>
|
||||
[id, topicId, timestamp, url, title, screenshot];
|
||||
List<GeneratedColumn> get $columns => [id, topicId, timestamp];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@override
|
||||
String get actualTableName => $name;
|
||||
static const String $name = 'tab';
|
||||
static const String $name = 'tab_link';
|
||||
@override
|
||||
Set<GeneratedColumn> get $primaryKey => {id};
|
||||
@override
|
||||
TabData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||
TabLinkData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||
return TabData(
|
||||
return TabLinkData(
|
||||
id: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}id'])!,
|
||||
topicId: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}topic_id']),
|
||||
timestamp: attachedDatabase.typeMapping
|
||||
.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
|
||||
Tab createAlias(String alias) {
|
||||
return Tab(attachedDatabase, alias);
|
||||
TabLink createAlias(String alias) {
|
||||
return TabLink(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
static TypeConverter<Uri, String> $converterurl = const UriConverter();
|
||||
@override
|
||||
bool get dontWriteConstraints => true;
|
||||
}
|
||||
|
||||
class TabData extends DataClass implements Insertable<TabData> {
|
||||
class TabLinkData extends DataClass implements Insertable<TabLinkData> {
|
||||
final String id;
|
||||
final String? topicId;
|
||||
final DateTime timestamp;
|
||||
final Uri url;
|
||||
final String? title;
|
||||
final Uint8List? screenshot;
|
||||
const TabData(
|
||||
{required this.id,
|
||||
this.topicId,
|
||||
required this.timestamp,
|
||||
required this.url,
|
||||
this.title,
|
||||
this.screenshot});
|
||||
const TabLinkData({required this.id, this.topicId, required this.timestamp});
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
@@ -228,28 +195,16 @@ class TabData extends DataClass implements Insertable<TabData> {
|
||||
map['topic_id'] = Variable<String>(topicId);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
factory TabData.fromJson(Map<String, dynamic> json,
|
||||
factory TabLinkData.fromJson(Map<String, dynamic> json,
|
||||
{ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return TabData(
|
||||
return TabLinkData(
|
||||
id: serializer.fromJson<String>(json['id']),
|
||||
topicId: serializer.fromJson<String?>(json['topic_id']),
|
||||
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
|
||||
@@ -259,130 +214,88 @@ class TabData extends DataClass implements Insertable<TabData> {
|
||||
'id': serializer.toJson<String>(id),
|
||||
'topic_id': serializer.toJson<String?>(topicId),
|
||||
'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,
|
||||
Value<String?> topicId = const Value.absent(),
|
||||
DateTime? timestamp,
|
||||
Uri? url,
|
||||
Value<String?> title = const Value.absent(),
|
||||
Value<Uint8List?> screenshot = const Value.absent()}) =>
|
||||
TabData(
|
||||
DateTime? timestamp}) =>
|
||||
TabLinkData(
|
||||
id: id ?? this.id,
|
||||
topicId: topicId.present ? topicId.value : this.topicId,
|
||||
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) {
|
||||
return TabData(
|
||||
TabLinkData copyWithCompanion(TabLinkCompanion data) {
|
||||
return TabLinkData(
|
||||
id: data.id.present ? data.id.value : this.id,
|
||||
topicId: data.topicId.present ? data.topicId.value : this.topicId,
|
||||
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
|
||||
String toString() {
|
||||
return (StringBuffer('TabData(')
|
||||
return (StringBuffer('TabLinkData(')
|
||||
..write('id: $id, ')
|
||||
..write('topicId: $topicId, ')
|
||||
..write('timestamp: $timestamp, ')
|
||||
..write('url: $url, ')
|
||||
..write('title: $title, ')
|
||||
..write('screenshot: $screenshot')
|
||||
..write('timestamp: $timestamp')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
id, topicId, timestamp, url, title, $driftBlobEquality.hash(screenshot));
|
||||
int get hashCode => Object.hash(id, topicId, timestamp);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is TabData &&
|
||||
(other is TabLinkData &&
|
||||
other.id == this.id &&
|
||||
other.topicId == this.topicId &&
|
||||
other.timestamp == this.timestamp &&
|
||||
other.url == this.url &&
|
||||
other.title == this.title &&
|
||||
$driftBlobEquality.equals(other.screenshot, this.screenshot));
|
||||
other.timestamp == this.timestamp);
|
||||
}
|
||||
|
||||
class TabCompanion extends UpdateCompanion<TabData> {
|
||||
class TabLinkCompanion extends UpdateCompanion<TabLinkData> {
|
||||
final Value<String> id;
|
||||
final Value<String?> topicId;
|
||||
final Value<DateTime> timestamp;
|
||||
final Value<Uri> url;
|
||||
final Value<String?> title;
|
||||
final Value<Uint8List?> screenshot;
|
||||
final Value<int> rowid;
|
||||
const TabCompanion({
|
||||
const TabLinkCompanion({
|
||||
this.id = const Value.absent(),
|
||||
this.topicId = 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(),
|
||||
});
|
||||
TabCompanion.insert({
|
||||
TabLinkCompanion.insert({
|
||||
required String id,
|
||||
this.topicId = const Value.absent(),
|
||||
required DateTime timestamp,
|
||||
required Uri url,
|
||||
this.title = const Value.absent(),
|
||||
this.screenshot = const Value.absent(),
|
||||
this.rowid = const Value.absent(),
|
||||
}) : id = Value(id),
|
||||
timestamp = Value(timestamp),
|
||||
url = Value(url);
|
||||
static Insertable<TabData> custom({
|
||||
timestamp = Value(timestamp);
|
||||
static Insertable<TabLinkData> custom({
|
||||
Expression<String>? id,
|
||||
Expression<String>? topicId,
|
||||
Expression<DateTime>? timestamp,
|
||||
Expression<String>? url,
|
||||
Expression<String>? title,
|
||||
Expression<Uint8List>? screenshot,
|
||||
Expression<int>? rowid,
|
||||
}) {
|
||||
return RawValuesInsertable({
|
||||
if (id != null) 'id': id,
|
||||
if (topicId != null) 'topic_id': topicId,
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
TabCompanion copyWith(
|
||||
TabLinkCompanion copyWith(
|
||||
{Value<String>? id,
|
||||
Value<String?>? topicId,
|
||||
Value<DateTime>? timestamp,
|
||||
Value<Uri>? url,
|
||||
Value<String?>? title,
|
||||
Value<Uint8List?>? screenshot,
|
||||
Value<int>? rowid}) {
|
||||
return TabCompanion(
|
||||
return TabLinkCompanion(
|
||||
id: id ?? this.id,
|
||||
topicId: topicId ?? this.topicId,
|
||||
timestamp: timestamp ?? this.timestamp,
|
||||
url: url ?? this.url,
|
||||
title: title ?? this.title,
|
||||
screenshot: screenshot ?? this.screenshot,
|
||||
rowid: rowid ?? this.rowid,
|
||||
);
|
||||
}
|
||||
@@ -399,15 +312,6 @@ class TabCompanion extends UpdateCompanion<TabData> {
|
||||
if (timestamp.present) {
|
||||
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) {
|
||||
map['rowid'] = Variable<int>(rowid.value);
|
||||
}
|
||||
@@ -416,13 +320,10 @@ class TabCompanion extends UpdateCompanion<TabData> {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('TabCompanion(')
|
||||
return (StringBuffer('TabLinkCompanion(')
|
||||
..write('id: $id, ')
|
||||
..write('topicId: $topicId, ')
|
||||
..write('timestamp: $timestamp, ')
|
||||
..write('url: $url, ')
|
||||
..write('title: $title, ')
|
||||
..write('screenshot: $screenshot, ')
|
||||
..write('rowid: $rowid')
|
||||
..write(')'))
|
||||
.toString();
|
||||
@@ -433,16 +334,16 @@ abstract class _$TabDatabase extends GeneratedDatabase {
|
||||
_$TabDatabase(QueryExecutor e) : super(e);
|
||||
$TabDatabaseManager get managers => $TabDatabaseManager(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 TabDao tabDao = TabDao(this as TabDatabase);
|
||||
late final TabLinkDao tabLinkDao = TabLinkDao(this as TabDatabase);
|
||||
Selectable<TopicDataWithCount> topicsWithCount() {
|
||||
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: [],
|
||||
readsFrom: {
|
||||
topic,
|
||||
tab,
|
||||
tabLink,
|
||||
}).map((QueryRow row) => TopicDataWithCount(
|
||||
id: row.read<String>('id'),
|
||||
name: row.readNullable<String>('name'),
|
||||
@@ -455,7 +356,7 @@ abstract class _$TabDatabase extends GeneratedDatabase {
|
||||
Iterable<TableInfo<Table, Object?>> get allTables =>
|
||||
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
||||
@override
|
||||
List<DatabaseSchemaEntity> get allSchemaEntities => [topic, tab];
|
||||
List<DatabaseSchemaEntity> get allSchemaEntities => [topic, tabLink];
|
||||
@override
|
||||
StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules(
|
||||
[
|
||||
@@ -463,7 +364,7 @@ abstract class _$TabDatabase extends GeneratedDatabase {
|
||||
on: TableUpdateQuery.onTableName('topic',
|
||||
limitUpdateKind: UpdateKind.delete),
|
||||
result: [
|
||||
TableUpdate('tab', kind: UpdateKind.delete),
|
||||
TableUpdate('tab_link', kind: UpdateKind.delete),
|
||||
],
|
||||
),
|
||||
],
|
||||
@@ -487,16 +388,16 @@ final class $TopicReferences
|
||||
extends BaseReferences<_$TabDatabase, Topic, TopicData> {
|
||||
$TopicReferences(super.$_db, super.$_table, super.$_typedResult);
|
||||
|
||||
static MultiTypedResultKey<Tab, List<TabData>> _tabRefsTable(
|
||||
static MultiTypedResultKey<TabLink, List<TabLinkData>> _tabLinkRefsTable(
|
||||
_$TabDatabase db) =>
|
||||
MultiTypedResultKey.fromTable(db.tab,
|
||||
aliasName: $_aliasNameGenerator(db.topic.id, db.tab.topicId));
|
||||
MultiTypedResultKey.fromTable(db.tabLink,
|
||||
aliasName: $_aliasNameGenerator(db.topic.id, db.tabLink.topicId));
|
||||
|
||||
$TabProcessedTableManager get tabRefs {
|
||||
final manager =
|
||||
$TabTableManager($_db, $_db.tab).filter((f) => f.topicId.id($_item.id));
|
||||
$TabLinkProcessedTableManager get tabLinkRefs {
|
||||
final manager = $TabLinkTableManager($_db, $_db.tabLink)
|
||||
.filter((f) => f.topicId.id($_item.id));
|
||||
|
||||
final cache = $_typedResult.readTableOrNull(_tabRefsTable($_db));
|
||||
final cache = $_typedResult.readTableOrNull(_tabLinkRefsTable($_db));
|
||||
return ProcessedTableManager(
|
||||
manager.$state.copyWith(prefetchedData: cache));
|
||||
}
|
||||
@@ -521,15 +422,16 @@ class $TopicFilterComposer extends FilterComposer<_$TabDatabase, Topic> {
|
||||
column,
|
||||
joinBuilders: joinBuilders));
|
||||
|
||||
ComposableFilter tabRefs(ComposableFilter Function($TabFilterComposer f) f) {
|
||||
final $TabFilterComposer composer = $state.composerBuilder(
|
||||
ComposableFilter tabLinkRefs(
|
||||
ComposableFilter Function($TabLinkFilterComposer f) f) {
|
||||
final $TabLinkFilterComposer composer = $state.composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.id,
|
||||
referencedTable: $state.db.tab,
|
||||
referencedTable: $state.db.tabLink,
|
||||
getReferencedColumn: (t) => t.topicId,
|
||||
builder: (joinBuilder, parentComposers) => $TabFilterComposer(
|
||||
builder: (joinBuilder, parentComposers) => $TabLinkFilterComposer(
|
||||
ComposerState(
|
||||
$state.db, $state.db.tab, joinBuilder, parentComposers)));
|
||||
$state.db, $state.db.tabLink, joinBuilder, parentComposers)));
|
||||
return f(composer);
|
||||
}
|
||||
}
|
||||
@@ -562,7 +464,7 @@ class $TopicTableManager extends RootTableManager<
|
||||
$TopicUpdateCompanionBuilder,
|
||||
(TopicData, $TopicReferences),
|
||||
TopicData,
|
||||
PrefetchHooks Function({bool tabRefs})> {
|
||||
PrefetchHooks Function({bool tabLinkRefs})> {
|
||||
$TopicTableManager(_$TabDatabase db, Topic table)
|
||||
: super(TableManagerState(
|
||||
db: db,
|
||||
@@ -596,19 +498,19 @@ class $TopicTableManager extends RootTableManager<
|
||||
withReferenceMapper: (p0) => p0
|
||||
.map((e) => (e.readTable(table), $TopicReferences(db, table, e)))
|
||||
.toList(),
|
||||
prefetchHooksCallback: ({tabRefs = false}) {
|
||||
prefetchHooksCallback: ({tabLinkRefs = false}) {
|
||||
return PrefetchHooks(
|
||||
db: db,
|
||||
explicitlyWatchedTables: [if (tabRefs) db.tab],
|
||||
explicitlyWatchedTables: [if (tabLinkRefs) db.tabLink],
|
||||
addJoins: null,
|
||||
getPrefetchedDataCallback: (items) async {
|
||||
return [
|
||||
if (tabRefs)
|
||||
if (tabLinkRefs)
|
||||
await $_getPrefetchedData(
|
||||
currentTable: table,
|
||||
referencedTable: $TopicReferences._tabRefsTable(db),
|
||||
referencedTable: $TopicReferences._tabLinkRefsTable(db),
|
||||
managerFromTypedResult: (p0) =>
|
||||
$TopicReferences(db, table, p0).tabRefs,
|
||||
$TopicReferences(db, table, p0).tabLinkRefs,
|
||||
referencedItemsForCurrentItem: (item,
|
||||
referencedItems) =>
|
||||
referencedItems.where((e) => e.topicId == item.id),
|
||||
@@ -630,31 +532,26 @@ typedef $TopicProcessedTableManager = ProcessedTableManager<
|
||||
$TopicUpdateCompanionBuilder,
|
||||
(TopicData, $TopicReferences),
|
||||
TopicData,
|
||||
PrefetchHooks Function({bool tabRefs})>;
|
||||
typedef $TabCreateCompanionBuilder = TabCompanion Function({
|
||||
PrefetchHooks Function({bool tabLinkRefs})>;
|
||||
typedef $TabLinkCreateCompanionBuilder = TabLinkCompanion Function({
|
||||
required String id,
|
||||
Value<String?> topicId,
|
||||
required DateTime timestamp,
|
||||
required Uri url,
|
||||
Value<String?> title,
|
||||
Value<Uint8List?> screenshot,
|
||||
Value<int> rowid,
|
||||
});
|
||||
typedef $TabUpdateCompanionBuilder = TabCompanion Function({
|
||||
typedef $TabLinkUpdateCompanionBuilder = TabLinkCompanion Function({
|
||||
Value<String> id,
|
||||
Value<String?> topicId,
|
||||
Value<DateTime> timestamp,
|
||||
Value<Uri> url,
|
||||
Value<String?> title,
|
||||
Value<Uint8List?> screenshot,
|
||||
Value<int> rowid,
|
||||
});
|
||||
|
||||
final class $TabReferences extends BaseReferences<_$TabDatabase, Tab, TabData> {
|
||||
$TabReferences(super.$_db, super.$_table, super.$_typedResult);
|
||||
final class $TabLinkReferences
|
||||
extends BaseReferences<_$TabDatabase, TabLink, TabLinkData> {
|
||||
$TabLinkReferences(super.$_db, super.$_table, super.$_typedResult);
|
||||
|
||||
static Topic _topicIdTable(_$TabDatabase db) =>
|
||||
db.topic.createAlias($_aliasNameGenerator(db.tab.topicId, db.topic.id));
|
||||
static Topic _topicIdTable(_$TabDatabase db) => db.topic
|
||||
.createAlias($_aliasNameGenerator(db.tabLink.topicId, db.topic.id));
|
||||
|
||||
$TopicProcessedTableManager? get topicId {
|
||||
if ($_item.topicId == null) return null;
|
||||
@@ -667,8 +564,8 @@ final class $TabReferences extends BaseReferences<_$TabDatabase, Tab, TabData> {
|
||||
}
|
||||
}
|
||||
|
||||
class $TabFilterComposer extends FilterComposer<_$TabDatabase, Tab> {
|
||||
$TabFilterComposer(super.$state);
|
||||
class $TabLinkFilterComposer extends FilterComposer<_$TabDatabase, TabLink> {
|
||||
$TabLinkFilterComposer(super.$state);
|
||||
ColumnFilters<String> get id => $state.composableBuilder(
|
||||
column: $state.table.id,
|
||||
builder: (column, joinBuilders) =>
|
||||
@@ -679,23 +576,6 @@ class $TabFilterComposer extends FilterComposer<_$TabDatabase, Tab> {
|
||||
builder: (column, 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 {
|
||||
final $TopicFilterComposer composer = $state.composerBuilder(
|
||||
composer: this,
|
||||
@@ -709,8 +589,9 @@ class $TabFilterComposer extends FilterComposer<_$TabDatabase, Tab> {
|
||||
}
|
||||
}
|
||||
|
||||
class $TabOrderingComposer extends OrderingComposer<_$TabDatabase, Tab> {
|
||||
$TabOrderingComposer(super.$state);
|
||||
class $TabLinkOrderingComposer
|
||||
extends OrderingComposer<_$TabDatabase, TabLink> {
|
||||
$TabLinkOrderingComposer(super.$state);
|
||||
ColumnOrderings<String> get id => $state.composableBuilder(
|
||||
column: $state.table.id,
|
||||
builder: (column, joinBuilders) =>
|
||||
@@ -721,21 +602,6 @@ class $TabOrderingComposer extends OrderingComposer<_$TabDatabase, Tab> {
|
||||
builder: (column, 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 {
|
||||
final $TopicOrderingComposer composer = $state.composerBuilder(
|
||||
composer: this,
|
||||
@@ -749,61 +615,50 @@ class $TabOrderingComposer extends OrderingComposer<_$TabDatabase, Tab> {
|
||||
}
|
||||
}
|
||||
|
||||
class $TabTableManager extends RootTableManager<
|
||||
class $TabLinkTableManager extends RootTableManager<
|
||||
_$TabDatabase,
|
||||
Tab,
|
||||
TabData,
|
||||
$TabFilterComposer,
|
||||
$TabOrderingComposer,
|
||||
$TabCreateCompanionBuilder,
|
||||
$TabUpdateCompanionBuilder,
|
||||
(TabData, $TabReferences),
|
||||
TabData,
|
||||
TabLink,
|
||||
TabLinkData,
|
||||
$TabLinkFilterComposer,
|
||||
$TabLinkOrderingComposer,
|
||||
$TabLinkCreateCompanionBuilder,
|
||||
$TabLinkUpdateCompanionBuilder,
|
||||
(TabLinkData, $TabLinkReferences),
|
||||
TabLinkData,
|
||||
PrefetchHooks Function({bool topicId})> {
|
||||
$TabTableManager(_$TabDatabase db, Tab table)
|
||||
$TabLinkTableManager(_$TabDatabase db, TabLink table)
|
||||
: super(TableManagerState(
|
||||
db: db,
|
||||
table: table,
|
||||
filteringComposer: $TabFilterComposer(ComposerState(db, table)),
|
||||
orderingComposer: $TabOrderingComposer(ComposerState(db, table)),
|
||||
filteringComposer: $TabLinkFilterComposer(ComposerState(db, table)),
|
||||
orderingComposer: $TabLinkOrderingComposer(ComposerState(db, table)),
|
||||
updateCompanionCallback: ({
|
||||
Value<String> id = const Value.absent(),
|
||||
Value<String?> topicId = 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(),
|
||||
}) =>
|
||||
TabCompanion(
|
||||
TabLinkCompanion(
|
||||
id: id,
|
||||
topicId: topicId,
|
||||
timestamp: timestamp,
|
||||
url: url,
|
||||
title: title,
|
||||
screenshot: screenshot,
|
||||
rowid: rowid,
|
||||
),
|
||||
createCompanionCallback: ({
|
||||
required String id,
|
||||
Value<String?> topicId = const Value.absent(),
|
||||
required DateTime timestamp,
|
||||
required Uri url,
|
||||
Value<String?> title = const Value.absent(),
|
||||
Value<Uint8List?> screenshot = const Value.absent(),
|
||||
Value<int> rowid = const Value.absent(),
|
||||
}) =>
|
||||
TabCompanion.insert(
|
||||
TabLinkCompanion.insert(
|
||||
id: id,
|
||||
topicId: topicId,
|
||||
timestamp: timestamp,
|
||||
url: url,
|
||||
title: title,
|
||||
screenshot: screenshot,
|
||||
rowid: rowid,
|
||||
),
|
||||
withReferenceMapper: (p0) => p0
|
||||
.map((e) => (e.readTable(table), $TabReferences(db, table, e)))
|
||||
.map(
|
||||
(e) => (e.readTable(table), $TabLinkReferences(db, table, e)))
|
||||
.toList(),
|
||||
prefetchHooksCallback: ({topicId = false}) {
|
||||
return PrefetchHooks(
|
||||
@@ -825,8 +680,8 @@ class $TabTableManager extends RootTableManager<
|
||||
state = state.withJoin(
|
||||
currentTable: table,
|
||||
currentColumn: table.topicId,
|
||||
referencedTable: $TabReferences._topicIdTable(db),
|
||||
referencedColumn: $TabReferences._topicIdTable(db).id,
|
||||
referencedTable: $TabLinkReferences._topicIdTable(db),
|
||||
referencedColumn: $TabLinkReferences._topicIdTable(db).id,
|
||||
) as T;
|
||||
}
|
||||
|
||||
@@ -840,21 +695,21 @@ class $TabTableManager extends RootTableManager<
|
||||
));
|
||||
}
|
||||
|
||||
typedef $TabProcessedTableManager = ProcessedTableManager<
|
||||
typedef $TabLinkProcessedTableManager = ProcessedTableManager<
|
||||
_$TabDatabase,
|
||||
Tab,
|
||||
TabData,
|
||||
$TabFilterComposer,
|
||||
$TabOrderingComposer,
|
||||
$TabCreateCompanionBuilder,
|
||||
$TabUpdateCompanionBuilder,
|
||||
(TabData, $TabReferences),
|
||||
TabData,
|
||||
TabLink,
|
||||
TabLinkData,
|
||||
$TabLinkFilterComposer,
|
||||
$TabLinkOrderingComposer,
|
||||
$TabLinkCreateCompanionBuilder,
|
||||
$TabLinkUpdateCompanionBuilder,
|
||||
(TabLinkData, $TabLinkReferences),
|
||||
TabLinkData,
|
||||
PrefetchHooks Function({bool topicId})>;
|
||||
|
||||
class $TabDatabaseManager {
|
||||
final _$TabDatabase _db;
|
||||
$TabDatabaseManager(this._db);
|
||||
$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/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_provider/path_provider.dart' as path_provider;
|
||||
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 'package:collection/collection.dart';
|
||||
import 'package:lensai/features/topics/data/models/topic_data.dart';
|
||||
import 'package:lensai/features/topics/data/providers.dart';
|
||||
import 'package:lensai/features/topics/domain/repositories/topic.dart';
|
||||
import 'package:lensai/features/topics/utils/color_palette.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/data/models/topic_data.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/data/providers.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/domain/repositories/topic.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/utils/color_palette.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'providers.g.dart';
|
||||
@@ -59,3 +59,15 @@ Future<Color> unusedRandomTopicColor(UnusedRandomTopicColorRef ref) async {
|
||||
|
||||
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 'package:lensai/features/topics/data/database/database.dart';
|
||||
import 'package:lensai/features/topics/data/models/topic_data.dart';
|
||||
import 'package:lensai/features/topics/data/providers.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 'topic.g.dart';
|
||||
@@ -12,9 +11,8 @@ class TopicRepository extends _$TopicRepository {
|
||||
late TabDatabase _db;
|
||||
|
||||
@override
|
||||
Stream<List<TopicDataWithCount>> build() {
|
||||
void build() {
|
||||
_db = ref.watch(tabDatabaseProvider);
|
||||
return _db.topicsWithCount().watch();
|
||||
}
|
||||
|
||||
Future<void> addTopic({required String? name, required Color color}) {
|
||||
+3
-3
@@ -6,12 +6,12 @@ part of 'topic.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$topicRepositoryHash() => r'3b75c5fb1a081ba6d90e2ccb781469d0aa87c415';
|
||||
String _$topicRepositoryHash() => r'6c456c17af4ea7d7ef1747c92d0442459d061080';
|
||||
|
||||
/// See also [TopicRepository].
|
||||
@ProviderFor(TopicRepository)
|
||||
final topicRepositoryProvider =
|
||||
StreamNotifierProvider<TopicRepository, List<TopicDataWithCount>>.internal(
|
||||
NotifierProvider<TopicRepository, void>.internal(
|
||||
TopicRepository.new,
|
||||
name: r'topicRepositoryProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
@@ -21,6 +21,6 @@ final topicRepositoryProvider =
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$TopicRepository = StreamNotifier<List<TopicDataWithCount>>;
|
||||
typedef _$TopicRepository = 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
|
||||
+4
-4
@@ -2,10 +2,10 @@ import 'package:fading_scroll/fading_scroll.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/features/topics/data/models/topic_data.dart';
|
||||
import 'package:lensai/features/topics/domain/providers.dart';
|
||||
import 'package:lensai/features/topics/domain/repositories/topic.dart';
|
||||
import 'package:lensai/features/topics/presentation/widgets/topic_dialog.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/data/models/topic_data.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/domain/providers.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/domain/repositories/topic.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/presentation/widgets/topic_dialog.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
|
||||
class _TopicTile extends HookWidget {
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
import 'package:fading_scroll/fading_scroll.dart';
|
||||
import 'package:flutter/gestures.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 {
|
||||
const MaterialPicker({
|
||||
+2
-2
@@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/core/routing/routes.dart';
|
||||
import 'package:lensai/features/topics/domain/providers.dart';
|
||||
import 'package:lensai/features/topics/domain/repositories/topic.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/domain/providers.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/domain/repositories/topic.dart';
|
||||
import 'package:lensai/presentation/widgets/selectable_chips.dart';
|
||||
|
||||
class TopicChips extends HookConsumerWidget {
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.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});
|
||||
|
||||
@@ -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
|
||||
// **************************************************************************
|
||||
|
||||
String _$sessionServiceHash() => r'fd2cd03bbf2dd254bb19e541c71a6fb249f5a7bb';
|
||||
String _$kagiSessionServiceHash() =>
|
||||
r'8bed5d74d319ff2f874f42bc8596d02f05347c1f';
|
||||
|
||||
/// See also [SessionService].
|
||||
@ProviderFor(SessionService)
|
||||
final sessionServiceProvider =
|
||||
AutoDisposeNotifierProvider<SessionService, void>.internal(
|
||||
SessionService.new,
|
||||
name: r'sessionServiceProvider',
|
||||
/// See also [KagiSessionService].
|
||||
@ProviderFor(KagiSessionService)
|
||||
final kagiSessionServiceProvider =
|
||||
AutoDisposeNotifierProvider<KagiSessionService, void>.internal(
|
||||
KagiSessionService.new,
|
||||
name: r'kagiSessionServiceProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$sessionServiceHash,
|
||||
: _$kagiSessionServiceHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$SessionService = AutoDisposeNotifier<void>;
|
||||
typedef _$KagiSessionService = AutoDisposeNotifier<void>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||
@@ -1,16 +1,16 @@
|
||||
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
|
||||
sealed class Sheet with FastEquatable {}
|
||||
|
||||
class CreateTab extends Sheet {
|
||||
class CreateTabSheet extends Sheet {
|
||||
final String? content;
|
||||
final KagiTool? preferredTool;
|
||||
|
||||
bool get hasParameters => content != null || preferredTool != null;
|
||||
|
||||
CreateTab({this.content, this.preferredTool});
|
||||
CreateTabSheet({this.content, this.preferredTool});
|
||||
|
||||
@override
|
||||
bool get cacheHash => true;
|
||||
@@ -19,7 +19,7 @@ class CreateTab extends Sheet {
|
||||
List<Object?> get hashParameters => [content, preferredTool];
|
||||
}
|
||||
|
||||
class ViewTabs extends Sheet {
|
||||
class ViewTabsSheet extends Sheet {
|
||||
@override
|
||||
bool get cacheHash => true;
|
||||
|
||||
|
||||
@@ -2,76 +2,16 @@
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:collection/collection.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/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/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:rxdart/rxdart.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)
|
||||
class SelectedBangTrigger extends _$SelectedBangTrigger {
|
||||
void setTrigger(String trigger) {
|
||||
@@ -143,65 +83,3 @@ class ShowFindInPage extends _$ShowFindInPage {
|
||||
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 _$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() =>
|
||||
r'b0e12bc95e93d50d04f1c658230cea9f15ff0385';
|
||||
|
||||
@@ -411,22 +364,5 @@ final showFindInPageProvider = NotifierProvider<ShowFindInPage, bool>.internal(
|
||||
);
|
||||
|
||||
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: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'dart:async';
|
||||
|
||||
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/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/share_intent/domain/services/sharing_intent.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
@@ -11,10 +11,10 @@ import 'package:rxdart/rxdart.dart';
|
||||
|
||||
part 'create_tab.g.dart';
|
||||
|
||||
final _parameterToCreateTabTransformer =
|
||||
StreamTransformer<ReceivedParameter, CreateTab>.fromHandlers(
|
||||
final _parameterToCreateTabSheetTransformer =
|
||||
StreamTransformer<ReceivedParameter, CreateTabSheet>.fromHandlers(
|
||||
handleData: (parameter, sink) {
|
||||
final createTab = CreateTab(
|
||||
final createTab = CreateTabSheet(
|
||||
preferredTool: KagiTool.values
|
||||
.firstWhereOrNull((tool) => tool.name == parameter.tool),
|
||||
content: parameter.content,
|
||||
@@ -28,10 +28,10 @@ final _parameterToCreateTabTransformer =
|
||||
|
||||
@Riverpod()
|
||||
class CreateTabStream extends _$CreateTabStream {
|
||||
late StreamController<CreateTab> _streamController;
|
||||
late StreamController<CreateTabSheet> _streamController;
|
||||
|
||||
@override
|
||||
Stream<CreateTab> build() {
|
||||
Stream<CreateTabSheet> build() {
|
||||
_streamController = StreamController();
|
||||
ref.onDispose(() async {
|
||||
await _streamController.close();
|
||||
@@ -41,13 +41,13 @@ class CreateTabStream extends _$CreateTabStream {
|
||||
final appWidgetLaunchStream = ref.watch(appWidgetLaunchStreamProvider);
|
||||
|
||||
return MergeStream([
|
||||
sharingItentStream.transform(_parameterToCreateTabTransformer),
|
||||
appWidgetLaunchStream.transform(_parameterToCreateTabTransformer),
|
||||
sharingItentStream.transform(_parameterToCreateTabSheetTransformer),
|
||||
appWidgetLaunchStream.transform(_parameterToCreateTabSheetTransformer),
|
||||
_streamController.stream,
|
||||
]);
|
||||
}
|
||||
|
||||
void createTab(CreateTab parameter) {
|
||||
void createTab(CreateTabSheet parameter) {
|
||||
_streamController.add(parameter);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,12 @@ part of 'create_tab.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$createTabStreamHash() => r'3e85020edbebf3ec9927dba6d05af9f06a360a14';
|
||||
String _$createTabStreamHash() => r'05ad914e41e0d346e082e4638afe5fe5a04052f1';
|
||||
|
||||
/// See also [CreateTabStream].
|
||||
@ProviderFor(CreateTabStream)
|
||||
final createTabStreamProvider =
|
||||
AutoDisposeStreamNotifierProvider<CreateTabStream, CreateTab>.internal(
|
||||
AutoDisposeStreamNotifierProvider<CreateTabStream, CreateTabSheet>.internal(
|
||||
CreateTabStream.new,
|
||||
name: r'createTabStreamProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
@@ -21,6 +21,6 @@ final createTabStreamProvider =
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$CreateTabStream = AutoDisposeStreamNotifier<CreateTab>;
|
||||
typedef _$CreateTabStream = AutoDisposeStreamNotifier<CreateTabSheet>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||
|
||||
@@ -1,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_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/features/topics/domain/repositories/topic.dart';
|
||||
import 'package:lensai/features/web_view/domain/entities/web_view_page.dart';
|
||||
import 'package:lensai/features/web_view/domain/repositories/web_view.dart';
|
||||
import 'package:lensai/features/web_view/presentation/widgets/favicon.dart';
|
||||
import 'package:lensai/features/geckoview/domain/entities/tab_state.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/domain/repositories/topic.dart';
|
||||
|
||||
class TabActionDialog extends HookConsumerWidget {
|
||||
final WebViewPage initialTab;
|
||||
final String tabId;
|
||||
|
||||
final void Function()? onDismiss;
|
||||
|
||||
const TabActionDialog({
|
||||
required this.initialTab,
|
||||
required this.tabId,
|
||||
this.onDismiss,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
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 =
|
||||
ref.watch(topicRepositoryProvider.select((value) => value.valueOrNull));
|
||||
final selectedTopic =
|
||||
@@ -42,13 +42,13 @@ class TabActionDialog extends HookConsumerWidget {
|
||||
vertical: 24.0,
|
||||
),
|
||||
title: ListTile(
|
||||
leading: FaviconImage(
|
||||
favicon: tabState.favicon,
|
||||
url: tabState.url,
|
||||
size: 24,
|
||||
leading: RawImage(
|
||||
image: tabState.icon?.value,
|
||||
width: 24,
|
||||
height: 24,
|
||||
),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(tabState.title ?? 'Unknown Title'),
|
||||
title: Text(tabState.title),
|
||||
subtitle: Text(tabState.url.authority),
|
||||
),
|
||||
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:hooks_riverpod/hooks_riverpod.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/providers.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/landing/content.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/settings/data/models/settings.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/widgets/animate_gradient_shader.dart';
|
||||
import 'package:lensai/presentation/widgets/animated_indexed_stack.dart';
|
||||
@@ -41,8 +37,8 @@ class KagiScreen extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final displayedSheet = ref.watch(bottomSheetProvider);
|
||||
final displayedOverlayDialog = ref.watch(overlayDialogProvider);
|
||||
final displayedSheet = ref.watch(bottomSheetControllerProvider);
|
||||
final displayedOverlayDialog = ref.watch(overlayDialogControllerProvider);
|
||||
|
||||
final showEarlyAccessFeatures = ref.watch(
|
||||
settingsRepositoryProvider.select(
|
||||
@@ -75,7 +71,7 @@ class KagiScreen extends HookConsumerWidget {
|
||||
final activeKagiTool = useValueNotifier<KagiTool?>(null, [displayedSheet]);
|
||||
|
||||
ref.listen(
|
||||
overlayDialogProvider,
|
||||
overlayDialogControllerProvider,
|
||||
(previous, next) {
|
||||
if (next != null) {
|
||||
overlayController.show();
|
||||
@@ -109,7 +105,7 @@ class KagiScreen extends HookConsumerWidget {
|
||||
|
||||
return (page != null)
|
||||
? AppBarTitle(
|
||||
page: page,
|
||||
tab: page,
|
||||
onTap: () {
|
||||
ref.read(overlayDialogProvider.notifier).show(
|
||||
WebPageDialog(
|
||||
@@ -149,7 +145,8 @@ class KagiScreen extends HookConsumerWidget {
|
||||
ref
|
||||
.read(createTabStreamProvider.notifier)
|
||||
.createTab(
|
||||
CreateTab(preferredTool: KagiTool.search),
|
||||
CreateTabSheet(
|
||||
preferredTool: KagiTool.search),
|
||||
);
|
||||
},
|
||||
icon: Icon(KagiTool.search.icon),
|
||||
@@ -162,7 +159,8 @@ class KagiScreen extends HookConsumerWidget {
|
||||
ref
|
||||
.read(createTabStreamProvider.notifier)
|
||||
.createTab(
|
||||
CreateTab(preferredTool: KagiTool.summarizer),
|
||||
CreateTabSheet(
|
||||
preferredTool: KagiTool.summarizer),
|
||||
);
|
||||
},
|
||||
icon: Icon(KagiTool.summarizer.icon),
|
||||
@@ -176,7 +174,7 @@ class KagiScreen extends HookConsumerWidget {
|
||||
ref
|
||||
.read(createTabStreamProvider.notifier)
|
||||
.createTab(
|
||||
CreateTab(
|
||||
CreateTabSheet(
|
||||
preferredTool: KagiTool.assistant,
|
||||
),
|
||||
);
|
||||
@@ -278,7 +276,7 @@ class KagiScreen extends HookConsumerWidget {
|
||||
if (quickAction != null)
|
||||
InkWell(
|
||||
onTap: () async {
|
||||
var tab = CreateTab(
|
||||
var tab = CreateTabSheet(
|
||||
preferredTool: quickAction,
|
||||
);
|
||||
|
||||
@@ -303,7 +301,7 @@ class KagiScreen extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
tab = CreateTab(
|
||||
tab = CreateTabSheet(
|
||||
preferredTool: quickAction,
|
||||
content: await completer.future,
|
||||
);
|
||||
@@ -320,12 +318,12 @@ class KagiScreen extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
TabsActionButton(
|
||||
isActive: displayedSheet is ViewTabs,
|
||||
isActive: displayedSheet is ViewTabsSheet,
|
||||
onTap: () {
|
||||
if (displayedSheet case ViewTabs()) {
|
||||
if (displayedSheet case ViewTabsSheet()) {
|
||||
ref.read(bottomSheetProvider.notifier).dismiss();
|
||||
} else {
|
||||
ref.read(bottomSheetProvider.notifier).show(ViewTabs());
|
||||
ref.read(bottomSheetProvider.notifier).show(ViewTabsSheet());
|
||||
}
|
||||
},
|
||||
),
|
||||
@@ -387,7 +385,7 @@ class KagiScreen extends HookConsumerWidget {
|
||||
MenuItemButton(
|
||||
onPressed: () {
|
||||
ref.read(createTabStreamProvider.notifier).createTab(
|
||||
CreateTab(preferredTool: KagiTool.assistant),
|
||||
CreateTabSheet(preferredTool: KagiTool.assistant),
|
||||
);
|
||||
},
|
||||
leadingIcon: Icon(KagiTool.assistant.icon),
|
||||
@@ -396,7 +394,7 @@ class KagiScreen extends HookConsumerWidget {
|
||||
MenuItemButton(
|
||||
onPressed: () {
|
||||
ref.read(createTabStreamProvider.notifier).createTab(
|
||||
CreateTab(preferredTool: KagiTool.summarizer),
|
||||
CreateTabSheet(preferredTool: KagiTool.summarizer),
|
||||
);
|
||||
},
|
||||
leadingIcon: Icon(KagiTool.summarizer.icon),
|
||||
@@ -405,7 +403,7 @@ class KagiScreen extends HookConsumerWidget {
|
||||
MenuItemButton(
|
||||
onPressed: () {
|
||||
ref.read(createTabStreamProvider.notifier).createTab(
|
||||
CreateTab(preferredTool: KagiTool.search),
|
||||
CreateTabSheet(preferredTool: KagiTool.search),
|
||||
);
|
||||
},
|
||||
leadingIcon: Icon(KagiTool.search.icon),
|
||||
@@ -649,7 +647,7 @@ class KagiScreen extends HookConsumerWidget {
|
||||
return false;
|
||||
},
|
||||
child: switch (displayedSheet) {
|
||||
ViewTabs() => DraggableScrollableSheet(
|
||||
ViewTabsSheet() => DraggableScrollableSheet(
|
||||
key: ValueKey(displayedSheet),
|
||||
expand: false,
|
||||
minChildSize: 0.1,
|
||||
@@ -669,7 +667,7 @@ class KagiScreen extends HookConsumerWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
final CreateTab parameter => DraggableScrollableSheet(
|
||||
final CreateTabSheet parameter => DraggableScrollableSheet(
|
||||
key: ValueKey(displayedSheet),
|
||||
expand: false,
|
||||
initialChildSize: 0.8,
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
import 'package:flutter/material.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/web_view/presentation/widgets/favicon.dart';
|
||||
import 'package:lensai/features/geckoview/domain/entities/tab_state.dart';
|
||||
import 'package:text_scroll/text_scroll.dart';
|
||||
|
||||
class AppBarTitle extends StatelessWidget {
|
||||
final WebViewPage page;
|
||||
final TabState tab;
|
||||
|
||||
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) {
|
||||
if (page.url.isScheme('http')) {
|
||||
Icon _securityStatusIcon(BuildContext context) {
|
||||
if (tab.url.isScheme('http')) {
|
||||
return Icon(
|
||||
MdiIcons.lockOff,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
size: 14,
|
||||
);
|
||||
} else if (page.sslError != null) {
|
||||
} else if (!tab.securityInfoState.secure) {
|
||||
return Icon(
|
||||
MdiIcons.lockAlert,
|
||||
color: Theme.of(context).colorScheme.errorContainer,
|
||||
@@ -39,9 +38,10 @@ class AppBarTitle extends StatelessWidget {
|
||||
onTap: onTap,
|
||||
child: Row(
|
||||
children: [
|
||||
FaviconImage(
|
||||
favicon: page.favicon,
|
||||
url: page.url,
|
||||
RawImage(
|
||||
image: tab.icon?.value,
|
||||
height: 16,
|
||||
width: 16,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
@@ -52,8 +52,8 @@ class AppBarTitle extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextScroll(
|
||||
key: ValueKey(page.title),
|
||||
page.title ?? 'New Tab',
|
||||
key: ValueKey(tab.title),
|
||||
tab.title,
|
||||
style: theme.textTheme.bodyLarge
|
||||
?.copyWith(color: theme.colorScheme.onSurface),
|
||||
// mode: TextScrollMode.bouncing,
|
||||
@@ -68,13 +68,13 @@ class AppBarTitle extends StatelessWidget {
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
_securityStatusIcon(context, page),
|
||||
_securityStatusIcon(context),
|
||||
const SizedBox(
|
||||
width: 4,
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
page.url.authority,
|
||||
tab.url.authority,
|
||||
style: theme.textTheme.bodyMedium
|
||||
?.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/features/search_browser/presentation/widgets/error_container.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';
|
||||
|
||||
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_hooks/flutter_hooks.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/presentation/widgets/tabs/assistant_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);
|
||||
|
||||
class SharedContentSheet extends HookConsumerWidget {
|
||||
final CreateTab parameter;
|
||||
final CreateTabSheet parameter;
|
||||
final OnSubmitUri onSubmit;
|
||||
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_hooks/flutter_hooks.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/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 {
|
||||
static const _headerSize = 104.0;
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/features/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/presentation/widgets/sheets/shared_content_sheet.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/search_field.dart';
|
||||
import 'package:lensai/features/search_browser/domain/providers.dart';
|
||||
import 'package:lensai/features/search_browser/presentation/controllers/bottom_sheet.dart';
|
||||
import 'package:lensai/features/search_browser/presentation/widgets/sheets/shared_content_sheet.dart';
|
||||
import 'package:lensai/features/share_intent/domain/entities/shared_content.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_pdf_text/flutter_pdf_text.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/utils/url_builder.dart'
|
||||
as uri_builder;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.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 {
|
||||
final bool isActive;
|
||||
@@ -17,7 +17,7 @@ class TabsActionButton extends HookConsumerWidget {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
final tabCount =
|
||||
ref.watch(activeTabsProvider.select((tabs) => tabs.length));
|
||||
ref.watch(tabRepositoryProvider.select((tabs) => tabs.length));
|
||||
|
||||
return InkWell(
|
||||
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';
|
||||
|
||||
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:flutter/material.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';
|
||||
|
||||
|
||||
@@ -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/repositories/data.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/repositories/settings_repository.dart';
|
||||
import 'package:lensai/features/settings/presentation/controllers/save_settings.dart';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.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
|
||||
?.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/model/sharing_file.dart';
|
||||
import 'package:lensai/core/logger.dart';
|
||||
import 'package:lensai/domain/entities/received_parameter.dart';
|
||||
import 'package:lensai/features/search_browser/domain/entities/modes.dart';
|
||||
import 'package:lensai/data/models/received_parameter.dart';
|
||||
import 'package:lensai/features/kagi/data/entities/modes.dart';
|
||||
import 'package:mime/mime.dart' as mime;
|
||||
import 'package:riverpod_annotation/riverpod_annotation.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