intermediate
This commit is contained in:
@@ -64,6 +64,23 @@ android {
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
splits {
|
||||
abi {
|
||||
enable true
|
||||
reset()
|
||||
include 'arm64-v8a' //for now build only one target
|
||||
universalApk false
|
||||
}
|
||||
}
|
||||
|
||||
applicationVariants.all { variant ->
|
||||
variant.outputs.all {
|
||||
//rename build to make it detect by flutter
|
||||
outputFileName = "../../flutter-apk/app-${variant.name}.apk"
|
||||
}
|
||||
}
|
||||
}
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
|
||||
@@ -6,7 +6,7 @@ class EquatableCollection<T> with FastEquatable {
|
||||
|
||||
EquatableCollection(this.collection, {required this.immutable})
|
||||
: assert(
|
||||
collection is Map || collection is Iterable,
|
||||
collection is Map || collection is Iterable || collection == null,
|
||||
'Collection type not supported',
|
||||
);
|
||||
|
||||
|
||||
@@ -9,9 +9,12 @@ class WebPageInfo {
|
||||
final String? title;
|
||||
final BrowserIcon? favicon;
|
||||
|
||||
bool get isPageInfoComplete =>
|
||||
(title?.isNotEmpty ?? false) && favicon != null;
|
||||
|
||||
WebPageInfo({
|
||||
required this.url,
|
||||
required this.title,
|
||||
required this.favicon,
|
||||
this.title,
|
||||
this.favicon,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:exceptions/exceptions.dart';
|
||||
@@ -10,6 +11,7 @@ import 'package:http/http.dart' as http;
|
||||
import 'package:lensai/core/http_error_handler.dart';
|
||||
import 'package:lensai/data/models/web_page_info.dart';
|
||||
import 'package:lensai/features/geckoview/domain/entities/browser_icon.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/cache.dart';
|
||||
import 'package:lensai/utils/lru_cache.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
@@ -56,15 +58,19 @@ class GenericWebsiteService extends _$GenericWebsiteService {
|
||||
|
||||
late http.Client _client;
|
||||
|
||||
final LRUCache<String, BrowserIcon> _iconCache;
|
||||
//Global icon cache
|
||||
late CacheRepository _cacheRepository;
|
||||
//Local decoded icon cache
|
||||
final LRUCache<String, BrowserIcon> _browserIconCache;
|
||||
|
||||
GenericWebsiteService()
|
||||
: _iconsService = GeckoIconService(),
|
||||
_iconCache = LRUCache(50);
|
||||
_browserIconCache = LRUCache(50);
|
||||
|
||||
@override
|
||||
void build() {
|
||||
_client = http.Client();
|
||||
_cacheRepository = ref.watch(cacheRepositoryProvider.notifier);
|
||||
}
|
||||
|
||||
static Map<String, dynamic> _serializeResource(Resource resource) {
|
||||
@@ -89,7 +95,14 @@ class GenericWebsiteService extends _$GenericWebsiteService {
|
||||
);
|
||||
}
|
||||
|
||||
static List<Resource> _extractIcons(Document document) {
|
||||
static Uri _resolveRelativeUri(Uri baseUri, Uri uri) {
|
||||
if (!uri.isAbsolute) {
|
||||
return baseUri.resolveUri(uri);
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
static List<Resource> _extractIcons(Uri baseUrl, Document document) {
|
||||
final List<Resource> icons = [];
|
||||
|
||||
void collectLinkIcons(String rel) {
|
||||
@@ -99,9 +112,10 @@ class GenericWebsiteService extends _$GenericWebsiteService {
|
||||
final type = _typeMap[rel];
|
||||
final mimeType = link.attributes['type'];
|
||||
if (href != null && type != null) {
|
||||
if (Uri.tryParse(href) case final Uri url) {
|
||||
icons.add(
|
||||
Resource(
|
||||
url: href,
|
||||
url: _resolveRelativeUri(baseUrl, url).toString(),
|
||||
type: type,
|
||||
sizes: sizesToList(link.attributes['sizes']).toList(),
|
||||
mimeType: (mimeType?.isNotEmpty ?? true) ? null : mimeType,
|
||||
@@ -111,6 +125,7 @@ class GenericWebsiteService extends _$GenericWebsiteService {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void collectMetaPropertyIcons(String property) {
|
||||
final metas = document.querySelectorAll('meta[property="$property"]');
|
||||
@@ -118,10 +133,11 @@ class GenericWebsiteService extends _$GenericWebsiteService {
|
||||
final content = meta.attributes['content'];
|
||||
final type = _typeMap[property];
|
||||
if (content != null && type != null) {
|
||||
if (Uri.tryParse(content) case final Uri url) {
|
||||
icons.add(
|
||||
Resource(
|
||||
type: type,
|
||||
url: content,
|
||||
url: _resolveRelativeUri(baseUrl, url).toString(),
|
||||
sizes: [],
|
||||
maskable: false,
|
||||
),
|
||||
@@ -129,6 +145,7 @@ class GenericWebsiteService extends _$GenericWebsiteService {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void collectMetaNameIcons(String name) {
|
||||
final metas = document.querySelectorAll('meta[name="$name"]');
|
||||
@@ -136,10 +153,11 @@ class GenericWebsiteService extends _$GenericWebsiteService {
|
||||
final content = meta.attributes['content'];
|
||||
final type = _typeMap[name];
|
||||
if (content != null && type != null) {
|
||||
if (Uri.tryParse(content) case final Uri url) {
|
||||
icons.add(
|
||||
Resource(
|
||||
type: type,
|
||||
url: content,
|
||||
url: _resolveRelativeUri(baseUrl, url).toString(),
|
||||
sizes: [],
|
||||
maskable: false,
|
||||
),
|
||||
@@ -147,6 +165,7 @@ class GenericWebsiteService extends _$GenericWebsiteService {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
collectLinkIcons("icon");
|
||||
collectLinkIcons("shortcut icon");
|
||||
@@ -166,7 +185,7 @@ class GenericWebsiteService extends _$GenericWebsiteService {
|
||||
return icons;
|
||||
}
|
||||
|
||||
Future<Result<WebPageInfo>> getInfo(Uri url) async {
|
||||
Future<Result<WebPageInfo>> fetchPageInfo(Uri url) async {
|
||||
return Result.fromAsync(
|
||||
() async {
|
||||
final response =
|
||||
@@ -175,23 +194,28 @@ class GenericWebsiteService extends _$GenericWebsiteService {
|
||||
final result = await compute(
|
||||
(args) async {
|
||||
final document = html_parser.parse(args[0]);
|
||||
final baseUri = Uri.parse(args[1]);
|
||||
|
||||
final title = document.querySelector('title')?.text;
|
||||
final resources = _extractIcons(document);
|
||||
final resources = _extractIcons(baseUri, document);
|
||||
|
||||
return {
|
||||
'title': title,
|
||||
'resources': resources.map(_serializeResource).toList(),
|
||||
};
|
||||
},
|
||||
[response.body],
|
||||
[response.body, url.toString()],
|
||||
);
|
||||
|
||||
final resources = (result['resources']! as List<Map<String, dynamic>>)
|
||||
.map(_deserializeResource)
|
||||
.toList();
|
||||
|
||||
final favicon = await getUrlFavicon(url: url, resources: resources);
|
||||
final favicon = await getCachedIcon(url) ??
|
||||
await loadIcon(
|
||||
url: url,
|
||||
resources: resources,
|
||||
);
|
||||
|
||||
return WebPageInfo(
|
||||
url: url,
|
||||
@@ -203,17 +227,44 @@ class GenericWebsiteService extends _$GenericWebsiteService {
|
||||
);
|
||||
}
|
||||
|
||||
Future<BrowserIcon> getUrlFavicon({
|
||||
required Uri url,
|
||||
List<Resource> resources = const [],
|
||||
}) async {
|
||||
final cached = _iconCache.get(url.host);
|
||||
if (cached == null) {
|
||||
final result =
|
||||
await _iconsService.loadIcon(url: url, resources: resources);
|
||||
Future<BrowserIcon?> getCachedIcon(Uri url) async {
|
||||
final cachedBrowserIcon = _browserIconCache.get(url.origin);
|
||||
if (cachedBrowserIcon != null) {
|
||||
return cachedBrowserIcon;
|
||||
}
|
||||
|
||||
return _iconCache.set(
|
||||
url.host,
|
||||
final cachedIcon = await _cacheRepository.getCachedIcon(url.origin);
|
||||
if (cachedIcon != null) {
|
||||
return _browserIconCache.set(
|
||||
url.origin,
|
||||
await BrowserIcon.fromBytes(
|
||||
cachedIcon,
|
||||
dominantColor: null,
|
||||
source: IconSource.disk,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<BrowserIcon> loadIcon({
|
||||
required Uri url,
|
||||
required List<Resource> resources,
|
||||
bool isPrivate = false,
|
||||
bool waitOnNetworkLoad = true,
|
||||
}) async {
|
||||
final result = await _iconsService.loadIcon(
|
||||
url: url,
|
||||
resources: resources,
|
||||
isPrivate: isPrivate,
|
||||
waitOnNetworkLoad: waitOnNetworkLoad,
|
||||
);
|
||||
|
||||
// unawaited(_cacheRepository.cacheIcon(url, result.image));
|
||||
|
||||
return _browserIconCache.set(
|
||||
url.origin,
|
||||
await BrowserIcon.fromBytes(
|
||||
result.image,
|
||||
dominantColor: (result.color != null) ? Color(result.color!) : null,
|
||||
@@ -222,9 +273,6 @@ class GenericWebsiteService extends _$GenericWebsiteService {
|
||||
);
|
||||
}
|
||||
|
||||
return cached;
|
||||
}
|
||||
|
||||
// Future<Uri?> tryUpgradeToHttps(Uri httpUri) async {
|
||||
// if (httpUri.isScheme('https')) {
|
||||
// return httpUri;
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'generic_website.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$genericWebsiteServiceHash() =>
|
||||
r'7dea42d6914b0f06250a96bb425ba10a331ef0d3';
|
||||
r'8eab8ab1448d4826dc0cda0ff6b47143735ba176';
|
||||
|
||||
/// See also [GenericWebsiteService].
|
||||
@ProviderFor(GenericWebsiteService)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:drift/drift.dart';
|
||||
|
||||
extension TableSIze on DatabaseConnectionUser {
|
||||
extension TableSize on DatabaseConnectionUser {
|
||||
SingleSelectable<double> tableSize(TableInfo table) {
|
||||
return customSelect(
|
||||
'SELECT SUM(pgsize) /(1024.0 * 1024.0)AS pgsize_mb FROM dbstat WHERE name = ?1',
|
||||
|
||||
@@ -94,6 +94,27 @@ class BangDao extends DatabaseAccessor<BangDatabase> with _$BangDaoMixin {
|
||||
return db.bangQuery(query: db.buildFtsQuery(searchString));
|
||||
}
|
||||
|
||||
Future<int> addSearchEntry(String trigger, String searchQuery) {
|
||||
return db.bangHistory.insertOne(
|
||||
BangHistoryCompanion.insert(
|
||||
searchQuery: searchQuery,
|
||||
trigger: trigger,
|
||||
searchDate: DateTime.now(),
|
||||
),
|
||||
onConflict: DoUpdate(
|
||||
target: [db.bangHistory.searchQuery],
|
||||
(old) => BangHistoryCompanion.custom(
|
||||
trigger: Variable(trigger),
|
||||
searchDate: Variable(DateTime.now()),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<int> removeSearchEntry(String searchQuery) {
|
||||
return db.bangHistory.deleteWhere((t) => t.searchQuery.equals(searchQuery));
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:lensai/features/bangs/data/database/daos/sync.dart';
|
||||
import 'package:lensai/features/bangs/data/database/drift/converters/bang_format.dart';
|
||||
import 'package:lensai/features/bangs/data/models/bang.dart';
|
||||
import 'package:lensai/features/bangs/data/models/bang_data.dart';
|
||||
import 'package:lensai/features/bangs/data/models/search_history_entry.dart';
|
||||
import 'package:lensai/features/search/domain/fts_tokenizer.dart';
|
||||
|
||||
part 'database.g.dart';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:lensai/features/bangs/data/database/drift/converters/bang_format.dart';
|
||||
import 'package:lensai/features/bangs/data/models/bang_data.dart';
|
||||
import 'package:lensai/features/bangs/data/models/bang.dart';
|
||||
import 'package:lensai/features/bangs/data/models/search_history_entry.dart';
|
||||
|
||||
CREATE TABLE bang (
|
||||
"trigger" TEXT PRIMARY KEY NOT NULL,
|
||||
@@ -24,10 +25,10 @@ CREATE TABLE bang_frequency (
|
||||
last_used DATETIME NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE bang_icon (
|
||||
"trigger" TEXT PRIMARY KEY NOT NULL REFERENCES bang ("trigger") ON DELETE CASCADE,
|
||||
icon_data BLOB NOT NULL,
|
||||
fetch_date DATETIME NOT NULL
|
||||
CREATE TABLE bang_history (
|
||||
search_query TEXT UNIQUE NOT NULL,
|
||||
"trigger" TEXT NOT NULL REFERENCES bang ("trigger"),
|
||||
search_date DATETIME NOT NULL
|
||||
);
|
||||
|
||||
CREATE VIRTUAL TABLE bang_fts
|
||||
@@ -42,14 +43,11 @@ CREATE VIEW bang_data_view WITH BangData AS
|
||||
SELECT
|
||||
b.*,
|
||||
bf.frequency,
|
||||
bf.last_used,
|
||||
bi.icon_data
|
||||
bf.last_used
|
||||
FROM
|
||||
bang b
|
||||
LEFT JOIN
|
||||
bang_frequency bf ON b."trigger" = bf."trigger"
|
||||
LEFT JOIN
|
||||
bang_icon bi ON b."trigger" = bi."trigger";
|
||||
bang_frequency bf ON b."trigger" = bf."trigger";
|
||||
|
||||
-- Triggers to keep the FTS index up to date.
|
||||
CREATE TRIGGER bang_after_insert AFTER INSERT ON bang BEGIN
|
||||
@@ -78,16 +76,13 @@ bangQuery WITH BangData:
|
||||
SELECT
|
||||
b.*,
|
||||
bf.frequency,
|
||||
bf.last_used,
|
||||
bi.icon_data
|
||||
bf.last_used
|
||||
FROM
|
||||
bang_fts(:query) fts
|
||||
INNER JOIN
|
||||
bang b ON b.rowid = fts.rowid
|
||||
LEFT JOIN
|
||||
bang_frequency bf ON b."trigger" = bf."trigger"
|
||||
LEFT JOIN
|
||||
bang_icon bi ON b."trigger" = bi."trigger"
|
||||
ORDER BY
|
||||
RANK,
|
||||
bf.frequency NULLS LAST;
|
||||
@@ -114,3 +109,18 @@ categoriesJson:
|
||||
json(c.sub_categories)
|
||||
) AS categories_json
|
||||
FROM categories c;
|
||||
|
||||
searchHistoryEntries WITH SearchHistoryEntry:
|
||||
SELECT *
|
||||
FROM bang_history
|
||||
ORDER BY search_date DESC
|
||||
LIMIT :limit;
|
||||
|
||||
evictHistoryEntries:
|
||||
DELETE FROM bang_history
|
||||
WHERE rowid IN (
|
||||
SELECT rowid
|
||||
FROM bang_history
|
||||
ORDER BY search_date DESC
|
||||
LIMIT -1 OFFSET :limit
|
||||
);
|
||||
@@ -599,171 +599,173 @@ class BangFrequencyCompanion extends UpdateCompanion<BangFrequencyData> {
|
||||
}
|
||||
}
|
||||
|
||||
class BangIcon extends Table with TableInfo<BangIcon, BangIconData> {
|
||||
class BangHistory extends Table with TableInfo<BangHistory, BangHistoryData> {
|
||||
@override
|
||||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
BangIcon(this.attachedDatabase, [this._alias]);
|
||||
BangHistory(this.attachedDatabase, [this._alias]);
|
||||
late final GeneratedColumn<String> searchQuery = GeneratedColumn<String>(
|
||||
'search_query', aliasedName, false,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'UNIQUE NOT NULL');
|
||||
late final GeneratedColumn<String> trigger = GeneratedColumn<String>(
|
||||
'trigger', aliasedName, false,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints:
|
||||
'PRIMARY KEY NOT NULL REFERENCES bang("trigger")ON DELETE CASCADE');
|
||||
late final GeneratedColumn<Uint8List> iconData = GeneratedColumn<Uint8List>(
|
||||
'icon_data', aliasedName, false,
|
||||
type: DriftSqlType.blob,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL');
|
||||
late final GeneratedColumn<DateTime> fetchDate = GeneratedColumn<DateTime>(
|
||||
'fetch_date', aliasedName, false,
|
||||
$customConstraints: 'NOT NULL REFERENCES bang("trigger")');
|
||||
late final GeneratedColumn<DateTime> searchDate = GeneratedColumn<DateTime>(
|
||||
'search_date', aliasedName, false,
|
||||
type: DriftSqlType.dateTime,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL');
|
||||
@override
|
||||
List<GeneratedColumn> get $columns => [trigger, iconData, fetchDate];
|
||||
List<GeneratedColumn> get $columns => [searchQuery, trigger, searchDate];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@override
|
||||
String get actualTableName => $name;
|
||||
static const String $name = 'bang_icon';
|
||||
static const String $name = 'bang_history';
|
||||
@override
|
||||
Set<GeneratedColumn> get $primaryKey => {trigger};
|
||||
Set<GeneratedColumn> get $primaryKey => const {};
|
||||
@override
|
||||
BangIconData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||
BangHistoryData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||
return BangIconData(
|
||||
return BangHistoryData(
|
||||
searchQuery: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}search_query'])!,
|
||||
trigger: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}trigger'])!,
|
||||
iconData: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.blob, data['${effectivePrefix}icon_data'])!,
|
||||
fetchDate: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.dateTime, data['${effectivePrefix}fetch_date'])!,
|
||||
searchDate: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.dateTime, data['${effectivePrefix}search_date'])!,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
BangIcon createAlias(String alias) {
|
||||
return BangIcon(attachedDatabase, alias);
|
||||
BangHistory createAlias(String alias) {
|
||||
return BangHistory(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get dontWriteConstraints => true;
|
||||
}
|
||||
|
||||
class BangIconData extends DataClass implements Insertable<BangIconData> {
|
||||
class BangHistoryData extends DataClass implements Insertable<BangHistoryData> {
|
||||
final String searchQuery;
|
||||
final String trigger;
|
||||
final Uint8List iconData;
|
||||
final DateTime fetchDate;
|
||||
const BangIconData(
|
||||
{required this.trigger, required this.iconData, required this.fetchDate});
|
||||
final DateTime searchDate;
|
||||
const BangHistoryData(
|
||||
{required this.searchQuery,
|
||||
required this.trigger,
|
||||
required this.searchDate});
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
map['search_query'] = Variable<String>(searchQuery);
|
||||
map['trigger'] = Variable<String>(trigger);
|
||||
map['icon_data'] = Variable<Uint8List>(iconData);
|
||||
map['fetch_date'] = Variable<DateTime>(fetchDate);
|
||||
map['search_date'] = Variable<DateTime>(searchDate);
|
||||
return map;
|
||||
}
|
||||
|
||||
factory BangIconData.fromJson(Map<String, dynamic> json,
|
||||
factory BangHistoryData.fromJson(Map<String, dynamic> json,
|
||||
{ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return BangIconData(
|
||||
return BangHistoryData(
|
||||
searchQuery: serializer.fromJson<String>(json['search_query']),
|
||||
trigger: serializer.fromJson<String>(json['trigger']),
|
||||
iconData: serializer.fromJson<Uint8List>(json['icon_data']),
|
||||
fetchDate: serializer.fromJson<DateTime>(json['fetch_date']),
|
||||
searchDate: serializer.fromJson<DateTime>(json['search_date']),
|
||||
);
|
||||
}
|
||||
@override
|
||||
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return <String, dynamic>{
|
||||
'search_query': serializer.toJson<String>(searchQuery),
|
||||
'trigger': serializer.toJson<String>(trigger),
|
||||
'icon_data': serializer.toJson<Uint8List>(iconData),
|
||||
'fetch_date': serializer.toJson<DateTime>(fetchDate),
|
||||
'search_date': serializer.toJson<DateTime>(searchDate),
|
||||
};
|
||||
}
|
||||
|
||||
BangIconData copyWith(
|
||||
{String? trigger, Uint8List? iconData, DateTime? fetchDate}) =>
|
||||
BangIconData(
|
||||
BangHistoryData copyWith(
|
||||
{String? searchQuery, String? trigger, DateTime? searchDate}) =>
|
||||
BangHistoryData(
|
||||
searchQuery: searchQuery ?? this.searchQuery,
|
||||
trigger: trigger ?? this.trigger,
|
||||
iconData: iconData ?? this.iconData,
|
||||
fetchDate: fetchDate ?? this.fetchDate,
|
||||
searchDate: searchDate ?? this.searchDate,
|
||||
);
|
||||
BangIconData copyWithCompanion(BangIconCompanion data) {
|
||||
return BangIconData(
|
||||
BangHistoryData copyWithCompanion(BangHistoryCompanion data) {
|
||||
return BangHistoryData(
|
||||
searchQuery:
|
||||
data.searchQuery.present ? data.searchQuery.value : this.searchQuery,
|
||||
trigger: data.trigger.present ? data.trigger.value : this.trigger,
|
||||
iconData: data.iconData.present ? data.iconData.value : this.iconData,
|
||||
fetchDate: data.fetchDate.present ? data.fetchDate.value : this.fetchDate,
|
||||
searchDate:
|
||||
data.searchDate.present ? data.searchDate.value : this.searchDate,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('BangIconData(')
|
||||
return (StringBuffer('BangHistoryData(')
|
||||
..write('searchQuery: $searchQuery, ')
|
||||
..write('trigger: $trigger, ')
|
||||
..write('iconData: $iconData, ')
|
||||
..write('fetchDate: $fetchDate')
|
||||
..write('searchDate: $searchDate')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(trigger, $driftBlobEquality.hash(iconData), fetchDate);
|
||||
int get hashCode => Object.hash(searchQuery, trigger, searchDate);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is BangIconData &&
|
||||
(other is BangHistoryData &&
|
||||
other.searchQuery == this.searchQuery &&
|
||||
other.trigger == this.trigger &&
|
||||
$driftBlobEquality.equals(other.iconData, this.iconData) &&
|
||||
other.fetchDate == this.fetchDate);
|
||||
other.searchDate == this.searchDate);
|
||||
}
|
||||
|
||||
class BangIconCompanion extends UpdateCompanion<BangIconData> {
|
||||
class BangHistoryCompanion extends UpdateCompanion<BangHistoryData> {
|
||||
final Value<String> searchQuery;
|
||||
final Value<String> trigger;
|
||||
final Value<Uint8List> iconData;
|
||||
final Value<DateTime> fetchDate;
|
||||
final Value<DateTime> searchDate;
|
||||
final Value<int> rowid;
|
||||
const BangIconCompanion({
|
||||
const BangHistoryCompanion({
|
||||
this.searchQuery = const Value.absent(),
|
||||
this.trigger = const Value.absent(),
|
||||
this.iconData = const Value.absent(),
|
||||
this.fetchDate = const Value.absent(),
|
||||
this.searchDate = const Value.absent(),
|
||||
this.rowid = const Value.absent(),
|
||||
});
|
||||
BangIconCompanion.insert({
|
||||
BangHistoryCompanion.insert({
|
||||
required String searchQuery,
|
||||
required String trigger,
|
||||
required Uint8List iconData,
|
||||
required DateTime fetchDate,
|
||||
required DateTime searchDate,
|
||||
this.rowid = const Value.absent(),
|
||||
}) : trigger = Value(trigger),
|
||||
iconData = Value(iconData),
|
||||
fetchDate = Value(fetchDate);
|
||||
static Insertable<BangIconData> custom({
|
||||
}) : searchQuery = Value(searchQuery),
|
||||
trigger = Value(trigger),
|
||||
searchDate = Value(searchDate);
|
||||
static Insertable<BangHistoryData> custom({
|
||||
Expression<String>? searchQuery,
|
||||
Expression<String>? trigger,
|
||||
Expression<Uint8List>? iconData,
|
||||
Expression<DateTime>? fetchDate,
|
||||
Expression<DateTime>? searchDate,
|
||||
Expression<int>? rowid,
|
||||
}) {
|
||||
return RawValuesInsertable({
|
||||
if (searchQuery != null) 'search_query': searchQuery,
|
||||
if (trigger != null) 'trigger': trigger,
|
||||
if (iconData != null) 'icon_data': iconData,
|
||||
if (fetchDate != null) 'fetch_date': fetchDate,
|
||||
if (searchDate != null) 'search_date': searchDate,
|
||||
if (rowid != null) 'rowid': rowid,
|
||||
});
|
||||
}
|
||||
|
||||
BangIconCompanion copyWith(
|
||||
{Value<String>? trigger,
|
||||
Value<Uint8List>? iconData,
|
||||
Value<DateTime>? fetchDate,
|
||||
BangHistoryCompanion copyWith(
|
||||
{Value<String>? searchQuery,
|
||||
Value<String>? trigger,
|
||||
Value<DateTime>? searchDate,
|
||||
Value<int>? rowid}) {
|
||||
return BangIconCompanion(
|
||||
return BangHistoryCompanion(
|
||||
searchQuery: searchQuery ?? this.searchQuery,
|
||||
trigger: trigger ?? this.trigger,
|
||||
iconData: iconData ?? this.iconData,
|
||||
fetchDate: fetchDate ?? this.fetchDate,
|
||||
searchDate: searchDate ?? this.searchDate,
|
||||
rowid: rowid ?? this.rowid,
|
||||
);
|
||||
}
|
||||
@@ -771,14 +773,14 @@ class BangIconCompanion extends UpdateCompanion<BangIconData> {
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
if (searchQuery.present) {
|
||||
map['search_query'] = Variable<String>(searchQuery.value);
|
||||
}
|
||||
if (trigger.present) {
|
||||
map['trigger'] = Variable<String>(trigger.value);
|
||||
}
|
||||
if (iconData.present) {
|
||||
map['icon_data'] = Variable<Uint8List>(iconData.value);
|
||||
}
|
||||
if (fetchDate.present) {
|
||||
map['fetch_date'] = Variable<DateTime>(fetchDate.value);
|
||||
if (searchDate.present) {
|
||||
map['search_date'] = Variable<DateTime>(searchDate.value);
|
||||
}
|
||||
if (rowid.present) {
|
||||
map['rowid'] = Variable<int>(rowid.value);
|
||||
@@ -788,10 +790,10 @@ class BangIconCompanion extends UpdateCompanion<BangIconData> {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('BangIconCompanion(')
|
||||
return (StringBuffer('BangHistoryCompanion(')
|
||||
..write('searchQuery: $searchQuery, ')
|
||||
..write('trigger: $trigger, ')
|
||||
..write('iconData: $iconData, ')
|
||||
..write('fetchDate: $fetchDate, ')
|
||||
..write('searchDate: $searchDate, ')
|
||||
..write('rowid: $rowid')
|
||||
..write(')'))
|
||||
.toString();
|
||||
@@ -985,8 +987,7 @@ class BangDataView extends ViewInfo<BangDataView, BangData>
|
||||
subCategory,
|
||||
format,
|
||||
frequency,
|
||||
lastUsed,
|
||||
iconData
|
||||
lastUsed
|
||||
];
|
||||
@override
|
||||
String get aliasedName => _alias ?? entityName;
|
||||
@@ -995,7 +996,7 @@ class BangDataView extends ViewInfo<BangDataView, BangData>
|
||||
@override
|
||||
Map<SqlDialect, String> get createViewStatements => {
|
||||
SqlDialect.sqlite:
|
||||
'CREATE VIEW bang_data_view AS SELECT b.*, bf.frequency, bf.last_used, bi.icon_data FROM bang AS b LEFT JOIN bang_frequency AS bf ON b."trigger" = bf."trigger" LEFT JOIN bang_icon AS bi ON b."trigger" = bi."trigger"',
|
||||
'CREATE VIEW bang_data_view AS SELECT b.*, bf.frequency, bf.last_used FROM bang AS b LEFT JOIN bang_frequency AS bf ON b."trigger" = bf."trigger"',
|
||||
};
|
||||
@override
|
||||
BangDataView get asDslTable => this;
|
||||
@@ -1055,9 +1056,6 @@ class BangDataView extends ViewInfo<BangDataView, BangData>
|
||||
late final GeneratedColumn<DateTime> lastUsed = GeneratedColumn<DateTime>(
|
||||
'last_used', aliasedName, true,
|
||||
type: DriftSqlType.dateTime);
|
||||
late final GeneratedColumn<Uint8List> iconData = GeneratedColumn<Uint8List>(
|
||||
'icon_data', aliasedName, true,
|
||||
type: DriftSqlType.blob);
|
||||
@override
|
||||
BangDataView createAlias(String alias) {
|
||||
return BangDataView(attachedDatabase, alias);
|
||||
@@ -1066,7 +1064,7 @@ class BangDataView extends ViewInfo<BangDataView, BangData>
|
||||
@override
|
||||
Query? get query => null;
|
||||
@override
|
||||
Set<String> get readTables => const {'bang', 'bang_frequency', 'bang_icon'};
|
||||
Set<String> get readTables => const {'bang', 'bang_frequency'};
|
||||
}
|
||||
|
||||
abstract class _$BangDatabase extends GeneratedDatabase {
|
||||
@@ -1075,7 +1073,7 @@ abstract class _$BangDatabase extends GeneratedDatabase {
|
||||
late final BangTable bang = BangTable(this);
|
||||
late final BangSync bangSync = BangSync(this);
|
||||
late final BangFrequency bangFrequency = BangFrequency(this);
|
||||
late final BangIcon bangIcon = BangIcon(this);
|
||||
late final BangHistory bangHistory = BangHistory(this);
|
||||
late final BangFts bangFts = BangFts(this);
|
||||
late final BangDataView bangDataView = BangDataView(this);
|
||||
late final Trigger bangAfterInsert = Trigger(
|
||||
@@ -1099,13 +1097,12 @@ abstract class _$BangDatabase extends GeneratedDatabase {
|
||||
|
||||
Selectable<BangData> bangQuery({required String query}) {
|
||||
return customSelect(
|
||||
'SELECT b.*, bf.frequency, bf.last_used, bi.icon_data FROM bang_fts(?1)AS fts INNER JOIN bang AS b ON b."rowid" = fts."rowid" LEFT JOIN bang_frequency AS bf ON b."trigger" = bf."trigger" LEFT JOIN bang_icon AS bi ON b."trigger" = bi."trigger" ORDER BY RANK, bf.frequency NULLS LAST',
|
||||
'SELECT b.*, bf.frequency, bf.last_used FROM bang_fts(?1)AS fts INNER JOIN bang AS b ON b."rowid" = fts."rowid" LEFT JOIN bang_frequency AS bf ON b."trigger" = bf."trigger" ORDER BY RANK, bf.frequency NULLS LAST',
|
||||
variables: [
|
||||
Variable<String>(query)
|
||||
],
|
||||
readsFrom: {
|
||||
bangFrequency,
|
||||
bangIcon,
|
||||
bang,
|
||||
bangFts,
|
||||
}).map((QueryRow row) => BangData(
|
||||
@@ -1131,6 +1128,30 @@ abstract class _$BangDatabase extends GeneratedDatabase {
|
||||
}).map((QueryRow row) => row.read<String>('categories_json'));
|
||||
}
|
||||
|
||||
Selectable<SearchHistoryEntry> searchHistoryEntries({required int limit}) {
|
||||
return customSelect(
|
||||
'SELECT * FROM bang_history ORDER BY search_date DESC LIMIT ?1',
|
||||
variables: [
|
||||
Variable<int>(limit)
|
||||
],
|
||||
readsFrom: {
|
||||
bangHistory,
|
||||
}).map((QueryRow row) => SearchHistoryEntry(
|
||||
searchQuery: row.read<String>('search_query'),
|
||||
trigger: row.read<String>('trigger'),
|
||||
searchDate: row.read<DateTime>('search_date'),
|
||||
));
|
||||
}
|
||||
|
||||
Future<int> evictHistoryEntries({required int limit}) {
|
||||
return customUpdate(
|
||||
'DELETE FROM bang_history WHERE "rowid" IN (SELECT "rowid" FROM bang_history ORDER BY search_date DESC LIMIT -1 OFFSET ?1)',
|
||||
variables: [Variable<int>(limit)],
|
||||
updates: {bangHistory},
|
||||
updateKind: UpdateKind.delete,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Iterable<TableInfo<Table, Object?>> get allTables =>
|
||||
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
||||
@@ -1139,7 +1160,7 @@ abstract class _$BangDatabase extends GeneratedDatabase {
|
||||
bang,
|
||||
bangSync,
|
||||
bangFrequency,
|
||||
bangIcon,
|
||||
bangHistory,
|
||||
bangFts,
|
||||
bangDataView,
|
||||
bangAfterInsert,
|
||||
@@ -1156,13 +1177,6 @@ abstract class _$BangDatabase extends GeneratedDatabase {
|
||||
TableUpdate('bang_frequency', kind: UpdateKind.delete),
|
||||
],
|
||||
),
|
||||
WritePropagation(
|
||||
on: TableUpdateQuery.onTableName('bang',
|
||||
limitUpdateKind: UpdateKind.delete),
|
||||
result: [
|
||||
TableUpdate('bang_icon', kind: UpdateKind.delete),
|
||||
],
|
||||
),
|
||||
WritePropagation(
|
||||
on: TableUpdateQuery.onTableName('bang',
|
||||
limitUpdateKind: UpdateKind.insert),
|
||||
@@ -1230,17 +1244,17 @@ final class $BangTableReferences
|
||||
manager.$state.copyWith(prefetchedData: cache));
|
||||
}
|
||||
|
||||
static MultiTypedResultKey<BangIcon, List<BangIconData>> _bangIconRefsTable(
|
||||
_$BangDatabase db) =>
|
||||
MultiTypedResultKey.fromTable(db.bangIcon,
|
||||
static MultiTypedResultKey<BangHistory, List<BangHistoryData>>
|
||||
_bangHistoryRefsTable(_$BangDatabase db) => MultiTypedResultKey.fromTable(
|
||||
db.bangHistory,
|
||||
aliasName:
|
||||
$_aliasNameGenerator(db.bang.trigger, db.bangIcon.trigger));
|
||||
$_aliasNameGenerator(db.bang.trigger, db.bangHistory.trigger));
|
||||
|
||||
$BangIconProcessedTableManager get bangIconRefs {
|
||||
final manager = $BangIconTableManager($_db, $_db.bangIcon)
|
||||
$BangHistoryProcessedTableManager get bangHistoryRefs {
|
||||
final manager = $BangHistoryTableManager($_db, $_db.bangHistory)
|
||||
.filter((f) => f.trigger.trigger($_item.trigger));
|
||||
|
||||
final cache = $_typedResult.readTableOrNull(_bangIconRefsTable($_db));
|
||||
final cache = $_typedResult.readTableOrNull(_bangHistoryRefsTable($_db));
|
||||
return ProcessedTableManager(
|
||||
manager.$state.copyWith(prefetchedData: cache));
|
||||
}
|
||||
@@ -1303,19 +1317,19 @@ class $BangTableFilterComposer extends Composer<_$BangDatabase, BangTable> {
|
||||
return f(composer);
|
||||
}
|
||||
|
||||
Expression<bool> bangIconRefs(
|
||||
Expression<bool> Function($BangIconFilterComposer f) f) {
|
||||
final $BangIconFilterComposer composer = $composerBuilder(
|
||||
Expression<bool> bangHistoryRefs(
|
||||
Expression<bool> Function($BangHistoryFilterComposer f) f) {
|
||||
final $BangHistoryFilterComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.trigger,
|
||||
referencedTable: $db.bangIcon,
|
||||
referencedTable: $db.bangHistory,
|
||||
getReferencedColumn: (t) => t.trigger,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
$BangIconFilterComposer(
|
||||
$BangHistoryFilterComposer(
|
||||
$db: $db,
|
||||
$table: $db.bangIcon,
|
||||
$table: $db.bangHistory,
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
@@ -1411,19 +1425,19 @@ class $BangTableAnnotationComposer extends Composer<_$BangDatabase, BangTable> {
|
||||
return f(composer);
|
||||
}
|
||||
|
||||
Expression<T> bangIconRefs<T extends Object>(
|
||||
Expression<T> Function($BangIconAnnotationComposer a) f) {
|
||||
final $BangIconAnnotationComposer composer = $composerBuilder(
|
||||
Expression<T> bangHistoryRefs<T extends Object>(
|
||||
Expression<T> Function($BangHistoryAnnotationComposer a) f) {
|
||||
final $BangHistoryAnnotationComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.trigger,
|
||||
referencedTable: $db.bangIcon,
|
||||
referencedTable: $db.bangHistory,
|
||||
getReferencedColumn: (t) => t.trigger,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
$BangIconAnnotationComposer(
|
||||
$BangHistoryAnnotationComposer(
|
||||
$db: $db,
|
||||
$table: $db.bangIcon,
|
||||
$table: $db.bangHistory,
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
@@ -1444,7 +1458,7 @@ class $BangTableTableManager extends RootTableManager<
|
||||
$BangTableUpdateCompanionBuilder,
|
||||
(Bang, $BangTableReferences),
|
||||
Bang,
|
||||
PrefetchHooks Function({bool bangFrequencyRefs, bool bangIconRefs})> {
|
||||
PrefetchHooks Function({bool bangFrequencyRefs, bool bangHistoryRefs})> {
|
||||
$BangTableTableManager(_$BangDatabase db, BangTable table)
|
||||
: super(TableManagerState(
|
||||
db: db,
|
||||
@@ -1504,12 +1518,12 @@ class $BangTableTableManager extends RootTableManager<
|
||||
(e.readTable(table), $BangTableReferences(db, table, e)))
|
||||
.toList(),
|
||||
prefetchHooksCallback: (
|
||||
{bangFrequencyRefs = false, bangIconRefs = false}) {
|
||||
{bangFrequencyRefs = false, bangHistoryRefs = false}) {
|
||||
return PrefetchHooks(
|
||||
db: db,
|
||||
explicitlyWatchedTables: [
|
||||
if (bangFrequencyRefs) db.bangFrequency,
|
||||
if (bangIconRefs) db.bangIcon
|
||||
if (bangHistoryRefs) db.bangHistory
|
||||
],
|
||||
addJoins: null,
|
||||
getPrefetchedDataCallback: (items) async {
|
||||
@@ -1526,13 +1540,13 @@ class $BangTableTableManager extends RootTableManager<
|
||||
(item, referencedItems) => referencedItems
|
||||
.where((e) => e.trigger == item.trigger),
|
||||
typedResults: items),
|
||||
if (bangIconRefs)
|
||||
if (bangHistoryRefs)
|
||||
await $_getPrefetchedData(
|
||||
currentTable: table,
|
||||
referencedTable:
|
||||
$BangTableReferences._bangIconRefsTable(db),
|
||||
$BangTableReferences._bangHistoryRefsTable(db),
|
||||
managerFromTypedResult: (p0) =>
|
||||
$BangTableReferences(db, table, p0).bangIconRefs,
|
||||
$BangTableReferences(db, table, p0).bangHistoryRefs,
|
||||
referencedItemsForCurrentItem:
|
||||
(item, referencedItems) => referencedItems
|
||||
.where((e) => e.trigger == item.trigger),
|
||||
@@ -1555,7 +1569,7 @@ typedef $BangTableProcessedTableManager = ProcessedTableManager<
|
||||
$BangTableUpdateCompanionBuilder,
|
||||
(Bang, $BangTableReferences),
|
||||
Bang,
|
||||
PrefetchHooks Function({bool bangFrequencyRefs, bool bangIconRefs})>;
|
||||
PrefetchHooks Function({bool bangFrequencyRefs, bool bangHistoryRefs})>;
|
||||
typedef $BangSyncCreateCompanionBuilder = BangSyncCompanion Function({
|
||||
Value<BangGroup> group,
|
||||
required DateTime lastSync,
|
||||
@@ -1906,25 +1920,25 @@ typedef $BangFrequencyProcessedTableManager = ProcessedTableManager<
|
||||
(BangFrequencyData, $BangFrequencyReferences),
|
||||
BangFrequencyData,
|
||||
PrefetchHooks Function({bool trigger})>;
|
||||
typedef $BangIconCreateCompanionBuilder = BangIconCompanion Function({
|
||||
typedef $BangHistoryCreateCompanionBuilder = BangHistoryCompanion Function({
|
||||
required String searchQuery,
|
||||
required String trigger,
|
||||
required Uint8List iconData,
|
||||
required DateTime fetchDate,
|
||||
required DateTime searchDate,
|
||||
Value<int> rowid,
|
||||
});
|
||||
typedef $BangIconUpdateCompanionBuilder = BangIconCompanion Function({
|
||||
typedef $BangHistoryUpdateCompanionBuilder = BangHistoryCompanion Function({
|
||||
Value<String> searchQuery,
|
||||
Value<String> trigger,
|
||||
Value<Uint8List> iconData,
|
||||
Value<DateTime> fetchDate,
|
||||
Value<DateTime> searchDate,
|
||||
Value<int> rowid,
|
||||
});
|
||||
|
||||
final class $BangIconReferences
|
||||
extends BaseReferences<_$BangDatabase, BangIcon, BangIconData> {
|
||||
$BangIconReferences(super.$_db, super.$_table, super.$_typedResult);
|
||||
final class $BangHistoryReferences
|
||||
extends BaseReferences<_$BangDatabase, BangHistory, BangHistoryData> {
|
||||
$BangHistoryReferences(super.$_db, super.$_table, super.$_typedResult);
|
||||
|
||||
static BangTable _triggerTable(_$BangDatabase db) => db.bang
|
||||
.createAlias($_aliasNameGenerator(db.bangIcon.trigger, db.bang.trigger));
|
||||
static BangTable _triggerTable(_$BangDatabase db) => db.bang.createAlias(
|
||||
$_aliasNameGenerator(db.bangHistory.trigger, db.bang.trigger));
|
||||
|
||||
$BangTableProcessedTableManager get trigger {
|
||||
final manager = $BangTableTableManager($_db, $_db.bang)
|
||||
@@ -1936,19 +1950,19 @@ final class $BangIconReferences
|
||||
}
|
||||
}
|
||||
|
||||
class $BangIconFilterComposer extends Composer<_$BangDatabase, BangIcon> {
|
||||
$BangIconFilterComposer({
|
||||
class $BangHistoryFilterComposer extends Composer<_$BangDatabase, BangHistory> {
|
||||
$BangHistoryFilterComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
ColumnFilters<Uint8List> get iconData => $composableBuilder(
|
||||
column: $table.iconData, builder: (column) => ColumnFilters(column));
|
||||
ColumnFilters<String> get searchQuery => $composableBuilder(
|
||||
column: $table.searchQuery, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<DateTime> get fetchDate => $composableBuilder(
|
||||
column: $table.fetchDate, builder: (column) => ColumnFilters(column));
|
||||
ColumnFilters<DateTime> get searchDate => $composableBuilder(
|
||||
column: $table.searchDate, builder: (column) => ColumnFilters(column));
|
||||
|
||||
$BangTableFilterComposer get trigger {
|
||||
final $BangTableFilterComposer composer = $composerBuilder(
|
||||
@@ -1971,19 +1985,20 @@ class $BangIconFilterComposer extends Composer<_$BangDatabase, BangIcon> {
|
||||
}
|
||||
}
|
||||
|
||||
class $BangIconOrderingComposer extends Composer<_$BangDatabase, BangIcon> {
|
||||
$BangIconOrderingComposer({
|
||||
class $BangHistoryOrderingComposer
|
||||
extends Composer<_$BangDatabase, BangHistory> {
|
||||
$BangHistoryOrderingComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
ColumnOrderings<Uint8List> get iconData => $composableBuilder(
|
||||
column: $table.iconData, builder: (column) => ColumnOrderings(column));
|
||||
ColumnOrderings<String> get searchQuery => $composableBuilder(
|
||||
column: $table.searchQuery, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<DateTime> get fetchDate => $composableBuilder(
|
||||
column: $table.fetchDate, builder: (column) => ColumnOrderings(column));
|
||||
ColumnOrderings<DateTime> get searchDate => $composableBuilder(
|
||||
column: $table.searchDate, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
$BangTableOrderingComposer get trigger {
|
||||
final $BangTableOrderingComposer composer = $composerBuilder(
|
||||
@@ -2006,19 +2021,20 @@ class $BangIconOrderingComposer extends Composer<_$BangDatabase, BangIcon> {
|
||||
}
|
||||
}
|
||||
|
||||
class $BangIconAnnotationComposer extends Composer<_$BangDatabase, BangIcon> {
|
||||
$BangIconAnnotationComposer({
|
||||
class $BangHistoryAnnotationComposer
|
||||
extends Composer<_$BangDatabase, BangHistory> {
|
||||
$BangHistoryAnnotationComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
GeneratedColumn<Uint8List> get iconData =>
|
||||
$composableBuilder(column: $table.iconData, builder: (column) => column);
|
||||
GeneratedColumn<String> get searchQuery => $composableBuilder(
|
||||
column: $table.searchQuery, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<DateTime> get fetchDate =>
|
||||
$composableBuilder(column: $table.fetchDate, builder: (column) => column);
|
||||
GeneratedColumn<DateTime> get searchDate => $composableBuilder(
|
||||
column: $table.searchDate, builder: (column) => column);
|
||||
|
||||
$BangTableAnnotationComposer get trigger {
|
||||
final $BangTableAnnotationComposer composer = $composerBuilder(
|
||||
@@ -2041,55 +2057,55 @@ class $BangIconAnnotationComposer extends Composer<_$BangDatabase, BangIcon> {
|
||||
}
|
||||
}
|
||||
|
||||
class $BangIconTableManager extends RootTableManager<
|
||||
class $BangHistoryTableManager extends RootTableManager<
|
||||
_$BangDatabase,
|
||||
BangIcon,
|
||||
BangIconData,
|
||||
$BangIconFilterComposer,
|
||||
$BangIconOrderingComposer,
|
||||
$BangIconAnnotationComposer,
|
||||
$BangIconCreateCompanionBuilder,
|
||||
$BangIconUpdateCompanionBuilder,
|
||||
(BangIconData, $BangIconReferences),
|
||||
BangIconData,
|
||||
BangHistory,
|
||||
BangHistoryData,
|
||||
$BangHistoryFilterComposer,
|
||||
$BangHistoryOrderingComposer,
|
||||
$BangHistoryAnnotationComposer,
|
||||
$BangHistoryCreateCompanionBuilder,
|
||||
$BangHistoryUpdateCompanionBuilder,
|
||||
(BangHistoryData, $BangHistoryReferences),
|
||||
BangHistoryData,
|
||||
PrefetchHooks Function({bool trigger})> {
|
||||
$BangIconTableManager(_$BangDatabase db, BangIcon table)
|
||||
$BangHistoryTableManager(_$BangDatabase db, BangHistory table)
|
||||
: super(TableManagerState(
|
||||
db: db,
|
||||
table: table,
|
||||
createFilteringComposer: () =>
|
||||
$BangIconFilterComposer($db: db, $table: table),
|
||||
$BangHistoryFilterComposer($db: db, $table: table),
|
||||
createOrderingComposer: () =>
|
||||
$BangIconOrderingComposer($db: db, $table: table),
|
||||
$BangHistoryOrderingComposer($db: db, $table: table),
|
||||
createComputedFieldComposer: () =>
|
||||
$BangIconAnnotationComposer($db: db, $table: table),
|
||||
$BangHistoryAnnotationComposer($db: db, $table: table),
|
||||
updateCompanionCallback: ({
|
||||
Value<String> searchQuery = const Value.absent(),
|
||||
Value<String> trigger = const Value.absent(),
|
||||
Value<Uint8List> iconData = const Value.absent(),
|
||||
Value<DateTime> fetchDate = const Value.absent(),
|
||||
Value<DateTime> searchDate = const Value.absent(),
|
||||
Value<int> rowid = const Value.absent(),
|
||||
}) =>
|
||||
BangIconCompanion(
|
||||
BangHistoryCompanion(
|
||||
searchQuery: searchQuery,
|
||||
trigger: trigger,
|
||||
iconData: iconData,
|
||||
fetchDate: fetchDate,
|
||||
searchDate: searchDate,
|
||||
rowid: rowid,
|
||||
),
|
||||
createCompanionCallback: ({
|
||||
required String searchQuery,
|
||||
required String trigger,
|
||||
required Uint8List iconData,
|
||||
required DateTime fetchDate,
|
||||
required DateTime searchDate,
|
||||
Value<int> rowid = const Value.absent(),
|
||||
}) =>
|
||||
BangIconCompanion.insert(
|
||||
BangHistoryCompanion.insert(
|
||||
searchQuery: searchQuery,
|
||||
trigger: trigger,
|
||||
iconData: iconData,
|
||||
fetchDate: fetchDate,
|
||||
searchDate: searchDate,
|
||||
rowid: rowid,
|
||||
),
|
||||
withReferenceMapper: (p0) => p0
|
||||
.map((e) =>
|
||||
(e.readTable(table), $BangIconReferences(db, table, e)))
|
||||
(e.readTable(table), $BangHistoryReferences(db, table, e)))
|
||||
.toList(),
|
||||
prefetchHooksCallback: ({trigger = false}) {
|
||||
return PrefetchHooks(
|
||||
@@ -2112,9 +2128,9 @@ class $BangIconTableManager extends RootTableManager<
|
||||
state = state.withJoin(
|
||||
currentTable: table,
|
||||
currentColumn: table.trigger,
|
||||
referencedTable: $BangIconReferences._triggerTable(db),
|
||||
referencedTable: $BangHistoryReferences._triggerTable(db),
|
||||
referencedColumn:
|
||||
$BangIconReferences._triggerTable(db).trigger,
|
||||
$BangHistoryReferences._triggerTable(db).trigger,
|
||||
) as T;
|
||||
}
|
||||
|
||||
@@ -2128,17 +2144,17 @@ class $BangIconTableManager extends RootTableManager<
|
||||
));
|
||||
}
|
||||
|
||||
typedef $BangIconProcessedTableManager = ProcessedTableManager<
|
||||
typedef $BangHistoryProcessedTableManager = ProcessedTableManager<
|
||||
_$BangDatabase,
|
||||
BangIcon,
|
||||
BangIconData,
|
||||
$BangIconFilterComposer,
|
||||
$BangIconOrderingComposer,
|
||||
$BangIconAnnotationComposer,
|
||||
$BangIconCreateCompanionBuilder,
|
||||
$BangIconUpdateCompanionBuilder,
|
||||
(BangIconData, $BangIconReferences),
|
||||
BangIconData,
|
||||
BangHistory,
|
||||
BangHistoryData,
|
||||
$BangHistoryFilterComposer,
|
||||
$BangHistoryOrderingComposer,
|
||||
$BangHistoryAnnotationComposer,
|
||||
$BangHistoryCreateCompanionBuilder,
|
||||
$BangHistoryUpdateCompanionBuilder,
|
||||
(BangHistoryData, $BangHistoryReferences),
|
||||
BangHistoryData,
|
||||
PrefetchHooks Function({bool trigger})>;
|
||||
typedef $BangFtsCreateCompanionBuilder = BangFtsCompanion Function({
|
||||
required String trigger,
|
||||
@@ -2266,7 +2282,7 @@ class $BangDatabaseManager {
|
||||
$BangSyncTableManager(_db, _db.bangSync);
|
||||
$BangFrequencyTableManager get bangFrequency =>
|
||||
$BangFrequencyTableManager(_db, _db.bangFrequency);
|
||||
$BangIconTableManager get bangIcon =>
|
||||
$BangIconTableManager(_db, _db.bangIcon);
|
||||
$BangHistoryTableManager get bangHistory =>
|
||||
$BangHistoryTableManager(_db, _db.bangHistory);
|
||||
$BangFtsTableManager get bangFts => $BangFtsTableManager(_db, _db.bangFts);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import 'package:fast_equatable/fast_equatable.dart';
|
||||
|
||||
class SearchHistoryEntry with FastEquatable {
|
||||
final String searchQuery;
|
||||
final String trigger;
|
||||
final DateTime searchDate;
|
||||
|
||||
SearchHistoryEntry({
|
||||
required this.searchQuery,
|
||||
required this.trigger,
|
||||
required this.searchDate,
|
||||
});
|
||||
|
||||
@override
|
||||
bool get cacheHash => true;
|
||||
|
||||
@override
|
||||
List<Object?> get hashParameters => [
|
||||
searchQuery,
|
||||
trigger,
|
||||
searchDate,
|
||||
];
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:lensai/features/bangs/data/models/bang.dart';
|
||||
import 'package:lensai/features/bangs/data/models/bang_data.dart';
|
||||
import 'package:lensai/features/bangs/data/models/search_history_entry.dart';
|
||||
import 'package:lensai/features/bangs/domain/repositories/data.dart';
|
||||
import 'package:lensai/features/bangs/domain/repositories/sync.dart';
|
||||
import 'package:riverpod/riverpod.dart';
|
||||
@@ -50,6 +51,12 @@ Stream<List<BangData>> frequentBangDataList(Ref ref) {
|
||||
return repository.watchFrequentBangs();
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
Stream<List<SearchHistoryEntry>> searchHistory(Ref ref) {
|
||||
final repository = ref.watch(bangDataRepositoryProvider.notifier);
|
||||
return repository.watchSearchHistory(limit: 3); //TODO: make count dynamic
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
Future<BangData> bangDataEnsureIcon(
|
||||
Ref ref,
|
||||
@@ -78,9 +85,3 @@ Stream<int> bangCountOfGroup(
|
||||
final repository = ref.watch(bangDataRepositoryProvider.notifier);
|
||||
return repository.watchBangCount(group);
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
Stream<double> bangIconCacheSizeMegabytes(Ref ref) {
|
||||
final repository = ref.watch(bangDataRepositoryProvider.notifier);
|
||||
return repository.watchIconCacheSize();
|
||||
}
|
||||
|
||||
@@ -368,6 +368,25 @@ final frequentBangDataListProvider =
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef FrequentBangDataListRef = AutoDisposeStreamProviderRef<List<BangData>>;
|
||||
String _$searchHistoryHash() => r'7b97798729643de8e44fd8024cdc02f35124b08b';
|
||||
|
||||
/// See also [searchHistory].
|
||||
@ProviderFor(searchHistory)
|
||||
final searchHistoryProvider =
|
||||
AutoDisposeStreamProvider<List<SearchHistoryEntry>>.internal(
|
||||
searchHistory,
|
||||
name: r'searchHistoryProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$searchHistoryHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef SearchHistoryRef
|
||||
= AutoDisposeStreamProviderRef<List<SearchHistoryEntry>>;
|
||||
String _$bangDataEnsureIconHash() =>
|
||||
r'423b8e92fd89ec328c2fcc8f9e55365c30633fd2';
|
||||
|
||||
@@ -760,25 +779,5 @@ class _BangCountOfGroupProviderElement
|
||||
@override
|
||||
BangGroup get group => (origin as BangCountOfGroupProvider).group;
|
||||
}
|
||||
|
||||
String _$bangIconCacheSizeMegabytesHash() =>
|
||||
r'e08bfdc7bb47a56c079464478f0b8b5516b2effe';
|
||||
|
||||
/// See also [bangIconCacheSizeMegabytes].
|
||||
@ProviderFor(bangIconCacheSizeMegabytes)
|
||||
final bangIconCacheSizeMegabytesProvider =
|
||||
AutoDisposeStreamProvider<double>.internal(
|
||||
bangIconCacheSizeMegabytes,
|
||||
name: r'bangIconCacheSizeMegabytesProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$bangIconCacheSizeMegabytesHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef BangIconCacheSizeMegabytesRef = AutoDisposeStreamProviderRef<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, deprecated_member_use_from_same_package
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import 'package:lensai/features/bangs/data/models/bang_data.dart';
|
||||
import 'package:lensai/features/bangs/domain/repositories/data.dart';
|
||||
import 'package:riverpod/riverpod.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'search.g.dart';
|
||||
|
||||
@Riverpod()
|
||||
Future<Uri> triggerBangSearch(
|
||||
Ref ref,
|
||||
BangData bang,
|
||||
String searchQuery,
|
||||
) async {
|
||||
final bangDataNotifier = ref.watch(bangDataRepositoryProvider.notifier);
|
||||
|
||||
await bangDataNotifier.increaseFrequency(bang.trigger);
|
||||
await bangDataNotifier.addSearchEntry(
|
||||
bang.trigger,
|
||||
searchQuery,
|
||||
maxEntryCount: 3,
|
||||
); //TODO: make count dynamic
|
||||
|
||||
return bang.getUrl(searchQuery);
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'search.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$triggerBangSearchHash() => r'a5cff116ae09b26d4fa476ce6a2e6052fa6fa177';
|
||||
|
||||
/// 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 [triggerBangSearch].
|
||||
@ProviderFor(triggerBangSearch)
|
||||
const triggerBangSearchProvider = TriggerBangSearchFamily();
|
||||
|
||||
/// See also [triggerBangSearch].
|
||||
class TriggerBangSearchFamily extends Family<AsyncValue<Uri>> {
|
||||
/// See also [triggerBangSearch].
|
||||
const TriggerBangSearchFamily();
|
||||
|
||||
/// See also [triggerBangSearch].
|
||||
TriggerBangSearchProvider call(
|
||||
BangData bang,
|
||||
String searchQuery,
|
||||
) {
|
||||
return TriggerBangSearchProvider(
|
||||
bang,
|
||||
searchQuery,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TriggerBangSearchProvider getProviderOverride(
|
||||
covariant TriggerBangSearchProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.bang,
|
||||
provider.searchQuery,
|
||||
);
|
||||
}
|
||||
|
||||
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'triggerBangSearchProvider';
|
||||
}
|
||||
|
||||
/// See also [triggerBangSearch].
|
||||
class TriggerBangSearchProvider extends AutoDisposeFutureProvider<Uri> {
|
||||
/// See also [triggerBangSearch].
|
||||
TriggerBangSearchProvider(
|
||||
BangData bang,
|
||||
String searchQuery,
|
||||
) : this._internal(
|
||||
(ref) => triggerBangSearch(
|
||||
ref as TriggerBangSearchRef,
|
||||
bang,
|
||||
searchQuery,
|
||||
),
|
||||
from: triggerBangSearchProvider,
|
||||
name: r'triggerBangSearchProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$triggerBangSearchHash,
|
||||
dependencies: TriggerBangSearchFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
TriggerBangSearchFamily._allTransitiveDependencies,
|
||||
bang: bang,
|
||||
searchQuery: searchQuery,
|
||||
);
|
||||
|
||||
TriggerBangSearchProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.bang,
|
||||
required this.searchQuery,
|
||||
}) : super.internal();
|
||||
|
||||
final BangData bang;
|
||||
final String searchQuery;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
FutureOr<Uri> Function(TriggerBangSearchRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: TriggerBangSearchProvider._internal(
|
||||
(ref) => create(ref as TriggerBangSearchRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
bang: bang,
|
||||
searchQuery: searchQuery,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeFutureProviderElement<Uri> createElement() {
|
||||
return _TriggerBangSearchProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is TriggerBangSearchProvider &&
|
||||
other.bang == bang &&
|
||||
other.searchQuery == searchQuery;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, bang.hashCode);
|
||||
hash = _SystemHash.combine(hash, searchQuery.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin TriggerBangSearchRef on AutoDisposeFutureProviderRef<Uri> {
|
||||
/// The parameter `bang` of this provider.
|
||||
BangData get bang;
|
||||
|
||||
/// The parameter `searchQuery` of this provider.
|
||||
String get searchQuery;
|
||||
}
|
||||
|
||||
class _TriggerBangSearchProviderElement
|
||||
extends AutoDisposeFutureProviderElement<Uri> with TriggerBangSearchRef {
|
||||
_TriggerBangSearchProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
BangData get bang => (origin as TriggerBangSearchProvider).bang;
|
||||
@override
|
||||
String get searchQuery => (origin as TriggerBangSearchProvider).searchQuery;
|
||||
}
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
@@ -3,10 +3,10 @@ import 'dart:convert';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:exceptions/exceptions.dart';
|
||||
import 'package:lensai/domain/services/generic_website.dart';
|
||||
import 'package:lensai/extensions/database_table_size.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';
|
||||
import 'package:lensai/features/bangs/data/models/search_history_entry.dart';
|
||||
import 'package:lensai/features/bangs/data/providers.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
@@ -63,29 +63,50 @@ class BangDataRepository extends _$BangDataRepository {
|
||||
return _db.bangDao.getFrequentBangDataList(groups: groups).watch();
|
||||
}
|
||||
|
||||
Stream<List<SearchHistoryEntry>> watchSearchHistory({required int limit}) {
|
||||
return _db.searchHistoryEntries(limit: limit).watch();
|
||||
}
|
||||
|
||||
Future<void> increaseFrequency(String trigger) {
|
||||
return _db.bangDao.increaseBangFrequency(trigger);
|
||||
}
|
||||
|
||||
Future<void> addSearchEntry(
|
||||
String trigger,
|
||||
String searchQuery, {
|
||||
required int maxEntryCount,
|
||||
}) {
|
||||
//Pack in a transaction to bundle rebuilds of watch() queries
|
||||
return _db.transaction(
|
||||
() async {
|
||||
await _db.bangDao.addSearchEntry(trigger, searchQuery);
|
||||
await _db.evictHistoryEntries(limit: maxEntryCount);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> removeSearchEntry(String searchQuery) {
|
||||
return _db.bangDao.removeSearchEntry(searchQuery);
|
||||
}
|
||||
|
||||
Future<Result<BangData>> ensureIconAvailable(BangData bang) async {
|
||||
if (bang.icon != null) {
|
||||
return Result.success(bang);
|
||||
}
|
||||
|
||||
final icon = await ref
|
||||
.read(genericWebsiteServiceProvider.notifier)
|
||||
.getUrlFavicon(url: Uri.parse(bang.getUrl('').origin));
|
||||
final url = bang.getUrl('');
|
||||
|
||||
// await _db.bangDao.upsertBangIcon(bang.trigger, icon);
|
||||
return Result.success(bang.copyWith.icon(icon));
|
||||
final websiteProvider = ref.read(genericWebsiteServiceProvider.notifier);
|
||||
final cachedIcon = await websiteProvider.getCachedIcon(url);
|
||||
|
||||
if (cachedIcon != null) {
|
||||
return Result.success(bang.copyWith.icon(cachedIcon));
|
||||
}
|
||||
|
||||
Stream<double> watchIconCacheSize() {
|
||||
return _db.tableSize(_db.bangIcon).watchSingle();
|
||||
}
|
||||
|
||||
Future<int> clearIconData() {
|
||||
return _db.bangIcon.deleteAll();
|
||||
return websiteProvider.fetchPageInfo(url).then(
|
||||
(result) => result
|
||||
.flatMap((pageInfo) => bang.copyWith.icon(pageInfo.favicon)),
|
||||
);
|
||||
}
|
||||
|
||||
Future<int> resetFrequencies() {
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'data.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$bangDataRepositoryHash() =>
|
||||
r'abacc6db3a61958cae67a91cbfd312ae87c641b3';
|
||||
r'b51786552a812ddd54d1418450fe03ff89351e28';
|
||||
|
||||
/// See also [BangDataRepository].
|
||||
@ProviderFor(BangDataRepository)
|
||||
|
||||
@@ -8,8 +8,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/features/bangs/domain/repositories/search.dart';
|
||||
import 'package:lensai/features/bangs/presentation/widgets/bang_details.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/domain/providers.dart';
|
||||
import 'package:lensai/features/settings/data/models/settings.dart';
|
||||
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/settings.dart';
|
||||
import 'package:lensai/presentation/hooks/listenable_callback.dart';
|
||||
import 'package:lensai/presentation/widgets/failure_widget.dart';
|
||||
|
||||
@@ -22,9 +21,7 @@ class BangSearchScreen extends HookConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final resultsAsync = ref.watch(bangSearchProvider);
|
||||
final incognitoEnabled = ref.watch(
|
||||
settingsRepositoryProvider.select(
|
||||
(value) => (value.valueOrNull ?? Settings.withDefaults()).incognitoMode,
|
||||
),
|
||||
settingsRepositoryProvider.select((value) => value.incognitoMode),
|
||||
);
|
||||
|
||||
final textEditingController =
|
||||
|
||||
@@ -5,9 +5,6 @@ import 'package:lensai/core/routing/routes.dart';
|
||||
import 'package:lensai/features/bangs/data/models/bang_data.dart';
|
||||
import 'package:lensai/features/bangs/presentation/widgets/bang_icon.dart';
|
||||
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
|
||||
import 'package:lensai/features/settings/data/models/settings.dart';
|
||||
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
|
||||
import 'package:lensai/utils/ui_helper.dart' as ui_helper;
|
||||
|
||||
class BangDetails extends HookConsumerWidget {
|
||||
final BangData bangData;
|
||||
@@ -71,17 +68,6 @@ class BangDetails extends HookConsumerWidget {
|
||||
onPressed: () async {
|
||||
final url = Uri.parse(bangData.getUrl('').origin);
|
||||
|
||||
final launchExternal = ref.read(
|
||||
settingsRepositoryProvider.select(
|
||||
(value) =>
|
||||
(value.valueOrNull ?? Settings.withDefaults())
|
||||
.launchUrlExternal,
|
||||
),
|
||||
);
|
||||
|
||||
if (launchExternal) {
|
||||
await ui_helper.launchUrlFeedback(context, url);
|
||||
} else {
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.addTab(url: url);
|
||||
@@ -89,7 +75,6 @@ class BangDetails extends HookConsumerWidget {
|
||||
if (context.mounted) {
|
||||
context.go(BrowserRoute().location);
|
||||
}
|
||||
}
|
||||
},
|
||||
label: Text(bangData.domain),
|
||||
icon: const Icon(Icons.open_in_new),
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
// The default Material-style Autocomplete options.
|
||||
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/bangs/data/models/bang_data.dart';
|
||||
import 'package:lensai/features/search/domain/providers/search_suggestions.dart';
|
||||
import 'package:lensai/features/bangs/presentation/widgets/bang_icon.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/speech_to_text_button.dart';
|
||||
import 'package:lensai/features/settings/data/models/settings.dart';
|
||||
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
|
||||
import 'package:lensai/features/search/domain/providers/search_suggestions.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/settings.dart';
|
||||
import 'package:lensai/presentation/widgets/autocomplete.dart';
|
||||
|
||||
class SearchField extends HookConsumerWidget {
|
||||
@@ -31,19 +29,17 @@ class SearchField extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final incognitoEnabled = ref.watch(
|
||||
settingsRepositoryProvider.select(
|
||||
(value) => (value.valueOrNull ?? Settings.withDefaults()).incognitoMode,
|
||||
),
|
||||
settingsRepositoryProvider.select((value) => value.incognitoMode),
|
||||
);
|
||||
|
||||
final optionsStream = ref.watch(searchSuggestionsProvider());
|
||||
|
||||
final focusNode = useFocusNode();
|
||||
|
||||
final quickAnswer = useListenableSelector(
|
||||
textController,
|
||||
() => textController.text.endsWith('?'),
|
||||
);
|
||||
// final quickAnswer = useListenableSelector(
|
||||
// textController,
|
||||
// () => textController.text.endsWith('?'),
|
||||
// );
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@@ -89,7 +85,7 @@ class SearchField extends HookConsumerWidget {
|
||||
child: BangIcon(activeBang!, iconSize: 24.0),
|
||||
)
|
||||
: null,
|
||||
label: const Text('Query'),
|
||||
// label: const Text('Query'),
|
||||
hintText: 'Ask anything...',
|
||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||
suffixIcon: SpeechToTextButton(
|
||||
@@ -113,21 +109,21 @@ class SearchField extends HookConsumerWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
if (activeBang?.domain.endsWith('kagi.com') == true)
|
||||
SwitchListTile(
|
||||
value: quickAnswer,
|
||||
onChanged: (_) {
|
||||
if (textController.text.endsWith('?')) {
|
||||
textController.text = textController.text
|
||||
.substring(0, textController.text.length - 1);
|
||||
} else {
|
||||
textController.text = '${textController.text}?';
|
||||
}
|
||||
},
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: const Text('Quick Answer'),
|
||||
secondary: const Icon(MdiIcons.lightningBolt),
|
||||
),
|
||||
// if (activeBang?.domain.endsWith('kagi.com') == true)
|
||||
// SwitchListTile(
|
||||
// value: quickAnswer,
|
||||
// onChanged: (_) {
|
||||
// if (textController.text.endsWith('?')) {
|
||||
// textController.text = textController.text
|
||||
// .substring(0, textController.text.length - 1);
|
||||
// } else {
|
||||
// textController.text = '${textController.text}?';
|
||||
// }
|
||||
// },
|
||||
// contentPadding: EdgeInsets.zero,
|
||||
// title: const Text('Quick Answer'),
|
||||
// secondary: const Icon(MdiIcons.lightningBolt),
|
||||
// ),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ 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/bangs/data/models/bang_data.dart';
|
||||
import 'package:lensai/features/bangs/domain/repositories/data.dart';
|
||||
import 'package:lensai/features/bangs/domain/providers/search.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/geckoview/domain/repositories/tab.dart';
|
||||
@@ -36,13 +36,11 @@ class SiteSearch extends HookConsumerWidget {
|
||||
|
||||
Future<void> submitSearch() async {
|
||||
if (activeBang != null && (formKey.currentState?.validate() == true)) {
|
||||
await ref
|
||||
.read(bangDataRepositoryProvider.notifier)
|
||||
.increaseFrequency(activeBang.trigger);
|
||||
final searchUri = await ref.read(
|
||||
triggerBangSearchProvider(activeBang, textController.text).future,
|
||||
);
|
||||
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.addTab(url: activeBang.getUrl(textController.text));
|
||||
await ref.read(tabRepositoryProvider.notifier).addTab(url: searchUri);
|
||||
|
||||
ref.read(overlayDialogControllerProvider.notifier).dismiss();
|
||||
}
|
||||
|
||||
@@ -11,10 +11,7 @@ import 'package:lensai/features/chat_archive/domain/entities/chat_entity.dart';
|
||||
import 'package:lensai/features/chat_archive/domain/repositories/archive.dart';
|
||||
import 'package:lensai/features/chat_archive/utils/markdown_to_text.dart';
|
||||
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
|
||||
import 'package:lensai/features/settings/data/models/settings.dart';
|
||||
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
|
||||
import 'package:lensai/presentation/widgets/failure_widget.dart';
|
||||
import 'package:lensai/utils/ui_helper.dart' as ui_helper;
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
|
||||
class ChatArchiveDetailScreen extends HookConsumerWidget {
|
||||
@@ -103,16 +100,6 @@ class ChatArchiveDetailScreen extends HookConsumerWidget {
|
||||
onTapLink: (text, href, title) async {
|
||||
if (href != null) {
|
||||
if (Uri.parse(href) case final Uri url) {
|
||||
final launchExternal = ref.read(
|
||||
settingsRepositoryProvider.select(
|
||||
(value) => (value.valueOrNull ?? Settings.withDefaults())
|
||||
.launchUrlExternal,
|
||||
),
|
||||
);
|
||||
|
||||
if (launchExternal) {
|
||||
await ui_helper.launchUrlFeedback(context, Uri.parse(href));
|
||||
} else {
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.addTab(url: url);
|
||||
@@ -122,7 +109,6 @@ class ChatArchiveDetailScreen extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
error: (error, stackTrace) {
|
||||
|
||||
@@ -10,8 +10,7 @@ import 'package:lensai/core/routing/routes.dart';
|
||||
import 'package:lensai/extensions/date_time.dart';
|
||||
import 'package:lensai/features/chat_archive/domain/entities/chat_entity.dart';
|
||||
import 'package:lensai/features/chat_archive/domain/repositories/search.dart';
|
||||
import 'package:lensai/features/settings/data/models/settings.dart';
|
||||
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/settings.dart';
|
||||
import 'package:lensai/presentation/hooks/listenable_callback.dart';
|
||||
import 'package:lensai/presentation/widgets/failure_widget.dart';
|
||||
|
||||
@@ -22,9 +21,7 @@ class ChatArchiveSearchScreen extends HookConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final resultsAsync = ref.watch(chatArchiveSearchRepositoryProvider);
|
||||
final incognitoEnabled = ref.watch(
|
||||
settingsRepositoryProvider.select(
|
||||
(value) => (value.valueOrNull ?? Settings.withDefaults()).incognitoMode,
|
||||
),
|
||||
settingsRepositoryProvider.select((value) => value.incognitoMode),
|
||||
);
|
||||
|
||||
final textEditingController = useTextEditingController();
|
||||
|
||||
@@ -12,16 +12,14 @@ import 'package:lensai/features/geckoview/domain/entities/security_state.dart';
|
||||
part 'tab_state.g.dart';
|
||||
|
||||
@CopyWith()
|
||||
class TabState with FastEquatable implements WebPageInfo {
|
||||
class TabState extends WebPageInfo with FastEquatable {
|
||||
@CopyWithField(immutable: true)
|
||||
final String id;
|
||||
|
||||
final String? contextId;
|
||||
|
||||
@override
|
||||
final Uri url;
|
||||
@override
|
||||
final String title;
|
||||
String get title => super.title!;
|
||||
|
||||
final EquatableImage? icon;
|
||||
|
||||
@@ -50,8 +48,8 @@ class TabState with FastEquatable implements WebPageInfo {
|
||||
TabState({
|
||||
required this.id,
|
||||
required this.contextId,
|
||||
required this.url,
|
||||
required this.title,
|
||||
required super.url,
|
||||
required String title,
|
||||
required this.icon,
|
||||
required this.thumbnail,
|
||||
required this.progress,
|
||||
@@ -62,7 +60,7 @@ class TabState with FastEquatable implements WebPageInfo {
|
||||
required this.historyState,
|
||||
required this.readerableState,
|
||||
required this.findResultState,
|
||||
});
|
||||
}) : super(title: title);
|
||||
|
||||
factory TabState.$default(String tabId) => TabState(
|
||||
id: tabId,
|
||||
|
||||
@@ -36,8 +36,8 @@ class TabStates extends _$TabStates {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onIconChange(IconEvent event) async {
|
||||
final IconEvent(:tabId, :bytes) = event;
|
||||
Future<void> _onIconChange(IconChangeEvent event) async {
|
||||
final IconChangeEvent(:tabId, :bytes) = event;
|
||||
|
||||
final image = (bytes != null)
|
||||
? (await tryDecodeImage(bytes)
|
||||
@@ -131,7 +131,7 @@ class TabStates extends _$TabStates {
|
||||
await _lock.synchronized(() => _onTabContentStateChange(event));
|
||||
},
|
||||
),
|
||||
eventService.iconEvents.listen(
|
||||
eventService.iconChangeEvents.listen(
|
||||
(event) async {
|
||||
await _lock.synchronized(() => _onIconChange(event));
|
||||
},
|
||||
|
||||
@@ -173,7 +173,7 @@ final selectedTabStateProvider = AutoDisposeProvider<TabState?>.internal(
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef SelectedTabStateRef = AutoDisposeProviderRef<TabState?>;
|
||||
String _$tabStatesHash() => r'dcd293279a901eeaab7728979e39e5ca3bee8c66';
|
||||
String _$tabStatesHash() => r'77f07f330dfac8dc8d28d18737203c7c122f3b74';
|
||||
|
||||
/// See also [TabStates].
|
||||
@ProviderFor(TabStates)
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:lensai/data/models/equatable_iterable.dart';
|
||||
import 'package:lensai/features/bangs/data/models/bang_data.dart';
|
||||
import 'package:lensai/features/bangs/domain/repositories/data.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers/tab_list.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/providers.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/tab_search.dart';
|
||||
import 'package:lensai/features/kagi/data/entities/modes.dart';
|
||||
import 'package:riverpod/riverpod.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
@@ -97,3 +99,34 @@ List<String> availableTabIds(
|
||||
return containerTabs?.where((tabId) => tabStates.contains(tabId)).toList() ??
|
||||
[];
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
List<String> seamlessFilteredTabs(
|
||||
Ref ref,
|
||||
String? containerId,
|
||||
) {
|
||||
final tabSearchResults = ref
|
||||
.watch(
|
||||
tabSearchRepositoryProvider.select(
|
||||
(value) => EquatableCollection(
|
||||
value.valueOrNull?.map((tab) => tab.id).toList(),
|
||||
immutable: true,
|
||||
),
|
||||
),
|
||||
)
|
||||
.collection;
|
||||
|
||||
final availableTabs = ref
|
||||
.watch(
|
||||
availableTabIdsProvider(containerId).select(
|
||||
(value) => EquatableCollection(value, immutable: true),
|
||||
),
|
||||
)
|
||||
.collection;
|
||||
|
||||
if (tabSearchResults == null) {
|
||||
return availableTabs;
|
||||
}
|
||||
|
||||
return tabSearchResults.where((tab) => availableTabs.contains(tab)).toList();
|
||||
}
|
||||
|
||||
@@ -288,6 +288,140 @@ class _AvailableTabIdsProviderElement
|
||||
String? get containerId => (origin as AvailableTabIdsProvider).containerId;
|
||||
}
|
||||
|
||||
String _$seamlessFilteredTabsHash() =>
|
||||
r'1abfb0e9ae834cdde5ab64b6174f24f45f36a272';
|
||||
|
||||
/// See also [seamlessFilteredTabs].
|
||||
@ProviderFor(seamlessFilteredTabs)
|
||||
const seamlessFilteredTabsProvider = SeamlessFilteredTabsFamily();
|
||||
|
||||
/// See also [seamlessFilteredTabs].
|
||||
class SeamlessFilteredTabsFamily extends Family<List<String>> {
|
||||
/// See also [seamlessFilteredTabs].
|
||||
const SeamlessFilteredTabsFamily();
|
||||
|
||||
/// See also [seamlessFilteredTabs].
|
||||
SeamlessFilteredTabsProvider call(
|
||||
String? containerId,
|
||||
) {
|
||||
return SeamlessFilteredTabsProvider(
|
||||
containerId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
SeamlessFilteredTabsProvider getProviderOverride(
|
||||
covariant SeamlessFilteredTabsProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.containerId,
|
||||
);
|
||||
}
|
||||
|
||||
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'seamlessFilteredTabsProvider';
|
||||
}
|
||||
|
||||
/// See also [seamlessFilteredTabs].
|
||||
class SeamlessFilteredTabsProvider extends AutoDisposeProvider<List<String>> {
|
||||
/// See also [seamlessFilteredTabs].
|
||||
SeamlessFilteredTabsProvider(
|
||||
String? containerId,
|
||||
) : this._internal(
|
||||
(ref) => seamlessFilteredTabs(
|
||||
ref as SeamlessFilteredTabsRef,
|
||||
containerId,
|
||||
),
|
||||
from: seamlessFilteredTabsProvider,
|
||||
name: r'seamlessFilteredTabsProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$seamlessFilteredTabsHash,
|
||||
dependencies: SeamlessFilteredTabsFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
SeamlessFilteredTabsFamily._allTransitiveDependencies,
|
||||
containerId: containerId,
|
||||
);
|
||||
|
||||
SeamlessFilteredTabsProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.containerId,
|
||||
}) : super.internal();
|
||||
|
||||
final String? containerId;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
List<String> Function(SeamlessFilteredTabsRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: SeamlessFilteredTabsProvider._internal(
|
||||
(ref) => create(ref as SeamlessFilteredTabsRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
containerId: containerId,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeProviderElement<List<String>> createElement() {
|
||||
return _SeamlessFilteredTabsProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is SeamlessFilteredTabsProvider &&
|
||||
other.containerId == containerId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, containerId.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin SeamlessFilteredTabsRef on AutoDisposeProviderRef<List<String>> {
|
||||
/// The parameter `containerId` of this provider.
|
||||
String? get containerId;
|
||||
}
|
||||
|
||||
class _SeamlessFilteredTabsProviderElement
|
||||
extends AutoDisposeProviderElement<List<String>>
|
||||
with SeamlessFilteredTabsRef {
|
||||
_SeamlessFilteredTabsProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String? get containerId =>
|
||||
(origin as SeamlessFilteredTabsProvider).containerId;
|
||||
}
|
||||
|
||||
String _$selectedBangTriggerHash() =>
|
||||
r'b0e12bc95e93d50d04f1c658230cea9f15ff0385';
|
||||
|
||||
|
||||
+28
-43
@@ -6,7 +6,6 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/data/models/web_page_info.dart';
|
||||
import 'package:lensai/features/bangs/data/models/bang_data.dart';
|
||||
import 'package:lensai/features/bangs/domain/providers/bangs.dart';
|
||||
import 'package:lensai/features/bangs/presentation/widgets/site_search.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers.dart';
|
||||
@@ -14,13 +13,11 @@ import 'package:lensai/features/geckoview/domain/providers/tab_session.dart';
|
||||
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
|
||||
import 'package:lensai/features/kagi/data/entities/modes.dart';
|
||||
import 'package:lensai/features/kagi/utils/url_builder.dart' as uri_builder;
|
||||
import 'package:lensai/features/settings/data/models/settings.dart';
|
||||
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
|
||||
import 'package:lensai/features/share_intent/domain/entities/shared_content.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/settings.dart';
|
||||
import 'package:lensai/presentation/widgets/failure_widget.dart';
|
||||
import 'package:lensai/presentation/widgets/website_title_tile.dart';
|
||||
import 'package:lensai/utils/ui_helper.dart' as ui_helper;
|
||||
import 'package:lensai/utils/uri_parser.dart' as uri_parser;
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
class WebPageDialog extends HookConsumerWidget {
|
||||
@@ -39,9 +36,7 @@ class WebPageDialog extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final incognitoEnabled = ref.watch(
|
||||
settingsRepositoryProvider.select(
|
||||
(value) => (value.valueOrNull ?? Settings.withDefaults()).incognitoMode,
|
||||
),
|
||||
settingsRepositoryProvider.select((value) => value.incognitoMode),
|
||||
);
|
||||
|
||||
final availableBangsAsync = ref.watch(
|
||||
@@ -57,8 +52,9 @@ class WebPageDialog extends HookConsumerWidget {
|
||||
final availableBangCount = availableBangsAsync.valueOrNull?.length;
|
||||
|
||||
final formKey = useMemoized(() => GlobalKey<FormState>());
|
||||
final urlTextController = useTextEditingController(text: url.toString());
|
||||
final urlTextFocusNode = useFocusNode();
|
||||
final addressTextController =
|
||||
useTextEditingController(text: url.toString());
|
||||
final addressTextFocusNode = useFocusNode();
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
@@ -83,46 +79,35 @@ class WebPageDialog extends HookConsumerWidget {
|
||||
child: Form(
|
||||
key: formKey,
|
||||
child: TextFormField(
|
||||
controller: urlTextController,
|
||||
focusNode: urlTextFocusNode,
|
||||
controller: addressTextController,
|
||||
focusNode: addressTextFocusNode,
|
||||
enableIMEPersonalizedLearning: !incognitoEnabled,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'URL',
|
||||
suffixIcon: IconButton(
|
||||
onPressed: () async {
|
||||
if (formKey.currentState?.validate() ?? false) {
|
||||
await ref
|
||||
.read(tabSessionProvider(tabId: null).notifier)
|
||||
.loadUrl(
|
||||
url: uri_parser.tryParseUrl(
|
||||
urlTextController.text,
|
||||
eagerParsing: true,
|
||||
)!,
|
||||
);
|
||||
|
||||
onDismiss?.call();
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.send),
|
||||
),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Address',
|
||||
),
|
||||
validator: (value) {
|
||||
if (uri_parser.tryParseUrl(value, eagerParsing: true) !=
|
||||
null) {
|
||||
if (value != null && Uri.tryParse(value) != null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return 'Invalid URL';
|
||||
return 'Invalid Address';
|
||||
},
|
||||
onTap: () {
|
||||
if (!urlTextFocusNode.hasFocus) {
|
||||
if (!addressTextFocusNode.hasFocus) {
|
||||
// Select all text when the field is tapped
|
||||
urlTextController.selection = TextSelection(
|
||||
addressTextController.selection = TextSelection(
|
||||
baseOffset: 0,
|
||||
extentOffset: urlTextController.text.length,
|
||||
extentOffset: addressTextController.text.length,
|
||||
);
|
||||
}
|
||||
},
|
||||
onFieldSubmitted: (value) async {
|
||||
await ref
|
||||
.read(tabSessionProvider(tabId: null).notifier)
|
||||
.loadUrl(url: Uri.tryParse(value)!);
|
||||
|
||||
onDismiss?.call();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -148,12 +133,12 @@ class WebPageDialog extends HookConsumerWidget {
|
||||
loading: () => SiteSearch(
|
||||
domain: url.host,
|
||||
availableBangs: [
|
||||
BangData(
|
||||
websiteName: 'websiteName',
|
||||
domain: 'domain',
|
||||
trigger: 'trigger',
|
||||
urlTemplate: 'urlTemplate',
|
||||
),
|
||||
// BangData(
|
||||
// websiteName: 'websiteName',
|
||||
// domain: 'domain',
|
||||
// trigger: 'trigger',
|
||||
// urlTemplate: 'urlTemplate',
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -181,7 +166,7 @@ class WebPageDialog extends HookConsumerWidget {
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(MdiIcons.tabPlus),
|
||||
title: const Text('Open in new tab'),
|
||||
title: const Text('Clone tab'),
|
||||
onTap: () async {
|
||||
await ref.read(tabRepositoryProvider.notifier).addTab(url: url);
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||
@@ -30,13 +28,10 @@ import 'package:lensai/features/geckoview/features/find_in_page/presentation/wid
|
||||
import 'package:lensai/features/geckoview/features/readerview/presentation/widgets/reader_appearance_button.dart';
|
||||
import 'package:lensai/features/geckoview/features/readerview/presentation/widgets/reader_button.dart';
|
||||
import 'package:lensai/features/kagi/data/entities/modes.dart';
|
||||
import 'package:lensai/features/kagi/data/services/session.dart';
|
||||
import 'package:lensai/features/settings/data/models/settings.dart';
|
||||
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/settings.dart';
|
||||
import 'package:lensai/presentation/hooks/overlay_portal_controller.dart';
|
||||
import 'package:lensai/utils/ui_helper.dart' as ui_helper;
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:speech_to_text_google_dialog/speech_to_text_google_dialog.dart';
|
||||
|
||||
class BrowserScreen extends HookConsumerWidget {
|
||||
const BrowserScreen({super.key});
|
||||
@@ -54,26 +49,6 @@ class BrowserScreen extends HookConsumerWidget {
|
||||
final displayedSheet = ref.watch(bottomSheetControllerProvider);
|
||||
final displayedOverlayDialog = ref.watch(overlayDialogControllerProvider);
|
||||
|
||||
final showEarlyAccessFeatures = ref.watch(
|
||||
settingsRepositoryProvider.select(
|
||||
(value) => (value.valueOrNull ?? Settings.withDefaults())
|
||||
.showEarlyAccessFeatures,
|
||||
),
|
||||
);
|
||||
|
||||
final quickAction = ref.watch(
|
||||
settingsRepositoryProvider.select(
|
||||
(value) => (value.valueOrNull ?? Settings.withDefaults()).quickAction,
|
||||
),
|
||||
);
|
||||
|
||||
final quickActionVoice = ref.watch(
|
||||
settingsRepositoryProvider.select(
|
||||
(value) => (value.valueOrNull ?? Settings.withDefaults())
|
||||
.quickActionVoiceInput,
|
||||
),
|
||||
);
|
||||
|
||||
final selectedTabId =
|
||||
ref.watch(selectedTabStateProvider.select((value) => value?.id));
|
||||
|
||||
@@ -97,26 +72,23 @@ class BrowserScreen extends HookConsumerWidget {
|
||||
);
|
||||
|
||||
ref.listen(
|
||||
settingsRepositoryProvider
|
||||
.select((value) => value.valueOrNull?.enableJavascript),
|
||||
settingsRepositoryProvider.select((value) => value.enableJavascript),
|
||||
(previous, next) async {
|
||||
if (next != null) {
|
||||
await ref.read(engineSettingsServiceProvider).javaScriptEnabled(next);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
ref.listen(
|
||||
settingsRepositoryProvider
|
||||
.select((value) => value.valueOrNull?.kagiSession),
|
||||
(previous, next) async {
|
||||
if (next != null && next.isNotEmpty) {
|
||||
await ref
|
||||
.read(kagiSessionServiceProvider.notifier)
|
||||
.setKagiSession(next);
|
||||
}
|
||||
},
|
||||
);
|
||||
// ref.listen(
|
||||
// settingsRepositoryProvider
|
||||
// .select((value) => value.valueOrNull?.kagiSession),
|
||||
// (previous, next) async {
|
||||
// if (next != null && next.isNotEmpty) {
|
||||
// await ref
|
||||
// .read(kagiSessionServiceProvider.notifier)
|
||||
// .setKagiSession(next);
|
||||
// }
|
||||
// },
|
||||
// );
|
||||
|
||||
return PopScope(
|
||||
//We need this for BackButtonListener to work downstream
|
||||
@@ -217,78 +189,78 @@ class BrowserScreen extends HookConsumerWidget {
|
||||
},
|
||||
icon: Icon(KagiTool.summarizer.icon),
|
||||
),
|
||||
if (showEarlyAccessFeatures)
|
||||
IconButton(
|
||||
color: (activeTool == KagiTool.assistant)
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: null,
|
||||
onPressed: () {
|
||||
ref
|
||||
.read(
|
||||
createTabStreamProvider.notifier,
|
||||
)
|
||||
.createTab(
|
||||
CreateTabSheet(
|
||||
preferredTool: KagiTool.assistant,
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: Icon(KagiTool.assistant.icon),
|
||||
),
|
||||
// if (showEarlyAccessFeatures)
|
||||
// IconButton(
|
||||
// color: (activeTool == KagiTool.assistant)
|
||||
// ? Theme.of(context).colorScheme.primary
|
||||
// : null,
|
||||
// onPressed: () {
|
||||
// ref
|
||||
// .read(
|
||||
// createTabStreamProvider.notifier,
|
||||
// )
|
||||
// .createTab(
|
||||
// CreateTabSheet(
|
||||
// preferredTool: KagiTool.assistant,
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// icon: Icon(KagiTool.assistant.icon),
|
||||
// ),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
if (selectedTabId != null) ReaderButton(),
|
||||
if (quickAction != null)
|
||||
InkWell(
|
||||
onTap: () async {
|
||||
var tab = CreateTabSheet(
|
||||
preferredTool: quickAction,
|
||||
);
|
||||
// if (quickAction != null)
|
||||
// InkWell(
|
||||
// onTap: () async {
|
||||
// var tab = CreateTabSheet(
|
||||
// preferredTool: quickAction,
|
||||
// );
|
||||
|
||||
if (quickActionVoice) {
|
||||
final completer = Completer<String>();
|
||||
// if (quickActionVoice) {
|
||||
// final completer = Completer<String>();
|
||||
|
||||
final isServiceAvailable =
|
||||
await SpeechToTextGoogleDialog.getInstance()
|
||||
.showGoogleDialog(
|
||||
onTextReceived: (data) {
|
||||
completer.complete(data.toString());
|
||||
},
|
||||
// locale: "en-US",
|
||||
);
|
||||
// final isServiceAvailable =
|
||||
// await SpeechToTextGoogleDialog.getInstance()
|
||||
// .showGoogleDialog(
|
||||
// onTextReceived: (data) {
|
||||
// completer.complete(data.toString());
|
||||
// },
|
||||
// // locale: "en-US",
|
||||
// );
|
||||
|
||||
if (!isServiceAvailable) {
|
||||
if (context.mounted) {
|
||||
ui_helper.showErrorMessage(
|
||||
context,
|
||||
'Service is not available',
|
||||
);
|
||||
}
|
||||
}
|
||||
// if (!isServiceAvailable) {
|
||||
// if (context.mounted) {
|
||||
// ui_helper.showErrorMessage(
|
||||
// context,
|
||||
// 'Service is not available',
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
tab = CreateTabSheet(
|
||||
preferredTool: quickAction,
|
||||
content: await completer.future,
|
||||
);
|
||||
}
|
||||
// tab = CreateTabSheet(
|
||||
// preferredTool: quickAction,
|
||||
// content: await completer.future,
|
||||
// );
|
||||
// }
|
||||
|
||||
ref
|
||||
.read(createTabStreamProvider.notifier)
|
||||
.createTab(tab);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 15.0,
|
||||
horizontal: 8.0,
|
||||
),
|
||||
child: Icon(
|
||||
quickActionVoice ? Icons.mic : quickAction.icon,
|
||||
),
|
||||
),
|
||||
),
|
||||
// ref
|
||||
// .read(createTabStreamProvider.notifier)
|
||||
// .createTab(tab);
|
||||
// },
|
||||
// child: Padding(
|
||||
// padding: const EdgeInsets.symmetric(
|
||||
// vertical: 15.0,
|
||||
// horizontal: 8.0,
|
||||
// ),
|
||||
// child: Icon(
|
||||
// quickActionVoice ? Icons.mic : quickAction.icon,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
TabsActionButton(
|
||||
isActive: displayedSheet is ViewTabsSheet,
|
||||
onTap: () {
|
||||
@@ -394,8 +366,7 @@ class BrowserScreen extends HookConsumerWidget {
|
||||
),
|
||||
MenuItemButton(
|
||||
onPressed: () async {
|
||||
await ref
|
||||
.read(addonServiceProvider)
|
||||
await addonService
|
||||
.startAddonManagerActivity();
|
||||
},
|
||||
leadingIcon: const Icon(MdiIcons.puzzleEdit),
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:lensai/features/geckoview/domain/providers.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers/web_extensions_state.dart';
|
||||
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/cache.dart';
|
||||
|
||||
class BrowserView extends StatefulHookConsumerWidget {
|
||||
final Duration screenshotPeriod;
|
||||
@@ -101,6 +102,11 @@ class _BrowserViewState extends ConsumerState<BrowserView>
|
||||
webExtensionsStateProvider(WebExtensionActionType.page),
|
||||
(previous, next) {},
|
||||
);
|
||||
|
||||
ref.listenManual(
|
||||
cacheRepositoryProvider,
|
||||
(previous, next) {},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
+8
-6
@@ -4,17 +4,19 @@ import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/core/routing/routes.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/error_container.dart';
|
||||
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
|
||||
|
||||
class LandingAction extends HookConsumerWidget {
|
||||
const LandingAction({super.key});
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final sessionTokenAvailable = ref.watch(
|
||||
settingsRepositoryProvider.select(
|
||||
(value) => value.valueOrNull?.kagiSession?.isNotEmpty ?? false,
|
||||
),
|
||||
);
|
||||
//TODO: REWORK!
|
||||
final sessionTokenAvailable = false;
|
||||
|
||||
// ref.watch(
|
||||
// settingsRepositoryProvider.select(
|
||||
// (value) => value.valueOrNull?.kagiSession?.isNotEmpty ?? false,
|
||||
// ),
|
||||
// );
|
||||
|
||||
return Visibility(
|
||||
visible: !sessionTokenAvailable,
|
||||
|
||||
+20
-28
@@ -4,11 +4,8 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/domain/entities/sheet.dart';
|
||||
import 'package:lensai/features/kagi/data/entities/modes.dart';
|
||||
import 'package:lensai/features/kagi/presentation/tabs/assistant_tab.dart';
|
||||
import 'package:lensai/features/kagi/presentation/tabs/search_tab.dart';
|
||||
import 'package:lensai/features/kagi/presentation/tabs/summarize_tab.dart';
|
||||
import 'package:lensai/features/settings/data/models/settings.dart';
|
||||
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
|
||||
import 'package:lensai/features/share_intent/domain/entities/shared_content.dart';
|
||||
import 'package:lensai/presentation/hooks/sync_page_tab.dart';
|
||||
|
||||
@@ -27,13 +24,6 @@ class CreateTabSheetWidget extends HookConsumerWidget {
|
||||
});
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final showEarlyAccessFeatures = ref.watch(
|
||||
settingsRepositoryProvider.select(
|
||||
(value) => (value.valueOrNull ?? Settings.withDefaults())
|
||||
.showEarlyAccessFeatures,
|
||||
),
|
||||
);
|
||||
|
||||
final sharedContent = useMemoized(
|
||||
() => (parameter.content != null)
|
||||
? SharedContent.parse(parameter.content!)
|
||||
@@ -45,20 +35,22 @@ class CreateTabSheetWidget extends HookConsumerWidget {
|
||||
() =>
|
||||
parameter.preferredTool?.index ??
|
||||
switch (sharedContent) {
|
||||
SharedText(text: final text) =>
|
||||
(showEarlyAccessFeatures && text.length > 25)
|
||||
? KagiTool.assistant.index
|
||||
: KagiTool.search.index,
|
||||
SharedText(text: final text) => KagiTool.search.index
|
||||
// (showEarlyAccessFeatures && text.length > 25)
|
||||
// ? KagiTool.assistant.index
|
||||
// : KagiTool.search.index,
|
||||
,
|
||||
SharedUrl() => KagiTool.summarizer.index,
|
||||
null => showEarlyAccessFeatures
|
||||
? KagiTool.assistant.index
|
||||
: KagiTool.search.index,
|
||||
null => KagiTool.search.index
|
||||
// showEarlyAccessFeatures
|
||||
// ? KagiTool.assistant.index
|
||||
// : KagiTool.search.index,
|
||||
},
|
||||
[sharedContent],
|
||||
);
|
||||
|
||||
final tabController = useTabController(
|
||||
initialLength: KagiTool.values.length - (showEarlyAccessFeatures ? 0 : 1),
|
||||
initialLength: KagiTool.values.length - 1,
|
||||
initialIndex: initialIndex,
|
||||
);
|
||||
final pageController = usePageController(initialPage: tabController.index);
|
||||
@@ -95,11 +87,11 @@ class CreateTabSheetWidget extends HookConsumerWidget {
|
||||
icon: Icon(KagiTool.summarizer.icon),
|
||||
text: 'Summarize',
|
||||
),
|
||||
if (showEarlyAccessFeatures)
|
||||
Tab(
|
||||
icon: Icon(KagiTool.assistant.icon),
|
||||
text: 'Assistant',
|
||||
),
|
||||
// if (showEarlyAccessFeatures)
|
||||
// Tab(
|
||||
// icon: Icon(KagiTool.assistant.icon),
|
||||
// text: 'Assistant',
|
||||
// ),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
@@ -115,11 +107,11 @@ class CreateTabSheetWidget extends HookConsumerWidget {
|
||||
sharedContent: sharedContent,
|
||||
onSubmit: onSubmit,
|
||||
),
|
||||
if (showEarlyAccessFeatures)
|
||||
AssistantTab(
|
||||
sharedContent: sharedContent,
|
||||
onSubmit: onSubmit,
|
||||
),
|
||||
// if (showEarlyAccessFeatures)
|
||||
// AssistantTab(
|
||||
// sharedContent: sharedContent,
|
||||
// onSubmit: onSubmit,
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
+135
-41
@@ -10,48 +10,65 @@ import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
|
||||
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/domain/providers.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/presentation/dialogs/tab_action.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/speech_to_text_button.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/tab_preview.dart';
|
||||
import 'package:lensai/features/geckoview/features/controllers/overlay_dialog.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/providers/selected_container.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/container.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/tab.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/tab_search.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/presentation/widgets/container_chips.dart';
|
||||
import 'package:lensai/presentation/hooks/listenable_callback.dart';
|
||||
import 'package:reorderable_grid/reorderable_grid.dart';
|
||||
|
||||
class _SliverHeaderDelagate extends SliverPersistentHeaderDelegate {
|
||||
static const _headerSize = 104.0;
|
||||
|
||||
class _Tab extends HookConsumerWidget {
|
||||
final VoidCallback onClose;
|
||||
|
||||
_SliverHeaderDelagate({required this.onClose});
|
||||
const _Tab({required this.onClose});
|
||||
|
||||
@override
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
double shrinkOffset,
|
||||
bool overlapsContent,
|
||||
) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final searchMode = useState(false);
|
||||
final searchTextFocus = useFocusNode();
|
||||
final searchTextController = useTextEditingController();
|
||||
|
||||
final hasSearchText = useListenableSelector(
|
||||
searchTextController,
|
||||
() => searchTextController.text.isNotEmpty,
|
||||
);
|
||||
|
||||
useListenableCallback(
|
||||
searchTextController,
|
||||
() async {
|
||||
await ref
|
||||
.read(tabSearchRepositoryProvider.notifier)
|
||||
.addQuery(searchTextController.text);
|
||||
},
|
||||
);
|
||||
|
||||
return Material(
|
||||
child: Consumer(
|
||||
builder: (context, ref, child) {
|
||||
//Fix layout issue https://github.com/flutter/flutter/issues/78748#issuecomment-1194680555
|
||||
return Align(
|
||||
child: Align(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: 8.0,
|
||||
left: 8.0,
|
||||
top: 8.0,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (!searchMode.value)
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
TextButton.icon(
|
||||
onPressed: () async {
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.addTab(url: Uri.https('kagi.com'));
|
||||
|
||||
onClose();
|
||||
icon: const Icon(Icons.search),
|
||||
label: const Text('Search'),
|
||||
onPressed: () {
|
||||
searchMode.value = true;
|
||||
searchTextFocus.requestFocus();
|
||||
},
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('New Tab'),
|
||||
),
|
||||
TextButton.icon(
|
||||
onPressed: () async {
|
||||
@@ -66,9 +83,39 @@ class _SliverHeaderDelagate extends SliverPersistentHeaderDelegate {
|
||||
label: const Text('Close All'),
|
||||
),
|
||||
],
|
||||
)
|
||||
else
|
||||
TextField(
|
||||
controller: searchTextController,
|
||||
focusNode: searchTextFocus,
|
||||
// enableIMEPersonalizedLearning: !incognitoEnabled,
|
||||
decoration: InputDecoration(
|
||||
// border: InputBorder.none,
|
||||
prefixIcon: const Icon(Icons.search),
|
||||
hintText: 'Search inside tabs...',
|
||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||
suffixIcon: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (!hasSearchText)
|
||||
SpeechToTextButton(
|
||||
onTextReceived: (data) {
|
||||
searchTextController.text = data.toString();
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
searchTextController.clear();
|
||||
searchMode.value = false;
|
||||
},
|
||||
icon: const Icon(Icons.clear),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0),
|
||||
padding: const EdgeInsets.only(left: 8.0, top: 12),
|
||||
child: Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final selectedContainer = ref.watch(
|
||||
@@ -95,17 +142,33 @@ class _SliverHeaderDelagate extends SliverPersistentHeaderDelegate {
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SliverHeaderDelagate extends SliverPersistentHeaderDelegate {
|
||||
static const headerSize = 124.0;
|
||||
|
||||
final VoidCallback onClose;
|
||||
|
||||
_SliverHeaderDelagate({required this.onClose});
|
||||
|
||||
@override
|
||||
double get minExtent => _headerSize;
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
double shrinkOffset,
|
||||
bool overlapsContent,
|
||||
) {
|
||||
return _Tab(onClose: onClose);
|
||||
}
|
||||
|
||||
@override
|
||||
double get maxExtent => _headerSize;
|
||||
double get minExtent => headerSize;
|
||||
|
||||
@override
|
||||
double get maxExtent => headerSize;
|
||||
|
||||
@override
|
||||
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) =>
|
||||
@@ -143,7 +206,10 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return CustomScrollView(
|
||||
return Stack(
|
||||
alignment: Alignment.bottomRight,
|
||||
children: [
|
||||
CustomScrollView(
|
||||
controller: sheetScrollController,
|
||||
slivers: [
|
||||
SliverPersistentHeader(
|
||||
@@ -153,13 +219,15 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
||||
HookConsumer(
|
||||
builder: (context, ref, child) {
|
||||
final container = ref.watch(selectedContainerProvider);
|
||||
final availableTabs = ref
|
||||
|
||||
final filteredTabs = ref
|
||||
.watch(
|
||||
availableTabIdsProvider(container).select(
|
||||
seamlessFilteredTabsProvider(container).select(
|
||||
(value) => EquatableCollection(value, immutable: true),
|
||||
),
|
||||
)
|
||||
.collection;
|
||||
|
||||
final activeTab = ref.watch(selectedTabProvider);
|
||||
|
||||
final itemHeight = useMemoized(
|
||||
@@ -176,8 +244,8 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
final index =
|
||||
availableTabs.indexWhere((webView) => webView == activeTab);
|
||||
final index = filteredTabs
|
||||
.indexWhere((webView) => webView == activeTab);
|
||||
|
||||
if (index > -1) {
|
||||
final offset = (index ~/ 2) * itemHeight;
|
||||
@@ -195,13 +263,15 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
||||
|
||||
return null;
|
||||
},
|
||||
[availableTabs, activeTab],
|
||||
[filteredTabs, activeTab],
|
||||
);
|
||||
|
||||
final tabs = useMemoized(
|
||||
() => availableTabs
|
||||
() {
|
||||
return filteredTabs
|
||||
.mapIndexed(
|
||||
(index, tabId) => ReorderableGridDelayedDragStartListener(
|
||||
(index, tabId) =>
|
||||
ReorderableGridDelayedDragStartListener(
|
||||
key: ValueKey(tabId),
|
||||
index: index,
|
||||
child: Consumer(
|
||||
@@ -217,7 +287,8 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
||||
onClose();
|
||||
await ref
|
||||
.read(
|
||||
tabRepositoryProvider.notifier,
|
||||
tabRepositoryProvider
|
||||
.notifier,
|
||||
)
|
||||
.selectTab(tab.id);
|
||||
} else {
|
||||
@@ -255,14 +326,19 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
[availableTabs, activeTab],
|
||||
.toList();
|
||||
},
|
||||
[
|
||||
EquatableCollection(filteredTabs, immutable: true),
|
||||
activeTab
|
||||
],
|
||||
);
|
||||
|
||||
return SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
sliver: SliverReorderableGrid(
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
gridDelegate:
|
||||
const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
//Sync values for itemHeight calculation _calculateItemHeight
|
||||
childAspectRatio: 0.75,
|
||||
mainAxisSpacing: 8.0,
|
||||
@@ -275,7 +351,7 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
||||
final containerRepository =
|
||||
ref.read(containerRepositoryProvider.notifier);
|
||||
|
||||
final tabId = availableTabs[oldIndex];
|
||||
final tabId = filteredTabs[oldIndex];
|
||||
final containerId = await ref
|
||||
.read(tabDataRepositoryProvider.notifier)
|
||||
.containerTabId(tabId);
|
||||
@@ -284,13 +360,13 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
||||
if (newIndex <= 0) {
|
||||
key = await containerRepository
|
||||
.getLeadingOrderKey(containerId);
|
||||
} else if (newIndex >= availableTabs.length - 1) {
|
||||
} else if (newIndex >= filteredTabs.length - 1) {
|
||||
key = await containerRepository
|
||||
.getTrailingOrderKey(containerId);
|
||||
} else {
|
||||
final orderAfterIndex = newIndex - 1;
|
||||
final orderAfterIndex = newIndex;
|
||||
key = await containerRepository.getOrderKeyAfterTab(
|
||||
availableTabs[orderAfterIndex],
|
||||
filteredTabs[orderAfterIndex],
|
||||
containerId,
|
||||
);
|
||||
}
|
||||
@@ -304,6 +380,24 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: _SliverHeaderDelagate.headerSize + 4,
|
||||
right: 4,
|
||||
),
|
||||
child: FloatingActionButton.small(
|
||||
onPressed: () async {
|
||||
await ref
|
||||
.read(tabRepositoryProvider.notifier)
|
||||
.addTab(url: Uri.https('kagi.com'));
|
||||
|
||||
onClose();
|
||||
},
|
||||
child: Icon(Icons.add),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +91,7 @@ class TabPreview extends StatelessWidget {
|
||||
),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: RepaintBoundary(
|
||||
child: RawImage(
|
||||
image: tab.thumbnail!.value,
|
||||
fit: BoxFit.fitWidth,
|
||||
@@ -98,6 +99,7 @@ class TabPreview extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:lensai/core/logger.dart';
|
||||
import 'package:lensai/features/geckoview/domain/entities/tab_state.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/database/database.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/models/tab_query_result.dart';
|
||||
|
||||
@@ -138,6 +138,7 @@ queryTabsFullContent WITH TabQueryResult:
|
||||
1.0 as full_weight -- Full content matches less important
|
||||
)
|
||||
SELECT
|
||||
t.id,
|
||||
highlight(tab_fts, 0, :beforeMatch, :afterMatch) AS title,
|
||||
highlight(tab_fts, 1, :beforeMatch, :afterMatch) AS url,
|
||||
snippet(tab_fts, 2, :beforeMatch, :afterMatch, :ellipsis, :snippetLength) AS extracted_content,
|
||||
|
||||
@@ -977,6 +977,7 @@ abstract class _$TabDatabase extends GeneratedDatabase {
|
||||
tab,
|
||||
tabFts,
|
||||
}).map((QueryRow row) => TabQueryResult(
|
||||
id: row.read<String>('id'),
|
||||
title: row.read<String>('title'),
|
||||
url: row.read<String>('url'),
|
||||
weightedRank: row.read<double>('weighted_rank'),
|
||||
@@ -990,7 +991,7 @@ abstract class _$TabDatabase extends GeneratedDatabase {
|
||||
required int snippetLength,
|
||||
required String query}) {
|
||||
return customSelect(
|
||||
'WITH weights AS (SELECT 10.0 AS title_weight, 5.0 AS url_weight, 3.0 AS extracted_weight, 1.0 AS full_weight) SELECT highlight(tab_fts, 0, ?1, ?2) AS title, highlight(tab_fts, 1, ?1, ?2) AS url, snippet(tab_fts, 2, ?1, ?2, ?3, ?4) AS extracted_content, snippet(tab_fts, 3, ?1, ?2, ?3, ?4) AS full_content,(bm25(tab_fts, weights.title_weight, weights.url_weight, weights.extracted_weight, weights.full_weight))AS weighted_rank FROM tab_fts(?5)AS fts INNER JOIN tab AS t ON t."rowid" = fts."rowid" CROSS JOIN weights ORDER BY weighted_rank ASC, t.timestamp DESC',
|
||||
'WITH weights AS (SELECT 10.0 AS title_weight, 5.0 AS url_weight, 3.0 AS extracted_weight, 1.0 AS full_weight) SELECT t.id, highlight(tab_fts, 0, ?1, ?2) AS title, highlight(tab_fts, 1, ?1, ?2) AS url, snippet(tab_fts, 2, ?1, ?2, ?3, ?4) AS extracted_content, snippet(tab_fts, 3, ?1, ?2, ?3, ?4) AS full_content,(bm25(tab_fts, weights.title_weight, weights.url_weight, weights.extracted_weight, weights.full_weight))AS weighted_rank FROM tab_fts(?5)AS fts INNER JOIN tab AS t ON t."rowid" = fts."rowid" CROSS JOIN weights ORDER BY weighted_rank ASC, t.timestamp DESC',
|
||||
variables: [
|
||||
Variable<String>(beforeMatch),
|
||||
Variable<String>(afterMatch),
|
||||
@@ -999,9 +1000,10 @@ abstract class _$TabDatabase extends GeneratedDatabase {
|
||||
Variable<String>(query)
|
||||
],
|
||||
readsFrom: {
|
||||
tabFts,
|
||||
tab,
|
||||
tabFts,
|
||||
}).map((QueryRow row) => TabQueryResult(
|
||||
id: row.read<String>('id'),
|
||||
title: row.read<String>('title'),
|
||||
url: row.read<String>('url'),
|
||||
extractedContent: row.read<String>('extracted_content'),
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
class TabQueryResult {
|
||||
final String id;
|
||||
|
||||
final String title;
|
||||
final String url;
|
||||
|
||||
@@ -8,6 +10,7 @@ class TabQueryResult {
|
||||
final double weightedRank;
|
||||
|
||||
TabQueryResult({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.url,
|
||||
this.extractedContent,
|
||||
|
||||
@@ -30,13 +30,15 @@ class TabSearchRepository extends _$TabSearchRepository {
|
||||
)
|
||||
.get(),
|
||||
);
|
||||
} else {
|
||||
state = const AsyncValue.data(null);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<TabQueryResult>> build() {
|
||||
Future<List<TabQueryResult>?> build() {
|
||||
_db = ref.watch(tabDatabaseProvider);
|
||||
|
||||
return Future.value([]);
|
||||
return Future.value();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,12 @@ part of 'tab_search.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$tabSearchRepositoryHash() =>
|
||||
r'211ca4d7e9a744b5f8a32970ba3a6df721fc05e1';
|
||||
r'9bd42f89bcc3a8d94add093076737dd57383bae2';
|
||||
|
||||
/// See also [TabSearchRepository].
|
||||
@ProviderFor(TabSearchRepository)
|
||||
final tabSearchRepositoryProvider = AutoDisposeAsyncNotifierProvider<
|
||||
TabSearchRepository, List<TabQueryResult>>.internal(
|
||||
TabSearchRepository, List<TabQueryResult>?>.internal(
|
||||
TabSearchRepository.new,
|
||||
name: r'tabSearchRepositoryProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
@@ -22,6 +22,6 @@ final tabSearchRepositoryProvider = AutoDisposeAsyncNotifierProvider<
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$TabSearchRepository = AutoDisposeAsyncNotifier<List<TabQueryResult>>;
|
||||
typedef _$TabSearchRepository = AutoDisposeAsyncNotifier<List<TabQueryResult>?>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:exceptions/exceptions.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:lensai/core/http_error_handler.dart';
|
||||
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'chat.g.dart';
|
||||
@@ -18,17 +14,19 @@ class KagiChatService extends _$KagiChatService {
|
||||
}
|
||||
|
||||
Future<Result<String>> downloadChat(Uri url) {
|
||||
final kagiSession =
|
||||
ref.read(settingsRepositoryProvider).valueOrNull?.kagiSession;
|
||||
throw Exception('Not implemented !');
|
||||
|
||||
return Result.fromAsync(
|
||||
() async {
|
||||
final response = await _client
|
||||
.get(url.replace(queryParameters: {'token': kagiSession}));
|
||||
// final kagiSession =
|
||||
// ref.read(settingsRepositoryProvider).valueOrNull?.kagiSession;
|
||||
|
||||
return utf8.decode(response.bodyBytes);
|
||||
},
|
||||
exceptionHandler: handleHttpError,
|
||||
);
|
||||
// return Result.fromAsync(
|
||||
// () async {
|
||||
// final response = await _client
|
||||
// .get(url.replace(queryParameters: {'token': kagiSession}));
|
||||
|
||||
// return utf8.decode(response.bodyBytes);
|
||||
// },
|
||||
// exceptionHandler: handleHttpError,
|
||||
// );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ part of 'chat.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$kagiChatServiceHash() => r'fda3874c955812fa62d555afaca352a3776dd692';
|
||||
String _$kagiChatServiceHash() => r'a112b70b42fe9d650c9d4dc643bf965ddce7bfa6';
|
||||
|
||||
/// See also [KagiChatService].
|
||||
@ProviderFor(KagiChatService)
|
||||
|
||||
@@ -8,9 +8,8 @@ import 'package:lensai/features/geckoview/features/browser/presentation/widgets/
|
||||
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/speech_to_text_button.dart';
|
||||
import 'package:lensai/features/kagi/data/entities/modes.dart';
|
||||
import 'package:lensai/features/kagi/utils/url_builder.dart' as uri_builder;
|
||||
import 'package:lensai/features/settings/data/models/settings.dart';
|
||||
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
|
||||
import 'package:lensai/features/share_intent/domain/entities/shared_content.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/settings.dart';
|
||||
import 'package:lensai/presentation/hooks/sync_page_tab.dart';
|
||||
|
||||
class AssistantTab extends HookConsumerWidget {
|
||||
@@ -26,9 +25,7 @@ class AssistantTab extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final incognitoEnabled = ref.watch(
|
||||
settingsRepositoryProvider.select(
|
||||
(value) => (value.valueOrNull ?? Settings.withDefaults()).incognitoMode,
|
||||
),
|
||||
settingsRepositoryProvider.select((value) => value.incognitoMode),
|
||||
);
|
||||
|
||||
final researchVariant = ref.watch(activeResearchVariantProvider);
|
||||
|
||||
@@ -5,6 +5,7 @@ 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/bangs/domain/providers/bangs.dart';
|
||||
import 'package:lensai/features/bangs/domain/providers/search.dart';
|
||||
import 'package:lensai/features/bangs/domain/repositories/data.dart';
|
||||
import 'package:lensai/features/bangs/presentation/widgets/bang_icon.dart';
|
||||
import 'package:lensai/features/bangs/presentation/widgets/search_field.dart';
|
||||
@@ -33,7 +34,8 @@ class SearchTab extends HookConsumerWidget {
|
||||
final selectedBang = ref
|
||||
.watch(selectedBangDataProvider().select((value) => value.valueOrNull));
|
||||
final defaultSearchBang = ref.watch(
|
||||
defaultSearchBangDataProvider.select((value) => value.valueOrNull));
|
||||
defaultSearchBangDataProvider.select((value) => value.valueOrNull),
|
||||
);
|
||||
|
||||
final activeBang = selectedBang ?? defaultSearchBang;
|
||||
|
||||
@@ -42,11 +44,11 @@ class SearchTab extends HookConsumerWidget {
|
||||
|
||||
Future<void> submitSearch() async {
|
||||
if (activeBang != null && (formKey.currentState?.validate() == true)) {
|
||||
await ref
|
||||
.read(bangDataRepositoryProvider.notifier)
|
||||
.increaseFrequency(activeBang.trigger);
|
||||
final searchUri = await ref.read(
|
||||
triggerBangSearchProvider(activeBang, textController.text).future,
|
||||
);
|
||||
|
||||
onSubmit(activeBang.getUrl(textController.text));
|
||||
onSubmit(searchUri);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,9 +9,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/sheets/create_tab.dart';
|
||||
import 'package:lensai/features/kagi/data/entities/modes.dart';
|
||||
import 'package:lensai/features/kagi/utils/url_builder.dart' as uri_builder;
|
||||
import 'package:lensai/features/settings/data/models/settings.dart';
|
||||
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
|
||||
import 'package:lensai/features/share_intent/domain/entities/shared_content.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/settings.dart';
|
||||
import 'package:lensai/presentation/widgets/website_title_tile.dart';
|
||||
import 'package:lensai/utils/uri_parser.dart' as uri_parser;
|
||||
|
||||
@@ -24,7 +23,7 @@ class _InputField extends ConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final incognitoEnabled = ref.watch(
|
||||
settingsRepositoryProvider.select(
|
||||
(value) => (value.valueOrNull ?? Settings.withDefaults()).incognitoMode,
|
||||
(value) => value.incognitoMode,
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -52,7 +52,10 @@ sealed class FtsQueryBuilder {
|
||||
//Merge short tokens
|
||||
_mergeShortBarewords(barewords, minTokenLength);
|
||||
|
||||
_tokens = barewords.take(tokenLimit).toList();
|
||||
_tokens = barewords
|
||||
.where((bareword) => bareword.word.length >= minTokenLength)
|
||||
.take(tokenLimit)
|
||||
.toList();
|
||||
}
|
||||
|
||||
static void _mergeShortBarewords(
|
||||
@@ -62,7 +65,7 @@ sealed class FtsQueryBuilder {
|
||||
for (var i = 0; i < barewords.length; i++) {
|
||||
final bareword = barewords[i];
|
||||
|
||||
if (i != 0) {
|
||||
if (i > 0) {
|
||||
if (bareword.word.length < minTokenLength) {
|
||||
barewords[i] = barewords[i - 1].join(bareword);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
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/data/models/equatable_iterable.dart';
|
||||
import 'package:lensai/features/bangs/data/models/bang_data.dart';
|
||||
import 'package:lensai/features/bangs/domain/providers/bangs.dart';
|
||||
import 'package:lensai/features/bangs/domain/providers/search.dart';
|
||||
import 'package:lensai/features/bangs/domain/repositories/data.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
|
||||
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/domain/providers.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/providers/selected_container.dart';
|
||||
@@ -75,6 +79,20 @@ class SearchScreen extends HookConsumerWidget {
|
||||
},
|
||||
);
|
||||
|
||||
Future<void> submitSearch(String query) async {
|
||||
if (activeBang != null && (formKey.currentState?.validate() == true)) {
|
||||
final searchUri = await ref.read(
|
||||
triggerBangSearchProvider(activeBang, query).future,
|
||||
);
|
||||
|
||||
await ref.read(tabRepositoryProvider.notifier).addTab(url: searchUri);
|
||||
|
||||
if (context.mounted) {
|
||||
context.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final selectedContainer = useState<ContainerData?>(globalSelectedContainer);
|
||||
|
||||
final availableTabs = ref
|
||||
@@ -111,7 +129,7 @@ class SearchScreen extends HookConsumerWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Site Search',
|
||||
'Search Provider',
|
||||
style: Theme.of(context).textTheme.labelSmall,
|
||||
),
|
||||
BangChips(
|
||||
@@ -150,7 +168,7 @@ class SearchScreen extends HookConsumerWidget {
|
||||
),
|
||||
HookConsumer(
|
||||
builder: (context, ref, child) {
|
||||
final hasSearchText = useListenableSelector(
|
||||
useListenableSelector(
|
||||
searchTextController,
|
||||
() => searchTextController.text.isNotEmpty,
|
||||
);
|
||||
@@ -158,20 +176,52 @@ class SearchScreen extends HookConsumerWidget {
|
||||
final suggestions =
|
||||
useStream(ref.watch(searchSuggestionsProvider()));
|
||||
|
||||
return (!hasSearchText || !suggestions.hasData)
|
||||
? SliverList.builder(
|
||||
itemCount: 5,
|
||||
final searchHistory = ref.watch(searchHistoryProvider);
|
||||
|
||||
final searchText = searchTextController.text;
|
||||
|
||||
if ((!searchText.isNotEmpty || !suggestions.hasData) &&
|
||||
(searchHistory.value?.isNotEmpty ?? false)) {
|
||||
final entries = searchHistory.value!;
|
||||
|
||||
return SliverList.builder(
|
||||
itemCount: entries.length,
|
||||
itemBuilder: (context, index) {
|
||||
final query = entries[index].searchQuery;
|
||||
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.history),
|
||||
title: Text('Search History $index'),
|
||||
title: Text(query),
|
||||
onLongPress: () {
|
||||
searchTextController.text = query;
|
||||
},
|
||||
onTap: () async {
|
||||
await submitSearch(query);
|
||||
},
|
||||
trailing: IconButton(
|
||||
onPressed: () async {
|
||||
await ref
|
||||
.read(bangDataRepositoryProvider.notifier)
|
||||
.removeSearchEntry(query);
|
||||
},
|
||||
icon: const Icon(Icons.close),
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
: SliverList.builder(
|
||||
itemCount: suggestions.data!.length,
|
||||
);
|
||||
}
|
||||
|
||||
final prioritizedSuggestions = [
|
||||
if (searchText.isNotEmpty) searchText,
|
||||
if (suggestions.data != null)
|
||||
...suggestions.data!
|
||||
.whereNot((suggestion) => suggestion == searchText),
|
||||
];
|
||||
|
||||
return SliverList.builder(
|
||||
itemCount: prioritizedSuggestions.length,
|
||||
itemBuilder: (context, index) {
|
||||
final suggestion = suggestions.data![index];
|
||||
final suggestion = prioritizedSuggestions[index];
|
||||
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.search),
|
||||
@@ -179,6 +229,9 @@ class SearchScreen extends HookConsumerWidget {
|
||||
onLongPress: () {
|
||||
searchTextController.text = suggestion;
|
||||
},
|
||||
onTap: () async {
|
||||
await submitSearch(suggestion);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -220,6 +273,10 @@ class SearchScreen extends HookConsumerWidget {
|
||||
enabled: tabs.isLoading,
|
||||
child: tabs.when(
|
||||
data: (data) {
|
||||
if (data == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return SliverList.builder(
|
||||
itemCount: data.length,
|
||||
itemBuilder: (context, index) {
|
||||
@@ -282,11 +339,13 @@ class SearchScreen extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
return ListTile(
|
||||
leading: RawImage(
|
||||
leading: RepaintBoundary(
|
||||
child: RawImage(
|
||||
image: tab.icon?.value,
|
||||
height: 24,
|
||||
width: 24,
|
||||
),
|
||||
),
|
||||
title: Text(tab.title),
|
||||
subtitle: Text(
|
||||
tab.url.toString(),
|
||||
|
||||
@@ -66,7 +66,8 @@ class BangChips extends HookConsumerWidget {
|
||||
);
|
||||
|
||||
return availableBangs.when(
|
||||
data: (availableBangs) => SizedBox(
|
||||
data: (availableBangs) {
|
||||
return SizedBox(
|
||||
height: 48,
|
||||
child: Row(
|
||||
children: [
|
||||
@@ -112,7 +113,8 @@ class BangChips extends HookConsumerWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
error: (error, stackTrace) => const SizedBox.shrink(),
|
||||
loading: () => const SizedBox(
|
||||
height: 48,
|
||||
|
||||
@@ -4,8 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/features/bangs/data/models/bang_data.dart';
|
||||
import 'package:lensai/features/bangs/presentation/widgets/bang_icon.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/presentation/widgets/speech_to_text_button.dart';
|
||||
import 'package:lensai/features/settings/data/models/settings.dart';
|
||||
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/settings.dart';
|
||||
|
||||
class SearchField extends HookConsumerWidget {
|
||||
final TextEditingController textEditingController;
|
||||
@@ -28,7 +27,7 @@ class SearchField extends HookConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final incognitoEnabled = ref.watch(
|
||||
settingsRepositoryProvider.select(
|
||||
(value) => (value.valueOrNull ?? Settings.withDefaults()).incognitoMode,
|
||||
(value) => value.incognitoMode,
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:lensai/features/settings/data/models/settings.dart';
|
||||
import 'package:lensai/features/settings/utils/preference_parser.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
part 'settings_repository.g.dart';
|
||||
|
||||
typedef UpdateSettingsFunc = Settings Function(Settings currentSettings);
|
||||
|
||||
enum _StorageKeys {
|
||||
kagiSession('kagi_session'),
|
||||
showEarlyAccessFeatures('show_early_access_features'),
|
||||
incognito('enabe_incognito'),
|
||||
javascript('enable_js'),
|
||||
launchExternal('enable_launch_external'),
|
||||
blockHttpProtocol('block_http'),
|
||||
themeMode('theme_mode'),
|
||||
quickAction('enable_quick_action'),
|
||||
quickActionVoiceInput('enable_quick_action_voice_input'),
|
||||
enableReadability('enable_readability');
|
||||
|
||||
final String key;
|
||||
|
||||
const _StorageKeys(this.key);
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class SettingsRepository extends _$SettingsRepository {
|
||||
final FlutterSecureStorage _flutterSecureStorage;
|
||||
final Future<SharedPreferences> _sharedPreferences;
|
||||
|
||||
SettingsRepository()
|
||||
: _flutterSecureStorage = const FlutterSecureStorage(),
|
||||
_sharedPreferences = SharedPreferences.getInstance();
|
||||
|
||||
Future<void> updateSettings(UpdateSettingsFunc updateWithCurrent) async {
|
||||
final sharedPreferences = await _sharedPreferences;
|
||||
final oldSettings = state.value!;
|
||||
final newSettings = updateWithCurrent(oldSettings);
|
||||
|
||||
if (oldSettings != newSettings) {
|
||||
if (oldSettings.kagiSession != newSettings.kagiSession) {
|
||||
await _flutterSecureStorage.write(
|
||||
key: _StorageKeys.kagiSession.key,
|
||||
value: (newSettings.kagiSession?.isNotEmpty ?? false)
|
||||
? newSettings.kagiSession
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
if (newSettings.showEarlyAccessFeatures !=
|
||||
oldSettings.showEarlyAccessFeatures) {
|
||||
await sharedPreferences.setBool(
|
||||
_StorageKeys.showEarlyAccessFeatures.key,
|
||||
newSettings.showEarlyAccessFeatures,
|
||||
);
|
||||
}
|
||||
|
||||
if (newSettings.incognitoMode != oldSettings.incognitoMode) {
|
||||
await sharedPreferences.setBool(
|
||||
_StorageKeys.incognito.key,
|
||||
newSettings.incognitoMode,
|
||||
);
|
||||
}
|
||||
|
||||
if (newSettings.enableJavascript != oldSettings.enableJavascript) {
|
||||
await sharedPreferences.setBool(
|
||||
_StorageKeys.javascript.key,
|
||||
newSettings.enableJavascript,
|
||||
);
|
||||
}
|
||||
|
||||
if (newSettings.launchUrlExternal != oldSettings.launchUrlExternal) {
|
||||
await sharedPreferences.setBool(
|
||||
_StorageKeys.launchExternal.key,
|
||||
newSettings.launchUrlExternal,
|
||||
);
|
||||
}
|
||||
|
||||
if (newSettings.blockHttpProtocol != oldSettings.blockHttpProtocol) {
|
||||
await sharedPreferences.setBool(
|
||||
_StorageKeys.blockHttpProtocol.key,
|
||||
newSettings.blockHttpProtocol,
|
||||
);
|
||||
}
|
||||
|
||||
if (newSettings.themeMode != oldSettings.themeMode) {
|
||||
await sharedPreferences.setInt(
|
||||
_StorageKeys.themeMode.key,
|
||||
newSettings.themeMode.index,
|
||||
);
|
||||
}
|
||||
|
||||
if (newSettings.quickAction != oldSettings.quickAction) {
|
||||
final index = newSettings.quickAction?.index;
|
||||
if (index != null) {
|
||||
await sharedPreferences.setInt(
|
||||
_StorageKeys.quickAction.key,
|
||||
index,
|
||||
);
|
||||
} else {
|
||||
await sharedPreferences.remove(_StorageKeys.quickAction.key);
|
||||
}
|
||||
}
|
||||
|
||||
if (newSettings.quickActionVoiceInput !=
|
||||
oldSettings.quickActionVoiceInput) {
|
||||
await sharedPreferences.setBool(
|
||||
_StorageKeys.quickActionVoiceInput.key,
|
||||
newSettings.quickActionVoiceInput,
|
||||
);
|
||||
}
|
||||
|
||||
if (newSettings.enableReadability != oldSettings.enableReadability) {
|
||||
await sharedPreferences.setBool(
|
||||
_StorageKeys.enableReadability.key,
|
||||
newSettings.enableReadability,
|
||||
);
|
||||
}
|
||||
|
||||
ref.invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<Settings> build() async {
|
||||
final sharedPreferences = await _sharedPreferences;
|
||||
|
||||
return Settings.withDefaults(
|
||||
kagiSession:
|
||||
await _flutterSecureStorage.read(key: _StorageKeys.kagiSession.key),
|
||||
showEarlyAccessFeatures:
|
||||
sharedPreferences.getBool(_StorageKeys.showEarlyAccessFeatures.key),
|
||||
incognitoMode: sharedPreferences.getBool(_StorageKeys.incognito.key),
|
||||
enableJavascript: sharedPreferences.getBool(_StorageKeys.javascript.key),
|
||||
launchUrlExternal:
|
||||
sharedPreferences.getBool(_StorageKeys.launchExternal.key),
|
||||
blockHttpProtocol:
|
||||
sharedPreferences.getBool(_StorageKeys.blockHttpProtocol.key),
|
||||
themeMode:
|
||||
parseThemeMode(sharedPreferences.getInt(_StorageKeys.themeMode.key)),
|
||||
quickAction:
|
||||
parseKagiTool(sharedPreferences.getInt(_StorageKeys.quickAction.key)),
|
||||
quickActionVoiceInput:
|
||||
sharedPreferences.getBool(_StorageKeys.quickActionVoiceInput.key),
|
||||
enableReadability:
|
||||
sharedPreferences.getBool(_StorageKeys.enableReadability.key),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/settings.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'save_settings.g.dart';
|
||||
|
||||
@@ -1,21 +1,16 @@
|
||||
import 'package:fading_scroll/fading_scroll.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/features/bangs/data/models/bang.dart';
|
||||
import 'package:lensai/features/bangs/domain/providers/bangs.dart';
|
||||
import 'package:lensai/features/bangs/domain/repositories/data.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';
|
||||
import 'package:lensai/features/settings/presentation/widgets/bang_group_list_tile.dart';
|
||||
import 'package:lensai/features/settings/presentation/widgets/custom_list_tile.dart';
|
||||
import 'package:lensai/features/settings/utils/session_link_extractor.dart';
|
||||
import 'package:lensai/presentation/hooks/listenable_callback.dart';
|
||||
import 'package:lensai/utils/ui_helper.dart' as ui_helper;
|
||||
import 'package:lensai/features/user/data/models/settings.dart';
|
||||
import 'package:lensai/features/user/domain/providers/cache.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/cache.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/settings.dart';
|
||||
|
||||
class SettingsScreen extends HookConsumerWidget {
|
||||
const SettingsScreen({super.key});
|
||||
@@ -44,40 +39,7 @@ class SettingsScreen extends HookConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
final settings = ref.watch(
|
||||
settingsRepositoryProvider.select(
|
||||
(value) => value.valueOrNull ?? Settings.withDefaults(),
|
||||
),
|
||||
);
|
||||
|
||||
final kagiSessionTextController = useTextEditingController(
|
||||
text: settings.kagiSession,
|
||||
);
|
||||
final hideSessionText = useState(true);
|
||||
|
||||
useListenableCallback(kagiSessionTextController, () async {
|
||||
var text = kagiSessionTextController.text;
|
||||
if (Uri.tryParse(text) case final Uri uri) {
|
||||
if (extractKagiSession(uri) case final String session) {
|
||||
text = session;
|
||||
}
|
||||
}
|
||||
|
||||
await ref.read(saveSettingsControllerProvider.notifier).save(
|
||||
(currentSettings) => currentSettings.copyWith.kagiSession(text),
|
||||
);
|
||||
});
|
||||
|
||||
ref.listen(
|
||||
settingsRepositoryProvider.select(
|
||||
(settings) => settings.valueOrNull?.kagiSession,
|
||||
), (previous, next) {
|
||||
if (next != null &&
|
||||
next.isNotEmpty &&
|
||||
kagiSessionTextController.text != next) {
|
||||
kagiSessionTextController.text = next;
|
||||
}
|
||||
});
|
||||
final settings = ref.watch(settingsRepositoryProvider);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Settings')),
|
||||
@@ -87,67 +49,6 @@ class SettingsScreen extends HookConsumerWidget {
|
||||
return ListView(
|
||||
controller: controller,
|
||||
children: [
|
||||
_buildSection(theme, 'Kagi'),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 16,
|
||||
right: 16,
|
||||
bottom: 8,
|
||||
),
|
||||
child: TextField(
|
||||
enableIMEPersonalizedLearning: false,
|
||||
autocorrect: false,
|
||||
controller: kagiSessionTextController,
|
||||
obscureText: hideSessionText.value,
|
||||
decoration: InputDecoration(
|
||||
label: const Text('Kagi Session Token'),
|
||||
hintText: 'https://kagi.com/search?token=...',
|
||||
helperMaxLines: 2,
|
||||
helper: Markdown(
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.zero,
|
||||
data:
|
||||
'You can visit your [Kagi Account Settings](user_details) to get your Session Link.',
|
||||
onTapLink: (text, href, title) async {
|
||||
if (href == 'user_details') {
|
||||
await ui_helper.launchUrlFeedback(
|
||||
context,
|
||||
Uri.parse(
|
||||
'https://kagi.com/settings?p=user_details',
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
suffixIcon: IconButton(
|
||||
onPressed: () {
|
||||
hideSessionText.value = !hideSessionText.value;
|
||||
},
|
||||
icon: Icon(
|
||||
hideSessionText.value
|
||||
? Icons.visibility
|
||||
: Icons.visibility_off,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SwitchListTile.adaptive(
|
||||
title: const Text('Show Early Access Features'),
|
||||
subtitle: const Text(
|
||||
"Displays Kagi's early access features in the UI. As an Ultimate subscriber, you will likely want to have this enabled.",
|
||||
),
|
||||
value: settings.showEarlyAccessFeatures,
|
||||
onChanged: (value) async {
|
||||
await ref.read(saveSettingsControllerProvider.notifier).save(
|
||||
(currentSettings) => currentSettings.copyWith
|
||||
.showEarlyAccessFeatures(value),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
_buildSection(theme, 'General'),
|
||||
Padding(
|
||||
padding:
|
||||
@@ -232,19 +133,6 @@ class SettingsScreen extends HookConsumerWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
SwitchListTile.adaptive(
|
||||
title: const Text('Launch Links Externally'),
|
||||
subtitle: const Text(
|
||||
'Opens all links (except for kagi.com) in your default browser.',
|
||||
),
|
||||
value: settings.launchUrlExternal,
|
||||
onChanged: (value) async {
|
||||
await ref.read(saveSettingsControllerProvider.notifier).save(
|
||||
(currentSettings) =>
|
||||
currentSettings.copyWith.launchUrlExternal(value),
|
||||
);
|
||||
},
|
||||
),
|
||||
SwitchListTile.adaptive(
|
||||
title: const Text('Enable Reader Mode'),
|
||||
subtitle: const Text(
|
||||
@@ -261,77 +149,6 @@ class SettingsScreen extends HookConsumerWidget {
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
_buildSection(theme, 'Appearance'),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const CustomListTile(
|
||||
title: 'Quick Action',
|
||||
subtitle:
|
||||
'Appears in the browser app bar between website title and tab count.',
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Center(
|
||||
child: SegmentedButton<KagiTool>(
|
||||
emptySelectionAllowed: true,
|
||||
segments: [
|
||||
ButtonSegment(
|
||||
value: KagiTool.search,
|
||||
icon: Icon(KagiTool.search.icon),
|
||||
label: const Text('Search'),
|
||||
),
|
||||
ButtonSegment(
|
||||
value: KagiTool.summarizer,
|
||||
icon: Icon(KagiTool.summarizer.icon),
|
||||
label: const Text('Summarizer'),
|
||||
),
|
||||
ButtonSegment(
|
||||
value: KagiTool.assistant,
|
||||
icon: Icon(KagiTool.assistant.icon),
|
||||
label: const Text('Assistant'),
|
||||
),
|
||||
],
|
||||
selected: {
|
||||
if (settings.quickAction != null)
|
||||
settings.quickAction!,
|
||||
},
|
||||
onSelectionChanged: (value) async {
|
||||
await ref
|
||||
.read(saveSettingsControllerProvider.notifier)
|
||||
.save(
|
||||
(currentSettings) =>
|
||||
currentSettings.copyWith.quickAction(
|
||||
value.isNotEmpty ? value.first : null,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SwitchListTile.adaptive(
|
||||
title: const Text('Quick Action - STT'),
|
||||
subtitle: const Text(
|
||||
'Quick option will open with speech-to-text input.',
|
||||
),
|
||||
value: settings.quickActionVoiceInput,
|
||||
onChanged: (settings.quickAction != null)
|
||||
? (value) async {
|
||||
await ref
|
||||
.read(saveSettingsControllerProvider.notifier)
|
||||
.save(
|
||||
(currentSettings) => currentSettings.copyWith
|
||||
.quickActionVoiceInput(value),
|
||||
);
|
||||
}
|
||||
: null,
|
||||
),
|
||||
_buildSection(theme, 'Content Blocking'),
|
||||
_buildSubSection(theme, 'Lists'),
|
||||
const SizedBox(
|
||||
@@ -354,7 +171,7 @@ class SettingsScreen extends HookConsumerWidget {
|
||||
Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final size = ref.watch(
|
||||
bangIconCacheSizeMegabytesProvider.select(
|
||||
iconCacheSizeMegabytesProvider.select(
|
||||
(value) => value.valueOrNull,
|
||||
),
|
||||
);
|
||||
@@ -384,8 +201,8 @@ class SettingsScreen extends HookConsumerWidget {
|
||||
suffix: FilledButton.icon(
|
||||
onPressed: () async {
|
||||
await ref
|
||||
.read(bangDataRepositoryProvider.notifier)
|
||||
.clearIconData();
|
||||
.read(cacheRepositoryProvider.notifier)
|
||||
.clearCache();
|
||||
},
|
||||
icon: const Icon(Icons.delete),
|
||||
label: const Text('Clear'),
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lensai/features/kagi/data/entities/modes.dart';
|
||||
|
||||
ThemeMode? parseThemeMode(int? index) {
|
||||
if (index != null && index < ThemeMode.values.length) {
|
||||
return ThemeMode.values[index];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
KagiTool? parseKagiTool(int? index) {
|
||||
if (index != null && index < ThemeMode.values.length) {
|
||||
return KagiTool.values[index];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
String? extractKagiSession(Uri uri) {
|
||||
if (uri.authority == 'kagi.com') {
|
||||
return uri.queryParameters['token'];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:lensai/extensions/database_table_size.dart';
|
||||
import 'package:lensai/features/user/data/database/database.dart';
|
||||
|
||||
part 'cache.g.dart';
|
||||
|
||||
@DriftAccessor()
|
||||
class CacheDao extends DatabaseAccessor<UserDatabase> with _$CacheDaoMixin {
|
||||
CacheDao(super.attachedDatabase);
|
||||
|
||||
SingleSelectable<double> iconCacheSize() {
|
||||
return db.tableSize(db.iconCache);
|
||||
}
|
||||
|
||||
Future<int> clearIconCache() {
|
||||
return db.iconCache.deleteAll();
|
||||
}
|
||||
|
||||
SingleOrNullSelectable<Uint8List?> getCachedIcon(String origin) {
|
||||
final query = selectOnly(db.iconCache)
|
||||
..addColumns([db.iconCache.iconData])
|
||||
..where(db.iconCache.origin.equals(origin));
|
||||
|
||||
return query.map((row) => row.read(db.iconCache.iconData));
|
||||
}
|
||||
|
||||
Future<int> cacheIcon(String origin, Uint8List bytes) {
|
||||
return db.iconCache.insertOne(
|
||||
IconCacheCompanion.insert(
|
||||
origin: origin,
|
||||
iconData: bytes,
|
||||
fetchDate: DateTime.now(),
|
||||
),
|
||||
onConflict: DoUpdate(
|
||||
(old) => IconCacheCompanion.custom(
|
||||
iconData: Variable(bytes),
|
||||
fetchDate: Variable(DateTime.now()),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'cache.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
mixin _$CacheDaoMixin on DatabaseAccessor<UserDatabase> {}
|
||||
@@ -0,0 +1,27 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:lensai/features/user/data/database/database.dart';
|
||||
|
||||
part 'setting.g.dart';
|
||||
|
||||
@DriftAccessor()
|
||||
class SettingDao extends DatabaseAccessor<UserDatabase> with _$SettingDaoMixin {
|
||||
SettingDao(super.attachedDatabase);
|
||||
|
||||
Future<int> updateSetting(String key, Object? value) {
|
||||
final driftvalue = (value != null) ? DriftAny(value) : null;
|
||||
|
||||
return db.setting.insertOne(
|
||||
SettingCompanion.insert(
|
||||
key: key,
|
||||
value: Value(driftvalue),
|
||||
),
|
||||
onConflict: DoUpdate(
|
||||
(old) => SettingCompanion.custom(value: Variable(driftvalue)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Selectable<MapEntry<String, DriftAny?>> allSettings() {
|
||||
return db.setting.select().map((row) => MapEntry(row.key, row.value));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'setting.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
mixin _$SettingDaoMixin on DatabaseAccessor<UserDatabase> {}
|
||||
@@ -0,0 +1,25 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:lensai/features/user/data/database/daos/cache.dart';
|
||||
import 'package:lensai/features/user/data/database/daos/setting.dart';
|
||||
|
||||
part 'database.g.dart';
|
||||
|
||||
@DriftDatabase(include: {
|
||||
'database.drift'
|
||||
}, daos: [
|
||||
SettingDao,
|
||||
CacheDao,
|
||||
])
|
||||
class UserDatabase extends _$UserDatabase {
|
||||
@override
|
||||
final int schemaVersion = 1;
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration => MigrationStrategy(
|
||||
beforeOpen: (details) async {
|
||||
await customStatement('PRAGMA foreign_keys = ON;');
|
||||
},
|
||||
);
|
||||
|
||||
UserDatabase(super.e);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
CREATE TABLE setting (
|
||||
"key" TEXT PRIMARY KEY NOT NULL,
|
||||
"value" ANY
|
||||
) STRICT;
|
||||
|
||||
CREATE TABLE icon_cache (
|
||||
origin TEXT PRIMARY KEY NOT NULL,
|
||||
icon_data BLOB NOT NULL,
|
||||
fetch_date DATETIME NOT NULL
|
||||
);
|
||||
|
||||
evictCacheEntries:
|
||||
DELETE FROM icon_cache
|
||||
WHERE rowid IN (
|
||||
SELECT rowid
|
||||
FROM icon_cache
|
||||
ORDER BY fetch_date DESC
|
||||
LIMIT -1 OFFSET :limit
|
||||
);
|
||||
@@ -0,0 +1,653 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'database.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
class Setting extends Table with TableInfo<Setting, SettingData> {
|
||||
@override
|
||||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
Setting(this.attachedDatabase, [this._alias]);
|
||||
late final GeneratedColumn<String> key = GeneratedColumn<String>(
|
||||
'key', aliasedName, false,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'PRIMARY KEY NOT NULL');
|
||||
late final GeneratedColumn<DriftAny> value = GeneratedColumn<DriftAny>(
|
||||
'value', aliasedName, true,
|
||||
type: DriftSqlType.any,
|
||||
requiredDuringInsert: false,
|
||||
$customConstraints: '');
|
||||
@override
|
||||
List<GeneratedColumn> get $columns => [key, value];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@override
|
||||
String get actualTableName => $name;
|
||||
static const String $name = 'setting';
|
||||
@override
|
||||
Set<GeneratedColumn> get $primaryKey => {key};
|
||||
@override
|
||||
SettingData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||
return SettingData(
|
||||
key: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}key'])!,
|
||||
value: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.any, data['${effectivePrefix}value']),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Setting createAlias(String alias) {
|
||||
return Setting(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get isStrict => true;
|
||||
@override
|
||||
bool get dontWriteConstraints => true;
|
||||
}
|
||||
|
||||
class SettingData extends DataClass implements Insertable<SettingData> {
|
||||
final String key;
|
||||
final DriftAny? value;
|
||||
const SettingData({required this.key, this.value});
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
map['key'] = Variable<String>(key);
|
||||
if (!nullToAbsent || value != null) {
|
||||
map['value'] = Variable<DriftAny>(value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
factory SettingData.fromJson(Map<String, dynamic> json,
|
||||
{ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return SettingData(
|
||||
key: serializer.fromJson<String>(json['key']),
|
||||
value: serializer.fromJson<DriftAny?>(json['value']),
|
||||
);
|
||||
}
|
||||
@override
|
||||
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return <String, dynamic>{
|
||||
'key': serializer.toJson<String>(key),
|
||||
'value': serializer.toJson<DriftAny?>(value),
|
||||
};
|
||||
}
|
||||
|
||||
SettingData copyWith(
|
||||
{String? key, Value<DriftAny?> value = const Value.absent()}) =>
|
||||
SettingData(
|
||||
key: key ?? this.key,
|
||||
value: value.present ? value.value : this.value,
|
||||
);
|
||||
SettingData copyWithCompanion(SettingCompanion data) {
|
||||
return SettingData(
|
||||
key: data.key.present ? data.key.value : this.key,
|
||||
value: data.value.present ? data.value.value : this.value,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('SettingData(')
|
||||
..write('key: $key, ')
|
||||
..write('value: $value')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(key, value);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is SettingData &&
|
||||
other.key == this.key &&
|
||||
other.value == this.value);
|
||||
}
|
||||
|
||||
class SettingCompanion extends UpdateCompanion<SettingData> {
|
||||
final Value<String> key;
|
||||
final Value<DriftAny?> value;
|
||||
final Value<int> rowid;
|
||||
const SettingCompanion({
|
||||
this.key = const Value.absent(),
|
||||
this.value = const Value.absent(),
|
||||
this.rowid = const Value.absent(),
|
||||
});
|
||||
SettingCompanion.insert({
|
||||
required String key,
|
||||
this.value = const Value.absent(),
|
||||
this.rowid = const Value.absent(),
|
||||
}) : key = Value(key);
|
||||
static Insertable<SettingData> custom({
|
||||
Expression<String>? key,
|
||||
Expression<DriftAny>? value,
|
||||
Expression<int>? rowid,
|
||||
}) {
|
||||
return RawValuesInsertable({
|
||||
if (key != null) 'key': key,
|
||||
if (value != null) 'value': value,
|
||||
if (rowid != null) 'rowid': rowid,
|
||||
});
|
||||
}
|
||||
|
||||
SettingCompanion copyWith(
|
||||
{Value<String>? key, Value<DriftAny?>? value, Value<int>? rowid}) {
|
||||
return SettingCompanion(
|
||||
key: key ?? this.key,
|
||||
value: value ?? this.value,
|
||||
rowid: rowid ?? this.rowid,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
if (key.present) {
|
||||
map['key'] = Variable<String>(key.value);
|
||||
}
|
||||
if (value.present) {
|
||||
map['value'] = Variable<DriftAny>(value.value);
|
||||
}
|
||||
if (rowid.present) {
|
||||
map['rowid'] = Variable<int>(rowid.value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('SettingCompanion(')
|
||||
..write('key: $key, ')
|
||||
..write('value: $value, ')
|
||||
..write('rowid: $rowid')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
class IconCache extends Table with TableInfo<IconCache, IconCacheData> {
|
||||
@override
|
||||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
IconCache(this.attachedDatabase, [this._alias]);
|
||||
late final GeneratedColumn<String> origin = GeneratedColumn<String>(
|
||||
'origin', aliasedName, false,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'PRIMARY KEY NOT NULL');
|
||||
late final GeneratedColumn<Uint8List> iconData = GeneratedColumn<Uint8List>(
|
||||
'icon_data', aliasedName, false,
|
||||
type: DriftSqlType.blob,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL');
|
||||
late final GeneratedColumn<DateTime> fetchDate = GeneratedColumn<DateTime>(
|
||||
'fetch_date', aliasedName, false,
|
||||
type: DriftSqlType.dateTime,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL');
|
||||
@override
|
||||
List<GeneratedColumn> get $columns => [origin, iconData, fetchDate];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@override
|
||||
String get actualTableName => $name;
|
||||
static const String $name = 'icon_cache';
|
||||
@override
|
||||
Set<GeneratedColumn> get $primaryKey => {origin};
|
||||
@override
|
||||
IconCacheData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||
return IconCacheData(
|
||||
origin: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}origin'])!,
|
||||
iconData: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.blob, data['${effectivePrefix}icon_data'])!,
|
||||
fetchDate: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.dateTime, data['${effectivePrefix}fetch_date'])!,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
IconCache createAlias(String alias) {
|
||||
return IconCache(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get dontWriteConstraints => true;
|
||||
}
|
||||
|
||||
class IconCacheData extends DataClass implements Insertable<IconCacheData> {
|
||||
final String origin;
|
||||
final Uint8List iconData;
|
||||
final DateTime fetchDate;
|
||||
const IconCacheData(
|
||||
{required this.origin, required this.iconData, required this.fetchDate});
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
map['origin'] = Variable<String>(origin);
|
||||
map['icon_data'] = Variable<Uint8List>(iconData);
|
||||
map['fetch_date'] = Variable<DateTime>(fetchDate);
|
||||
return map;
|
||||
}
|
||||
|
||||
factory IconCacheData.fromJson(Map<String, dynamic> json,
|
||||
{ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return IconCacheData(
|
||||
origin: serializer.fromJson<String>(json['origin']),
|
||||
iconData: serializer.fromJson<Uint8List>(json['icon_data']),
|
||||
fetchDate: serializer.fromJson<DateTime>(json['fetch_date']),
|
||||
);
|
||||
}
|
||||
@override
|
||||
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return <String, dynamic>{
|
||||
'origin': serializer.toJson<String>(origin),
|
||||
'icon_data': serializer.toJson<Uint8List>(iconData),
|
||||
'fetch_date': serializer.toJson<DateTime>(fetchDate),
|
||||
};
|
||||
}
|
||||
|
||||
IconCacheData copyWith(
|
||||
{String? origin, Uint8List? iconData, DateTime? fetchDate}) =>
|
||||
IconCacheData(
|
||||
origin: origin ?? this.origin,
|
||||
iconData: iconData ?? this.iconData,
|
||||
fetchDate: fetchDate ?? this.fetchDate,
|
||||
);
|
||||
IconCacheData copyWithCompanion(IconCacheCompanion data) {
|
||||
return IconCacheData(
|
||||
origin: data.origin.present ? data.origin.value : this.origin,
|
||||
iconData: data.iconData.present ? data.iconData.value : this.iconData,
|
||||
fetchDate: data.fetchDate.present ? data.fetchDate.value : this.fetchDate,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('IconCacheData(')
|
||||
..write('origin: $origin, ')
|
||||
..write('iconData: $iconData, ')
|
||||
..write('fetchDate: $fetchDate')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(origin, $driftBlobEquality.hash(iconData), fetchDate);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is IconCacheData &&
|
||||
other.origin == this.origin &&
|
||||
$driftBlobEquality.equals(other.iconData, this.iconData) &&
|
||||
other.fetchDate == this.fetchDate);
|
||||
}
|
||||
|
||||
class IconCacheCompanion extends UpdateCompanion<IconCacheData> {
|
||||
final Value<String> origin;
|
||||
final Value<Uint8List> iconData;
|
||||
final Value<DateTime> fetchDate;
|
||||
final Value<int> rowid;
|
||||
const IconCacheCompanion({
|
||||
this.origin = const Value.absent(),
|
||||
this.iconData = const Value.absent(),
|
||||
this.fetchDate = const Value.absent(),
|
||||
this.rowid = const Value.absent(),
|
||||
});
|
||||
IconCacheCompanion.insert({
|
||||
required String origin,
|
||||
required Uint8List iconData,
|
||||
required DateTime fetchDate,
|
||||
this.rowid = const Value.absent(),
|
||||
}) : origin = Value(origin),
|
||||
iconData = Value(iconData),
|
||||
fetchDate = Value(fetchDate);
|
||||
static Insertable<IconCacheData> custom({
|
||||
Expression<String>? origin,
|
||||
Expression<Uint8List>? iconData,
|
||||
Expression<DateTime>? fetchDate,
|
||||
Expression<int>? rowid,
|
||||
}) {
|
||||
return RawValuesInsertable({
|
||||
if (origin != null) 'origin': origin,
|
||||
if (iconData != null) 'icon_data': iconData,
|
||||
if (fetchDate != null) 'fetch_date': fetchDate,
|
||||
if (rowid != null) 'rowid': rowid,
|
||||
});
|
||||
}
|
||||
|
||||
IconCacheCompanion copyWith(
|
||||
{Value<String>? origin,
|
||||
Value<Uint8List>? iconData,
|
||||
Value<DateTime>? fetchDate,
|
||||
Value<int>? rowid}) {
|
||||
return IconCacheCompanion(
|
||||
origin: origin ?? this.origin,
|
||||
iconData: iconData ?? this.iconData,
|
||||
fetchDate: fetchDate ?? this.fetchDate,
|
||||
rowid: rowid ?? this.rowid,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
if (origin.present) {
|
||||
map['origin'] = Variable<String>(origin.value);
|
||||
}
|
||||
if (iconData.present) {
|
||||
map['icon_data'] = Variable<Uint8List>(iconData.value);
|
||||
}
|
||||
if (fetchDate.present) {
|
||||
map['fetch_date'] = Variable<DateTime>(fetchDate.value);
|
||||
}
|
||||
if (rowid.present) {
|
||||
map['rowid'] = Variable<int>(rowid.value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('IconCacheCompanion(')
|
||||
..write('origin: $origin, ')
|
||||
..write('iconData: $iconData, ')
|
||||
..write('fetchDate: $fetchDate, ')
|
||||
..write('rowid: $rowid')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _$UserDatabase extends GeneratedDatabase {
|
||||
_$UserDatabase(QueryExecutor e) : super(e);
|
||||
$UserDatabaseManager get managers => $UserDatabaseManager(this);
|
||||
late final Setting setting = Setting(this);
|
||||
late final IconCache iconCache = IconCache(this);
|
||||
late final SettingDao settingDao = SettingDao(this as UserDatabase);
|
||||
late final CacheDao cacheDao = CacheDao(this as UserDatabase);
|
||||
Future<int> evictCacheEntries({required int limit}) {
|
||||
return customUpdate(
|
||||
'DELETE FROM icon_cache WHERE "rowid" IN (SELECT "rowid" FROM icon_cache ORDER BY fetch_date DESC LIMIT -1 OFFSET ?1)',
|
||||
variables: [Variable<int>(limit)],
|
||||
updates: {iconCache},
|
||||
updateKind: UpdateKind.delete,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Iterable<TableInfo<Table, Object?>> get allTables =>
|
||||
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
||||
@override
|
||||
List<DatabaseSchemaEntity> get allSchemaEntities => [setting, iconCache];
|
||||
}
|
||||
|
||||
typedef $SettingCreateCompanionBuilder = SettingCompanion Function({
|
||||
required String key,
|
||||
Value<DriftAny?> value,
|
||||
Value<int> rowid,
|
||||
});
|
||||
typedef $SettingUpdateCompanionBuilder = SettingCompanion Function({
|
||||
Value<String> key,
|
||||
Value<DriftAny?> value,
|
||||
Value<int> rowid,
|
||||
});
|
||||
|
||||
class $SettingFilterComposer extends Composer<_$UserDatabase, Setting> {
|
||||
$SettingFilterComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
ColumnFilters<String> get key => $composableBuilder(
|
||||
column: $table.key, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<DriftAny> get value => $composableBuilder(
|
||||
column: $table.value, builder: (column) => ColumnFilters(column));
|
||||
}
|
||||
|
||||
class $SettingOrderingComposer extends Composer<_$UserDatabase, Setting> {
|
||||
$SettingOrderingComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
ColumnOrderings<String> get key => $composableBuilder(
|
||||
column: $table.key, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<DriftAny> get value => $composableBuilder(
|
||||
column: $table.value, builder: (column) => ColumnOrderings(column));
|
||||
}
|
||||
|
||||
class $SettingAnnotationComposer extends Composer<_$UserDatabase, Setting> {
|
||||
$SettingAnnotationComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
GeneratedColumn<String> get key =>
|
||||
$composableBuilder(column: $table.key, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<DriftAny> get value =>
|
||||
$composableBuilder(column: $table.value, builder: (column) => column);
|
||||
}
|
||||
|
||||
class $SettingTableManager extends RootTableManager<
|
||||
_$UserDatabase,
|
||||
Setting,
|
||||
SettingData,
|
||||
$SettingFilterComposer,
|
||||
$SettingOrderingComposer,
|
||||
$SettingAnnotationComposer,
|
||||
$SettingCreateCompanionBuilder,
|
||||
$SettingUpdateCompanionBuilder,
|
||||
(SettingData, BaseReferences<_$UserDatabase, Setting, SettingData>),
|
||||
SettingData,
|
||||
PrefetchHooks Function()> {
|
||||
$SettingTableManager(_$UserDatabase db, Setting table)
|
||||
: super(TableManagerState(
|
||||
db: db,
|
||||
table: table,
|
||||
createFilteringComposer: () =>
|
||||
$SettingFilterComposer($db: db, $table: table),
|
||||
createOrderingComposer: () =>
|
||||
$SettingOrderingComposer($db: db, $table: table),
|
||||
createComputedFieldComposer: () =>
|
||||
$SettingAnnotationComposer($db: db, $table: table),
|
||||
updateCompanionCallback: ({
|
||||
Value<String> key = const Value.absent(),
|
||||
Value<DriftAny?> value = const Value.absent(),
|
||||
Value<int> rowid = const Value.absent(),
|
||||
}) =>
|
||||
SettingCompanion(
|
||||
key: key,
|
||||
value: value,
|
||||
rowid: rowid,
|
||||
),
|
||||
createCompanionCallback: ({
|
||||
required String key,
|
||||
Value<DriftAny?> value = const Value.absent(),
|
||||
Value<int> rowid = const Value.absent(),
|
||||
}) =>
|
||||
SettingCompanion.insert(
|
||||
key: key,
|
||||
value: value,
|
||||
rowid: rowid,
|
||||
),
|
||||
withReferenceMapper: (p0) => p0
|
||||
.map((e) => (e.readTable(table), BaseReferences(db, table, e)))
|
||||
.toList(),
|
||||
prefetchHooksCallback: null,
|
||||
));
|
||||
}
|
||||
|
||||
typedef $SettingProcessedTableManager = ProcessedTableManager<
|
||||
_$UserDatabase,
|
||||
Setting,
|
||||
SettingData,
|
||||
$SettingFilterComposer,
|
||||
$SettingOrderingComposer,
|
||||
$SettingAnnotationComposer,
|
||||
$SettingCreateCompanionBuilder,
|
||||
$SettingUpdateCompanionBuilder,
|
||||
(SettingData, BaseReferences<_$UserDatabase, Setting, SettingData>),
|
||||
SettingData,
|
||||
PrefetchHooks Function()>;
|
||||
typedef $IconCacheCreateCompanionBuilder = IconCacheCompanion Function({
|
||||
required String origin,
|
||||
required Uint8List iconData,
|
||||
required DateTime fetchDate,
|
||||
Value<int> rowid,
|
||||
});
|
||||
typedef $IconCacheUpdateCompanionBuilder = IconCacheCompanion Function({
|
||||
Value<String> origin,
|
||||
Value<Uint8List> iconData,
|
||||
Value<DateTime> fetchDate,
|
||||
Value<int> rowid,
|
||||
});
|
||||
|
||||
class $IconCacheFilterComposer extends Composer<_$UserDatabase, IconCache> {
|
||||
$IconCacheFilterComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
ColumnFilters<String> get origin => $composableBuilder(
|
||||
column: $table.origin, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<Uint8List> get iconData => $composableBuilder(
|
||||
column: $table.iconData, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<DateTime> get fetchDate => $composableBuilder(
|
||||
column: $table.fetchDate, builder: (column) => ColumnFilters(column));
|
||||
}
|
||||
|
||||
class $IconCacheOrderingComposer extends Composer<_$UserDatabase, IconCache> {
|
||||
$IconCacheOrderingComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
ColumnOrderings<String> get origin => $composableBuilder(
|
||||
column: $table.origin, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<Uint8List> get iconData => $composableBuilder(
|
||||
column: $table.iconData, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<DateTime> get fetchDate => $composableBuilder(
|
||||
column: $table.fetchDate, builder: (column) => ColumnOrderings(column));
|
||||
}
|
||||
|
||||
class $IconCacheAnnotationComposer extends Composer<_$UserDatabase, IconCache> {
|
||||
$IconCacheAnnotationComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
GeneratedColumn<String> get origin =>
|
||||
$composableBuilder(column: $table.origin, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<Uint8List> get iconData =>
|
||||
$composableBuilder(column: $table.iconData, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<DateTime> get fetchDate =>
|
||||
$composableBuilder(column: $table.fetchDate, builder: (column) => column);
|
||||
}
|
||||
|
||||
class $IconCacheTableManager extends RootTableManager<
|
||||
_$UserDatabase,
|
||||
IconCache,
|
||||
IconCacheData,
|
||||
$IconCacheFilterComposer,
|
||||
$IconCacheOrderingComposer,
|
||||
$IconCacheAnnotationComposer,
|
||||
$IconCacheCreateCompanionBuilder,
|
||||
$IconCacheUpdateCompanionBuilder,
|
||||
(IconCacheData, BaseReferences<_$UserDatabase, IconCache, IconCacheData>),
|
||||
IconCacheData,
|
||||
PrefetchHooks Function()> {
|
||||
$IconCacheTableManager(_$UserDatabase db, IconCache table)
|
||||
: super(TableManagerState(
|
||||
db: db,
|
||||
table: table,
|
||||
createFilteringComposer: () =>
|
||||
$IconCacheFilterComposer($db: db, $table: table),
|
||||
createOrderingComposer: () =>
|
||||
$IconCacheOrderingComposer($db: db, $table: table),
|
||||
createComputedFieldComposer: () =>
|
||||
$IconCacheAnnotationComposer($db: db, $table: table),
|
||||
updateCompanionCallback: ({
|
||||
Value<String> origin = const Value.absent(),
|
||||
Value<Uint8List> iconData = const Value.absent(),
|
||||
Value<DateTime> fetchDate = const Value.absent(),
|
||||
Value<int> rowid = const Value.absent(),
|
||||
}) =>
|
||||
IconCacheCompanion(
|
||||
origin: origin,
|
||||
iconData: iconData,
|
||||
fetchDate: fetchDate,
|
||||
rowid: rowid,
|
||||
),
|
||||
createCompanionCallback: ({
|
||||
required String origin,
|
||||
required Uint8List iconData,
|
||||
required DateTime fetchDate,
|
||||
Value<int> rowid = const Value.absent(),
|
||||
}) =>
|
||||
IconCacheCompanion.insert(
|
||||
origin: origin,
|
||||
iconData: iconData,
|
||||
fetchDate: fetchDate,
|
||||
rowid: rowid,
|
||||
),
|
||||
withReferenceMapper: (p0) => p0
|
||||
.map((e) => (e.readTable(table), BaseReferences(db, table, e)))
|
||||
.toList(),
|
||||
prefetchHooksCallback: null,
|
||||
));
|
||||
}
|
||||
|
||||
typedef $IconCacheProcessedTableManager = ProcessedTableManager<
|
||||
_$UserDatabase,
|
||||
IconCache,
|
||||
IconCacheData,
|
||||
$IconCacheFilterComposer,
|
||||
$IconCacheOrderingComposer,
|
||||
$IconCacheAnnotationComposer,
|
||||
$IconCacheCreateCompanionBuilder,
|
||||
$IconCacheUpdateCompanionBuilder,
|
||||
(IconCacheData, BaseReferences<_$UserDatabase, IconCache, IconCacheData>),
|
||||
IconCacheData,
|
||||
PrefetchHooks Function()>;
|
||||
|
||||
class $UserDatabaseManager {
|
||||
final _$UserDatabase _db;
|
||||
$UserDatabaseManager(this._db);
|
||||
$SettingTableManager get setting => $SettingTableManager(_db, _db.setting);
|
||||
$IconCacheTableManager get iconCache =>
|
||||
$IconCacheTableManager(_db, _db.iconCache);
|
||||
}
|
||||
+8
-25
@@ -1,70 +1,53 @@
|
||||
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/kagi/data/entities/modes.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'settings.g.dart';
|
||||
|
||||
@CopyWith()
|
||||
@JsonSerializable(includeIfNull: true, constructor: 'withDefaults')
|
||||
class Settings with FastEquatable {
|
||||
final String? kagiSession;
|
||||
final bool showEarlyAccessFeatures;
|
||||
final bool incognitoMode;
|
||||
final bool enableJavascript;
|
||||
final bool launchUrlExternal;
|
||||
final bool blockHttpProtocol;
|
||||
final ThemeMode themeMode;
|
||||
final KagiTool? quickAction;
|
||||
final bool quickActionVoiceInput;
|
||||
final bool enableReadability;
|
||||
|
||||
Settings({
|
||||
required this.kagiSession,
|
||||
required this.showEarlyAccessFeatures,
|
||||
required this.incognitoMode,
|
||||
required this.enableJavascript,
|
||||
required this.launchUrlExternal,
|
||||
required this.blockHttpProtocol,
|
||||
required this.themeMode,
|
||||
required this.quickAction,
|
||||
required this.quickActionVoiceInput,
|
||||
required this.enableReadability,
|
||||
});
|
||||
|
||||
Settings.withDefaults({
|
||||
this.kagiSession,
|
||||
bool? showEarlyAccessFeatures,
|
||||
bool? incognitoMode,
|
||||
bool? enableJavascript,
|
||||
bool? launchUrlExternal,
|
||||
bool? blockHttpProtocol,
|
||||
ThemeMode? themeMode,
|
||||
this.quickAction,
|
||||
bool? quickActionVoiceInput,
|
||||
bool? enableReadability,
|
||||
}) : showEarlyAccessFeatures = showEarlyAccessFeatures ?? true,
|
||||
incognitoMode = incognitoMode ?? true,
|
||||
}) : incognitoMode = incognitoMode ?? true,
|
||||
enableJavascript = enableJavascript ?? true,
|
||||
launchUrlExternal = launchUrlExternal ?? false,
|
||||
blockHttpProtocol = blockHttpProtocol ?? false,
|
||||
themeMode = themeMode ?? ThemeMode.dark,
|
||||
quickActionVoiceInput = quickActionVoiceInput ?? false,
|
||||
enableReadability = enableReadability ?? true;
|
||||
|
||||
factory Settings.fromJson(Map<String, dynamic> json) =>
|
||||
_$SettingsFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$SettingsToJson(this);
|
||||
|
||||
@override
|
||||
bool get cacheHash => true;
|
||||
|
||||
@override
|
||||
List<Object?> get hashParameters => [
|
||||
kagiSession,
|
||||
showEarlyAccessFeatures,
|
||||
incognitoMode,
|
||||
enableJavascript,
|
||||
launchUrlExternal,
|
||||
blockHttpProtocol,
|
||||
themeMode,
|
||||
quickAction,
|
||||
quickActionVoiceInput,
|
||||
enableReadability,
|
||||
];
|
||||
}
|
||||
+26
-60
@@ -7,24 +7,14 @@ part of 'settings.dart';
|
||||
// **************************************************************************
|
||||
|
||||
abstract class _$SettingsCWProxy {
|
||||
Settings kagiSession(String? kagiSession);
|
||||
|
||||
Settings showEarlyAccessFeatures(bool showEarlyAccessFeatures);
|
||||
|
||||
Settings incognitoMode(bool incognitoMode);
|
||||
|
||||
Settings enableJavascript(bool enableJavascript);
|
||||
|
||||
Settings launchUrlExternal(bool launchUrlExternal);
|
||||
|
||||
Settings blockHttpProtocol(bool blockHttpProtocol);
|
||||
|
||||
Settings themeMode(ThemeMode themeMode);
|
||||
|
||||
Settings quickAction(KagiTool? quickAction);
|
||||
|
||||
Settings quickActionVoiceInput(bool quickActionVoiceInput);
|
||||
|
||||
Settings enableReadability(bool enableReadability);
|
||||
|
||||
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `Settings(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
|
||||
@@ -34,15 +24,10 @@ abstract class _$SettingsCWProxy {
|
||||
/// Settings(...).copyWith(id: 12, name: "My name")
|
||||
/// ````
|
||||
Settings call({
|
||||
String? kagiSession,
|
||||
bool showEarlyAccessFeatures,
|
||||
bool incognitoMode,
|
||||
bool enableJavascript,
|
||||
bool launchUrlExternal,
|
||||
bool blockHttpProtocol,
|
||||
ThemeMode themeMode,
|
||||
KagiTool? quickAction,
|
||||
bool quickActionVoiceInput,
|
||||
bool enableReadability,
|
||||
});
|
||||
}
|
||||
@@ -53,13 +38,6 @@ class _$SettingsCWProxyImpl implements _$SettingsCWProxy {
|
||||
|
||||
final Settings _value;
|
||||
|
||||
@override
|
||||
Settings kagiSession(String? kagiSession) => this(kagiSession: kagiSession);
|
||||
|
||||
@override
|
||||
Settings showEarlyAccessFeatures(bool showEarlyAccessFeatures) =>
|
||||
this(showEarlyAccessFeatures: showEarlyAccessFeatures);
|
||||
|
||||
@override
|
||||
Settings incognitoMode(bool incognitoMode) =>
|
||||
this(incognitoMode: incognitoMode);
|
||||
@@ -68,10 +46,6 @@ class _$SettingsCWProxyImpl implements _$SettingsCWProxy {
|
||||
Settings enableJavascript(bool enableJavascript) =>
|
||||
this(enableJavascript: enableJavascript);
|
||||
|
||||
@override
|
||||
Settings launchUrlExternal(bool launchUrlExternal) =>
|
||||
this(launchUrlExternal: launchUrlExternal);
|
||||
|
||||
@override
|
||||
Settings blockHttpProtocol(bool blockHttpProtocol) =>
|
||||
this(blockHttpProtocol: blockHttpProtocol);
|
||||
@@ -79,13 +53,6 @@ class _$SettingsCWProxyImpl implements _$SettingsCWProxy {
|
||||
@override
|
||||
Settings themeMode(ThemeMode themeMode) => this(themeMode: themeMode);
|
||||
|
||||
@override
|
||||
Settings quickAction(KagiTool? quickAction) => this(quickAction: quickAction);
|
||||
|
||||
@override
|
||||
Settings quickActionVoiceInput(bool quickActionVoiceInput) =>
|
||||
this(quickActionVoiceInput: quickActionVoiceInput);
|
||||
|
||||
@override
|
||||
Settings enableReadability(bool enableReadability) =>
|
||||
this(enableReadability: enableReadability);
|
||||
@@ -99,27 +66,13 @@ class _$SettingsCWProxyImpl implements _$SettingsCWProxy {
|
||||
/// Settings(...).copyWith(id: 12, name: "My name")
|
||||
/// ````
|
||||
Settings call({
|
||||
Object? kagiSession = const $CopyWithPlaceholder(),
|
||||
Object? showEarlyAccessFeatures = const $CopyWithPlaceholder(),
|
||||
Object? incognitoMode = const $CopyWithPlaceholder(),
|
||||
Object? enableJavascript = const $CopyWithPlaceholder(),
|
||||
Object? launchUrlExternal = const $CopyWithPlaceholder(),
|
||||
Object? blockHttpProtocol = const $CopyWithPlaceholder(),
|
||||
Object? themeMode = const $CopyWithPlaceholder(),
|
||||
Object? quickAction = const $CopyWithPlaceholder(),
|
||||
Object? quickActionVoiceInput = const $CopyWithPlaceholder(),
|
||||
Object? enableReadability = const $CopyWithPlaceholder(),
|
||||
}) {
|
||||
return Settings(
|
||||
kagiSession: kagiSession == const $CopyWithPlaceholder()
|
||||
? _value.kagiSession
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: kagiSession as String?,
|
||||
showEarlyAccessFeatures:
|
||||
showEarlyAccessFeatures == const $CopyWithPlaceholder()
|
||||
? _value.showEarlyAccessFeatures
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: showEarlyAccessFeatures as bool,
|
||||
incognitoMode: incognitoMode == const $CopyWithPlaceholder()
|
||||
? _value.incognitoMode
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
@@ -128,10 +81,6 @@ class _$SettingsCWProxyImpl implements _$SettingsCWProxy {
|
||||
? _value.enableJavascript
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: enableJavascript as bool,
|
||||
launchUrlExternal: launchUrlExternal == const $CopyWithPlaceholder()
|
||||
? _value.launchUrlExternal
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: launchUrlExternal as bool,
|
||||
blockHttpProtocol: blockHttpProtocol == const $CopyWithPlaceholder()
|
||||
? _value.blockHttpProtocol
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
@@ -140,15 +89,6 @@ class _$SettingsCWProxyImpl implements _$SettingsCWProxy {
|
||||
? _value.themeMode
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: themeMode as ThemeMode,
|
||||
quickAction: quickAction == const $CopyWithPlaceholder()
|
||||
? _value.quickAction
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: quickAction as KagiTool?,
|
||||
quickActionVoiceInput:
|
||||
quickActionVoiceInput == const $CopyWithPlaceholder()
|
||||
? _value.quickActionVoiceInput
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: quickActionVoiceInput as bool,
|
||||
enableReadability: enableReadability == const $CopyWithPlaceholder()
|
||||
? _value.enableReadability
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
@@ -162,3 +102,29 @@ extension $SettingsCopyWith on Settings {
|
||||
// ignore: library_private_types_in_public_api
|
||||
_$SettingsCWProxy get copyWith => _$SettingsCWProxyImpl(this);
|
||||
}
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
Settings _$SettingsFromJson(Map<String, dynamic> json) => Settings.withDefaults(
|
||||
incognitoMode: json['incognitoMode'] as bool?,
|
||||
enableJavascript: json['enableJavascript'] as bool?,
|
||||
blockHttpProtocol: json['blockHttpProtocol'] as bool?,
|
||||
themeMode: $enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']),
|
||||
enableReadability: json['enableReadability'] as bool?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SettingsToJson(Settings instance) => <String, dynamic>{
|
||||
'incognitoMode': instance.incognitoMode,
|
||||
'enableJavascript': instance.enableJavascript,
|
||||
'blockHttpProtocol': instance.blockHttpProtocol,
|
||||
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
|
||||
'enableReadability': instance.enableReadability,
|
||||
};
|
||||
|
||||
const _$ThemeModeEnumMap = {
|
||||
ThemeMode.system: 'system',
|
||||
ThemeMode.light: 'light',
|
||||
ThemeMode.dark: 'dark',
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:lensai/features/user/data/database/database.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart' as path_provider;
|
||||
import 'package:riverpod/riverpod.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:sqlite3/sqlite3.dart';
|
||||
import 'package:sqlite3_flutter_libs/sqlite3_flutter_libs.dart';
|
||||
import 'package:universal_io/io.dart';
|
||||
|
||||
part 'providers.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
UserDatabase userDatabase(Ref ref) {
|
||||
return UserDatabase(
|
||||
LazyDatabase(() async {
|
||||
// put the database file, called db.sqlite here, into the documents folder
|
||||
// for your app.
|
||||
final dbFolder = await path_provider.getApplicationDocumentsDirectory();
|
||||
final file = File(p.join(dbFolder.path, 'user.db'));
|
||||
|
||||
// Also work around limitations on old Android versions
|
||||
if (Platform.isAndroid) {
|
||||
await applyWorkaroundToOpenSqlite3OnOldAndroidVersions();
|
||||
}
|
||||
|
||||
// Make sqlite3 pick a more suitable location for temporary files - the
|
||||
// one from the system may be inaccessible due to sandboxing.
|
||||
final cachebase = (await path_provider.getTemporaryDirectory()).path;
|
||||
// We can't access /tmp on Android, which sqlite3 would try by default.
|
||||
// Explicitly tell it about the correct temporary directory.
|
||||
sqlite3.tempDirectory = cachebase;
|
||||
|
||||
return NativeDatabase.createInBackground(file);
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'providers.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$userDatabaseHash() => r'a84f951e1673e34b6857e1618cd7d80497c6f3a1';
|
||||
|
||||
/// See also [userDatabase].
|
||||
@ProviderFor(userDatabase)
|
||||
final userDatabaseProvider = Provider<UserDatabase>.internal(
|
||||
userDatabase,
|
||||
name: r'userDatabaseProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product') ? null : _$userDatabaseHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef UserDatabaseRef = ProviderRef<UserDatabase>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
@@ -0,0 +1,11 @@
|
||||
import 'package:lensai/features/user/data/providers.dart';
|
||||
import 'package:riverpod/riverpod.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'cache.g.dart';
|
||||
|
||||
@Riverpod()
|
||||
Stream<double> iconCacheSizeMegabytes(Ref ref) {
|
||||
final repository = ref.watch(userDatabaseProvider);
|
||||
return repository.cacheDao.iconCacheSize().watchSingle();
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'cache.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$iconCacheSizeMegabytesHash() =>
|
||||
r'43162fce3ce8b04a5a55ed52021b274afbde2861';
|
||||
|
||||
/// See also [iconCacheSizeMegabytes].
|
||||
@ProviderFor(iconCacheSizeMegabytes)
|
||||
final iconCacheSizeMegabytesProvider =
|
||||
AutoDisposeStreamProvider<double>.internal(
|
||||
iconCacheSizeMegabytes,
|
||||
name: r'iconCacheSizeMegabytesProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$iconCacheSizeMegabytesHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef IconCacheSizeMegabytesRef = AutoDisposeStreamProviderRef<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, deprecated_member_use_from_same_package
|
||||
@@ -0,0 +1,42 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:lensai/features/geckoview/domain/providers.dart';
|
||||
import 'package:lensai/features/user/data/database/database.dart';
|
||||
import 'package:lensai/features/user/data/providers.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'cache.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class CacheRepository extends _$CacheRepository {
|
||||
late UserDatabase _db;
|
||||
|
||||
Future<void> clearCache() {
|
||||
return _db.cacheDao.clearIconCache();
|
||||
}
|
||||
|
||||
Future<void> cacheIcon(Uri url, Uint8List bytes) {
|
||||
return _db.cacheDao.cacheIcon(url.origin, bytes);
|
||||
}
|
||||
|
||||
Future<Uint8List?> getCachedIcon(String origin) {
|
||||
return _db.cacheDao.getCachedIcon(origin).getSingleOrNull();
|
||||
}
|
||||
|
||||
@override
|
||||
void build() {
|
||||
final eventService = ref.watch(eventServiceProvider);
|
||||
|
||||
_db = ref.watch(userDatabaseProvider);
|
||||
|
||||
final sub = eventService.iconUpdateEvents.listen((event) async {
|
||||
if (Uri.tryParse(event.url) case final Uri url) {
|
||||
await _db.cacheDao.cacheIcon(url.origin, event.bytes);
|
||||
}
|
||||
});
|
||||
|
||||
ref.onDispose(() async {
|
||||
await sub.cancel();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'cache.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$cacheRepositoryHash() => r'37b25ff308b9f25575625984e55cb02f9c0293f0';
|
||||
|
||||
/// See also [CacheRepository].
|
||||
@ProviderFor(CacheRepository)
|
||||
final cacheRepositoryProvider =
|
||||
NotifierProvider<CacheRepository, void>.internal(
|
||||
CacheRepository.new,
|
||||
name: r'cacheRepositoryProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$cacheRepositoryHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$CacheRepository = 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, deprecated_member_use_from_same_package
|
||||
@@ -0,0 +1,57 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:lensai/features/user/data/database/database.dart';
|
||||
import 'package:lensai/features/user/data/models/settings.dart';
|
||||
import 'package:lensai/features/user/data/providers.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'settings.g.dart';
|
||||
|
||||
typedef UpdateSettingsFunc = Settings Function(Settings currentSettings);
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class SettingsRepository extends _$SettingsRepository {
|
||||
late UserDatabase _db;
|
||||
|
||||
Future<void> updateSettings(UpdateSettingsFunc updateWithCurrent) {
|
||||
final oldJson = state.toJson();
|
||||
final newJson = updateWithCurrent(state).toJson();
|
||||
|
||||
return _db.transaction(() async {
|
||||
for (final MapEntry(:key, :value) in newJson.entries) {
|
||||
if (oldJson[key] != value) {
|
||||
await _db.settingDao.updateSetting(key, value);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Settings build() {
|
||||
_db = ref.watch(userDatabaseProvider);
|
||||
|
||||
final watchSub = _db.settingDao.allSettings().watch().listen((entries) {
|
||||
final settings = Map.fromEntries(entries);
|
||||
|
||||
state = Settings.fromJson({
|
||||
'incognitoMode': settings['incognitoMode']
|
||||
?.readAs(DriftSqlType.bool, _db.typeMapping),
|
||||
'enableJavascript': settings['enableJavascript']
|
||||
?.readAs(DriftSqlType.bool, _db.typeMapping),
|
||||
'blockHttpProtocol': settings['blockHttpProtocol']
|
||||
?.readAs(DriftSqlType.bool, _db.typeMapping),
|
||||
'themeMode':
|
||||
settings['themeMode']?.readAs(DriftSqlType.string, _db.typeMapping),
|
||||
'enableReadability': settings['enableReadability']
|
||||
?.readAs(DriftSqlType.bool, _db.typeMapping),
|
||||
});
|
||||
});
|
||||
|
||||
ref.onDispose(() {
|
||||
unawaited(watchSub.cancel());
|
||||
});
|
||||
|
||||
return Settings.withDefaults();
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -1,18 +1,18 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'settings_repository.dart';
|
||||
part of 'settings.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$settingsRepositoryHash() =>
|
||||
r'b0a498899546b6a0c8566458ed8848fef410c537';
|
||||
r'7e3e032957f5092aa3d445d2b7b05a882e3cfd20';
|
||||
|
||||
/// See also [SettingsRepository].
|
||||
@ProviderFor(SettingsRepository)
|
||||
final settingsRepositoryProvider =
|
||||
AsyncNotifierProvider<SettingsRepository, Settings>.internal(
|
||||
NotifierProvider<SettingsRepository, Settings>.internal(
|
||||
SettingsRepository.new,
|
||||
name: r'settingsRepositoryProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
@@ -22,6 +22,6 @@ final settingsRepositoryProvider =
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$SettingsRepository = AsyncNotifier<Settings>;
|
||||
typedef _$SettingsRepository = Notifier<Settings>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
@@ -126,7 +126,7 @@
|
||||
|
||||
// final showEarlyAccessFeatures = ref.watch(
|
||||
// settingsRepositoryProvider.select(
|
||||
// (value) => (value.valueOrNull ?? Settings.withDefaults())
|
||||
// (value) => value
|
||||
// .showEarlyAccessFeatures,
|
||||
// ),
|
||||
// );
|
||||
@@ -148,7 +148,7 @@
|
||||
// ref.listen(
|
||||
// settingsRepositoryProvider.select(
|
||||
// (value) =>
|
||||
// (value.valueOrNull ?? Settings.withDefaults()).enableJavascript,
|
||||
// value.enableJavascript,
|
||||
// ),
|
||||
// (previous, next) async {
|
||||
// final controller = ref.read(webViewControllerProvider(widget.tabId));
|
||||
@@ -409,7 +409,7 @@
|
||||
// if (url != null) {
|
||||
// final launchExternal = ref.read(
|
||||
// settingsRepositoryProvider.select(
|
||||
// (value) => (value.valueOrNull ?? Settings.withDefaults())
|
||||
// (value) => value
|
||||
// .launchUrlExternal,
|
||||
// ),
|
||||
// );
|
||||
|
||||
+2
-4
@@ -4,8 +4,7 @@ import 'package:home_widget/home_widget.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/core/error_observer.dart';
|
||||
import 'package:lensai/domain/services/app_initialization.dart';
|
||||
import 'package:lensai/features/settings/data/models/settings.dart';
|
||||
import 'package:lensai/features/settings/data/repositories/settings_repository.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/settings.dart';
|
||||
import 'package:lensai/presentation/hooks/on_initialization.dart';
|
||||
import 'package:lensai/presentation/widgets/main_app.dart';
|
||||
|
||||
@@ -25,8 +24,7 @@ void main() async {
|
||||
builder: (context, ref, child) {
|
||||
final themeMode = ref.watch(
|
||||
settingsRepositoryProvider.select(
|
||||
(value) =>
|
||||
(value.valueOrNull ?? Settings.withDefaults()).themeMode,
|
||||
(value) => value.themeMode,
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -8,5 +8,5 @@ part 'website_title.g.dart';
|
||||
@Riverpod(keepAlive: true)
|
||||
Future<WebPageInfo> pageInfo(Ref ref, Uri url) {
|
||||
final websiteService = ref.watch(genericWebsiteServiceProvider.notifier);
|
||||
return websiteService.getInfo(url).then((value) => value.value);
|
||||
return websiteService.fetchPageInfo(url).then((value) => value.value);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ part of 'website_title.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$pageInfoHash() => r'f83bf4b19670abedf0fac5cfe59f190c37544123';
|
||||
String _$pageInfoHash() => r'f777a45aba30c82cf7b8790c07a804be364cfeb1';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
||||
@@ -13,7 +13,9 @@ class WebsiteTitleTile extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final pageInfoAsync = ref.watch(pageInfoProvider(url));
|
||||
final pageInfoAsync = (precachedInfo?.isPageInfoComplete ?? false)
|
||||
? AsyncValue.data(precachedInfo!)
|
||||
: ref.watch(pageInfoProvider(url));
|
||||
|
||||
return Skeletonizer(
|
||||
enabled: pageInfoAsync.isLoading && precachedInfo == null,
|
||||
|
||||
+6
-8
@@ -115,8 +115,13 @@ class GeckoTabsApiImpl : GeckoTabsApi {
|
||||
|
||||
private suspend fun handleIconChange(tab: SessionState) {
|
||||
try {
|
||||
val iconBytes: ByteArray?
|
||||
if (tab.content.icon != null) {
|
||||
val iconBytes = tab.content.icon?.toWebPBytes()
|
||||
iconBytes = tab.content.icon?.toWebPBytes()
|
||||
} else {
|
||||
iconBytes = null
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
components.flutterEvents.onIconChange(
|
||||
System.currentTimeMillis(),
|
||||
@@ -124,13 +129,6 @@ class GeckoTabsApiImpl : GeckoTabsApi {
|
||||
iconBytes
|
||||
) { }
|
||||
}
|
||||
} else {
|
||||
val result = components.core.icons.loadIcon(IconRequest(url = tab.content.url)).await()
|
||||
val iconBytes = result.bitmap.toWebPBytes()
|
||||
withContext(Dispatchers.Main) {
|
||||
components.flutterEvents.onIconChange(System.currentTimeMillis(), tab.id, iconBytes) { }
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logger.error("$TAG: Failed to handle icon change for tab ${tab.id}", e)
|
||||
}
|
||||
|
||||
+11
@@ -70,6 +70,17 @@ class FlutterEventMiddleware(private val flutterEvents: GeckoStateEvents) : Midd
|
||||
) { _ -> }
|
||||
}
|
||||
}
|
||||
is ContentAction.UpdateIconAction -> {
|
||||
val bytes = action.icon.toWebPBytes()
|
||||
|
||||
runOnUiThread {
|
||||
flutterEvents.onIconUpdate(
|
||||
System.currentTimeMillis(),
|
||||
action.pageUrl,
|
||||
bytes
|
||||
) { _ -> }
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
//logger.debug("Event fired: " + action.javaClass.name)
|
||||
}
|
||||
|
||||
+18
-1
@@ -1,4 +1,4 @@
|
||||
// Autogenerated from Pigeon (v22.7.0), do not edit directly.
|
||||
// Autogenerated from Pigeon (v22.7.1), do not edit directly.
|
||||
// See also: https://pub.dev/packages/pigeon
|
||||
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
|
||||
|
||||
@@ -2430,6 +2430,23 @@ class GeckoStateEvents(private val binaryMessenger: BinaryMessenger, private val
|
||||
}
|
||||
}
|
||||
}
|
||||
fun onIconUpdate(timestampArg: Long, urlArg: String, bytesArg: ByteArray, callback: (Result<Unit>) -> Unit)
|
||||
{
|
||||
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
|
||||
val channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onIconUpdate$separatedMessageChannelSuffix"
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
|
||||
channel.send(listOf(timestampArg, urlArg, bytesArg)) {
|
||||
if (it is List<*>) {
|
||||
if (it.size > 1) {
|
||||
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
|
||||
} else {
|
||||
callback(Result.success(Unit))
|
||||
}
|
||||
} else {
|
||||
callback(Result.failure(createConnectionError(channelName)))
|
||||
}
|
||||
}
|
||||
}
|
||||
fun onTabAdded(timestampArg: Long, tabIdArg: String, callback: (Result<Unit>) -> Unit)
|
||||
{
|
||||
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
|
||||
|
||||
@@ -8,7 +8,8 @@ import 'package:rxdart/rxdart.dart';
|
||||
typedef HistoryEvent = ({String tabId, HistoryState history});
|
||||
typedef ReaderableEvent = ({String tabId, ReaderableState readerable});
|
||||
typedef SecurityInfoEvent = ({String tabId, SecurityInfoState securityInfo});
|
||||
typedef IconEvent = ({String tabId, Uint8List? bytes});
|
||||
typedef IconChangeEvent = ({String tabId, Uint8List? bytes});
|
||||
typedef IconUpdateEvent = ({String url, Uint8List bytes});
|
||||
typedef ThumbnailEvent = ({String tabId, Uint8List? bytes});
|
||||
typedef FindResultsEvent = ({String tabId, List<FindResultState> results});
|
||||
|
||||
@@ -24,7 +25,8 @@ class GeckoEventService extends GeckoStateEvents {
|
||||
final _securityInfoSubject = ReplaySubject<SecurityInfoEvent>();
|
||||
final _readerableSubject = ReplaySubject<ReaderableEvent>();
|
||||
|
||||
final _iconSubject = PublishSubject<IconEvent>();
|
||||
final _iconChangeSubject = PublishSubject<IconChangeEvent>();
|
||||
final _iconUpdateSubject = PublishSubject<IconUpdateEvent>();
|
||||
final _thumbnailSubject = PublishSubject<ThumbnailEvent>();
|
||||
final _findResultsSubject = PublishSubject<FindResultsEvent>();
|
||||
|
||||
@@ -41,7 +43,8 @@ class GeckoEventService extends GeckoStateEvents {
|
||||
Stream<ReaderableEvent> get readerableEvents => _readerableSubject.stream;
|
||||
Stream<SecurityInfoEvent> get securityInfoEvents =>
|
||||
_securityInfoSubject.stream;
|
||||
Stream<IconEvent> get iconEvents => _iconSubject.stream;
|
||||
Stream<IconChangeEvent> get iconChangeEvents => _iconChangeSubject.stream;
|
||||
Stream<IconUpdateEvent> get iconUpdateEvents => _iconUpdateSubject.stream;
|
||||
Stream<ThumbnailEvent> get thumbnailEvents => _thumbnailSubject.stream;
|
||||
Stream<FindResultsEvent> get findResultsEvent => _findResultsSubject.stream;
|
||||
|
||||
@@ -114,7 +117,14 @@ class GeckoEventService extends GeckoStateEvents {
|
||||
|
||||
@override
|
||||
void onIconChange(int timestamp, String id, Uint8List? bytes) {
|
||||
_iconSubject.addWhenMoreRecent(timestamp, id, (tabId: id, bytes: bytes));
|
||||
_iconChangeSubject
|
||||
.addWhenMoreRecent(timestamp, id, (tabId: id, bytes: bytes));
|
||||
}
|
||||
|
||||
@override
|
||||
void onIconUpdate(int timestamp, String url, Uint8List bytes) {
|
||||
_iconUpdateSubject
|
||||
.addWhenMoreRecent(timestamp, url, (url: url, bytes: bytes));
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -162,7 +172,7 @@ class GeckoEventService extends GeckoStateEvents {
|
||||
unawaited(_historySubject.close());
|
||||
unawaited(_readerableSubject.close());
|
||||
unawaited(_securityInfoSubject.close());
|
||||
unawaited(_iconSubject.close());
|
||||
unawaited(_iconChangeSubject.close());
|
||||
unawaited(_thumbnailSubject.close());
|
||||
unawaited(_findResultsSubject.close());
|
||||
unawaited(_tabAddedSubject.close());
|
||||
|
||||
@@ -10,14 +10,17 @@ class GeckoIconService {
|
||||
Future<IconResult> loadIcon({
|
||||
required Uri url,
|
||||
List<Resource> resources = const [],
|
||||
IconSize size = IconSize.defaultSize,
|
||||
bool isPrivate = false,
|
||||
bool waitOnNetworkLoad = true,
|
||||
}) async {
|
||||
return _api.loadIcon(
|
||||
IconRequest(
|
||||
url: url.toString(),
|
||||
size: IconSize.defaultSize,
|
||||
size: size,
|
||||
resources: resources,
|
||||
isPrivate: false,
|
||||
waitOnNetworkLoad: true,
|
||||
isPrivate: isPrivate,
|
||||
waitOnNetworkLoad: waitOnNetworkLoad,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Autogenerated from Pigeon (v22.7.0), do not edit directly.
|
||||
// Autogenerated from Pigeon (v22.7.1), do not edit directly.
|
||||
// See also: https://pub.dev/packages/pigeon
|
||||
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers
|
||||
|
||||
@@ -2492,6 +2492,8 @@ abstract class GeckoStateEvents {
|
||||
|
||||
void onEngineReadyStateChange(int timestamp, bool state);
|
||||
|
||||
void onIconUpdate(int timestamp, String url, Uint8List bytes);
|
||||
|
||||
void onTabAdded(int timestamp, String tabId);
|
||||
|
||||
void onTabListChange(int timestamp, List<String> tabIds);
|
||||
@@ -2570,6 +2572,37 @@ abstract class GeckoStateEvents {
|
||||
});
|
||||
}
|
||||
}
|
||||
{
|
||||
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onIconUpdate$messageChannelSuffix', pigeonChannelCodec,
|
||||
binaryMessenger: binaryMessenger);
|
||||
if (api == null) {
|
||||
pigeonVar_channel.setMessageHandler(null);
|
||||
} else {
|
||||
pigeonVar_channel.setMessageHandler((Object? message) async {
|
||||
assert(message != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onIconUpdate was null.');
|
||||
final List<Object?> args = (message as List<Object?>?)!;
|
||||
final int? arg_timestamp = (args[0] as int?);
|
||||
assert(arg_timestamp != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onIconUpdate was null, expected non-null int.');
|
||||
final String? arg_url = (args[1] as String?);
|
||||
assert(arg_url != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onIconUpdate was null, expected non-null String.');
|
||||
final Uint8List? arg_bytes = (args[2] as Uint8List?);
|
||||
assert(arg_bytes != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onIconUpdate was null, expected non-null Uint8List.');
|
||||
try {
|
||||
api.onIconUpdate(arg_timestamp!, arg_url!, arg_bytes!);
|
||||
return wrapResponse(empty: true);
|
||||
} on PlatformException catch (e) {
|
||||
return wrapResponse(error: e);
|
||||
} catch (e) {
|
||||
return wrapResponse(error: PlatformException(code: 'error', message: e.toString()));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
{
|
||||
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onTabAdded$messageChannelSuffix', pigeonChannelCodec,
|
||||
|
||||
@@ -853,6 +853,7 @@ abstract class GeckoCookieApi {
|
||||
abstract class GeckoStateEvents {
|
||||
void onViewReadyStateChange(int timestamp, bool state);
|
||||
void onEngineReadyStateChange(int timestamp, bool state);
|
||||
void onIconUpdate(int timestamp, String url, Uint8List bytes);
|
||||
|
||||
void onTabAdded(int timestamp, String tabId);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user