one step closer
This commit is contained in:
@@ -24,6 +24,7 @@ analyzer:
|
||||
plugins:
|
||||
- custom_lint
|
||||
exclude:
|
||||
- "**.drift"
|
||||
- "**.g.dart"
|
||||
- "**.swagger.dart"
|
||||
- "**.freezed.dart"
|
||||
|
||||
@@ -39,7 +39,8 @@
|
||||
android:label="Lensai"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/launcher_icon"
|
||||
android:usesCleartextTraffic="true">
|
||||
android:usesCleartextTraffic="true"
|
||||
android:enableOnBackInvokedCallback="true">
|
||||
<activity android:name="eu.lensai.flutter_mozilla_components.NotificationActivity" android:theme="@style/Theme.AppCompat.Translucent" />
|
||||
|
||||
<activity
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
package me.movenext.bang_navigator
|
||||
|
||||
import io.flutter.embedding.android.FlutterFragmentActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
|
||||
class MainActivity: FlutterFragmentActivity()
|
||||
class MainActivity: FlutterFragmentActivity() {
|
||||
private val CHANNEL = "me.movenext.flutter_mozilla_components/trim_memory"
|
||||
private lateinit var channel: MethodChannel
|
||||
|
||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
|
||||
}
|
||||
|
||||
override fun onTrimMemory(level: Int) {
|
||||
super.onTrimMemory(level)
|
||||
channel.invokeMethod("onTrimMemory", level)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,10 @@ targets:
|
||||
dialect: sqlite
|
||||
options:
|
||||
version: "3.45"
|
||||
known_functions:
|
||||
lexo_rank_next: "text (int, text null)"
|
||||
lexo_rank_previous: "text (int, text null)"
|
||||
lexo_rank_reorder_after: "text (text null, text null)"
|
||||
modules:
|
||||
- json1
|
||||
- fts5
|
||||
|
||||
@@ -9,7 +9,7 @@ import 'package:lensai/features/chat_archive/presentation/screens/detail.dart';
|
||||
import 'package:lensai/features/chat_archive/presentation/screens/list.dart';
|
||||
import 'package:lensai/features/chat_archive/presentation/screens/search.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/screens/browser.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/presentation/screens/topic_list.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/presentation/screens/container_list.dart';
|
||||
import 'package:lensai/features/settings/presentation/screens/settings.dart';
|
||||
|
||||
part 'routes.g.dart';
|
||||
@@ -42,9 +42,9 @@ part 'routes.g.dart';
|
||||
),
|
||||
],
|
||||
),
|
||||
TypedGoRoute<TopicListRoute>(
|
||||
name: 'TopicsRoute',
|
||||
path: 'topics',
|
||||
TypedGoRoute<ContainerListRoute>(
|
||||
name: 'ContainerListRoute',
|
||||
path: 'containers',
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -102,10 +102,10 @@ class BangSearchRoute extends GoRouteData {
|
||||
}
|
||||
}
|
||||
|
||||
class TopicListRoute extends GoRouteData {
|
||||
class ContainerListRoute extends GoRouteData {
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) {
|
||||
return const TopicListScreen();
|
||||
return const ContainerListScreen();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,9 +47,9 @@ RouteBase get $browserRoute => GoRouteData.$route(
|
||||
],
|
||||
),
|
||||
GoRouteData.$route(
|
||||
path: 'topics',
|
||||
name: 'TopicsRoute',
|
||||
factory: $TopicListRouteExtension._fromState,
|
||||
path: 'containers',
|
||||
name: 'ContainerListRoute',
|
||||
factory: $ContainerListRouteExtension._fromState,
|
||||
),
|
||||
],
|
||||
);
|
||||
@@ -163,11 +163,12 @@ extension $BangSubCategoryRouteExtension on BangSubCategoryRoute {
|
||||
void replace(BuildContext context) => context.replace(location);
|
||||
}
|
||||
|
||||
extension $TopicListRouteExtension on TopicListRoute {
|
||||
static TopicListRoute _fromState(GoRouterState state) => TopicListRoute();
|
||||
extension $ContainerListRouteExtension on ContainerListRoute {
|
||||
static ContainerListRoute _fromState(GoRouterState state) =>
|
||||
ContainerListRoute();
|
||||
|
||||
String get location => GoRouteData.$location(
|
||||
'/topics',
|
||||
'/containers',
|
||||
);
|
||||
|
||||
void go(BuildContext context) => context.go(location);
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
class IconDataJsonConverter
|
||||
implements JsonConverter<IconData, Map<String, dynamic>> {
|
||||
const IconDataJsonConverter();
|
||||
|
||||
@override
|
||||
IconData fromJson(Map<String, dynamic> json) {
|
||||
return IconData(
|
||||
json['codePoint'] as int,
|
||||
fontFamily: json['fontFamily'] as String,
|
||||
fontPackage: json['fontPackage'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson(IconData iconData) {
|
||||
return <String, dynamic>{
|
||||
'codePoint': iconData.codePoint,
|
||||
'fontFamily': iconData.fontFamily,
|
||||
'fontPackage': iconData.fontPackage,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class IconDataTypeConverter implements TypeConverter<IconData, String> {
|
||||
const IconDataTypeConverter();
|
||||
|
||||
@override
|
||||
IconData fromSql(String fromDb) {
|
||||
final json = jsonDecode(fromDb) as Map<String, dynamic>;
|
||||
return const IconDataJsonConverter().fromJson(json);
|
||||
}
|
||||
|
||||
@override
|
||||
String toSql(IconData value) {
|
||||
assert(
|
||||
value.fontFamily != null,
|
||||
'Font family must be provided to identify icon',
|
||||
);
|
||||
|
||||
return jsonEncode(const IconDataJsonConverter().toJson(value));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import 'package:lexo_rank/lexo_rank.dart';
|
||||
import 'package:lexo_rank/lexo_rank/lexo_rank_bucket.dart';
|
||||
import 'package:sqlite3/common.dart';
|
||||
|
||||
String _nextRankOrMiddle(List<Object?> args) {
|
||||
final parsedBucket = LexoRankBucket.resolve(args[0]! as int);
|
||||
|
||||
if (args[1] != null) {
|
||||
final parsedRank = LexoRank.parse(args[1]! as String);
|
||||
|
||||
return parsedRank.genNext().value;
|
||||
} else {
|
||||
return LexoRank.middle(bucket: parsedBucket).value;
|
||||
}
|
||||
}
|
||||
|
||||
String _previousRankOrMiddle(List<Object?> args) {
|
||||
final parsedBucket = LexoRankBucket.resolve(args[0]! as int);
|
||||
|
||||
if (args[1] != null) {
|
||||
final parsedRank = LexoRank.parse(args[1]! as String);
|
||||
|
||||
return parsedRank.genPrev().value;
|
||||
} else {
|
||||
return LexoRank.middle(bucket: parsedBucket).value;
|
||||
}
|
||||
}
|
||||
|
||||
String _reorderAfter(List<Object?> args) {
|
||||
final first = (args[0] != null) ? LexoRank.parse(args[0]! as String) : null;
|
||||
final last = (args[1] != null) ? LexoRank.parse(args[1]! as String) : null;
|
||||
|
||||
if (first == null) {
|
||||
throw Exception('Tab not found');
|
||||
} else if (last == null) {
|
||||
return first.genNext().value;
|
||||
} else {
|
||||
return first.genBetween(last).value;
|
||||
}
|
||||
}
|
||||
|
||||
void registerLexorankFunctions(CommonDatabase database) {
|
||||
database.createFunction(
|
||||
functionName: 'lexo_rank_next',
|
||||
argumentCount: const AllowedArgumentCount(2),
|
||||
function: _nextRankOrMiddle,
|
||||
);
|
||||
database.createFunction(
|
||||
functionName: 'lexo_rank_previous',
|
||||
argumentCount: const AllowedArgumentCount(2),
|
||||
function: _previousRankOrMiddle,
|
||||
);
|
||||
database.createFunction(
|
||||
functionName: 'lexo_rank_reorder_after',
|
||||
argumentCount: const AllowedArgumentCount(2),
|
||||
function: _reorderAfter,
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||
import 'package:lensai/core/logger.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers/tab_session.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
@@ -10,8 +11,6 @@ part 'providers.g.dart';
|
||||
GeckoEventService eventService(EventServiceRef ref) {
|
||||
final service = GeckoEventService.setUp();
|
||||
|
||||
unawaited(GeckoTabService().syncEvents());
|
||||
|
||||
ref.onDispose(() {
|
||||
service.dispose();
|
||||
});
|
||||
@@ -19,6 +18,41 @@ GeckoEventService eventService(EventServiceRef ref) {
|
||||
return service;
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class EngineReadyState extends _$EngineReadyState {
|
||||
@override
|
||||
bool build() {
|
||||
final eventService = ref.watch(eventServiceProvider);
|
||||
|
||||
final currentState =
|
||||
eventService.engineReadyStateEvents.valueOrNull ?? false;
|
||||
|
||||
if (!currentState) {
|
||||
unawaited(
|
||||
eventService.engineReadyStateEvents
|
||||
.firstWhere((value) => value == true)
|
||||
.timeout(
|
||||
const Duration(seconds: 5),
|
||||
onTimeout: () {
|
||||
logger.w('Waiting for engine ready state timed out');
|
||||
return true;
|
||||
},
|
||||
).whenComplete(() => state = true),
|
||||
);
|
||||
}
|
||||
|
||||
final sub = eventService.engineReadyStateEvents.listen((value) {
|
||||
state = value;
|
||||
});
|
||||
|
||||
ref.onDispose(() async {
|
||||
await sub.cancel();
|
||||
});
|
||||
|
||||
return currentState;
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
TabSession selectedTabSessionNotifier(SelectedTabSessionNotifierRef ref) {
|
||||
return ref.watch(tabSessionProvider(null).notifier);
|
||||
|
||||
@@ -6,7 +6,7 @@ part of 'providers.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$eventServiceHash() => r'c36c6d895629002945c7b4eafba8389771e2eb2d';
|
||||
String _$eventServiceHash() => r'5aa357fdf0d217677a9a66ecb50417ac18929cad';
|
||||
|
||||
/// See also [eventService].
|
||||
@ProviderFor(eventService)
|
||||
@@ -37,5 +37,21 @@ final selectedTabSessionNotifierProvider =
|
||||
);
|
||||
|
||||
typedef SelectedTabSessionNotifierRef = AutoDisposeProviderRef<TabSession>;
|
||||
String _$engineReadyStateHash() => r'c682333e2e07cf0635aa7ae793a2088ca648c950';
|
||||
|
||||
/// See also [EngineReadyState].
|
||||
@ProviderFor(EngineReadyState)
|
||||
final engineReadyStateProvider =
|
||||
NotifierProvider<EngineReadyState, bool>.internal(
|
||||
EngineReadyState.new,
|
||||
name: r'engineReadyStateProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$engineReadyStateHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$EngineReadyState = Notifier<bool>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/data/database/database.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/data/providers.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/database/database.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/providers.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'selected_tab.g.dart';
|
||||
@@ -13,21 +14,29 @@ class SelectedTab extends _$SelectedTab {
|
||||
|
||||
@override
|
||||
String? build() {
|
||||
_db = ref.watch(tabDatabaseProvider);
|
||||
|
||||
final eventSerivce = ref.watch(eventServiceProvider);
|
||||
|
||||
final selectedTabSub = eventSerivce.selectedTabEvents.listen(
|
||||
(tabId) {
|
||||
state = tabId;
|
||||
_db = ref.watch(tabDatabaseProvider);
|
||||
|
||||
ref.listen(
|
||||
fireImmediately: true,
|
||||
engineReadyStateProvider,
|
||||
(previous, next) async {
|
||||
if (next) {
|
||||
await GeckoTabService().syncEvents(onSelectedTabChange: true);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
ref.listenSelf((previous, next) async {
|
||||
if (next != null) {
|
||||
await _db.tabLinkDao.touchTabLink(next, timestamp: DateTime.now());
|
||||
}
|
||||
});
|
||||
final selectedTabSub = eventSerivce.selectedTabEvents.listen(
|
||||
(tabId) async {
|
||||
state = tabId;
|
||||
|
||||
if (tabId != null) {
|
||||
await _db.tabDao.touchTab(tabId, timestamp: DateTime.now());
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
ref.onDispose(() {
|
||||
unawaited(selectedTabSub.cancel());
|
||||
|
||||
@@ -6,7 +6,7 @@ part of 'selected_tab.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$selectedTabHash() => r'd57003aa20d8a6f711918821ae355e5537a55685';
|
||||
String _$selectedTabHash() => r'e49fb1c3938a97f384673e158742e56479aa8a8f';
|
||||
|
||||
/// See also [SelectedTab].
|
||||
@ProviderFor(SelectedTab)
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/database/database.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/providers.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'tab_list.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class TabList extends _$TabList {
|
||||
late TabDatabase _db;
|
||||
|
||||
List<String> build() {
|
||||
final eventService = ref.watch(eventServiceProvider);
|
||||
|
||||
_db = ref.watch(tabDatabaseProvider);
|
||||
|
||||
ref.listen(
|
||||
fireImmediately: true,
|
||||
engineReadyStateProvider,
|
||||
(previous, next) async {
|
||||
if (next) {
|
||||
await GeckoTabService().syncEvents(onTabListChange: true);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
final tabListSub = eventService.tabListEvents.listen(
|
||||
(tabs) async {
|
||||
if (!const DeepCollectionEquality().equals(state, tabs)) {
|
||||
//Make sure we only syncing empty lists when there has been tabs
|
||||
//before as security measurement
|
||||
final syncTabs = tabs.isNotEmpty || state.isNotEmpty;
|
||||
|
||||
state = tabs;
|
||||
|
||||
if (syncTabs) {
|
||||
await _db.tabDao.syncTabs(retainTabIds: tabs);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
ref.onDispose(() {
|
||||
unawaited(tabListSub.cancel());
|
||||
});
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'tab_list.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$tabListHash() => r'fd97d4692b1c74e91c5ebd4a75e6ff45ceb82ef9';
|
||||
|
||||
/// See also [TabList].
|
||||
@ProviderFor(TabList)
|
||||
final tabListProvider = NotifierProvider<TabList, List<String>>.internal(
|
||||
TabList.new,
|
||||
name: r'tabListProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product') ? null : _$tabListHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$TabList = Notifier<List<String>>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||
@@ -33,6 +33,10 @@ class TabSession extends _$TabSession {
|
||||
return _sessionService.goForward();
|
||||
}
|
||||
|
||||
Future<void> exitFullscreen() {
|
||||
return _sessionService.exitFullscreen();
|
||||
}
|
||||
|
||||
Future<Uint8List?> requestScreenshot() {
|
||||
return _sessionService.requestScreenshot();
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ part of 'tab_session.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$tabSessionHash() => r'25afbead5c1110ca865e48a5009da70eb20d3a28';
|
||||
String _$tabSessionHash() => r'29b20f40af01e599ef80e198aec5f093d74efc6a';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||
import 'package:lensai/extensions/image.dart';
|
||||
import 'package:lensai/features/geckoview/domain/entities/find_result_state.dart';
|
||||
@@ -10,33 +9,25 @@ import 'package:lensai/features/geckoview/domain/entities/security_state.dart';
|
||||
import 'package:lensai/features/geckoview/domain/entities/tab_state.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers/selected_tab.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/data/database/database.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/data/providers.dart';
|
||||
import 'package:lensai/features/geckoview/utils/image_helper.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:synchronized/synchronized.dart';
|
||||
|
||||
part 'tab_state.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class TabStates extends _$TabStates {
|
||||
final _tabsService = GeckoTabService();
|
||||
|
||||
late TabDatabase _db;
|
||||
|
||||
void _onTabListChange(List<String> tabs) {
|
||||
state = {
|
||||
for (final tabId in tabs) tabId: state[tabId] ?? TabState.$default(tabId),
|
||||
};
|
||||
}
|
||||
final _lock = Lock();
|
||||
|
||||
void _onTabContentStateChange(TabContentState contentState) {
|
||||
final current =
|
||||
state[contentState.id] ?? TabState.$default(contentState.id);
|
||||
|
||||
state = {...state}..[contentState.id] = current.copyWith(
|
||||
contextId: contentState.contextId,
|
||||
url: Uri.parse(contentState.url),
|
||||
title: contentState.title,
|
||||
title: (contentState.title.isNotEmpty)
|
||||
? contentState.title
|
||||
: current.title,
|
||||
progress: contentState.progress,
|
||||
isPrivate: contentState.isPrivate,
|
||||
isFullScreen: contentState.isFullScreen,
|
||||
@@ -47,26 +38,24 @@ class TabStates extends _$TabStates {
|
||||
Future<void> _onIconChange(IconEvent event) async {
|
||||
final IconEvent(:tabId, :bytes) = event;
|
||||
|
||||
final current = state[tabId] ?? TabState.$default(tabId);
|
||||
|
||||
final image = (bytes != null)
|
||||
? (await tryDecodeImage(bytes)
|
||||
.then((image) async => image?.toEquatable()))
|
||||
: null;
|
||||
|
||||
final current = state[tabId] ?? TabState.$default(tabId);
|
||||
state = {...state}..[tabId] = current.copyWith.icon(image);
|
||||
}
|
||||
|
||||
Future<void> _onThumbnailChange(ThumbnailEvent event) async {
|
||||
final ThumbnailEvent(:tabId, :bytes) = event;
|
||||
|
||||
final current = state[tabId] ?? TabState.$default(tabId);
|
||||
|
||||
final image = (bytes != null)
|
||||
? (await tryDecodeImage(bytes)
|
||||
.then((image) async => image?.toEquatable()))
|
||||
: null;
|
||||
|
||||
final current = state[tabId] ?? TabState.$default(tabId);
|
||||
state = {...state}..[tabId] = current.copyWith.thumbnail(image);
|
||||
}
|
||||
|
||||
@@ -74,7 +63,6 @@ class TabStates extends _$TabStates {
|
||||
final SecurityInfoEvent(:tabId, :securityInfo) = event;
|
||||
|
||||
final current = state[tabId] ?? TabState.$default(tabId);
|
||||
|
||||
state = {...state}..[tabId] = current.copyWith.securityInfoState(
|
||||
SecurityState(
|
||||
secure: securityInfo.secure,
|
||||
@@ -88,7 +76,6 @@ class TabStates extends _$TabStates {
|
||||
final HistoryEvent(:tabId, :history) = event;
|
||||
|
||||
final current = state[tabId] ?? TabState.$default(tabId);
|
||||
|
||||
state = {...state}..[tabId] = current.copyWith.historyState(
|
||||
HistoryState(
|
||||
items: history.items.nonNulls
|
||||
@@ -108,7 +95,6 @@ class TabStates extends _$TabStates {
|
||||
final ReaderableEvent(:tabId, :readerable) = event;
|
||||
|
||||
final current = state[tabId] ?? TabState.$default(tabId);
|
||||
|
||||
state = {...state}..[tabId] = current.copyWith.readerableState(
|
||||
ReaderableState(
|
||||
readerable: readerable.readerable,
|
||||
@@ -117,13 +103,13 @@ class TabStates extends _$TabStates {
|
||||
);
|
||||
}
|
||||
|
||||
void _onFindResults(FindResultsEvent event) {
|
||||
void _onFindResultsChange(FindResultsEvent event) {
|
||||
final FindResultsEvent(:tabId, :results) = event;
|
||||
|
||||
if (results.isNotEmpty) {
|
||||
final current = state[tabId] ?? TabState.$default(tabId);
|
||||
final result = results.last;
|
||||
|
||||
final current = state[tabId] ?? TabState.$default(tabId);
|
||||
state = {...state}..[tabId] = current.copyWith.findResultState(
|
||||
FindResultState(
|
||||
activeMatchOrdinal: result.activeMatchOrdinal,
|
||||
@@ -134,64 +120,61 @@ class TabStates extends _$TabStates {
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> addTab({
|
||||
Uri? url,
|
||||
bool selectTab = true,
|
||||
bool startLoading = true,
|
||||
String? parentId,
|
||||
LoadUrlFlags flags = LoadUrlFlags.NONE,
|
||||
String? contextId,
|
||||
Source source = Internal.newTab,
|
||||
bool private = false,
|
||||
HistoryMetadataKey? historyMetadata,
|
||||
Map<String, String>? additionalHeaders,
|
||||
}) {
|
||||
return _tabsService.addTab(
|
||||
url: url,
|
||||
selectTab: selectTab,
|
||||
startLoading: startLoading,
|
||||
parentId: parentId,
|
||||
flags: flags,
|
||||
contextId: contextId,
|
||||
source: source,
|
||||
private: private,
|
||||
historyMetadata: historyMetadata,
|
||||
additionalHeaders: additionalHeaders,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> closeTabs(List<String> tabIds) {
|
||||
return _tabsService.removeTabs(ids: tabIds);
|
||||
}
|
||||
|
||||
Future<void> closeTab(String tabIds) {
|
||||
return _tabsService.removeTab(tabId: tabIds);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, TabState> build() {
|
||||
final eventService = ref.watch(eventServiceProvider);
|
||||
_db = ref.watch(tabDatabaseProvider);
|
||||
|
||||
final subscriptions = [
|
||||
eventService.tabListEvents.listen(_onTabListChange),
|
||||
eventService.tabContentEvents.listen(_onTabContentStateChange),
|
||||
eventService.iconEvents.listen(_onIconChange),
|
||||
eventService.thumbnailEvents.listen(_onThumbnailChange),
|
||||
eventService.securityInfoEvents.listen(_onSecurityInfoStateChange),
|
||||
eventService.historyEvents.listen(_onHistoryStateChange),
|
||||
eventService.readerableEvents.listen(_onReaderableStateChange),
|
||||
eventService.findResultsEvent.listen(_onFindResults),
|
||||
eventService.tabContentEvents.listen(
|
||||
(event) async {
|
||||
await _lock.synchronized(() => _onTabContentStateChange(event));
|
||||
},
|
||||
),
|
||||
eventService.iconEvents.listen(
|
||||
(event) async {
|
||||
await _lock.synchronized(() => _onIconChange(event));
|
||||
},
|
||||
),
|
||||
eventService.thumbnailEvents.listen(
|
||||
(event) async {
|
||||
await _lock.synchronized(() => _onThumbnailChange(event));
|
||||
},
|
||||
),
|
||||
eventService.securityInfoEvents.listen(
|
||||
(event) async {
|
||||
await _lock.synchronized(() => _onSecurityInfoStateChange(event));
|
||||
},
|
||||
),
|
||||
eventService.historyEvents.listen(
|
||||
(event) async {
|
||||
await _lock.synchronized(() => _onHistoryStateChange(event));
|
||||
},
|
||||
),
|
||||
eventService.readerableEvents.listen(
|
||||
(event) async {
|
||||
await _lock.synchronized(() => _onReaderableStateChange(event));
|
||||
},
|
||||
),
|
||||
eventService.findResultsEvent.listen(
|
||||
(event) async {
|
||||
await _lock.synchronized(() => _onFindResultsChange(event));
|
||||
},
|
||||
),
|
||||
];
|
||||
|
||||
ref.listenSelf(
|
||||
ref.listen(
|
||||
fireImmediately: true,
|
||||
engineReadyStateProvider,
|
||||
(previous, next) async {
|
||||
if (!const DeepCollectionEquality()
|
||||
.equals(previous?.keys.toSet(), next.keys.toSet())) {
|
||||
final ids = next.keys.toList();
|
||||
if (ids.isNotEmpty) {
|
||||
await _db.tabLinkDao.syncTabLinks(retainTabIds: ids);
|
||||
}
|
||||
if (next) {
|
||||
await GeckoTabService().syncEvents(
|
||||
onTabContentStateChange: true,
|
||||
onIconChange: true,
|
||||
onThumbnailChange: true,
|
||||
onSecurityInfoStateChange: true,
|
||||
onHistoryStateChange: true,
|
||||
onFindResults: true,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -169,7 +169,7 @@ final selectedTabStateProvider = AutoDisposeProvider<TabState?>.internal(
|
||||
);
|
||||
|
||||
typedef SelectedTabStateRef = AutoDisposeProviderRef<TabState?>;
|
||||
String _$tabStatesHash() => r'f3ae09e49150955dcb29ec89f0f3fe63155a1d6e';
|
||||
String _$tabStatesHash() => r'dcd293279a901eeaab7728979e39e5ca3bee8c66';
|
||||
|
||||
/// See also [TabStates].
|
||||
@ProviderFor(TabStates)
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/database/database.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/providers.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/providers/selected_container.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'tab.g.dart';
|
||||
@@ -9,6 +14,8 @@ part 'tab.g.dart';
|
||||
class TabRepository extends _$TabRepository {
|
||||
final _tabsService = GeckoTabService();
|
||||
|
||||
late TabDatabase _db;
|
||||
|
||||
Future<String> addTab({
|
||||
Uri? url,
|
||||
bool selectTab = true,
|
||||
@@ -48,5 +55,20 @@ class TabRepository extends _$TabRepository {
|
||||
}
|
||||
|
||||
@override
|
||||
void build() {}
|
||||
void build() {
|
||||
final eventSerivce = ref.watch(eventServiceProvider);
|
||||
|
||||
_db = ref.watch(tabDatabaseProvider);
|
||||
|
||||
final tabAddedSub = eventSerivce.tabAddedStream.listen(
|
||||
(tabId) async {
|
||||
final containerId = ref.read(selectedContainerProvider);
|
||||
await _db.tabDao.upsertTab(tabId, containerId: Value(containerId));
|
||||
},
|
||||
);
|
||||
|
||||
ref.onDispose(() {
|
||||
unawaited(tabAddedSub.cancel());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ part of 'tab.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$tabRepositoryHash() => r'0abf38ef42f29015d81ccd11eb5990b741ebc616';
|
||||
String _$tabRepositoryHash() => r'05040223b83cd5ff80afaf89407043a10e4777cb';
|
||||
|
||||
/// See also [TabRepository].
|
||||
@ProviderFor(TabRepository)
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
|
||||
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_state.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/data/providers.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/kagi/data/entities/modes.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
@@ -85,27 +84,15 @@ class ShowFindInPage extends _$ShowFindInPage {
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
Stream<List<String>> availableTabIds(AvailableTabIdsRef ref, String? topicId) {
|
||||
final db = ref.watch(tabDatabaseProvider);
|
||||
final openTabs = ref
|
||||
.watch(
|
||||
tabStatesProvider.select(
|
||||
(states) =>
|
||||
EquatableCollection(states.keys.toSet(), immutable: false),
|
||||
),
|
||||
)
|
||||
.collection;
|
||||
List<String> availableTabIds(
|
||||
AvailableTabIdsRef ref,
|
||||
String? containerId,
|
||||
) {
|
||||
final containerTabs = ref.watch(
|
||||
containerTabIdsProvider(containerId).select((value) => value.valueOrNull),
|
||||
);
|
||||
final tabStates = ref.watch(tabListProvider);
|
||||
|
||||
if (topicId != null) {
|
||||
return db.tabLinkDao.topicTabIds(topicId).watch().map(
|
||||
(tabIds) =>
|
||||
tabIds.where((tabId) => openTabs.contains(tabId)).toList(),
|
||||
);
|
||||
} else {
|
||||
return db.tabLinkDao.allTabIds().watch().map(
|
||||
(assignedTabIds) => openTabs
|
||||
.where((tabId) => !assignedTabIds.contains(tabId))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
return containerTabs?.where((tabId) => tabStates.contains(tabId)).toList() ??
|
||||
[];
|
||||
}
|
||||
|
||||
@@ -156,23 +156,23 @@ class _SelectedBangDataProviderElement
|
||||
String? get domain => (origin as SelectedBangDataProvider).domain;
|
||||
}
|
||||
|
||||
String _$availableTabIdsHash() => r'bea231cdf6211fc876b8031b5d72999eae5d7b15';
|
||||
String _$availableTabIdsHash() => r'427ea2e58c6bc27e2f16cffb850dc6621875e247';
|
||||
|
||||
/// See also [availableTabIds].
|
||||
@ProviderFor(availableTabIds)
|
||||
const availableTabIdsProvider = AvailableTabIdsFamily();
|
||||
|
||||
/// See also [availableTabIds].
|
||||
class AvailableTabIdsFamily extends Family<AsyncValue<List<String>>> {
|
||||
class AvailableTabIdsFamily extends Family<List<String>> {
|
||||
/// See also [availableTabIds].
|
||||
const AvailableTabIdsFamily();
|
||||
|
||||
/// See also [availableTabIds].
|
||||
AvailableTabIdsProvider call(
|
||||
String? topicId,
|
||||
String? containerId,
|
||||
) {
|
||||
return AvailableTabIdsProvider(
|
||||
topicId,
|
||||
containerId,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ class AvailableTabIdsFamily extends Family<AsyncValue<List<String>>> {
|
||||
covariant AvailableTabIdsProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.topicId,
|
||||
provider.containerId,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -201,14 +201,14 @@ class AvailableTabIdsFamily extends Family<AsyncValue<List<String>>> {
|
||||
}
|
||||
|
||||
/// See also [availableTabIds].
|
||||
class AvailableTabIdsProvider extends AutoDisposeStreamProvider<List<String>> {
|
||||
class AvailableTabIdsProvider extends AutoDisposeProvider<List<String>> {
|
||||
/// See also [availableTabIds].
|
||||
AvailableTabIdsProvider(
|
||||
String? topicId,
|
||||
String? containerId,
|
||||
) : this._internal(
|
||||
(ref) => availableTabIds(
|
||||
ref as AvailableTabIdsRef,
|
||||
topicId,
|
||||
containerId,
|
||||
),
|
||||
from: availableTabIdsProvider,
|
||||
name: r'availableTabIdsProvider',
|
||||
@@ -219,7 +219,7 @@ class AvailableTabIdsProvider extends AutoDisposeStreamProvider<List<String>> {
|
||||
dependencies: AvailableTabIdsFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
AvailableTabIdsFamily._allTransitiveDependencies,
|
||||
topicId: topicId,
|
||||
containerId: containerId,
|
||||
);
|
||||
|
||||
AvailableTabIdsProvider._internal(
|
||||
@@ -229,14 +229,14 @@ class AvailableTabIdsProvider extends AutoDisposeStreamProvider<List<String>> {
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.topicId,
|
||||
required this.containerId,
|
||||
}) : super.internal();
|
||||
|
||||
final String? topicId;
|
||||
final String? containerId;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
Stream<List<String>> Function(AvailableTabIdsRef provider) create,
|
||||
List<String> Function(AvailableTabIdsRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
@@ -247,42 +247,41 @@ class AvailableTabIdsProvider extends AutoDisposeStreamProvider<List<String>> {
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
topicId: topicId,
|
||||
containerId: containerId,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeStreamProviderElement<List<String>> createElement() {
|
||||
AutoDisposeProviderElement<List<String>> createElement() {
|
||||
return _AvailableTabIdsProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is AvailableTabIdsProvider && other.topicId == topicId;
|
||||
return other is AvailableTabIdsProvider && other.containerId == containerId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, topicId.hashCode);
|
||||
hash = _SystemHash.combine(hash, containerId.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
mixin AvailableTabIdsRef on AutoDisposeStreamProviderRef<List<String>> {
|
||||
/// The parameter `topicId` of this provider.
|
||||
String? get topicId;
|
||||
mixin AvailableTabIdsRef on AutoDisposeProviderRef<List<String>> {
|
||||
/// The parameter `containerId` of this provider.
|
||||
String? get containerId;
|
||||
}
|
||||
|
||||
class _AvailableTabIdsProviderElement
|
||||
extends AutoDisposeStreamProviderElement<List<String>>
|
||||
with AvailableTabIdsRef {
|
||||
extends AutoDisposeProviderElement<List<String>> with AvailableTabIdsRef {
|
||||
_AvailableTabIdsProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String? get topicId => (origin as AvailableTabIdsProvider).topicId;
|
||||
String? get containerId => (origin as AvailableTabIdsProvider).containerId;
|
||||
}
|
||||
|
||||
String _$selectedBangTriggerHash() =>
|
||||
|
||||
@@ -5,8 +5,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/data/models/equatable_iterable.dart';
|
||||
import 'package:lensai/features/geckoview/domain/entities/tab_state.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/domain/providers.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/domain/repositories/tab_link.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/providers.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/tab.dart';
|
||||
|
||||
class TabActionDialog extends HookConsumerWidget {
|
||||
final TabState initialTab;
|
||||
@@ -22,20 +22,21 @@ class TabActionDialog extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final tabState = ref.watch(tabStateProvider(initialTab.id)) ?? initialTab;
|
||||
final tabTopicId = ref.watch(
|
||||
tabTopicIdProvider(initialTab.id).select((value) => value.valueOrNull),
|
||||
final tabContainerId = ref.watch(
|
||||
tabContainerIdProvider(initialTab.id)
|
||||
.select((value) => value.valueOrNull),
|
||||
);
|
||||
|
||||
final topics = ref
|
||||
final containers = ref
|
||||
.watch(
|
||||
topicsWithCountProvider.select(
|
||||
containersWithCountProvider.select(
|
||||
(value) => EquatableCollection(value.valueOrNull, immutable: true),
|
||||
),
|
||||
)
|
||||
.collection;
|
||||
|
||||
final selectedTopic =
|
||||
topics?.firstWhereOrNull((topic) => topic.id == tabTopicId);
|
||||
final selectedContainer = containers
|
||||
?.firstWhereOrNull((container) => container.id == tabContainerId);
|
||||
|
||||
final expansionController = useExpansionTileController();
|
||||
|
||||
@@ -68,22 +69,23 @@ class TabActionDialog extends HookConsumerWidget {
|
||||
width: double.maxFinite,
|
||||
child: ExpansionTile(
|
||||
controller: expansionController,
|
||||
leading: (selectedTopic != null)
|
||||
? CircleAvatar(backgroundColor: selectedTopic.color)
|
||||
leading: (selectedContainer != null)
|
||||
? CircleAvatar(backgroundColor: selectedContainer.color)
|
||||
: null,
|
||||
title: (selectedTopic != null)
|
||||
? Text(selectedTopic.name ?? 'New Topic')
|
||||
: const Text('Assign a Topic'),
|
||||
children: topics
|
||||
?.where((topic) => topic.id != tabTopicId)
|
||||
title: (selectedContainer != null)
|
||||
? Text(selectedContainer.name ?? 'New Container')
|
||||
: const Text('Assign a Container'),
|
||||
children: containers
|
||||
?.where((container) => container.id != tabContainerId)
|
||||
.map(
|
||||
(topic) => ListTile(
|
||||
leading: CircleAvatar(backgroundColor: topic.color),
|
||||
title: Text(topic.name ?? 'New Topic'),
|
||||
(container) => ListTile(
|
||||
leading:
|
||||
CircleAvatar(backgroundColor: container.color),
|
||||
title: Text(container.name ?? 'New Container'),
|
||||
onTap: () async {
|
||||
await ref
|
||||
.read(tabLinkRepositoryProvider.notifier)
|
||||
.assignTab(initialTab.id, topic.id);
|
||||
.read(tabDataRepositoryProvider.notifier)
|
||||
.assignContainer(initialTab.id, container.id);
|
||||
|
||||
expansionController.collapse();
|
||||
},
|
||||
|
||||
+42
-19
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||
import 'package:lensai/features/geckoview/domain/entities/tab_state.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
import 'package:text_scroll/text_scroll.dart';
|
||||
|
||||
class AppBarTitle extends StatelessWidget {
|
||||
@@ -23,11 +24,16 @@ class AppBarTitle extends StatelessWidget {
|
||||
color: Theme.of(context).colorScheme.errorContainer,
|
||||
size: 14,
|
||||
);
|
||||
} else {
|
||||
} else if (!tab.isLoading) {
|
||||
return const Icon(
|
||||
MdiIcons.lock,
|
||||
size: 14,
|
||||
);
|
||||
} else {
|
||||
return const Icon(
|
||||
MdiIcons.timerSand,
|
||||
size: 14,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,10 +44,18 @@ class AppBarTitle extends StatelessWidget {
|
||||
onTap: onTap,
|
||||
child: Row(
|
||||
children: [
|
||||
RawImage(
|
||||
image: tab.icon?.value,
|
||||
height: 16,
|
||||
width: 16,
|
||||
Skeletonizer(
|
||||
enabled: tab.icon == null,
|
||||
child: Skeleton.replace(
|
||||
replacement: const Bone.icon(
|
||||
size: 16,
|
||||
),
|
||||
child: RawImage(
|
||||
image: tab.icon?.value,
|
||||
height: 16,
|
||||
width: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
@@ -51,20 +65,29 @@ class AppBarTitle extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextScroll(
|
||||
key: ValueKey(tab.title),
|
||||
tab.title,
|
||||
style: theme.textTheme.bodyLarge
|
||||
?.copyWith(color: theme.colorScheme.onSurface),
|
||||
// mode: TextScrollMode.bouncing,
|
||||
velocity: const Velocity(pixelsPerSecond: Offset(75, 0)),
|
||||
delayBefore: const Duration(milliseconds: 500),
|
||||
pauseBetween: const Duration(milliseconds: 5000),
|
||||
fadedBorder: true,
|
||||
fadeBorderSide: FadeBorderSide.right,
|
||||
fadedBorderWidth: 0.05,
|
||||
intervalSpaces: 4,
|
||||
numberOfReps: 2,
|
||||
Skeletonizer(
|
||||
enabled: tab.title.isEmpty,
|
||||
child: Skeleton.replace(
|
||||
replacement: const Padding(
|
||||
padding: EdgeInsets.only(right: 4, top: 1, bottom: 1),
|
||||
child: Bone.text(),
|
||||
),
|
||||
child: TextScroll(
|
||||
key: ValueKey(tab.title),
|
||||
tab.title,
|
||||
style: theme.textTheme.bodyLarge
|
||||
?.copyWith(color: theme.colorScheme.onSurface),
|
||||
// mode: TextScrollMode.bouncing,
|
||||
velocity: const Velocity(pixelsPerSecond: Offset(75, 0)),
|
||||
delayBefore: const Duration(milliseconds: 500),
|
||||
pauseBetween: const Duration(milliseconds: 5000),
|
||||
fadedBorder: true,
|
||||
fadeBorderSide: FadeBorderSide.right,
|
||||
fadedBorderWidth: 0.05,
|
||||
intervalSpaces: 4,
|
||||
numberOfReps: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
|
||||
+68
-31
@@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
@@ -11,9 +12,11 @@ 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/tab_preview.dart';
|
||||
import 'package:lensai/features/geckoview/features/controllers/overlay_dialog.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/domain/providers/selected_topic.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/domain/repositories/tab_link.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/presentation/widgets/topic_chips.dart';
|
||||
import 'package:lensai/features/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/presentation/widgets/container_chips.dart';
|
||||
import 'package:reorderable_grid/reorderable_grid.dart';
|
||||
|
||||
class _SliverHeaderDelagate extends SliverPersistentHeaderDelegate {
|
||||
static const _headerSize = 104.0;
|
||||
@@ -52,10 +55,10 @@ class _SliverHeaderDelagate extends SliverPersistentHeaderDelegate {
|
||||
),
|
||||
TextButton.icon(
|
||||
onPressed: () async {
|
||||
final topic = ref.read(selectedTopicProvider);
|
||||
final container = ref.read(selectedContainerProvider);
|
||||
await ref
|
||||
.read(tabLinkRepositoryProvider.notifier)
|
||||
.closeAllTabs(topic);
|
||||
.read(tabDataRepositoryProvider.notifier)
|
||||
.closeAllTabs(container);
|
||||
|
||||
onClose();
|
||||
},
|
||||
@@ -64,7 +67,7 @@ class _SliverHeaderDelagate extends SliverPersistentHeaderDelegate {
|
||||
),
|
||||
],
|
||||
),
|
||||
TopicChips(),
|
||||
ContainerChips(),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
),
|
||||
@@ -125,14 +128,11 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
||||
),
|
||||
HookConsumer(
|
||||
builder: (context, ref, child) {
|
||||
final topic = ref.watch(selectedTopicProvider);
|
||||
final container = ref.watch(selectedContainerProvider);
|
||||
final availableTabs = ref
|
||||
.watch(
|
||||
availableTabIdsProvider(topic).select(
|
||||
(value) => EquatableCollection(
|
||||
value.valueOrNull ?? [],
|
||||
immutable: true,
|
||||
),
|
||||
availableTabIdsProvider(container).select(
|
||||
(value) => EquatableCollection(value, immutable: true),
|
||||
),
|
||||
)
|
||||
.collection;
|
||||
@@ -156,8 +156,7 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
||||
availableTabs.indexWhere((webView) => webView == activeTab);
|
||||
|
||||
if (index > -1) {
|
||||
final reversedIndex = availableTabs.length - 1 - index;
|
||||
final offset = (reversedIndex ~/ 2) * itemHeight;
|
||||
final offset = (index ~/ 2) * itemHeight;
|
||||
|
||||
if (offset != sheetScrollController.offset) {
|
||||
unawaited(
|
||||
@@ -175,21 +174,15 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
||||
[availableTabs, activeTab],
|
||||
);
|
||||
|
||||
return SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
sliver: SliverGrid.count(
|
||||
//Sync values for itemHeight calculation _calculateItemHeight
|
||||
childAspectRatio: 0.75,
|
||||
mainAxisSpacing: 8.0,
|
||||
crossAxisSpacing: 8.0,
|
||||
crossAxisCount: 2,
|
||||
children: availableTabs.reversed
|
||||
.map(
|
||||
(tabId) => Consumer(
|
||||
key: ValueKey(tabId),
|
||||
final tabs = useMemoized(
|
||||
() => availableTabs
|
||||
.mapIndexed(
|
||||
(index, tabId) => ReorderableGridDelayedDragStartListener(
|
||||
key: ValueKey(tabId),
|
||||
index: index,
|
||||
child: Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final tab = ref.watch(tabStateProvider(tabId));
|
||||
|
||||
return (tab != null)
|
||||
? TabPreview(
|
||||
tab: tab,
|
||||
@@ -207,7 +200,7 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
||||
onClose();
|
||||
}
|
||||
},
|
||||
onLongPress: () {
|
||||
onDoubleTap: () {
|
||||
ref
|
||||
.read(
|
||||
overlayDialogControllerProvider
|
||||
@@ -236,8 +229,52 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
|
||||
: const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
[availableTabs, activeTab],
|
||||
);
|
||||
|
||||
return SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
sliver: SliverReorderableGrid(
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
//Sync values for itemHeight calculation _calculateItemHeight
|
||||
childAspectRatio: 0.75,
|
||||
mainAxisSpacing: 8.0,
|
||||
crossAxisSpacing: 8.0,
|
||||
crossAxisCount: 2,
|
||||
),
|
||||
itemCount: tabs.length,
|
||||
itemBuilder: (context, index) => tabs[index],
|
||||
onReorder: (oldIndex, newIndex) async {
|
||||
final containerRepository =
|
||||
ref.read(containerRepositoryProvider.notifier);
|
||||
|
||||
final tabId = availableTabs[oldIndex];
|
||||
final containerId = await ref
|
||||
.read(tabDataRepositoryProvider.notifier)
|
||||
.containerTabId(tabId);
|
||||
|
||||
final String key;
|
||||
if (newIndex <= 0) {
|
||||
key = await containerRepository
|
||||
.getLeadingOrderKey(containerId);
|
||||
} else if (newIndex >= availableTabs.length - 1) {
|
||||
key = await containerRepository
|
||||
.getTrailingOrderKey(containerId);
|
||||
} else {
|
||||
final orderAfterIndex = newIndex - 1;
|
||||
key = await containerRepository.getOrderKeyAfterTab(
|
||||
availableTabs[orderAfterIndex],
|
||||
containerId,
|
||||
);
|
||||
}
|
||||
|
||||
await ref
|
||||
.read(tabDataRepositoryProvider.notifier)
|
||||
.assignOrderKey(tabId, key);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -6,14 +6,14 @@ class TabPreview extends StatelessWidget {
|
||||
final bool isActive;
|
||||
|
||||
final VoidCallback? onTap;
|
||||
final VoidCallback? onLongPress;
|
||||
final VoidCallback? onDoubleTap;
|
||||
final VoidCallback? onDelete;
|
||||
|
||||
const TabPreview({
|
||||
required this.tab,
|
||||
required this.isActive,
|
||||
this.onTap,
|
||||
this.onLongPress,
|
||||
this.onDoubleTap,
|
||||
this.onDelete,
|
||||
super.key,
|
||||
});
|
||||
@@ -35,7 +35,7 @@ class TabPreview extends StatelessWidget {
|
||||
child: InkWell(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(16.0)),
|
||||
onTap: onTap,
|
||||
onLongPress: onLongPress,
|
||||
onDoubleTap: onDoubleTap,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
|
||||
import 'package:lensai/features/geckoview/domain/providers/tab_list.dart';
|
||||
|
||||
class TabsActionButton extends HookConsumerWidget {
|
||||
final bool isActive;
|
||||
@@ -16,7 +16,7 @@ class TabsActionButton extends HookConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
final tabCount = ref.watch(tabStatesProvider.select((tabs) => tabs.length));
|
||||
final tabCount = ref.watch(tabListProvider.select((tabs) => tabs.length));
|
||||
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+1
@@ -11,6 +11,7 @@ class ReaderableScreenController extends _$ReaderableScreenController {
|
||||
|
||||
Future<void> toggleReaderView(bool enable) async {
|
||||
state = const AsyncValue.loading();
|
||||
|
||||
final eventChange = ref.read(eventServiceProvider).readerableEvents.first;
|
||||
final toggle = _service.toggleReaderView(enable);
|
||||
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:lensai/core/uuid.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/database/database.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
|
||||
|
||||
part 'container.g.dart';
|
||||
|
||||
@DriftAccessor()
|
||||
class ContainerDao extends DatabaseAccessor<TabDatabase>
|
||||
with _$ContainerDaoMixin {
|
||||
ContainerDao(super.db);
|
||||
|
||||
Future<void> addContainer({String? name, required Color color}) {
|
||||
return db.container.insertOne(
|
||||
ContainerCompanion.insert(
|
||||
id: uuid.v7(),
|
||||
name: Value(name),
|
||||
color: color,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> replaceContainer(
|
||||
String id, {
|
||||
required String? name,
|
||||
required Color color,
|
||||
}) {
|
||||
return db.container.replaceOne(
|
||||
ContainerCompanion(
|
||||
id: Value(id),
|
||||
name: Value(name),
|
||||
color: Value(color),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> deleteContainer(String id) {
|
||||
return db.container.deleteOne(ContainerCompanion.custom(id: Variable(id)));
|
||||
}
|
||||
|
||||
SingleOrNullSelectable<ContainerData> getContainerData(String id) {
|
||||
return select(db.container)..where((t) => t.id.equals(id));
|
||||
}
|
||||
|
||||
Selectable<Color> getDistinctColors() {
|
||||
final query = db.selectOnly(db.container, distinct: true)
|
||||
..addColumns([db.container.color])
|
||||
..where(db.container.color.isNotNull());
|
||||
|
||||
return query
|
||||
.map((row) => row.readWithConverter<Color?, int>(db.container.color)!);
|
||||
}
|
||||
|
||||
SingleSelectable<String> generateLeadingOrderKey(
|
||||
String? containerId, {
|
||||
int bucket = 0,
|
||||
}) {
|
||||
return db.leadingOrderKey(bucket: bucket, containerId: containerId);
|
||||
}
|
||||
|
||||
SingleSelectable<String> generateTrailingOrderKey(
|
||||
String? containerId, {
|
||||
int bucket = 0,
|
||||
}) {
|
||||
return db.trailingOrderKey(bucket: bucket, containerId: containerId);
|
||||
}
|
||||
|
||||
SingleSelectable<String> generateOrderKeyAfterTabId(
|
||||
String? containerId,
|
||||
String tabId,
|
||||
) {
|
||||
return db.orderKeyAfterTab(containerId: containerId, tabId: tabId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'container.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
mixin _$ContainerDaoMixin on DatabaseAccessor<TabDatabase> {}
|
||||
@@ -0,0 +1,128 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/database/database.dart';
|
||||
import 'package:lexo_rank/lexo_rank.dart';
|
||||
|
||||
part 'tab.g.dart';
|
||||
|
||||
@DriftAccessor()
|
||||
class TabDao extends DatabaseAccessor<TabDatabase> with _$TabDaoMixin {
|
||||
TabDao(super.db);
|
||||
|
||||
Selectable<String> containerTabIds(String? containerId) {
|
||||
final query = selectOnly(db.tab)
|
||||
..addColumns([db.tab.id])
|
||||
..where(
|
||||
(containerId != null)
|
||||
? db.tab.containerId.equals(containerId)
|
||||
: db.tab.containerId.isNull(),
|
||||
)
|
||||
..orderBy([OrderingTerm.asc(db.tab.orderKey)]);
|
||||
|
||||
return query.map((row) => row.read(db.tab.id)!);
|
||||
}
|
||||
|
||||
Selectable<String> allTabIds() {
|
||||
final query = selectOnly(db.tab)
|
||||
..addColumns([db.tab.id])
|
||||
..orderBy([OrderingTerm.asc(db.tab.orderKey)]);
|
||||
|
||||
return query.map((row) => row.read(db.tab.id)!);
|
||||
}
|
||||
|
||||
SingleSelectable<String?> tabContainerId(String tabId) {
|
||||
final query = selectOnly(db.tab)
|
||||
..addColumns([db.tab.containerId])
|
||||
..where(db.tab.id.equals(tabId));
|
||||
|
||||
return query.map((row) => row.read(db.tab.containerId));
|
||||
}
|
||||
|
||||
Future<String> upsertTab(
|
||||
String tabId, {
|
||||
Value<String?> containerId = const Value.absent(),
|
||||
Value<String?> orderKey = const Value.absent(),
|
||||
}) {
|
||||
return db.transaction(() async {
|
||||
final currentOrderKey = orderKey.value ??
|
||||
await db.containerDao
|
||||
.generateLeadingOrderKey(containerId.value)
|
||||
.getSingle();
|
||||
|
||||
await db.tab.insertOne(
|
||||
TabCompanion.insert(
|
||||
id: tabId,
|
||||
timestamp: DateTime.now(),
|
||||
containerId: containerId,
|
||||
orderKey: currentOrderKey,
|
||||
),
|
||||
onConflict: DoUpdate(
|
||||
(old) => TabCompanion.custom(
|
||||
containerId:
|
||||
(containerId.present) ? Variable(containerId.value) : null,
|
||||
orderKey: (orderKey.present) ? Variable(orderKey.value) : null,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return tabId;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> assignContainer(
|
||||
String id, {
|
||||
required String? containerId,
|
||||
}) {
|
||||
final statement = db.tab.update()..where((t) => t.id.equals(id));
|
||||
return statement.write(
|
||||
TabCompanion(containerId: Value(containerId)),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> assignOrderKey(
|
||||
String id, {
|
||||
required String orderKey,
|
||||
}) {
|
||||
final statement = db.tab.update()..where((t) => t.id.equals(id));
|
||||
return statement.write(
|
||||
TabCompanion(orderKey: Value(orderKey)),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> touchTab(
|
||||
String id, {
|
||||
required DateTime timestamp,
|
||||
}) {
|
||||
final statement = db.tab.update()..where((t) => t.id.equals(id));
|
||||
return statement.write(
|
||||
TabCompanion(timestamp: Value(timestamp)),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> syncTabs({required List<String> retainTabIds}) async {
|
||||
return db.transaction(() async {
|
||||
await (db.tab.delete()..where((t) => t.id.isNotIn(retainTabIds))).go();
|
||||
|
||||
var currentOrderKey =
|
||||
await db.containerDao.generateLeadingOrderKey(null).getSingle();
|
||||
|
||||
await db.tab.insertAll(
|
||||
retainTabIds.map(
|
||||
(id) {
|
||||
final insertable = TabCompanion.insert(
|
||||
id: id,
|
||||
orderKey: currentOrderKey,
|
||||
timestamp: DateTime.now(),
|
||||
);
|
||||
|
||||
currentOrderKey = LexoRank.parse(currentOrderKey).genPrev().value;
|
||||
|
||||
return insertable;
|
||||
},
|
||||
),
|
||||
mode: InsertMode.insertOrIgnore,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -3,4 +3,4 @@
|
||||
part of 'tab.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
mixin _$TabLinkDaoMixin on DatabaseAccessor<TabDatabase> {}
|
||||
mixin _$TabDaoMixin on DatabaseAccessor<TabDatabase> {}
|
||||
+11
-13
@@ -1,17 +1,17 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/data/database/daos/tab.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/data/database/daos/topic.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/data/database/drift/converters/color.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/data/models/topic_data.dart';
|
||||
import 'package:flutter/widgets.dart' show Color, IconData;
|
||||
import 'package:lensai/data/database/converters/color.dart';
|
||||
import 'package:lensai/data/database/converters/icon_data.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/database/daos/container.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/database/daos/tab.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
|
||||
|
||||
part 'database.g.dart';
|
||||
|
||||
@DriftDatabase(
|
||||
include: {'database.drift'},
|
||||
daos: [TopicDao, TabLinkDao],
|
||||
daos: [ContainerDao, TabDao],
|
||||
)
|
||||
class TabDatabase extends _$TabDatabase {
|
||||
@override
|
||||
@@ -25,13 +25,11 @@ class TabDatabase extends _$TabDatabase {
|
||||
|
||||
if (from < 2) {
|
||||
await transaction(() async {
|
||||
await m.renameTable(tabLink, 'tab');
|
||||
// await m.dropColumn(tabLink, 'url');
|
||||
// await m.dropColumn(tabLink, 'title');
|
||||
// await m.dropColumn(tabLink, 'screenshot');
|
||||
|
||||
await m.dropColumn(tabLink, 'url');
|
||||
await m.dropColumn(tabLink, 'title');
|
||||
await m.dropColumn(tabLink, 'screenshot');
|
||||
|
||||
await m.alterTable(TableMigration(tabLink));
|
||||
// await m.alterTable(TableMigration(tab));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import 'package:lensai/data/database/converters/color.dart';
|
||||
import 'package:lensai/data/database/converters/icon_data.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
|
||||
|
||||
CREATE TABLE container (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
contextual_identity TEXT,
|
||||
name TEXT,
|
||||
color INTEGER NOT NULL MAPPED BY `const ColorConverter()`,
|
||||
icon TEXT MAPPED BY `const IconDataTypeConverter()`
|
||||
) WITH ContainerData;
|
||||
|
||||
CREATE TABLE tab (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
container_id TEXT REFERENCES container (id) ON DELETE CASCADE,
|
||||
order_key TEXT NOT NULL,
|
||||
timestamp DATETIME NOT NULL
|
||||
);
|
||||
|
||||
containersWithCount WITH ContainerDataWithCount:
|
||||
SELECT
|
||||
container.*,
|
||||
tab_agg.tab_count
|
||||
FROM container
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
container_id,
|
||||
COUNT(*) AS tab_count,
|
||||
MAX(timestamp) AS last_updated
|
||||
FROM tab
|
||||
GROUP BY container_id
|
||||
) AS tab_agg ON container.id = tab_agg.container_id
|
||||
ORDER BY tab_agg.last_updated DESC NULLS FIRST;
|
||||
|
||||
leadingOrderKey(:container_id AS TEXT OR NULL, :bucket AS INTEGER):
|
||||
SELECT lexo_rank_previous(
|
||||
:bucket,
|
||||
(
|
||||
SELECT order_key
|
||||
FROM tab
|
||||
WHERE container_id IS :container_id
|
||||
ORDER BY order_key
|
||||
LIMIT 1
|
||||
)
|
||||
);
|
||||
|
||||
trailingOrderKey(:container_id AS TEXT OR NULL, :bucket AS INTEGER):
|
||||
SELECT lexo_rank_next(
|
||||
:bucket,
|
||||
(
|
||||
SELECT order_key
|
||||
FROM tab
|
||||
WHERE container_id IS :container_id
|
||||
ORDER BY order_key DESC
|
||||
LIMIT 1
|
||||
)
|
||||
);
|
||||
|
||||
orderKeyAfterTab(:tab_id AS TEXT, :container_id AS TEXT OR NULL):
|
||||
WITH ordered_table AS (
|
||||
SELECT id,
|
||||
order_key,
|
||||
LEAD(order_key) OVER (ORDER BY order_key) AS next_order_key
|
||||
FROM tab
|
||||
WHERE container_id IS :container_id
|
||||
)
|
||||
SELECT lexo_rank_reorder_after(order_key, next_order_key)
|
||||
FROM ordered_table
|
||||
WHERE id = :tab_id;
|
||||
@@ -0,0 +1,996 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'database.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
class Container extends Table with TableInfo<Container, ContainerData> {
|
||||
@override
|
||||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
Container(this.attachedDatabase, [this._alias]);
|
||||
late final GeneratedColumn<String> id = GeneratedColumn<String>(
|
||||
'id', aliasedName, false,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'PRIMARY KEY NOT NULL');
|
||||
late final GeneratedColumn<String> contextualIdentity =
|
||||
GeneratedColumn<String>('contextual_identity', aliasedName, true,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: false,
|
||||
$customConstraints: '');
|
||||
late final GeneratedColumn<String> name = GeneratedColumn<String>(
|
||||
'name', aliasedName, true,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: false,
|
||||
$customConstraints: '');
|
||||
late final GeneratedColumnWithTypeConverter<Color, int> color =
|
||||
GeneratedColumn<int>('color', aliasedName, false,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL')
|
||||
.withConverter<Color>(Container.$convertercolor);
|
||||
late final GeneratedColumnWithTypeConverter<IconData?, String> icon =
|
||||
GeneratedColumn<String>('icon', aliasedName, true,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: false,
|
||||
$customConstraints: '')
|
||||
.withConverter<IconData?>(Container.$convertericonn);
|
||||
@override
|
||||
List<GeneratedColumn> get $columns =>
|
||||
[id, contextualIdentity, name, color, icon];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@override
|
||||
String get actualTableName => $name;
|
||||
static const String $name = 'container';
|
||||
@override
|
||||
Set<GeneratedColumn> get $primaryKey => {id};
|
||||
@override
|
||||
ContainerData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||
return ContainerData(
|
||||
id: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}id'])!,
|
||||
contextualIdentity: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.string, data['${effectivePrefix}contextual_identity']),
|
||||
name: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}name']),
|
||||
color: Container.$convertercolor.fromSql(attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.int, data['${effectivePrefix}color'])!),
|
||||
icon: Container.$convertericonn.fromSql(attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}icon'])),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Container createAlias(String alias) {
|
||||
return Container(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
static TypeConverter<Color, int> $convertercolor = const ColorConverter();
|
||||
static TypeConverter<IconData, String> $convertericon =
|
||||
const IconDataTypeConverter();
|
||||
static TypeConverter<IconData?, String?> $convertericonn =
|
||||
NullAwareTypeConverter.wrap($convertericon);
|
||||
@override
|
||||
bool get dontWriteConstraints => true;
|
||||
}
|
||||
|
||||
class ContainerCompanion extends UpdateCompanion<ContainerData> {
|
||||
final Value<String> id;
|
||||
final Value<String?> contextualIdentity;
|
||||
final Value<String?> name;
|
||||
final Value<Color> color;
|
||||
final Value<IconData?> icon;
|
||||
final Value<int> rowid;
|
||||
const ContainerCompanion({
|
||||
this.id = const Value.absent(),
|
||||
this.contextualIdentity = const Value.absent(),
|
||||
this.name = const Value.absent(),
|
||||
this.color = const Value.absent(),
|
||||
this.icon = const Value.absent(),
|
||||
this.rowid = const Value.absent(),
|
||||
});
|
||||
ContainerCompanion.insert({
|
||||
required String id,
|
||||
this.contextualIdentity = const Value.absent(),
|
||||
this.name = const Value.absent(),
|
||||
required Color color,
|
||||
this.icon = const Value.absent(),
|
||||
this.rowid = const Value.absent(),
|
||||
}) : id = Value(id),
|
||||
color = Value(color);
|
||||
static Insertable<ContainerData> custom({
|
||||
Expression<String>? id,
|
||||
Expression<String>? contextualIdentity,
|
||||
Expression<String>? name,
|
||||
Expression<int>? color,
|
||||
Expression<String>? icon,
|
||||
Expression<int>? rowid,
|
||||
}) {
|
||||
return RawValuesInsertable({
|
||||
if (id != null) 'id': id,
|
||||
if (contextualIdentity != null) 'contextual_identity': contextualIdentity,
|
||||
if (name != null) 'name': name,
|
||||
if (color != null) 'color': color,
|
||||
if (icon != null) 'icon': icon,
|
||||
if (rowid != null) 'rowid': rowid,
|
||||
});
|
||||
}
|
||||
|
||||
ContainerCompanion copyWith(
|
||||
{Value<String>? id,
|
||||
Value<String?>? contextualIdentity,
|
||||
Value<String?>? name,
|
||||
Value<Color>? color,
|
||||
Value<IconData?>? icon,
|
||||
Value<int>? rowid}) {
|
||||
return ContainerCompanion(
|
||||
id: id ?? this.id,
|
||||
contextualIdentity: contextualIdentity ?? this.contextualIdentity,
|
||||
name: name ?? this.name,
|
||||
color: color ?? this.color,
|
||||
icon: icon ?? this.icon,
|
||||
rowid: rowid ?? this.rowid,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
if (id.present) {
|
||||
map['id'] = Variable<String>(id.value);
|
||||
}
|
||||
if (contextualIdentity.present) {
|
||||
map['contextual_identity'] = Variable<String>(contextualIdentity.value);
|
||||
}
|
||||
if (name.present) {
|
||||
map['name'] = Variable<String>(name.value);
|
||||
}
|
||||
if (color.present) {
|
||||
map['color'] =
|
||||
Variable<int>(Container.$convertercolor.toSql(color.value));
|
||||
}
|
||||
if (icon.present) {
|
||||
map['icon'] =
|
||||
Variable<String>(Container.$convertericonn.toSql(icon.value));
|
||||
}
|
||||
if (rowid.present) {
|
||||
map['rowid'] = Variable<int>(rowid.value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('ContainerCompanion(')
|
||||
..write('id: $id, ')
|
||||
..write('contextualIdentity: $contextualIdentity, ')
|
||||
..write('name: $name, ')
|
||||
..write('color: $color, ')
|
||||
..write('icon: $icon, ')
|
||||
..write('rowid: $rowid')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
class Tab extends Table with TableInfo<Tab, TabData> {
|
||||
@override
|
||||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
Tab(this.attachedDatabase, [this._alias]);
|
||||
late final GeneratedColumn<String> id = GeneratedColumn<String>(
|
||||
'id', aliasedName, false,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'PRIMARY KEY NOT NULL');
|
||||
late final GeneratedColumn<String> containerId = GeneratedColumn<String>(
|
||||
'container_id', aliasedName, true,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: false,
|
||||
$customConstraints: 'REFERENCES container(id)ON DELETE CASCADE');
|
||||
late final GeneratedColumn<String> orderKey = GeneratedColumn<String>(
|
||||
'order_key', aliasedName, false,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL');
|
||||
late final GeneratedColumn<DateTime> timestamp = GeneratedColumn<DateTime>(
|
||||
'timestamp', aliasedName, false,
|
||||
type: DriftSqlType.dateTime,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL');
|
||||
@override
|
||||
List<GeneratedColumn> get $columns => [id, containerId, orderKey, timestamp];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@override
|
||||
String get actualTableName => $name;
|
||||
static const String $name = 'tab';
|
||||
@override
|
||||
Set<GeneratedColumn> get $primaryKey => {id};
|
||||
@override
|
||||
TabData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||
return TabData(
|
||||
id: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}id'])!,
|
||||
containerId: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}container_id']),
|
||||
orderKey: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}order_key'])!,
|
||||
timestamp: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.dateTime, data['${effectivePrefix}timestamp'])!,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Tab createAlias(String alias) {
|
||||
return Tab(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get dontWriteConstraints => true;
|
||||
}
|
||||
|
||||
class TabData extends DataClass implements Insertable<TabData> {
|
||||
final String id;
|
||||
final String? containerId;
|
||||
final String orderKey;
|
||||
final DateTime timestamp;
|
||||
const TabData(
|
||||
{required this.id,
|
||||
this.containerId,
|
||||
required this.orderKey,
|
||||
required this.timestamp});
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
map['id'] = Variable<String>(id);
|
||||
if (!nullToAbsent || containerId != null) {
|
||||
map['container_id'] = Variable<String>(containerId);
|
||||
}
|
||||
map['order_key'] = Variable<String>(orderKey);
|
||||
map['timestamp'] = Variable<DateTime>(timestamp);
|
||||
return map;
|
||||
}
|
||||
|
||||
factory TabData.fromJson(Map<String, dynamic> json,
|
||||
{ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return TabData(
|
||||
id: serializer.fromJson<String>(json['id']),
|
||||
containerId: serializer.fromJson<String?>(json['container_id']),
|
||||
orderKey: serializer.fromJson<String>(json['order_key']),
|
||||
timestamp: serializer.fromJson<DateTime>(json['timestamp']),
|
||||
);
|
||||
}
|
||||
@override
|
||||
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return <String, dynamic>{
|
||||
'id': serializer.toJson<String>(id),
|
||||
'container_id': serializer.toJson<String?>(containerId),
|
||||
'order_key': serializer.toJson<String>(orderKey),
|
||||
'timestamp': serializer.toJson<DateTime>(timestamp),
|
||||
};
|
||||
}
|
||||
|
||||
TabData copyWith(
|
||||
{String? id,
|
||||
Value<String?> containerId = const Value.absent(),
|
||||
String? orderKey,
|
||||
DateTime? timestamp}) =>
|
||||
TabData(
|
||||
id: id ?? this.id,
|
||||
containerId: containerId.present ? containerId.value : this.containerId,
|
||||
orderKey: orderKey ?? this.orderKey,
|
||||
timestamp: timestamp ?? this.timestamp,
|
||||
);
|
||||
TabData copyWithCompanion(TabCompanion data) {
|
||||
return TabData(
|
||||
id: data.id.present ? data.id.value : this.id,
|
||||
containerId:
|
||||
data.containerId.present ? data.containerId.value : this.containerId,
|
||||
orderKey: data.orderKey.present ? data.orderKey.value : this.orderKey,
|
||||
timestamp: data.timestamp.present ? data.timestamp.value : this.timestamp,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('TabData(')
|
||||
..write('id: $id, ')
|
||||
..write('containerId: $containerId, ')
|
||||
..write('orderKey: $orderKey, ')
|
||||
..write('timestamp: $timestamp')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(id, containerId, orderKey, timestamp);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is TabData &&
|
||||
other.id == this.id &&
|
||||
other.containerId == this.containerId &&
|
||||
other.orderKey == this.orderKey &&
|
||||
other.timestamp == this.timestamp);
|
||||
}
|
||||
|
||||
class TabCompanion extends UpdateCompanion<TabData> {
|
||||
final Value<String> id;
|
||||
final Value<String?> containerId;
|
||||
final Value<String> orderKey;
|
||||
final Value<DateTime> timestamp;
|
||||
final Value<int> rowid;
|
||||
const TabCompanion({
|
||||
this.id = const Value.absent(),
|
||||
this.containerId = const Value.absent(),
|
||||
this.orderKey = const Value.absent(),
|
||||
this.timestamp = const Value.absent(),
|
||||
this.rowid = const Value.absent(),
|
||||
});
|
||||
TabCompanion.insert({
|
||||
required String id,
|
||||
this.containerId = const Value.absent(),
|
||||
required String orderKey,
|
||||
required DateTime timestamp,
|
||||
this.rowid = const Value.absent(),
|
||||
}) : id = Value(id),
|
||||
orderKey = Value(orderKey),
|
||||
timestamp = Value(timestamp);
|
||||
static Insertable<TabData> custom({
|
||||
Expression<String>? id,
|
||||
Expression<String>? containerId,
|
||||
Expression<String>? orderKey,
|
||||
Expression<DateTime>? timestamp,
|
||||
Expression<int>? rowid,
|
||||
}) {
|
||||
return RawValuesInsertable({
|
||||
if (id != null) 'id': id,
|
||||
if (containerId != null) 'container_id': containerId,
|
||||
if (orderKey != null) 'order_key': orderKey,
|
||||
if (timestamp != null) 'timestamp': timestamp,
|
||||
if (rowid != null) 'rowid': rowid,
|
||||
});
|
||||
}
|
||||
|
||||
TabCompanion copyWith(
|
||||
{Value<String>? id,
|
||||
Value<String?>? containerId,
|
||||
Value<String>? orderKey,
|
||||
Value<DateTime>? timestamp,
|
||||
Value<int>? rowid}) {
|
||||
return TabCompanion(
|
||||
id: id ?? this.id,
|
||||
containerId: containerId ?? this.containerId,
|
||||
orderKey: orderKey ?? this.orderKey,
|
||||
timestamp: timestamp ?? this.timestamp,
|
||||
rowid: rowid ?? this.rowid,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
if (id.present) {
|
||||
map['id'] = Variable<String>(id.value);
|
||||
}
|
||||
if (containerId.present) {
|
||||
map['container_id'] = Variable<String>(containerId.value);
|
||||
}
|
||||
if (orderKey.present) {
|
||||
map['order_key'] = Variable<String>(orderKey.value);
|
||||
}
|
||||
if (timestamp.present) {
|
||||
map['timestamp'] = Variable<DateTime>(timestamp.value);
|
||||
}
|
||||
if (rowid.present) {
|
||||
map['rowid'] = Variable<int>(rowid.value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('TabCompanion(')
|
||||
..write('id: $id, ')
|
||||
..write('containerId: $containerId, ')
|
||||
..write('orderKey: $orderKey, ')
|
||||
..write('timestamp: $timestamp, ')
|
||||
..write('rowid: $rowid')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _$TabDatabase extends GeneratedDatabase {
|
||||
_$TabDatabase(QueryExecutor e) : super(e);
|
||||
$TabDatabaseManager get managers => $TabDatabaseManager(this);
|
||||
late final Container container = Container(this);
|
||||
late final Tab tab = Tab(this);
|
||||
late final ContainerDao containerDao = ContainerDao(this as TabDatabase);
|
||||
late final TabDao tabDao = TabDao(this as TabDatabase);
|
||||
Selectable<ContainerDataWithCount> containersWithCount() {
|
||||
return customSelect(
|
||||
'SELECT container.*, tab_agg.tab_count FROM container LEFT JOIN (SELECT container_id, COUNT(*) AS tab_count, MAX(timestamp) AS last_updated FROM tab GROUP BY container_id) AS tab_agg ON container.id = tab_agg.container_id ORDER BY tab_agg.last_updated DESC NULLS FIRST',
|
||||
variables: [],
|
||||
readsFrom: {
|
||||
container,
|
||||
tab,
|
||||
}).map((QueryRow row) => ContainerDataWithCount(
|
||||
id: row.read<String>('id'),
|
||||
contextualIdentity: row.readNullable<String>('contextual_identity'),
|
||||
name: row.readNullable<String>('name'),
|
||||
color: Container.$convertercolor.fromSql(row.read<int>('color')),
|
||||
icon: NullAwareTypeConverter.wrapFromSql(
|
||||
Container.$convertericon, row.readNullable<String>('icon')),
|
||||
tabCount: row.readNullable<int>('tab_count'),
|
||||
));
|
||||
}
|
||||
|
||||
Selectable<String> leadingOrderKey(
|
||||
{required int bucket, String? containerId}) {
|
||||
return customSelect(
|
||||
'SELECT lexo_rank_previous(?1, (SELECT order_key FROM tab WHERE container_id IS ?2 ORDER BY order_key LIMIT 1)) AS _c0',
|
||||
variables: [
|
||||
Variable<int>(bucket),
|
||||
Variable<String>(containerId)
|
||||
],
|
||||
readsFrom: {
|
||||
tab,
|
||||
}).map((QueryRow row) => row.read<String>('_c0'));
|
||||
}
|
||||
|
||||
Selectable<String> trailingOrderKey(
|
||||
{required int bucket, String? containerId}) {
|
||||
return customSelect(
|
||||
'SELECT lexo_rank_next(?1, (SELECT order_key FROM tab WHERE container_id IS ?2 ORDER BY order_key DESC LIMIT 1)) AS _c0',
|
||||
variables: [
|
||||
Variable<int>(bucket),
|
||||
Variable<String>(containerId)
|
||||
],
|
||||
readsFrom: {
|
||||
tab,
|
||||
}).map((QueryRow row) => row.read<String>('_c0'));
|
||||
}
|
||||
|
||||
Selectable<String> orderKeyAfterTab(
|
||||
{String? containerId, required String tabId}) {
|
||||
return customSelect(
|
||||
'WITH ordered_table AS (SELECT id, order_key, LEAD(order_key)OVER (ORDER BY order_key RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS) AS next_order_key FROM tab WHERE container_id IS ?1) SELECT lexo_rank_reorder_after(order_key, next_order_key) AS _c0 FROM ordered_table WHERE id = ?2',
|
||||
variables: [
|
||||
Variable<String>(containerId),
|
||||
Variable<String>(tabId)
|
||||
],
|
||||
readsFrom: {
|
||||
tab,
|
||||
}).map((QueryRow row) => row.read<String>('_c0'));
|
||||
}
|
||||
|
||||
@override
|
||||
Iterable<TableInfo<Table, Object?>> get allTables =>
|
||||
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
||||
@override
|
||||
List<DatabaseSchemaEntity> get allSchemaEntities => [container, tab];
|
||||
@override
|
||||
StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules(
|
||||
[
|
||||
WritePropagation(
|
||||
on: TableUpdateQuery.onTableName('container',
|
||||
limitUpdateKind: UpdateKind.delete),
|
||||
result: [
|
||||
TableUpdate('tab', kind: UpdateKind.delete),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
typedef $ContainerCreateCompanionBuilder = ContainerCompanion Function({
|
||||
required String id,
|
||||
Value<String?> contextualIdentity,
|
||||
Value<String?> name,
|
||||
required Color color,
|
||||
Value<IconData?> icon,
|
||||
Value<int> rowid,
|
||||
});
|
||||
typedef $ContainerUpdateCompanionBuilder = ContainerCompanion Function({
|
||||
Value<String> id,
|
||||
Value<String?> contextualIdentity,
|
||||
Value<String?> name,
|
||||
Value<Color> color,
|
||||
Value<IconData?> icon,
|
||||
Value<int> rowid,
|
||||
});
|
||||
|
||||
final class $ContainerReferences
|
||||
extends BaseReferences<_$TabDatabase, Container, ContainerData> {
|
||||
$ContainerReferences(super.$_db, super.$_table, super.$_typedResult);
|
||||
|
||||
static MultiTypedResultKey<Tab, List<TabData>> _tabRefsTable(
|
||||
_$TabDatabase db) =>
|
||||
MultiTypedResultKey.fromTable(db.tab,
|
||||
aliasName: $_aliasNameGenerator(db.container.id, db.tab.containerId));
|
||||
|
||||
$TabProcessedTableManager get tabRefs {
|
||||
final manager = $TabTableManager($_db, $_db.tab)
|
||||
.filter((f) => f.containerId.id($_item.id));
|
||||
|
||||
final cache = $_typedResult.readTableOrNull(_tabRefsTable($_db));
|
||||
return ProcessedTableManager(
|
||||
manager.$state.copyWith(prefetchedData: cache));
|
||||
}
|
||||
}
|
||||
|
||||
class $ContainerFilterComposer extends Composer<_$TabDatabase, Container> {
|
||||
$ContainerFilterComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
ColumnFilters<String> get id => $composableBuilder(
|
||||
column: $table.id, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<String> get contextualIdentity => $composableBuilder(
|
||||
column: $table.contextualIdentity,
|
||||
builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<String> get name => $composableBuilder(
|
||||
column: $table.name, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnWithTypeConverterFilters<Color, Color, int> get color =>
|
||||
$composableBuilder(
|
||||
column: $table.color,
|
||||
builder: (column) => ColumnWithTypeConverterFilters(column));
|
||||
|
||||
ColumnWithTypeConverterFilters<IconData?, IconData, String> get icon =>
|
||||
$composableBuilder(
|
||||
column: $table.icon,
|
||||
builder: (column) => ColumnWithTypeConverterFilters(column));
|
||||
|
||||
Expression<bool> tabRefs(Expression<bool> Function($TabFilterComposer f) f) {
|
||||
final $TabFilterComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.id,
|
||||
referencedTable: $db.tab,
|
||||
getReferencedColumn: (t) => t.containerId,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
$TabFilterComposer(
|
||||
$db: $db,
|
||||
$table: $db.tab,
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
));
|
||||
return f(composer);
|
||||
}
|
||||
}
|
||||
|
||||
class $ContainerOrderingComposer extends Composer<_$TabDatabase, Container> {
|
||||
$ContainerOrderingComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
ColumnOrderings<String> get id => $composableBuilder(
|
||||
column: $table.id, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<String> get contextualIdentity => $composableBuilder(
|
||||
column: $table.contextualIdentity,
|
||||
builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<String> get name => $composableBuilder(
|
||||
column: $table.name, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<int> get color => $composableBuilder(
|
||||
column: $table.color, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<String> get icon => $composableBuilder(
|
||||
column: $table.icon, builder: (column) => ColumnOrderings(column));
|
||||
}
|
||||
|
||||
class $ContainerAnnotationComposer extends Composer<_$TabDatabase, Container> {
|
||||
$ContainerAnnotationComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
GeneratedColumn<String> get id =>
|
||||
$composableBuilder(column: $table.id, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<String> get contextualIdentity => $composableBuilder(
|
||||
column: $table.contextualIdentity, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<String> get name =>
|
||||
$composableBuilder(column: $table.name, builder: (column) => column);
|
||||
|
||||
GeneratedColumnWithTypeConverter<Color, int> get color =>
|
||||
$composableBuilder(column: $table.color, builder: (column) => column);
|
||||
|
||||
GeneratedColumnWithTypeConverter<IconData?, String> get icon =>
|
||||
$composableBuilder(column: $table.icon, builder: (column) => column);
|
||||
|
||||
Expression<T> tabRefs<T extends Object>(
|
||||
Expression<T> Function($TabAnnotationComposer a) f) {
|
||||
final $TabAnnotationComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.id,
|
||||
referencedTable: $db.tab,
|
||||
getReferencedColumn: (t) => t.containerId,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
$TabAnnotationComposer(
|
||||
$db: $db,
|
||||
$table: $db.tab,
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
));
|
||||
return f(composer);
|
||||
}
|
||||
}
|
||||
|
||||
class $ContainerTableManager extends RootTableManager<
|
||||
_$TabDatabase,
|
||||
Container,
|
||||
ContainerData,
|
||||
$ContainerFilterComposer,
|
||||
$ContainerOrderingComposer,
|
||||
$ContainerAnnotationComposer,
|
||||
$ContainerCreateCompanionBuilder,
|
||||
$ContainerUpdateCompanionBuilder,
|
||||
(ContainerData, $ContainerReferences),
|
||||
ContainerData,
|
||||
PrefetchHooks Function({bool tabRefs})> {
|
||||
$ContainerTableManager(_$TabDatabase db, Container table)
|
||||
: super(TableManagerState(
|
||||
db: db,
|
||||
table: table,
|
||||
createFilteringComposer: () =>
|
||||
$ContainerFilterComposer($db: db, $table: table),
|
||||
createOrderingComposer: () =>
|
||||
$ContainerOrderingComposer($db: db, $table: table),
|
||||
createComputedFieldComposer: () =>
|
||||
$ContainerAnnotationComposer($db: db, $table: table),
|
||||
updateCompanionCallback: ({
|
||||
Value<String> id = const Value.absent(),
|
||||
Value<String?> contextualIdentity = const Value.absent(),
|
||||
Value<String?> name = const Value.absent(),
|
||||
Value<Color> color = const Value.absent(),
|
||||
Value<IconData?> icon = const Value.absent(),
|
||||
Value<int> rowid = const Value.absent(),
|
||||
}) =>
|
||||
ContainerCompanion(
|
||||
id: id,
|
||||
contextualIdentity: contextualIdentity,
|
||||
name: name,
|
||||
color: color,
|
||||
icon: icon,
|
||||
rowid: rowid,
|
||||
),
|
||||
createCompanionCallback: ({
|
||||
required String id,
|
||||
Value<String?> contextualIdentity = const Value.absent(),
|
||||
Value<String?> name = const Value.absent(),
|
||||
required Color color,
|
||||
Value<IconData?> icon = const Value.absent(),
|
||||
Value<int> rowid = const Value.absent(),
|
||||
}) =>
|
||||
ContainerCompanion.insert(
|
||||
id: id,
|
||||
contextualIdentity: contextualIdentity,
|
||||
name: name,
|
||||
color: color,
|
||||
icon: icon,
|
||||
rowid: rowid,
|
||||
),
|
||||
withReferenceMapper: (p0) => p0
|
||||
.map((e) =>
|
||||
(e.readTable(table), $ContainerReferences(db, table, e)))
|
||||
.toList(),
|
||||
prefetchHooksCallback: ({tabRefs = false}) {
|
||||
return PrefetchHooks(
|
||||
db: db,
|
||||
explicitlyWatchedTables: [if (tabRefs) db.tab],
|
||||
addJoins: null,
|
||||
getPrefetchedDataCallback: (items) async {
|
||||
return [
|
||||
if (tabRefs)
|
||||
await $_getPrefetchedData(
|
||||
currentTable: table,
|
||||
referencedTable: $ContainerReferences._tabRefsTable(db),
|
||||
managerFromTypedResult: (p0) =>
|
||||
$ContainerReferences(db, table, p0).tabRefs,
|
||||
referencedItemsForCurrentItem:
|
||||
(item, referencedItems) => referencedItems
|
||||
.where((e) => e.containerId == item.id),
|
||||
typedResults: items)
|
||||
];
|
||||
},
|
||||
);
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
typedef $ContainerProcessedTableManager = ProcessedTableManager<
|
||||
_$TabDatabase,
|
||||
Container,
|
||||
ContainerData,
|
||||
$ContainerFilterComposer,
|
||||
$ContainerOrderingComposer,
|
||||
$ContainerAnnotationComposer,
|
||||
$ContainerCreateCompanionBuilder,
|
||||
$ContainerUpdateCompanionBuilder,
|
||||
(ContainerData, $ContainerReferences),
|
||||
ContainerData,
|
||||
PrefetchHooks Function({bool tabRefs})>;
|
||||
typedef $TabCreateCompanionBuilder = TabCompanion Function({
|
||||
required String id,
|
||||
Value<String?> containerId,
|
||||
required String orderKey,
|
||||
required DateTime timestamp,
|
||||
Value<int> rowid,
|
||||
});
|
||||
typedef $TabUpdateCompanionBuilder = TabCompanion Function({
|
||||
Value<String> id,
|
||||
Value<String?> containerId,
|
||||
Value<String> orderKey,
|
||||
Value<DateTime> timestamp,
|
||||
Value<int> rowid,
|
||||
});
|
||||
|
||||
final class $TabReferences extends BaseReferences<_$TabDatabase, Tab, TabData> {
|
||||
$TabReferences(super.$_db, super.$_table, super.$_typedResult);
|
||||
|
||||
static Container _containerIdTable(_$TabDatabase db) => db.container
|
||||
.createAlias($_aliasNameGenerator(db.tab.containerId, db.container.id));
|
||||
|
||||
$ContainerProcessedTableManager? get containerId {
|
||||
if ($_item.containerId == null) return null;
|
||||
final manager = $ContainerTableManager($_db, $_db.container)
|
||||
.filter((f) => f.id($_item.containerId!));
|
||||
final item = $_typedResult.readTableOrNull(_containerIdTable($_db));
|
||||
if (item == null) return manager;
|
||||
return ProcessedTableManager(
|
||||
manager.$state.copyWith(prefetchedData: [item]));
|
||||
}
|
||||
}
|
||||
|
||||
class $TabFilterComposer extends Composer<_$TabDatabase, Tab> {
|
||||
$TabFilterComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
ColumnFilters<String> get id => $composableBuilder(
|
||||
column: $table.id, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<String> get orderKey => $composableBuilder(
|
||||
column: $table.orderKey, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<DateTime> get timestamp => $composableBuilder(
|
||||
column: $table.timestamp, builder: (column) => ColumnFilters(column));
|
||||
|
||||
$ContainerFilterComposer get containerId {
|
||||
final $ContainerFilterComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.containerId,
|
||||
referencedTable: $db.container,
|
||||
getReferencedColumn: (t) => t.id,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
$ContainerFilterComposer(
|
||||
$db: $db,
|
||||
$table: $db.container,
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
));
|
||||
return composer;
|
||||
}
|
||||
}
|
||||
|
||||
class $TabOrderingComposer extends Composer<_$TabDatabase, Tab> {
|
||||
$TabOrderingComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
ColumnOrderings<String> get id => $composableBuilder(
|
||||
column: $table.id, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<String> get orderKey => $composableBuilder(
|
||||
column: $table.orderKey, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<DateTime> get timestamp => $composableBuilder(
|
||||
column: $table.timestamp, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
$ContainerOrderingComposer get containerId {
|
||||
final $ContainerOrderingComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.containerId,
|
||||
referencedTable: $db.container,
|
||||
getReferencedColumn: (t) => t.id,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
$ContainerOrderingComposer(
|
||||
$db: $db,
|
||||
$table: $db.container,
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
));
|
||||
return composer;
|
||||
}
|
||||
}
|
||||
|
||||
class $TabAnnotationComposer extends Composer<_$TabDatabase, Tab> {
|
||||
$TabAnnotationComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
GeneratedColumn<String> get id =>
|
||||
$composableBuilder(column: $table.id, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<String> get orderKey =>
|
||||
$composableBuilder(column: $table.orderKey, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<DateTime> get timestamp =>
|
||||
$composableBuilder(column: $table.timestamp, builder: (column) => column);
|
||||
|
||||
$ContainerAnnotationComposer get containerId {
|
||||
final $ContainerAnnotationComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.containerId,
|
||||
referencedTable: $db.container,
|
||||
getReferencedColumn: (t) => t.id,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
$ContainerAnnotationComposer(
|
||||
$db: $db,
|
||||
$table: $db.container,
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
));
|
||||
return composer;
|
||||
}
|
||||
}
|
||||
|
||||
class $TabTableManager extends RootTableManager<
|
||||
_$TabDatabase,
|
||||
Tab,
|
||||
TabData,
|
||||
$TabFilterComposer,
|
||||
$TabOrderingComposer,
|
||||
$TabAnnotationComposer,
|
||||
$TabCreateCompanionBuilder,
|
||||
$TabUpdateCompanionBuilder,
|
||||
(TabData, $TabReferences),
|
||||
TabData,
|
||||
PrefetchHooks Function({bool containerId})> {
|
||||
$TabTableManager(_$TabDatabase db, Tab table)
|
||||
: super(TableManagerState(
|
||||
db: db,
|
||||
table: table,
|
||||
createFilteringComposer: () =>
|
||||
$TabFilterComposer($db: db, $table: table),
|
||||
createOrderingComposer: () =>
|
||||
$TabOrderingComposer($db: db, $table: table),
|
||||
createComputedFieldComposer: () =>
|
||||
$TabAnnotationComposer($db: db, $table: table),
|
||||
updateCompanionCallback: ({
|
||||
Value<String> id = const Value.absent(),
|
||||
Value<String?> containerId = const Value.absent(),
|
||||
Value<String> orderKey = const Value.absent(),
|
||||
Value<DateTime> timestamp = const Value.absent(),
|
||||
Value<int> rowid = const Value.absent(),
|
||||
}) =>
|
||||
TabCompanion(
|
||||
id: id,
|
||||
containerId: containerId,
|
||||
orderKey: orderKey,
|
||||
timestamp: timestamp,
|
||||
rowid: rowid,
|
||||
),
|
||||
createCompanionCallback: ({
|
||||
required String id,
|
||||
Value<String?> containerId = const Value.absent(),
|
||||
required String orderKey,
|
||||
required DateTime timestamp,
|
||||
Value<int> rowid = const Value.absent(),
|
||||
}) =>
|
||||
TabCompanion.insert(
|
||||
id: id,
|
||||
containerId: containerId,
|
||||
orderKey: orderKey,
|
||||
timestamp: timestamp,
|
||||
rowid: rowid,
|
||||
),
|
||||
withReferenceMapper: (p0) => p0
|
||||
.map((e) => (e.readTable(table), $TabReferences(db, table, e)))
|
||||
.toList(),
|
||||
prefetchHooksCallback: ({containerId = false}) {
|
||||
return PrefetchHooks(
|
||||
db: db,
|
||||
explicitlyWatchedTables: [],
|
||||
addJoins: <
|
||||
T extends TableManagerState<
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic>>(state) {
|
||||
if (containerId) {
|
||||
state = state.withJoin(
|
||||
currentTable: table,
|
||||
currentColumn: table.containerId,
|
||||
referencedTable: $TabReferences._containerIdTable(db),
|
||||
referencedColumn: $TabReferences._containerIdTable(db).id,
|
||||
) as T;
|
||||
}
|
||||
|
||||
return state;
|
||||
},
|
||||
getPrefetchedDataCallback: (items) async {
|
||||
return [];
|
||||
},
|
||||
);
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
typedef $TabProcessedTableManager = ProcessedTableManager<
|
||||
_$TabDatabase,
|
||||
Tab,
|
||||
TabData,
|
||||
$TabFilterComposer,
|
||||
$TabOrderingComposer,
|
||||
$TabAnnotationComposer,
|
||||
$TabCreateCompanionBuilder,
|
||||
$TabUpdateCompanionBuilder,
|
||||
(TabData, $TabReferences),
|
||||
TabData,
|
||||
PrefetchHooks Function({bool containerId})>;
|
||||
|
||||
class $TabDatabaseManager {
|
||||
final _$TabDatabase _db;
|
||||
$TabDatabaseManager(this._db);
|
||||
$ContainerTableManager get container =>
|
||||
$ContainerTableManager(_db, _db.container);
|
||||
$TabTableManager get tab => $TabTableManager(_db, _db.tab);
|
||||
}
|
||||
+17
-6
@@ -1,13 +1,20 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:fast_equatable/fast_equatable.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class TopicData with FastEquatable {
|
||||
class ContainerData with FastEquatable {
|
||||
final String id;
|
||||
final String? contextualIdentity;
|
||||
final String? name;
|
||||
final Color color;
|
||||
final IconData? icon;
|
||||
|
||||
TopicData({required this.id, this.name, required this.color});
|
||||
ContainerData({
|
||||
required this.id,
|
||||
this.contextualIdentity,
|
||||
this.name,
|
||||
required this.color,
|
||||
this.icon,
|
||||
});
|
||||
|
||||
@override
|
||||
bool get cacheHash => true;
|
||||
@@ -15,18 +22,22 @@ class TopicData with FastEquatable {
|
||||
@override
|
||||
List<Object?> get hashParameters => [
|
||||
id,
|
||||
contextualIdentity,
|
||||
name,
|
||||
color,
|
||||
icon,
|
||||
];
|
||||
}
|
||||
|
||||
class TopicDataWithCount extends TopicData {
|
||||
class ContainerDataWithCount extends ContainerData {
|
||||
final int? tabCount;
|
||||
|
||||
TopicDataWithCount({
|
||||
ContainerDataWithCount({
|
||||
required super.id,
|
||||
super.contextualIdentity,
|
||||
super.name,
|
||||
required super.color,
|
||||
super.icon,
|
||||
required this.tabCount,
|
||||
});
|
||||
|
||||
+9
-3
@@ -1,6 +1,7 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/data/database/database.dart';
|
||||
import 'package:lensai/data/database/functions/lexo_rank_functions.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/database/database.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart' as path_provider;
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
@@ -17,7 +18,7 @@ TabDatabase tabDatabase(TabDatabaseRef ref) {
|
||||
// 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, 'tab.db'));
|
||||
final file = File(p.join(dbFolder.path, 'tab2.db'));
|
||||
|
||||
// Also work around limitations on old Android versions
|
||||
if (Platform.isAndroid) {
|
||||
@@ -31,7 +32,12 @@ TabDatabase tabDatabase(TabDatabaseRef ref) {
|
||||
// Explicitly tell it about the correct temporary directory.
|
||||
sqlite3.tempDirectory = cachebase;
|
||||
|
||||
return NativeDatabase.createInBackground(file);
|
||||
return NativeDatabase.createInBackground(
|
||||
file,
|
||||
setup: (database) {
|
||||
registerLexorankFunctions(database);
|
||||
},
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
+1
-1
@@ -6,7 +6,7 @@ part of 'providers.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$tabDatabaseHash() => r'b886b089ae5fdb9bdcc0982efe5fcbe9bc66486f';
|
||||
String _$tabDatabaseHash() => r'567872961683d21156a9af463466f8ddbdd02f1f';
|
||||
|
||||
/// See also [tabDatabase].
|
||||
@ProviderFor(tabDatabase)
|
||||
@@ -0,0 +1,50 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/providers.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/container.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/utils/color_palette.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'providers.g.dart';
|
||||
|
||||
@Riverpod()
|
||||
Future<Color> unusedRandomContainerColor(
|
||||
UnusedRandomContainerColorRef ref,
|
||||
) async {
|
||||
final repository = ref.watch(containerRepositoryProvider.notifier);
|
||||
|
||||
final allColors = colorTypes.flattened.toList();
|
||||
final usedColors = await repository.getDistinctColors();
|
||||
|
||||
Color randomColor;
|
||||
do {
|
||||
randomColor = randomColorShade(allColors);
|
||||
} while (usedColors.contains(randomColor));
|
||||
|
||||
return randomColor;
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
Stream<List<ContainerDataWithCount>> containersWithCount(
|
||||
ContainersWithCountRef ref,
|
||||
) {
|
||||
final db = ref.watch(tabDatabaseProvider);
|
||||
return db.containersWithCount().watch();
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
Stream<String?> tabContainerId(TabContainerIdRef ref, String tabId) {
|
||||
final db = ref.watch(tabDatabaseProvider);
|
||||
return db.tabDao.tabContainerId(tabId).watchSingle();
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
Stream<List<String>> containerTabIds(
|
||||
ContainerTabIdsRef ref,
|
||||
String? containerId,
|
||||
) {
|
||||
final db = ref.watch(tabDatabaseProvider);
|
||||
return db.tabDao.containerTabIds(containerId).watch();
|
||||
}
|
||||
@@ -0,0 +1,322 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'providers.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$unusedRandomContainerColorHash() =>
|
||||
r'129cefaefa895012a6d7b35da69773e28e3289cc';
|
||||
|
||||
/// See also [unusedRandomContainerColor].
|
||||
@ProviderFor(unusedRandomContainerColor)
|
||||
final unusedRandomContainerColorProvider =
|
||||
AutoDisposeFutureProvider<Color>.internal(
|
||||
unusedRandomContainerColor,
|
||||
name: r'unusedRandomContainerColorProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$unusedRandomContainerColorHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef UnusedRandomContainerColorRef = AutoDisposeFutureProviderRef<Color>;
|
||||
String _$containersWithCountHash() =>
|
||||
r'e1bab28091dd1b9ecfd7d76e6393f459604c803a';
|
||||
|
||||
/// See also [containersWithCount].
|
||||
@ProviderFor(containersWithCount)
|
||||
final containersWithCountProvider =
|
||||
AutoDisposeStreamProvider<List<ContainerDataWithCount>>.internal(
|
||||
containersWithCount,
|
||||
name: r'containersWithCountProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$containersWithCountHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef ContainersWithCountRef
|
||||
= AutoDisposeStreamProviderRef<List<ContainerDataWithCount>>;
|
||||
String _$tabContainerIdHash() => r'8adbfdc2ef46219569073fe9e58967f4935a45e5';
|
||||
|
||||
/// 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 [tabContainerId].
|
||||
@ProviderFor(tabContainerId)
|
||||
const tabContainerIdProvider = TabContainerIdFamily();
|
||||
|
||||
/// See also [tabContainerId].
|
||||
class TabContainerIdFamily extends Family<AsyncValue<String?>> {
|
||||
/// See also [tabContainerId].
|
||||
const TabContainerIdFamily();
|
||||
|
||||
/// See also [tabContainerId].
|
||||
TabContainerIdProvider call(
|
||||
String tabId,
|
||||
) {
|
||||
return TabContainerIdProvider(
|
||||
tabId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TabContainerIdProvider getProviderOverride(
|
||||
covariant TabContainerIdProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.tabId,
|
||||
);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'tabContainerIdProvider';
|
||||
}
|
||||
|
||||
/// See also [tabContainerId].
|
||||
class TabContainerIdProvider extends AutoDisposeStreamProvider<String?> {
|
||||
/// See also [tabContainerId].
|
||||
TabContainerIdProvider(
|
||||
String tabId,
|
||||
) : this._internal(
|
||||
(ref) => tabContainerId(
|
||||
ref as TabContainerIdRef,
|
||||
tabId,
|
||||
),
|
||||
from: tabContainerIdProvider,
|
||||
name: r'tabContainerIdProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$tabContainerIdHash,
|
||||
dependencies: TabContainerIdFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
TabContainerIdFamily._allTransitiveDependencies,
|
||||
tabId: tabId,
|
||||
);
|
||||
|
||||
TabContainerIdProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.tabId,
|
||||
}) : super.internal();
|
||||
|
||||
final String tabId;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
Stream<String?> Function(TabContainerIdRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: TabContainerIdProvider._internal(
|
||||
(ref) => create(ref as TabContainerIdRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
tabId: tabId,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeStreamProviderElement<String?> createElement() {
|
||||
return _TabContainerIdProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is TabContainerIdProvider && other.tabId == tabId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, tabId.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
mixin TabContainerIdRef on AutoDisposeStreamProviderRef<String?> {
|
||||
/// The parameter `tabId` of this provider.
|
||||
String get tabId;
|
||||
}
|
||||
|
||||
class _TabContainerIdProviderElement
|
||||
extends AutoDisposeStreamProviderElement<String?> with TabContainerIdRef {
|
||||
_TabContainerIdProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String get tabId => (origin as TabContainerIdProvider).tabId;
|
||||
}
|
||||
|
||||
String _$containerTabIdsHash() => r'79e9bccfb17cf3ce802638c8b806c5a76fa8f732';
|
||||
|
||||
/// See also [containerTabIds].
|
||||
@ProviderFor(containerTabIds)
|
||||
const containerTabIdsProvider = ContainerTabIdsFamily();
|
||||
|
||||
/// See also [containerTabIds].
|
||||
class ContainerTabIdsFamily extends Family<AsyncValue<List<String>>> {
|
||||
/// See also [containerTabIds].
|
||||
const ContainerTabIdsFamily();
|
||||
|
||||
/// See also [containerTabIds].
|
||||
ContainerTabIdsProvider call(
|
||||
String? containerId,
|
||||
) {
|
||||
return ContainerTabIdsProvider(
|
||||
containerId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
ContainerTabIdsProvider getProviderOverride(
|
||||
covariant ContainerTabIdsProvider 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'containerTabIdsProvider';
|
||||
}
|
||||
|
||||
/// See also [containerTabIds].
|
||||
class ContainerTabIdsProvider extends AutoDisposeStreamProvider<List<String>> {
|
||||
/// See also [containerTabIds].
|
||||
ContainerTabIdsProvider(
|
||||
String? containerId,
|
||||
) : this._internal(
|
||||
(ref) => containerTabIds(
|
||||
ref as ContainerTabIdsRef,
|
||||
containerId,
|
||||
),
|
||||
from: containerTabIdsProvider,
|
||||
name: r'containerTabIdsProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$containerTabIdsHash,
|
||||
dependencies: ContainerTabIdsFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
ContainerTabIdsFamily._allTransitiveDependencies,
|
||||
containerId: containerId,
|
||||
);
|
||||
|
||||
ContainerTabIdsProvider._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(
|
||||
Stream<List<String>> Function(ContainerTabIdsRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: ContainerTabIdsProvider._internal(
|
||||
(ref) => create(ref as ContainerTabIdsRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
containerId: containerId,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeStreamProviderElement<List<String>> createElement() {
|
||||
return _ContainerTabIdsProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is ContainerTabIdsProvider && other.containerId == containerId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, containerId.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
mixin ContainerTabIdsRef on AutoDisposeStreamProviderRef<List<String>> {
|
||||
/// The parameter `containerId` of this provider.
|
||||
String? get containerId;
|
||||
}
|
||||
|
||||
class _ContainerTabIdsProviderElement
|
||||
extends AutoDisposeStreamProviderElement<List<String>>
|
||||
with ContainerTabIdsRef {
|
||||
_ContainerTabIdsProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String? get containerId => (origin as ContainerTabIdsProvider).containerId;
|
||||
}
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||
@@ -0,0 +1,43 @@
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/providers.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'selected_container.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class SelectedContainer extends _$SelectedContainer {
|
||||
void setContainerId(String id) {
|
||||
state = id;
|
||||
}
|
||||
|
||||
void toggleContainer(String id) {
|
||||
if (state == id) {
|
||||
clearContainer();
|
||||
} else {
|
||||
setContainerId(id);
|
||||
}
|
||||
}
|
||||
|
||||
void clearContainer() {
|
||||
state = null;
|
||||
}
|
||||
|
||||
@override
|
||||
String? build() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
Stream<ContainerData?> selectedContainerData(SelectedContainerDataRef ref) {
|
||||
final db = ref.watch(tabDatabaseProvider);
|
||||
final selectedContainer = ref.watch(selectedContainerProvider);
|
||||
|
||||
if (selectedContainer != null) {
|
||||
return db.containerDao
|
||||
.getContainerData(selectedContainer)
|
||||
.watchSingleOrNull();
|
||||
}
|
||||
|
||||
return Stream.value(null);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'selected_container.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$selectedContainerDataHash() =>
|
||||
r'dc930d1bb47b3c7989c6c119b0459f37d687af84';
|
||||
|
||||
/// See also [selectedContainerData].
|
||||
@ProviderFor(selectedContainerData)
|
||||
final selectedContainerDataProvider =
|
||||
AutoDisposeStreamProvider<ContainerData?>.internal(
|
||||
selectedContainerData,
|
||||
name: r'selectedContainerDataProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$selectedContainerDataHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef SelectedContainerDataRef = AutoDisposeStreamProviderRef<ContainerData?>;
|
||||
String _$selectedContainerHash() => r'e38e86db5bd0584af9561156c26ceaea3d23aebf';
|
||||
|
||||
/// See also [SelectedContainer].
|
||||
@ProviderFor(SelectedContainer)
|
||||
final selectedContainerProvider =
|
||||
NotifierProvider<SelectedContainer, String?>.internal(
|
||||
SelectedContainer.new,
|
||||
name: r'selectedContainerProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$selectedContainerHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$SelectedContainer = Notifier<String?>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||
@@ -0,0 +1,54 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/database/database.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/providers.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'container.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class ContainerRepository extends _$ContainerRepository {
|
||||
late TabDatabase _db;
|
||||
|
||||
@override
|
||||
void build() {
|
||||
_db = ref.watch(tabDatabaseProvider);
|
||||
}
|
||||
|
||||
Future<void> addContainer({required String? name, required Color color}) {
|
||||
return _db.containerDao.addContainer(name: name, color: color);
|
||||
}
|
||||
|
||||
Future<void> replaceContainer({
|
||||
required String id,
|
||||
required String? name,
|
||||
required Color color,
|
||||
}) {
|
||||
return _db.containerDao.replaceContainer(id, name: name, color: color);
|
||||
}
|
||||
|
||||
Future<void> deleteContainer(String id) {
|
||||
return _db.containerDao.deleteContainer(id);
|
||||
}
|
||||
|
||||
Future<Set<Color>> getDistinctColors() {
|
||||
return _db.containerDao
|
||||
.getDistinctColors()
|
||||
.get()
|
||||
.then((colors) => colors.toSet());
|
||||
}
|
||||
|
||||
Future<String> getLeadingOrderKey(String? containerId) {
|
||||
return _db.containerDao.generateLeadingOrderKey(containerId).getSingle();
|
||||
}
|
||||
|
||||
Future<String> getTrailingOrderKey(String? containerId) {
|
||||
return _db.containerDao.generateTrailingOrderKey(containerId).getSingle();
|
||||
}
|
||||
|
||||
Future<String> getOrderKeyAfterTab(String tabId, String? containerId) {
|
||||
return _db.containerDao
|
||||
.generateOrderKeyAfterTabId(containerId, tabId)
|
||||
.getSingle();
|
||||
}
|
||||
}
|
||||
+11
-10
@@ -1,26 +1,27 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'tab_link.dart';
|
||||
part of 'container.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$tabLinkRepositoryHash() => r'cdc23ca6ea215925f39842b16f9abcc289b0efbb';
|
||||
String _$containerRepositoryHash() =>
|
||||
r'89ad15861fb213d6a3fa51232fd0ff9943f51e92';
|
||||
|
||||
/// See also [TabLinkRepository].
|
||||
@ProviderFor(TabLinkRepository)
|
||||
final tabLinkRepositoryProvider =
|
||||
NotifierProvider<TabLinkRepository, void>.internal(
|
||||
TabLinkRepository.new,
|
||||
name: r'tabLinkRepositoryProvider',
|
||||
/// See also [ContainerRepository].
|
||||
@ProviderFor(ContainerRepository)
|
||||
final containerRepositoryProvider =
|
||||
NotifierProvider<ContainerRepository, void>.internal(
|
||||
ContainerRepository.new,
|
||||
name: r'containerRepositoryProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$tabLinkRepositoryHash,
|
||||
: _$containerRepositoryHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$TabLinkRepository = Notifier<void>;
|
||||
typedef _$ContainerRepository = 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
|
||||
@@ -0,0 +1,41 @@
|
||||
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/database/database.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/providers.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'tab.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class TabDataRepository extends _$TabDataRepository {
|
||||
late TabDatabase _db;
|
||||
|
||||
@override
|
||||
void build() {
|
||||
_db = ref.watch(tabDatabaseProvider);
|
||||
}
|
||||
|
||||
Future<void> assignContainer(String tabId, String? containerId) {
|
||||
return _db.tabDao.assignContainer(
|
||||
tabId,
|
||||
containerId: containerId,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> assignOrderKey(String tabId, String orderKey) {
|
||||
return _db.tabDao.assignOrderKey(
|
||||
tabId,
|
||||
orderKey: orderKey,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> closeAllTabs(String? containerId) async {
|
||||
final tabIds = await _db.tabDao.containerTabIds(containerId).get();
|
||||
if (tabIds.isNotEmpty) {
|
||||
await ref.read(tabRepositoryProvider.notifier).closeTabs(tabIds);
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> containerTabId(String tabId) {
|
||||
return _db.tabDao.tabContainerId(tabId).getSingle();
|
||||
}
|
||||
}
|
||||
+10
-10
@@ -1,26 +1,26 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'topic.dart';
|
||||
part of 'tab.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$topicRepositoryHash() => r'6c456c17af4ea7d7ef1747c92d0442459d061080';
|
||||
String _$tabDataRepositoryHash() => r'c067bbbf319cd95ad869655740fdf49d13159b94';
|
||||
|
||||
/// See also [TopicRepository].
|
||||
@ProviderFor(TopicRepository)
|
||||
final topicRepositoryProvider =
|
||||
NotifierProvider<TopicRepository, void>.internal(
|
||||
TopicRepository.new,
|
||||
name: r'topicRepositoryProvider',
|
||||
/// See also [TabDataRepository].
|
||||
@ProviderFor(TabDataRepository)
|
||||
final tabDataRepositoryProvider =
|
||||
NotifierProvider<TabDataRepository, void>.internal(
|
||||
TabDataRepository.new,
|
||||
name: r'tabDataRepositoryProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$topicRepositoryHash,
|
||||
: _$tabDataRepositoryHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$TopicRepository = Notifier<void>;
|
||||
typedef _$TabDataRepository = 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
|
||||
+46
-46
@@ -2,23 +2,23 @@ import 'package:fading_scroll/fading_scroll.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/data/models/topic_data.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/domain/providers.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/domain/providers/selected_topic.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/domain/repositories/topic.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/presentation/widgets/topic_dialog.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/providers.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/presentation/widgets/container_dialog.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
|
||||
class _TopicTile extends HookWidget {
|
||||
final TopicData topic;
|
||||
class _ContainerTile extends HookWidget {
|
||||
final ContainerData container;
|
||||
final bool isSelected;
|
||||
|
||||
final void Function(TopicResult edited) onEdit;
|
||||
final void Function(ContainerResult edited) onEdit;
|
||||
final void Function() onDelete;
|
||||
final void Function() onTap;
|
||||
|
||||
const _TopicTile(
|
||||
this.topic, {
|
||||
const _ContainerTile(
|
||||
this.container, {
|
||||
required this.isSelected,
|
||||
required this.onEdit,
|
||||
required this.onDelete,
|
||||
@@ -32,8 +32,8 @@ class _TopicTile extends HookWidget {
|
||||
|
||||
return ListTile(
|
||||
selected: isSelected,
|
||||
leading: CircleAvatar(backgroundColor: topic.color),
|
||||
title: Text(topic.name ?? 'New Topic'),
|
||||
leading: CircleAvatar(backgroundColor: container.color),
|
||||
title: Text(container.name ?? 'New Container'),
|
||||
trailing: MenuAnchor(
|
||||
controller: menuController,
|
||||
builder: (context, controller, child) {
|
||||
@@ -57,11 +57,11 @@ class _TopicTile extends HookWidget {
|
||||
menuChildren: [
|
||||
MenuItemButton(
|
||||
onPressed: () async {
|
||||
final result = await showDialog<TopicResult?>(
|
||||
final result = await showDialog<ContainerResult?>(
|
||||
context: context,
|
||||
builder: (context) => TopicDialog.edit(
|
||||
name: topic.name,
|
||||
initialColor: topic.color,
|
||||
builder: (context) => ContainerDialog.edit(
|
||||
name: container.name,
|
||||
initialColor: container.color,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -78,9 +78,9 @@ class _TopicTile extends HookWidget {
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Delete Topic'),
|
||||
title: const Text('Delete Container'),
|
||||
content: const Text(
|
||||
'Are you sure you want to delete this topic and all attached tabs?',
|
||||
'Are you sure you want to delete this container and close all attached tabs?',
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
@@ -114,32 +114,32 @@ class _TopicTile extends HookWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class TopicListScreen extends HookConsumerWidget {
|
||||
const TopicListScreen();
|
||||
class ContainerListScreen extends HookConsumerWidget {
|
||||
const ContainerListScreen();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Topics'),
|
||||
title: const Text('Containers'),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
final initialColor =
|
||||
await ref.read(unusedRandomTopicColorProvider.future);
|
||||
await ref.read(unusedRandomContainerColorProvider.future);
|
||||
|
||||
if (context.mounted) {
|
||||
final result = await showDialog<TopicResult?>(
|
||||
final result = await showDialog<ContainerResult?>(
|
||||
context: context,
|
||||
builder: (context) => TopicDialog.create(
|
||||
builder: (context) => ContainerDialog.create(
|
||||
initialColor: initialColor,
|
||||
),
|
||||
);
|
||||
|
||||
if (result != null) {
|
||||
await ref
|
||||
.read(topicRepositoryProvider.notifier)
|
||||
.addTopic(name: result.name, color: result.color);
|
||||
.read(containerRepositoryProvider.notifier)
|
||||
.addContainer(name: result.name, color: result.color);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -149,42 +149,42 @@ class TopicListScreen extends HookConsumerWidget {
|
||||
),
|
||||
body: HookConsumer(
|
||||
builder: (context, ref, child) {
|
||||
final topicsAsync = ref.watch(topicsWithCountProvider);
|
||||
final selectedTopic = ref.watch(selectedTopicProvider);
|
||||
final containersAsync = ref.watch(containersWithCountProvider);
|
||||
final selectedContainer = ref.watch(selectedContainerProvider);
|
||||
|
||||
return Skeletonizer(
|
||||
enabled: topicsAsync.isLoading,
|
||||
child: topicsAsync.when(
|
||||
data: (topics) => FadingScroll(
|
||||
enabled: containersAsync.isLoading,
|
||||
child: containersAsync.when(
|
||||
data: (containers) => FadingScroll(
|
||||
fadingSize: 25,
|
||||
builder: (context, controller) {
|
||||
return ListView.builder(
|
||||
controller: controller,
|
||||
itemCount: topics.length,
|
||||
itemCount: containers.length,
|
||||
itemBuilder: (context, index) {
|
||||
final topic = topics[index];
|
||||
return _TopicTile(
|
||||
topic,
|
||||
key: ValueKey(topic.id),
|
||||
isSelected: topic.id == selectedTopic,
|
||||
final container = containers[index];
|
||||
return _ContainerTile(
|
||||
container,
|
||||
key: ValueKey(container.id),
|
||||
isSelected: container.id == selectedContainer,
|
||||
onEdit: (edited) async {
|
||||
await ref
|
||||
.read(topicRepositoryProvider.notifier)
|
||||
.replaceTopic(
|
||||
id: topic.id,
|
||||
.read(containerRepositoryProvider.notifier)
|
||||
.replaceContainer(
|
||||
id: container.id,
|
||||
name: edited.name,
|
||||
color: edited.color,
|
||||
);
|
||||
},
|
||||
onDelete: () async {
|
||||
await ref
|
||||
.read(topicRepositoryProvider.notifier)
|
||||
.deleteTopic(topic.id);
|
||||
.read(containerRepositoryProvider.notifier)
|
||||
.deleteContainer(container.id);
|
||||
},
|
||||
onTap: () {
|
||||
ref
|
||||
.read(selectedTopicProvider.notifier)
|
||||
.toggleTopic(topic.id);
|
||||
.read(selectedContainerProvider.notifier)
|
||||
.toggleContainer(container.id);
|
||||
},
|
||||
);
|
||||
},
|
||||
@@ -194,8 +194,8 @@ class TopicListScreen extends HookConsumerWidget {
|
||||
error: (error, stackTrace) => SizedBox.shrink(),
|
||||
loading: () => ListView.builder(
|
||||
itemCount: 3,
|
||||
itemBuilder: (context, index) => _TopicTile(
|
||||
TopicData(id: 'null', color: Colors.transparent),
|
||||
itemBuilder: (context, index) => _ContainerTile(
|
||||
ContainerData(id: 'null', color: Colors.transparent),
|
||||
isSelected: false,
|
||||
onEdit: (_) {},
|
||||
onDelete: () {},
|
||||
@@ -0,0 +1,79 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/core/routing/routes.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/providers.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/domain/providers/selected_container.dart';
|
||||
import 'package:lensai/presentation/widgets/selectable_chips.dart';
|
||||
|
||||
class ContainerChips extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final containersAsync = ref.watch(containersWithCountProvider);
|
||||
final selectedContainer = ref.watch(
|
||||
selectedContainerDataProvider.select((value) => value.valueOrNull),
|
||||
);
|
||||
|
||||
return containersAsync.when(
|
||||
data: (availableContainers) => SizedBox(
|
||||
height: 48,
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(width: 16),
|
||||
if (selectedContainer != null || availableContainers.isNotEmpty)
|
||||
Expanded(
|
||||
child: SelectableChips(
|
||||
deleteIcon: false,
|
||||
itemId: (container) => container.id,
|
||||
itemAvatar: (container) => Container(
|
||||
width: 20.0,
|
||||
height: 20.0,
|
||||
decoration: BoxDecoration(
|
||||
color: container.color,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
itemLabel: (container) =>
|
||||
Text(container.name ?? 'New Container'),
|
||||
itemBadgeCount: (container) => container.tabCount,
|
||||
availableItems: availableContainers,
|
||||
selectedItem: selectedContainer,
|
||||
onSelected: (container) {
|
||||
ref
|
||||
.read(selectedContainerProvider.notifier)
|
||||
.setContainerId(container.id);
|
||||
},
|
||||
onDeleted: (container) async {
|
||||
ref
|
||||
.read(selectedContainerProvider.notifier)
|
||||
.clearContainer();
|
||||
},
|
||||
),
|
||||
)
|
||||
else
|
||||
Expanded(
|
||||
child: Text(
|
||||
"Press '>' to manage Containers.",
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).hintColor,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
await context.push(ContainerListRoute().location);
|
||||
},
|
||||
icon: const Icon(Icons.chevron_right),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
error: (error, stackTrace) => const SizedBox.shrink(),
|
||||
loading: () => const SizedBox(
|
||||
height: 48,
|
||||
width: double.infinity,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+12
-12
@@ -1,35 +1,35 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/presentation/widgets/material_color_picker.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/presentation/widgets/material_color_picker.dart';
|
||||
|
||||
typedef TopicResult = ({String? name, Color color});
|
||||
typedef ContainerResult = ({String? name, Color color});
|
||||
|
||||
enum _DialogMode { create, edit }
|
||||
|
||||
class TopicDialog extends HookWidget {
|
||||
class ContainerDialog extends HookWidget {
|
||||
final _DialogMode _mode;
|
||||
|
||||
final String? initialName;
|
||||
final Color initialColor;
|
||||
|
||||
const TopicDialog._({
|
||||
const ContainerDialog._({
|
||||
required _DialogMode mode,
|
||||
required this.initialColor,
|
||||
this.initialName,
|
||||
}) : _mode = mode;
|
||||
|
||||
factory TopicDialog.create({required Color initialColor}) {
|
||||
return TopicDialog._(
|
||||
factory ContainerDialog.create({required Color initialColor}) {
|
||||
return ContainerDialog._(
|
||||
mode: _DialogMode.create,
|
||||
initialColor: initialColor,
|
||||
);
|
||||
}
|
||||
|
||||
factory TopicDialog.edit({
|
||||
factory ContainerDialog.edit({
|
||||
required String? name,
|
||||
required Color initialColor,
|
||||
}) {
|
||||
return TopicDialog._(
|
||||
return ContainerDialog._(
|
||||
mode: _DialogMode.edit,
|
||||
initialColor: initialColor,
|
||||
initialName: name,
|
||||
@@ -54,8 +54,8 @@ class TopicDialog extends HookWidget {
|
||||
),
|
||||
title: Text(
|
||||
switch (_mode) {
|
||||
_DialogMode.create => 'New Topic',
|
||||
_DialogMode.edit => 'Edit Topic',
|
||||
_DialogMode.create => 'New Container',
|
||||
_DialogMode.edit => 'Edit Container',
|
||||
},
|
||||
),
|
||||
children: [
|
||||
@@ -94,14 +94,14 @@ class TopicDialog extends HookWidget {
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop<TopicResult?>(context);
|
||||
Navigator.pop<ContainerResult?>(context);
|
||||
},
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
final name = textController.text.trim();
|
||||
Navigator.pop<TopicResult?>(
|
||||
Navigator.pop<ContainerResult?>(
|
||||
context,
|
||||
(
|
||||
name: name.isNotEmpty ? name : null,
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
import 'package:fading_scroll/fading_scroll.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/utils/color_palette.dart';
|
||||
import 'package:lensai/features/geckoview/features/tabs/utils/color_palette.dart';
|
||||
|
||||
class MaterialPicker extends StatefulWidget {
|
||||
const MaterialPicker({
|
||||
@@ -1,63 +0,0 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/data/database/database.dart';
|
||||
|
||||
part 'tab.g.dart';
|
||||
|
||||
@DriftAccessor()
|
||||
class TabLinkDao extends DatabaseAccessor<TabDatabase> with _$TabLinkDaoMixin {
|
||||
TabLinkDao(super.db);
|
||||
|
||||
Selectable<String> topicTabIds(String topicId) {
|
||||
final query = selectOnly(db.tabLink)
|
||||
..addColumns([db.tabLink.id])
|
||||
..where(db.tabLink.topicId.equals(topicId))
|
||||
..orderBy([OrderingTerm.asc(db.tabLink.id)]);
|
||||
|
||||
return query.map((row) => row.read(db.tabLink.id)!);
|
||||
}
|
||||
|
||||
Selectable<String> allTabIds() {
|
||||
final query = selectOnly(db.tabLink)
|
||||
..addColumns([db.tabLink.id])
|
||||
..orderBy([OrderingTerm.asc(db.tabLink.id)]);
|
||||
|
||||
return query.map((row) => row.read(db.tabLink.id)!);
|
||||
}
|
||||
|
||||
SingleOrNullSelectable<String> tabTopicId(String tabId) {
|
||||
final query = selectOnly(db.tabLink)
|
||||
..addColumns([db.tabLink.topicId])
|
||||
..where(db.tabLink.id.equals(tabId));
|
||||
|
||||
return query.map((row) => row.read(db.tabLink.topicId)!);
|
||||
}
|
||||
|
||||
Future<void> upsertTabLink(
|
||||
String tabId, {
|
||||
required DateTime timestamp,
|
||||
required String topicId,
|
||||
}) {
|
||||
return db.tabLink.insertOne(
|
||||
TabLinkCompanion.insert(
|
||||
id: tabId,
|
||||
timestamp: timestamp,
|
||||
topicId: topicId,
|
||||
),
|
||||
mode: InsertMode.insertOrReplace,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> touchTabLink(
|
||||
String id, {
|
||||
required DateTime timestamp,
|
||||
}) {
|
||||
final statement = db.tabLink.update()..where((t) => t.id.equals(id));
|
||||
return statement.write(
|
||||
TabLinkCompanion(timestamp: Value(timestamp)),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> syncTabLinks({required List<String> retainTabIds}) {
|
||||
return (db.tabLink.delete()..where((t) => t.id.isNotIn(retainTabIds))).go();
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:lensai/core/uuid.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/data/database/database.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/data/models/topic_data.dart';
|
||||
|
||||
part 'topic.g.dart';
|
||||
|
||||
@DriftAccessor()
|
||||
class TopicDao extends DatabaseAccessor<TabDatabase> with _$TopicDaoMixin {
|
||||
TopicDao(super.db);
|
||||
|
||||
Future<void> addTopic({String? name, required Color color}) {
|
||||
return db.topic.insertOne(
|
||||
TopicCompanion.insert(
|
||||
id: uuid.v7(),
|
||||
name: Value(name),
|
||||
color: color,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> replaceTopic(
|
||||
String id, {
|
||||
required String? name,
|
||||
required Color color,
|
||||
}) {
|
||||
return db.topic.replaceOne(
|
||||
TopicCompanion(
|
||||
id: Value(id),
|
||||
name: Value(name),
|
||||
color: Value(color),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> deleteTopic(String id) {
|
||||
return db.topic.deleteOne(TopicCompanion.custom(id: Variable(id)));
|
||||
}
|
||||
|
||||
SingleOrNullSelectable<TopicData> getTopicData(String id) {
|
||||
return select(db.topic)..where((t) => t.id.equals(id));
|
||||
}
|
||||
|
||||
Selectable<Color> getDistinctColors() {
|
||||
final query = db.selectOnly(db.topic, distinct: true)
|
||||
..addColumns([db.topic.color])
|
||||
..where(db.topic.color.isNotNull());
|
||||
|
||||
return query
|
||||
.map((row) => row.readWithConverter<Color?, int>(db.topic.color)!);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'topic.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
mixin _$TopicDaoMixin on DatabaseAccessor<TabDatabase> {}
|
||||
@@ -1,30 +0,0 @@
|
||||
import 'package:lensai/features/geckoview/features/topics/data/database/drift/converters/color.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/data/models/topic_data.dart';
|
||||
|
||||
CREATE TABLE topic (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
name TEXT,
|
||||
color INTEGER NOT NULL MAPPED BY `const ColorConverter()`
|
||||
) WITH TopicData;
|
||||
|
||||
CREATE TABLE tab_link (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
topic_id TEXT NOT NULL REFERENCES topic (id) ON DELETE CASCADE,
|
||||
timestamp DATETIME NOT NULL
|
||||
);
|
||||
|
||||
topicsWithCount WITH TopicDataWithCount:
|
||||
SELECT
|
||||
topic.*,
|
||||
tab_agg.tab_count
|
||||
FROM topic
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
topic_id,
|
||||
COUNT(*) AS tab_count,
|
||||
MAX(timestamp) AS last_updated
|
||||
FROM tab_link
|
||||
GROUP BY topic_id
|
||||
) AS tab_agg ON topic.id = tab_agg.topic_id
|
||||
ORDER BY tab_agg.last_updated DESC NULLS FIRST;
|
||||
|
||||
@@ -1,826 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'database.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
class Topic extends Table with TableInfo<Topic, TopicData> {
|
||||
@override
|
||||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
Topic(this.attachedDatabase, [this._alias]);
|
||||
late final GeneratedColumn<String> id = GeneratedColumn<String>(
|
||||
'id', aliasedName, false,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'PRIMARY KEY NOT NULL');
|
||||
late final GeneratedColumn<String> name = GeneratedColumn<String>(
|
||||
'name', aliasedName, true,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: false,
|
||||
$customConstraints: '');
|
||||
late final GeneratedColumnWithTypeConverter<Color, int> color =
|
||||
GeneratedColumn<int>('color', aliasedName, false,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL')
|
||||
.withConverter<Color>(Topic.$convertercolor);
|
||||
@override
|
||||
List<GeneratedColumn> get $columns => [id, name, color];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@override
|
||||
String get actualTableName => $name;
|
||||
static const String $name = 'topic';
|
||||
@override
|
||||
Set<GeneratedColumn> get $primaryKey => {id};
|
||||
@override
|
||||
TopicData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||
return TopicData(
|
||||
id: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}id'])!,
|
||||
name: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}name']),
|
||||
color: Topic.$convertercolor.fromSql(attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.int, data['${effectivePrefix}color'])!),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Topic createAlias(String alias) {
|
||||
return Topic(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
static TypeConverter<Color, int> $convertercolor = const ColorConverter();
|
||||
@override
|
||||
bool get dontWriteConstraints => true;
|
||||
}
|
||||
|
||||
class TopicCompanion extends UpdateCompanion<TopicData> {
|
||||
final Value<String> id;
|
||||
final Value<String?> name;
|
||||
final Value<Color> color;
|
||||
final Value<int> rowid;
|
||||
const TopicCompanion({
|
||||
this.id = const Value.absent(),
|
||||
this.name = const Value.absent(),
|
||||
this.color = const Value.absent(),
|
||||
this.rowid = const Value.absent(),
|
||||
});
|
||||
TopicCompanion.insert({
|
||||
required String id,
|
||||
this.name = const Value.absent(),
|
||||
required Color color,
|
||||
this.rowid = const Value.absent(),
|
||||
}) : id = Value(id),
|
||||
color = Value(color);
|
||||
static Insertable<TopicData> custom({
|
||||
Expression<String>? id,
|
||||
Expression<String>? name,
|
||||
Expression<int>? color,
|
||||
Expression<int>? rowid,
|
||||
}) {
|
||||
return RawValuesInsertable({
|
||||
if (id != null) 'id': id,
|
||||
if (name != null) 'name': name,
|
||||
if (color != null) 'color': color,
|
||||
if (rowid != null) 'rowid': rowid,
|
||||
});
|
||||
}
|
||||
|
||||
TopicCompanion copyWith(
|
||||
{Value<String>? id,
|
||||
Value<String?>? name,
|
||||
Value<Color>? color,
|
||||
Value<int>? rowid}) {
|
||||
return TopicCompanion(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
color: color ?? this.color,
|
||||
rowid: rowid ?? this.rowid,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
if (id.present) {
|
||||
map['id'] = Variable<String>(id.value);
|
||||
}
|
||||
if (name.present) {
|
||||
map['name'] = Variable<String>(name.value);
|
||||
}
|
||||
if (color.present) {
|
||||
map['color'] = Variable<int>(Topic.$convertercolor.toSql(color.value));
|
||||
}
|
||||
if (rowid.present) {
|
||||
map['rowid'] = Variable<int>(rowid.value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('TopicCompanion(')
|
||||
..write('id: $id, ')
|
||||
..write('name: $name, ')
|
||||
..write('color: $color, ')
|
||||
..write('rowid: $rowid')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
class TabLink extends Table with TableInfo<TabLink, TabLinkData> {
|
||||
@override
|
||||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
TabLink(this.attachedDatabase, [this._alias]);
|
||||
late final GeneratedColumn<String> id = GeneratedColumn<String>(
|
||||
'id', aliasedName, false,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'PRIMARY KEY NOT NULL');
|
||||
late final GeneratedColumn<String> topicId = GeneratedColumn<String>(
|
||||
'topic_id', aliasedName, false,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL REFERENCES topic(id)ON DELETE CASCADE');
|
||||
late final GeneratedColumn<DateTime> timestamp = GeneratedColumn<DateTime>(
|
||||
'timestamp', aliasedName, false,
|
||||
type: DriftSqlType.dateTime,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL');
|
||||
@override
|
||||
List<GeneratedColumn> get $columns => [id, topicId, timestamp];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@override
|
||||
String get actualTableName => $name;
|
||||
static const String $name = 'tab_link';
|
||||
@override
|
||||
Set<GeneratedColumn> get $primaryKey => {id};
|
||||
@override
|
||||
TabLinkData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||
return TabLinkData(
|
||||
id: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}id'])!,
|
||||
topicId: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}topic_id'])!,
|
||||
timestamp: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.dateTime, data['${effectivePrefix}timestamp'])!,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TabLink createAlias(String alias) {
|
||||
return TabLink(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get dontWriteConstraints => true;
|
||||
}
|
||||
|
||||
class TabLinkData extends DataClass implements Insertable<TabLinkData> {
|
||||
final String id;
|
||||
final String topicId;
|
||||
final DateTime timestamp;
|
||||
const TabLinkData(
|
||||
{required this.id, required this.topicId, required this.timestamp});
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
map['id'] = Variable<String>(id);
|
||||
map['topic_id'] = Variable<String>(topicId);
|
||||
map['timestamp'] = Variable<DateTime>(timestamp);
|
||||
return map;
|
||||
}
|
||||
|
||||
factory TabLinkData.fromJson(Map<String, dynamic> json,
|
||||
{ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return TabLinkData(
|
||||
id: serializer.fromJson<String>(json['id']),
|
||||
topicId: serializer.fromJson<String>(json['topic_id']),
|
||||
timestamp: serializer.fromJson<DateTime>(json['timestamp']),
|
||||
);
|
||||
}
|
||||
@override
|
||||
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return <String, dynamic>{
|
||||
'id': serializer.toJson<String>(id),
|
||||
'topic_id': serializer.toJson<String>(topicId),
|
||||
'timestamp': serializer.toJson<DateTime>(timestamp),
|
||||
};
|
||||
}
|
||||
|
||||
TabLinkData copyWith({String? id, String? topicId, DateTime? timestamp}) =>
|
||||
TabLinkData(
|
||||
id: id ?? this.id,
|
||||
topicId: topicId ?? this.topicId,
|
||||
timestamp: timestamp ?? this.timestamp,
|
||||
);
|
||||
TabLinkData copyWithCompanion(TabLinkCompanion data) {
|
||||
return TabLinkData(
|
||||
id: data.id.present ? data.id.value : this.id,
|
||||
topicId: data.topicId.present ? data.topicId.value : this.topicId,
|
||||
timestamp: data.timestamp.present ? data.timestamp.value : this.timestamp,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('TabLinkData(')
|
||||
..write('id: $id, ')
|
||||
..write('topicId: $topicId, ')
|
||||
..write('timestamp: $timestamp')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(id, topicId, timestamp);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is TabLinkData &&
|
||||
other.id == this.id &&
|
||||
other.topicId == this.topicId &&
|
||||
other.timestamp == this.timestamp);
|
||||
}
|
||||
|
||||
class TabLinkCompanion extends UpdateCompanion<TabLinkData> {
|
||||
final Value<String> id;
|
||||
final Value<String> topicId;
|
||||
final Value<DateTime> timestamp;
|
||||
final Value<int> rowid;
|
||||
const TabLinkCompanion({
|
||||
this.id = const Value.absent(),
|
||||
this.topicId = const Value.absent(),
|
||||
this.timestamp = const Value.absent(),
|
||||
this.rowid = const Value.absent(),
|
||||
});
|
||||
TabLinkCompanion.insert({
|
||||
required String id,
|
||||
required String topicId,
|
||||
required DateTime timestamp,
|
||||
this.rowid = const Value.absent(),
|
||||
}) : id = Value(id),
|
||||
topicId = Value(topicId),
|
||||
timestamp = Value(timestamp);
|
||||
static Insertable<TabLinkData> custom({
|
||||
Expression<String>? id,
|
||||
Expression<String>? topicId,
|
||||
Expression<DateTime>? timestamp,
|
||||
Expression<int>? rowid,
|
||||
}) {
|
||||
return RawValuesInsertable({
|
||||
if (id != null) 'id': id,
|
||||
if (topicId != null) 'topic_id': topicId,
|
||||
if (timestamp != null) 'timestamp': timestamp,
|
||||
if (rowid != null) 'rowid': rowid,
|
||||
});
|
||||
}
|
||||
|
||||
TabLinkCompanion copyWith(
|
||||
{Value<String>? id,
|
||||
Value<String>? topicId,
|
||||
Value<DateTime>? timestamp,
|
||||
Value<int>? rowid}) {
|
||||
return TabLinkCompanion(
|
||||
id: id ?? this.id,
|
||||
topicId: topicId ?? this.topicId,
|
||||
timestamp: timestamp ?? this.timestamp,
|
||||
rowid: rowid ?? this.rowid,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
if (id.present) {
|
||||
map['id'] = Variable<String>(id.value);
|
||||
}
|
||||
if (topicId.present) {
|
||||
map['topic_id'] = Variable<String>(topicId.value);
|
||||
}
|
||||
if (timestamp.present) {
|
||||
map['timestamp'] = Variable<DateTime>(timestamp.value);
|
||||
}
|
||||
if (rowid.present) {
|
||||
map['rowid'] = Variable<int>(rowid.value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('TabLinkCompanion(')
|
||||
..write('id: $id, ')
|
||||
..write('topicId: $topicId, ')
|
||||
..write('timestamp: $timestamp, ')
|
||||
..write('rowid: $rowid')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _$TabDatabase extends GeneratedDatabase {
|
||||
_$TabDatabase(QueryExecutor e) : super(e);
|
||||
$TabDatabaseManager get managers => $TabDatabaseManager(this);
|
||||
late final Topic topic = Topic(this);
|
||||
late final TabLink tabLink = TabLink(this);
|
||||
late final TopicDao topicDao = TopicDao(this as TabDatabase);
|
||||
late final TabLinkDao tabLinkDao = TabLinkDao(this as TabDatabase);
|
||||
Selectable<TopicDataWithCount> topicsWithCount() {
|
||||
return customSelect(
|
||||
'SELECT topic.*, tab_agg.tab_count FROM topic LEFT JOIN (SELECT topic_id, COUNT(*) AS tab_count, MAX(timestamp) AS last_updated FROM tab_link GROUP BY topic_id) AS tab_agg ON topic.id = tab_agg.topic_id ORDER BY tab_agg.last_updated DESC NULLS FIRST',
|
||||
variables: [],
|
||||
readsFrom: {
|
||||
topic,
|
||||
tabLink,
|
||||
}).map((QueryRow row) => TopicDataWithCount(
|
||||
id: row.read<String>('id'),
|
||||
name: row.readNullable<String>('name'),
|
||||
color: Topic.$convertercolor.fromSql(row.read<int>('color')),
|
||||
tabCount: row.readNullable<int>('tab_count'),
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
Iterable<TableInfo<Table, Object?>> get allTables =>
|
||||
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
||||
@override
|
||||
List<DatabaseSchemaEntity> get allSchemaEntities => [topic, tabLink];
|
||||
@override
|
||||
StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules(
|
||||
[
|
||||
WritePropagation(
|
||||
on: TableUpdateQuery.onTableName('topic',
|
||||
limitUpdateKind: UpdateKind.delete),
|
||||
result: [
|
||||
TableUpdate('tab_link', kind: UpdateKind.delete),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
typedef $TopicCreateCompanionBuilder = TopicCompanion Function({
|
||||
required String id,
|
||||
Value<String?> name,
|
||||
required Color color,
|
||||
Value<int> rowid,
|
||||
});
|
||||
typedef $TopicUpdateCompanionBuilder = TopicCompanion Function({
|
||||
Value<String> id,
|
||||
Value<String?> name,
|
||||
Value<Color> color,
|
||||
Value<int> rowid,
|
||||
});
|
||||
|
||||
final class $TopicReferences
|
||||
extends BaseReferences<_$TabDatabase, Topic, TopicData> {
|
||||
$TopicReferences(super.$_db, super.$_table, super.$_typedResult);
|
||||
|
||||
static MultiTypedResultKey<TabLink, List<TabLinkData>> _tabLinkRefsTable(
|
||||
_$TabDatabase db) =>
|
||||
MultiTypedResultKey.fromTable(db.tabLink,
|
||||
aliasName: $_aliasNameGenerator(db.topic.id, db.tabLink.topicId));
|
||||
|
||||
$TabLinkProcessedTableManager get tabLinkRefs {
|
||||
final manager = $TabLinkTableManager($_db, $_db.tabLink)
|
||||
.filter((f) => f.topicId.id($_item.id));
|
||||
|
||||
final cache = $_typedResult.readTableOrNull(_tabLinkRefsTable($_db));
|
||||
return ProcessedTableManager(
|
||||
manager.$state.copyWith(prefetchedData: cache));
|
||||
}
|
||||
}
|
||||
|
||||
class $TopicFilterComposer extends Composer<_$TabDatabase, Topic> {
|
||||
$TopicFilterComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
ColumnFilters<String> get id => $composableBuilder(
|
||||
column: $table.id, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<String> get name => $composableBuilder(
|
||||
column: $table.name, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnWithTypeConverterFilters<Color, Color, int> get color =>
|
||||
$composableBuilder(
|
||||
column: $table.color,
|
||||
builder: (column) => ColumnWithTypeConverterFilters(column));
|
||||
|
||||
Expression<bool> tabLinkRefs(
|
||||
Expression<bool> Function($TabLinkFilterComposer f) f) {
|
||||
final $TabLinkFilterComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.id,
|
||||
referencedTable: $db.tabLink,
|
||||
getReferencedColumn: (t) => t.topicId,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
$TabLinkFilterComposer(
|
||||
$db: $db,
|
||||
$table: $db.tabLink,
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
));
|
||||
return f(composer);
|
||||
}
|
||||
}
|
||||
|
||||
class $TopicOrderingComposer extends Composer<_$TabDatabase, Topic> {
|
||||
$TopicOrderingComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
ColumnOrderings<String> get id => $composableBuilder(
|
||||
column: $table.id, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<String> get name => $composableBuilder(
|
||||
column: $table.name, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<int> get color => $composableBuilder(
|
||||
column: $table.color, builder: (column) => ColumnOrderings(column));
|
||||
}
|
||||
|
||||
class $TopicAnnotationComposer extends Composer<_$TabDatabase, Topic> {
|
||||
$TopicAnnotationComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
GeneratedColumn<String> get id =>
|
||||
$composableBuilder(column: $table.id, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<String> get name =>
|
||||
$composableBuilder(column: $table.name, builder: (column) => column);
|
||||
|
||||
GeneratedColumnWithTypeConverter<Color, int> get color =>
|
||||
$composableBuilder(column: $table.color, builder: (column) => column);
|
||||
|
||||
Expression<T> tabLinkRefs<T extends Object>(
|
||||
Expression<T> Function($TabLinkAnnotationComposer a) f) {
|
||||
final $TabLinkAnnotationComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.id,
|
||||
referencedTable: $db.tabLink,
|
||||
getReferencedColumn: (t) => t.topicId,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
$TabLinkAnnotationComposer(
|
||||
$db: $db,
|
||||
$table: $db.tabLink,
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
));
|
||||
return f(composer);
|
||||
}
|
||||
}
|
||||
|
||||
class $TopicTableManager extends RootTableManager<
|
||||
_$TabDatabase,
|
||||
Topic,
|
||||
TopicData,
|
||||
$TopicFilterComposer,
|
||||
$TopicOrderingComposer,
|
||||
$TopicAnnotationComposer,
|
||||
$TopicCreateCompanionBuilder,
|
||||
$TopicUpdateCompanionBuilder,
|
||||
(TopicData, $TopicReferences),
|
||||
TopicData,
|
||||
PrefetchHooks Function({bool tabLinkRefs})> {
|
||||
$TopicTableManager(_$TabDatabase db, Topic table)
|
||||
: super(TableManagerState(
|
||||
db: db,
|
||||
table: table,
|
||||
createFilteringComposer: () =>
|
||||
$TopicFilterComposer($db: db, $table: table),
|
||||
createOrderingComposer: () =>
|
||||
$TopicOrderingComposer($db: db, $table: table),
|
||||
createComputedFieldComposer: () =>
|
||||
$TopicAnnotationComposer($db: db, $table: table),
|
||||
updateCompanionCallback: ({
|
||||
Value<String> id = const Value.absent(),
|
||||
Value<String?> name = const Value.absent(),
|
||||
Value<Color> color = const Value.absent(),
|
||||
Value<int> rowid = const Value.absent(),
|
||||
}) =>
|
||||
TopicCompanion(
|
||||
id: id,
|
||||
name: name,
|
||||
color: color,
|
||||
rowid: rowid,
|
||||
),
|
||||
createCompanionCallback: ({
|
||||
required String id,
|
||||
Value<String?> name = const Value.absent(),
|
||||
required Color color,
|
||||
Value<int> rowid = const Value.absent(),
|
||||
}) =>
|
||||
TopicCompanion.insert(
|
||||
id: id,
|
||||
name: name,
|
||||
color: color,
|
||||
rowid: rowid,
|
||||
),
|
||||
withReferenceMapper: (p0) => p0
|
||||
.map((e) => (e.readTable(table), $TopicReferences(db, table, e)))
|
||||
.toList(),
|
||||
prefetchHooksCallback: ({tabLinkRefs = false}) {
|
||||
return PrefetchHooks(
|
||||
db: db,
|
||||
explicitlyWatchedTables: [if (tabLinkRefs) db.tabLink],
|
||||
addJoins: null,
|
||||
getPrefetchedDataCallback: (items) async {
|
||||
return [
|
||||
if (tabLinkRefs)
|
||||
await $_getPrefetchedData(
|
||||
currentTable: table,
|
||||
referencedTable: $TopicReferences._tabLinkRefsTable(db),
|
||||
managerFromTypedResult: (p0) =>
|
||||
$TopicReferences(db, table, p0).tabLinkRefs,
|
||||
referencedItemsForCurrentItem: (item,
|
||||
referencedItems) =>
|
||||
referencedItems.where((e) => e.topicId == item.id),
|
||||
typedResults: items)
|
||||
];
|
||||
},
|
||||
);
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
typedef $TopicProcessedTableManager = ProcessedTableManager<
|
||||
_$TabDatabase,
|
||||
Topic,
|
||||
TopicData,
|
||||
$TopicFilterComposer,
|
||||
$TopicOrderingComposer,
|
||||
$TopicAnnotationComposer,
|
||||
$TopicCreateCompanionBuilder,
|
||||
$TopicUpdateCompanionBuilder,
|
||||
(TopicData, $TopicReferences),
|
||||
TopicData,
|
||||
PrefetchHooks Function({bool tabLinkRefs})>;
|
||||
typedef $TabLinkCreateCompanionBuilder = TabLinkCompanion Function({
|
||||
required String id,
|
||||
required String topicId,
|
||||
required DateTime timestamp,
|
||||
Value<int> rowid,
|
||||
});
|
||||
typedef $TabLinkUpdateCompanionBuilder = TabLinkCompanion Function({
|
||||
Value<String> id,
|
||||
Value<String> topicId,
|
||||
Value<DateTime> timestamp,
|
||||
Value<int> rowid,
|
||||
});
|
||||
|
||||
final class $TabLinkReferences
|
||||
extends BaseReferences<_$TabDatabase, TabLink, TabLinkData> {
|
||||
$TabLinkReferences(super.$_db, super.$_table, super.$_typedResult);
|
||||
|
||||
static Topic _topicIdTable(_$TabDatabase db) => db.topic
|
||||
.createAlias($_aliasNameGenerator(db.tabLink.topicId, db.topic.id));
|
||||
|
||||
$TopicProcessedTableManager? get topicId {
|
||||
if ($_item.topicId == null) return null;
|
||||
final manager = $TopicTableManager($_db, $_db.topic)
|
||||
.filter((f) => f.id($_item.topicId!));
|
||||
final item = $_typedResult.readTableOrNull(_topicIdTable($_db));
|
||||
if (item == null) return manager;
|
||||
return ProcessedTableManager(
|
||||
manager.$state.copyWith(prefetchedData: [item]));
|
||||
}
|
||||
}
|
||||
|
||||
class $TabLinkFilterComposer extends Composer<_$TabDatabase, TabLink> {
|
||||
$TabLinkFilterComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
ColumnFilters<String> get id => $composableBuilder(
|
||||
column: $table.id, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<DateTime> get timestamp => $composableBuilder(
|
||||
column: $table.timestamp, builder: (column) => ColumnFilters(column));
|
||||
|
||||
$TopicFilterComposer get topicId {
|
||||
final $TopicFilterComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.topicId,
|
||||
referencedTable: $db.topic,
|
||||
getReferencedColumn: (t) => t.id,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
$TopicFilterComposer(
|
||||
$db: $db,
|
||||
$table: $db.topic,
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
));
|
||||
return composer;
|
||||
}
|
||||
}
|
||||
|
||||
class $TabLinkOrderingComposer extends Composer<_$TabDatabase, TabLink> {
|
||||
$TabLinkOrderingComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
ColumnOrderings<String> get id => $composableBuilder(
|
||||
column: $table.id, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<DateTime> get timestamp => $composableBuilder(
|
||||
column: $table.timestamp, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
$TopicOrderingComposer get topicId {
|
||||
final $TopicOrderingComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.topicId,
|
||||
referencedTable: $db.topic,
|
||||
getReferencedColumn: (t) => t.id,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
$TopicOrderingComposer(
|
||||
$db: $db,
|
||||
$table: $db.topic,
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
));
|
||||
return composer;
|
||||
}
|
||||
}
|
||||
|
||||
class $TabLinkAnnotationComposer extends Composer<_$TabDatabase, TabLink> {
|
||||
$TabLinkAnnotationComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
GeneratedColumn<String> get id =>
|
||||
$composableBuilder(column: $table.id, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<DateTime> get timestamp =>
|
||||
$composableBuilder(column: $table.timestamp, builder: (column) => column);
|
||||
|
||||
$TopicAnnotationComposer get topicId {
|
||||
final $TopicAnnotationComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.topicId,
|
||||
referencedTable: $db.topic,
|
||||
getReferencedColumn: (t) => t.id,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
$TopicAnnotationComposer(
|
||||
$db: $db,
|
||||
$table: $db.topic,
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
));
|
||||
return composer;
|
||||
}
|
||||
}
|
||||
|
||||
class $TabLinkTableManager extends RootTableManager<
|
||||
_$TabDatabase,
|
||||
TabLink,
|
||||
TabLinkData,
|
||||
$TabLinkFilterComposer,
|
||||
$TabLinkOrderingComposer,
|
||||
$TabLinkAnnotationComposer,
|
||||
$TabLinkCreateCompanionBuilder,
|
||||
$TabLinkUpdateCompanionBuilder,
|
||||
(TabLinkData, $TabLinkReferences),
|
||||
TabLinkData,
|
||||
PrefetchHooks Function({bool topicId})> {
|
||||
$TabLinkTableManager(_$TabDatabase db, TabLink table)
|
||||
: super(TableManagerState(
|
||||
db: db,
|
||||
table: table,
|
||||
createFilteringComposer: () =>
|
||||
$TabLinkFilterComposer($db: db, $table: table),
|
||||
createOrderingComposer: () =>
|
||||
$TabLinkOrderingComposer($db: db, $table: table),
|
||||
createComputedFieldComposer: () =>
|
||||
$TabLinkAnnotationComposer($db: db, $table: table),
|
||||
updateCompanionCallback: ({
|
||||
Value<String> id = const Value.absent(),
|
||||
Value<String> topicId = const Value.absent(),
|
||||
Value<DateTime> timestamp = const Value.absent(),
|
||||
Value<int> rowid = const Value.absent(),
|
||||
}) =>
|
||||
TabLinkCompanion(
|
||||
id: id,
|
||||
topicId: topicId,
|
||||
timestamp: timestamp,
|
||||
rowid: rowid,
|
||||
),
|
||||
createCompanionCallback: ({
|
||||
required String id,
|
||||
required String topicId,
|
||||
required DateTime timestamp,
|
||||
Value<int> rowid = const Value.absent(),
|
||||
}) =>
|
||||
TabLinkCompanion.insert(
|
||||
id: id,
|
||||
topicId: topicId,
|
||||
timestamp: timestamp,
|
||||
rowid: rowid,
|
||||
),
|
||||
withReferenceMapper: (p0) => p0
|
||||
.map(
|
||||
(e) => (e.readTable(table), $TabLinkReferences(db, table, e)))
|
||||
.toList(),
|
||||
prefetchHooksCallback: ({topicId = false}) {
|
||||
return PrefetchHooks(
|
||||
db: db,
|
||||
explicitlyWatchedTables: [],
|
||||
addJoins: <
|
||||
T extends TableManagerState<
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic>>(state) {
|
||||
if (topicId) {
|
||||
state = state.withJoin(
|
||||
currentTable: table,
|
||||
currentColumn: table.topicId,
|
||||
referencedTable: $TabLinkReferences._topicIdTable(db),
|
||||
referencedColumn: $TabLinkReferences._topicIdTable(db).id,
|
||||
) as T;
|
||||
}
|
||||
|
||||
return state;
|
||||
},
|
||||
getPrefetchedDataCallback: (items) async {
|
||||
return [];
|
||||
},
|
||||
);
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
typedef $TabLinkProcessedTableManager = ProcessedTableManager<
|
||||
_$TabDatabase,
|
||||
TabLink,
|
||||
TabLinkData,
|
||||
$TabLinkFilterComposer,
|
||||
$TabLinkOrderingComposer,
|
||||
$TabLinkAnnotationComposer,
|
||||
$TabLinkCreateCompanionBuilder,
|
||||
$TabLinkUpdateCompanionBuilder,
|
||||
(TabLinkData, $TabLinkReferences),
|
||||
TabLinkData,
|
||||
PrefetchHooks Function({bool topicId})>;
|
||||
|
||||
class $TabDatabaseManager {
|
||||
final _$TabDatabase _db;
|
||||
$TabDatabaseManager(this._db);
|
||||
$TopicTableManager get topic => $TopicTableManager(_db, _db.topic);
|
||||
$TabLinkTableManager get tabLink => $TabLinkTableManager(_db, _db.tabLink);
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/data/models/topic_data.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/data/providers.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/domain/repositories/topic.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/utils/color_palette.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'providers.g.dart';
|
||||
|
||||
@Riverpod()
|
||||
Future<Color> unusedRandomTopicColor(UnusedRandomTopicColorRef ref) async {
|
||||
final repository = ref.watch(topicRepositoryProvider.notifier);
|
||||
|
||||
final allColors = colorTypes.flattened.toList();
|
||||
final usedColors = await repository.getDistinctColors();
|
||||
|
||||
Color randomColor;
|
||||
do {
|
||||
randomColor = randomColorShade(allColors);
|
||||
} while (usedColors.contains(randomColor));
|
||||
|
||||
return randomColor;
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
Stream<List<TopicDataWithCount>> topicsWithCount(TopicsWithCountRef ref) {
|
||||
final db = ref.watch(tabDatabaseProvider);
|
||||
return db.topicsWithCount().watch();
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
Stream<List<String>> topicTabIds(TopicTabIdsRef ref, String topicId) {
|
||||
final db = ref.watch(tabDatabaseProvider);
|
||||
return db.tabLinkDao.topicTabIds(topicId).watch();
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
Stream<String?> tabTopicId(TabTopicIdRef ref, String tabId) {
|
||||
final db = ref.watch(tabDatabaseProvider);
|
||||
return db.tabLinkDao.tabTopicId(tabId).watchSingleOrNull();
|
||||
}
|
||||
@@ -1,320 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'providers.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$unusedRandomTopicColorHash() =>
|
||||
r'099730b0d987cc37bcae4ef5c999cbebfa67d7da';
|
||||
|
||||
/// See also [unusedRandomTopicColor].
|
||||
@ProviderFor(unusedRandomTopicColor)
|
||||
final unusedRandomTopicColorProvider =
|
||||
AutoDisposeFutureProvider<Color>.internal(
|
||||
unusedRandomTopicColor,
|
||||
name: r'unusedRandomTopicColorProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$unusedRandomTopicColorHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef UnusedRandomTopicColorRef = AutoDisposeFutureProviderRef<Color>;
|
||||
String _$topicsWithCountHash() => r'cd9792b0e0af12cd8796a3f0b76b42171a4200e6';
|
||||
|
||||
/// See also [topicsWithCount].
|
||||
@ProviderFor(topicsWithCount)
|
||||
final topicsWithCountProvider =
|
||||
AutoDisposeStreamProvider<List<TopicDataWithCount>>.internal(
|
||||
topicsWithCount,
|
||||
name: r'topicsWithCountProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$topicsWithCountHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef TopicsWithCountRef
|
||||
= AutoDisposeStreamProviderRef<List<TopicDataWithCount>>;
|
||||
String _$topicTabIdsHash() => r'43719c70e01cf9e030d75171405ab9650936ddf8';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
_SystemHash._();
|
||||
|
||||
static int combine(int hash, int value) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + value);
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||
return hash ^ (hash >> 6);
|
||||
}
|
||||
|
||||
static int finish(int hash) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||
// ignore: parameter_assignments
|
||||
hash = hash ^ (hash >> 11);
|
||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||
}
|
||||
}
|
||||
|
||||
/// See also [topicTabIds].
|
||||
@ProviderFor(topicTabIds)
|
||||
const topicTabIdsProvider = TopicTabIdsFamily();
|
||||
|
||||
/// See also [topicTabIds].
|
||||
class TopicTabIdsFamily extends Family<AsyncValue<List<String>>> {
|
||||
/// See also [topicTabIds].
|
||||
const TopicTabIdsFamily();
|
||||
|
||||
/// See also [topicTabIds].
|
||||
TopicTabIdsProvider call(
|
||||
String topicId,
|
||||
) {
|
||||
return TopicTabIdsProvider(
|
||||
topicId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TopicTabIdsProvider getProviderOverride(
|
||||
covariant TopicTabIdsProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.topicId,
|
||||
);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'topicTabIdsProvider';
|
||||
}
|
||||
|
||||
/// See also [topicTabIds].
|
||||
class TopicTabIdsProvider extends AutoDisposeStreamProvider<List<String>> {
|
||||
/// See also [topicTabIds].
|
||||
TopicTabIdsProvider(
|
||||
String topicId,
|
||||
) : this._internal(
|
||||
(ref) => topicTabIds(
|
||||
ref as TopicTabIdsRef,
|
||||
topicId,
|
||||
),
|
||||
from: topicTabIdsProvider,
|
||||
name: r'topicTabIdsProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$topicTabIdsHash,
|
||||
dependencies: TopicTabIdsFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
TopicTabIdsFamily._allTransitiveDependencies,
|
||||
topicId: topicId,
|
||||
);
|
||||
|
||||
TopicTabIdsProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.topicId,
|
||||
}) : super.internal();
|
||||
|
||||
final String topicId;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
Stream<List<String>> Function(TopicTabIdsRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: TopicTabIdsProvider._internal(
|
||||
(ref) => create(ref as TopicTabIdsRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
topicId: topicId,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeStreamProviderElement<List<String>> createElement() {
|
||||
return _TopicTabIdsProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is TopicTabIdsProvider && other.topicId == topicId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, topicId.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
mixin TopicTabIdsRef on AutoDisposeStreamProviderRef<List<String>> {
|
||||
/// The parameter `topicId` of this provider.
|
||||
String get topicId;
|
||||
}
|
||||
|
||||
class _TopicTabIdsProviderElement
|
||||
extends AutoDisposeStreamProviderElement<List<String>> with TopicTabIdsRef {
|
||||
_TopicTabIdsProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String get topicId => (origin as TopicTabIdsProvider).topicId;
|
||||
}
|
||||
|
||||
String _$tabTopicIdHash() => r'284164ac3acd9be3ba4e15eba99fc8d97602ee84';
|
||||
|
||||
/// See also [tabTopicId].
|
||||
@ProviderFor(tabTopicId)
|
||||
const tabTopicIdProvider = TabTopicIdFamily();
|
||||
|
||||
/// See also [tabTopicId].
|
||||
class TabTopicIdFamily extends Family<AsyncValue<String?>> {
|
||||
/// See also [tabTopicId].
|
||||
const TabTopicIdFamily();
|
||||
|
||||
/// See also [tabTopicId].
|
||||
TabTopicIdProvider call(
|
||||
String tabId,
|
||||
) {
|
||||
return TabTopicIdProvider(
|
||||
tabId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TabTopicIdProvider getProviderOverride(
|
||||
covariant TabTopicIdProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.tabId,
|
||||
);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'tabTopicIdProvider';
|
||||
}
|
||||
|
||||
/// See also [tabTopicId].
|
||||
class TabTopicIdProvider extends AutoDisposeStreamProvider<String?> {
|
||||
/// See also [tabTopicId].
|
||||
TabTopicIdProvider(
|
||||
String tabId,
|
||||
) : this._internal(
|
||||
(ref) => tabTopicId(
|
||||
ref as TabTopicIdRef,
|
||||
tabId,
|
||||
),
|
||||
from: tabTopicIdProvider,
|
||||
name: r'tabTopicIdProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$tabTopicIdHash,
|
||||
dependencies: TabTopicIdFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
TabTopicIdFamily._allTransitiveDependencies,
|
||||
tabId: tabId,
|
||||
);
|
||||
|
||||
TabTopicIdProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.tabId,
|
||||
}) : super.internal();
|
||||
|
||||
final String tabId;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
Stream<String?> Function(TabTopicIdRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: TabTopicIdProvider._internal(
|
||||
(ref) => create(ref as TabTopicIdRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
tabId: tabId,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeStreamProviderElement<String?> createElement() {
|
||||
return _TabTopicIdProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is TabTopicIdProvider && other.tabId == tabId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, tabId.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
mixin TabTopicIdRef on AutoDisposeStreamProviderRef<String?> {
|
||||
/// The parameter `tabId` of this provider.
|
||||
String get tabId;
|
||||
}
|
||||
|
||||
class _TabTopicIdProviderElement
|
||||
extends AutoDisposeStreamProviderElement<String?> with TabTopicIdRef {
|
||||
_TabTopicIdProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String get tabId => (origin as TabTopicIdProvider).tabId;
|
||||
}
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||
@@ -1,41 +0,0 @@
|
||||
import 'package:lensai/features/geckoview/features/topics/data/models/topic_data.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/data/providers.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'selected_topic.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class SelectedTopic extends _$SelectedTopic {
|
||||
void setTopic(String id) {
|
||||
state = id;
|
||||
}
|
||||
|
||||
void toggleTopic(String id) {
|
||||
if (state == id) {
|
||||
clearTopic();
|
||||
} else {
|
||||
setTopic(id);
|
||||
}
|
||||
}
|
||||
|
||||
void clearTopic() {
|
||||
state = null;
|
||||
}
|
||||
|
||||
@override
|
||||
String? build() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
Stream<TopicData?> selectedTopicData(SelectedTopicDataRef ref) {
|
||||
final db = ref.watch(tabDatabaseProvider);
|
||||
final selectedTopic = ref.watch(selectedTopicProvider);
|
||||
|
||||
if (selectedTopic != null) {
|
||||
return db.topicDao.getTopicData(selectedTopic).watchSingleOrNull();
|
||||
}
|
||||
|
||||
return Stream.value(null);
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'selected_topic.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class SelectedTopicRepository extends _$SelectedTopicRepository {
|
||||
void setTopic(String id) {
|
||||
state = id;
|
||||
}
|
||||
|
||||
void toggleTopic(String id) {
|
||||
if (state == id) {
|
||||
clearTopic();
|
||||
} else {
|
||||
setTopic(id);
|
||||
}
|
||||
}
|
||||
|
||||
void clearTopic() {
|
||||
state = null;
|
||||
}
|
||||
|
||||
@override
|
||||
String? build() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'selected_topic.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$selectedTopicRepositoryHash() =>
|
||||
r'd7ffe4ff5501aff747e5155d74b07974784f26f9';
|
||||
|
||||
/// See also [SelectedTopicRepository].
|
||||
@ProviderFor(SelectedTopicRepository)
|
||||
final selectedTopicRepositoryProvider =
|
||||
NotifierProvider<SelectedTopicRepository, String?>.internal(
|
||||
SelectedTopicRepository.new,
|
||||
name: r'selectedTopicRepositoryProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$selectedTopicRepositoryHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$SelectedTopicRepository = Notifier<String?>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||
@@ -1,42 +0,0 @@
|
||||
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/topics/data/database/database.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/data/providers.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'tab_link.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class TabLinkRepository extends _$TabLinkRepository {
|
||||
late TabDatabase _db;
|
||||
|
||||
@override
|
||||
void build() {
|
||||
_db = ref.watch(tabDatabaseProvider);
|
||||
}
|
||||
|
||||
Future<void> assignTab(String tabId, String topicId) {
|
||||
return _db.tabLinkDao.upsertTabLink(
|
||||
tabId,
|
||||
timestamp: DateTime.now(),
|
||||
topicId: topicId,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> closeAllTabs(String? topicId) async {
|
||||
final List<String> tabIds;
|
||||
if (topicId != null) {
|
||||
tabIds = await _db.tabLinkDao.topicTabIds(topicId).get();
|
||||
} else {
|
||||
final openTabs = ref.read(tabStatesProvider).keys.toSet();
|
||||
final assignedTabIds = await _db.tabLinkDao.allTabIds().get();
|
||||
|
||||
tabIds =
|
||||
openTabs.where((tabId) => !assignedTabIds.contains(tabId)).toList();
|
||||
}
|
||||
|
||||
if (tabIds.isNotEmpty) {
|
||||
await ref.read(tabRepositoryProvider.notifier).closeTabs(tabIds);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:lensai/features/geckoview/features/topics/data/database/database.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/data/providers.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'topic.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class TopicRepository extends _$TopicRepository {
|
||||
late TabDatabase _db;
|
||||
|
||||
@override
|
||||
void build() {
|
||||
_db = ref.watch(tabDatabaseProvider);
|
||||
}
|
||||
|
||||
Future<void> addTopic({required String? name, required Color color}) {
|
||||
return _db.topicDao.addTopic(name: name, color: color);
|
||||
}
|
||||
|
||||
Future<void> replaceTopic({
|
||||
required String id,
|
||||
required String? name,
|
||||
required Color color,
|
||||
}) {
|
||||
return _db.topicDao.replaceTopic(id, name: name, color: color);
|
||||
}
|
||||
|
||||
Future<void> deleteTopic(String id) {
|
||||
return _db.topicDao.deleteTopic(id);
|
||||
}
|
||||
|
||||
Future<Set<Color>> getDistinctColors() {
|
||||
return _db.topicDao
|
||||
.getDistinctColors()
|
||||
.get()
|
||||
.then((colors) => colors.toSet());
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/core/routing/routes.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/domain/providers.dart';
|
||||
import 'package:lensai/features/geckoview/features/topics/domain/providers/selected_topic.dart';
|
||||
import 'package:lensai/presentation/widgets/selectable_chips.dart';
|
||||
|
||||
class TopicChips extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final topicsAsync = ref.watch(topicsWithCountProvider);
|
||||
final selectedTopic = ref
|
||||
.watch(selectedTopicDataProvider.select((value) => value.valueOrNull));
|
||||
|
||||
return topicsAsync.when(
|
||||
data: (availableTopics) => SizedBox(
|
||||
height: 48,
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(width: 16),
|
||||
if (selectedTopic != null || availableTopics.isNotEmpty)
|
||||
Expanded(
|
||||
child: SelectableChips(
|
||||
deleteIcon: false,
|
||||
itemId: (topic) => topic.id,
|
||||
itemAvatar: (topic) => Container(
|
||||
width: 20.0,
|
||||
height: 20.0,
|
||||
decoration: BoxDecoration(
|
||||
color: topic.color,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
itemLabel: (topic) => Text(topic.name ?? 'New Topic'),
|
||||
itemBadgeCount: (topic) => topic.tabCount,
|
||||
availableItems: availableTopics,
|
||||
selectedItem: selectedTopic,
|
||||
onSelected: (topic) {
|
||||
ref.read(selectedTopicProvider.notifier).setTopic(topic.id);
|
||||
},
|
||||
onDeleted: (topic) async {
|
||||
ref.read(selectedTopicProvider.notifier).clearTopic();
|
||||
},
|
||||
),
|
||||
)
|
||||
else
|
||||
Expanded(
|
||||
child: Text(
|
||||
"Press '>' to manage Topics.",
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).hintColor,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
await context.push(TopicListRoute().location);
|
||||
},
|
||||
icon: const Icon(Icons.chevron_right),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
error: (error, stackTrace) => const SizedBox.shrink(),
|
||||
loading: () => const SizedBox(
|
||||
height: 48,
|
||||
width: double.infinity,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
/home/fafre/.pub-cache/hosted/pub.dev/file_picker-8.1.2/
|
||||
/home/fafre/.pub-cache/hosted/pub.dev/file_picker-8.1.3/
|
||||
@@ -1 +1 @@
|
||||
/home/fafre/.pub-cache/hosted/pub.dev/package_info_plus-8.0.3/
|
||||
/home/fafre/.pub-cache/hosted/pub.dev/package_info_plus-8.1.0/
|
||||
@@ -1 +1 @@
|
||||
/home/fafre/.pub-cache/hosted/pub.dev/share_plus-10.0.3/
|
||||
/home/fafre/.pub-cache/hosted/pub.dev/share_plus-10.1.0/
|
||||
@@ -1 +1 @@
|
||||
/home/fafre/.pub-cache/hosted/pub.dev/sqlite3_flutter_libs-0.5.24/
|
||||
/home/fafre/.pub-cache/hosted/pub.dev/sqlite3_flutter_libs-0.5.25/
|
||||
+53
-28
@@ -202,10 +202,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: convert
|
||||
sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
|
||||
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
version: "3.1.2"
|
||||
copy_with_extension:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -234,10 +234,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: crypto
|
||||
sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27
|
||||
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.5"
|
||||
version: "3.0.6"
|
||||
csslib:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -378,18 +378,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: file_picker
|
||||
sha256: "167bb619cdddaa10ef2907609feb8a79c16dfa479d3afaf960f8e223f754bf12"
|
||||
sha256: aac85f20436608e01a6ffd1fdd4e746a7f33c93a2c83752e626bdfaea139b877
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.1.2"
|
||||
version: "8.1.3"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fixnum
|
||||
sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
|
||||
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
version: "1.1.1"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@@ -696,18 +696,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
sha256: "40f592dd352890c3b60fec1b68e786cefb9603e05ff303dbc4dda49b304ecdf4"
|
||||
sha256: "76d306a1c3afb33fe82e2bbacad62a61f409b5634c915fceb0d799de1a913360"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.0"
|
||||
version: "4.1.1"
|
||||
image:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image
|
||||
sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8"
|
||||
sha256: f31d52537dc417fdcde36088fdf11d191026fd5e4fae742491ebd40e5a8bea7d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.0"
|
||||
version: "4.3.0"
|
||||
intl:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -772,6 +772,15 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
lexo_rank:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: HEAD
|
||||
resolved-ref: "2922c07bc9d3865cc5566efe1976ce0955029512"
|
||||
url: "https://github.com/FaFre/lexo_rank.git"
|
||||
source: git
|
||||
version: "0.1.0"
|
||||
lint:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@@ -792,10 +801,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: logging
|
||||
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
|
||||
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
version: "1.3.0"
|
||||
macros:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -856,10 +865,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: package_info_plus
|
||||
sha256: "894f37107424311bdae3e476552229476777b8752c5a2a2369c0cb9a2d5442ef"
|
||||
sha256: df3eb3e0aed5c1107bb0fdb80a8e82e778114958b1c5ac5644fb1ac9cae8a998
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.0.3"
|
||||
version: "8.1.0"
|
||||
package_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -936,10 +945,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
|
||||
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.5"
|
||||
version: "3.1.6"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -980,6 +989,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.0"
|
||||
reorderable_grid:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: reorderable_grid
|
||||
sha256: "0b9cd95ef0f070ef99f92affe9cf85a4aa127099cd1334e5940950ce58cd981d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.10"
|
||||
riverpod:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1032,10 +1049,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: share_plus
|
||||
sha256: fec12c3c39f01e4df1ec6ad92b6e85503c5ca64ffd6e28d18c9ffe53fcc4cb11
|
||||
sha256: "334fcdf0ef9c0df0e3b428faebcac9568f35c747d59831474b2fc56e156d244e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.3"
|
||||
version: "10.1.0"
|
||||
share_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1180,10 +1197,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: sqlite3_flutter_libs
|
||||
sha256: "62bbb4073edbcdf53f40c80775f33eea01d301b7b81417e5b3fb7395416258c1"
|
||||
sha256: ccd29dd6cf6fb9351fa07cd6f92895809adbf0779c1d986acf5e3d53b3250e33
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.24"
|
||||
version: "0.5.25"
|
||||
sqlparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1232,6 +1249,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
synchronized:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: synchronized
|
||||
sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.0+3"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1276,10 +1301,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
|
||||
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.2"
|
||||
version: "1.4.0"
|
||||
universal_io:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1355,10 +1380,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_windows
|
||||
sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185"
|
||||
sha256: "44cf3aabcedde30f2dba119a9dea3b0f2672fbe6fa96e85536251d678216b3c4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
version: "3.1.3"
|
||||
uuid:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1419,10 +1444,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: e5c39a90447e7c81cfec14b041cdbd0d0916bd9ebbc7fe02ab69568be703b9bd
|
||||
sha256: "2735daae5150e8b1dfeb3eb0544b4d3af0061e9e82cef063adcd583bdae4306a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.6.0"
|
||||
version: "5.7.0"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
+10
-5
@@ -10,14 +10,14 @@ dependencies:
|
||||
animations: ^2.0.11
|
||||
collection: ^1.19.0
|
||||
copy_with_extension: ^5.0.4
|
||||
crypto: ^3.0.5
|
||||
crypto: ^3.0.6
|
||||
drift: ^2.21.0
|
||||
dynamic_color: ^1.7.0
|
||||
exceptions: ^0.6.1
|
||||
expandable_page_view: ^1.0.17
|
||||
fading_scroll: ^0.9.1
|
||||
fast_equatable: ^1.1.0
|
||||
file_picker: ^8.1.2
|
||||
file_picker: ^8.1.3
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_hooks: ^0.20.5
|
||||
@@ -36,22 +36,27 @@ dependencies:
|
||||
html: ^0.15.4
|
||||
http: ^1.2.2
|
||||
json_annotation: ^4.9.0
|
||||
lexo_rank:
|
||||
git:
|
||||
url: https://github.com/FaFre/lexo_rank.git
|
||||
logger: ^2.4.0
|
||||
markdown: ^7.2.2
|
||||
mime: ^2.0.0
|
||||
package_info_plus: ^8.0.3
|
||||
package_info_plus: ^8.1.0
|
||||
path: ^1.9.0
|
||||
path_provider: ^2.1.4
|
||||
reorderable_grid: ^1.0.10
|
||||
riverpod: ^2.5.3
|
||||
riverpod_annotation: ^2.5.3
|
||||
rxdart: ^0.28.0
|
||||
share_plus: ^10.0.3
|
||||
share_plus: ^10.1.0
|
||||
shared_preferences: ^2.3.2
|
||||
skeletonizer: ^1.4.2
|
||||
speech_to_text_google_dialog:
|
||||
path: ../../speech_to_text_google_dialog
|
||||
sqlite3: ^2.4.6
|
||||
sqlite3_flutter_libs: ^0.5.24
|
||||
sqlite3_flutter_libs: ^0.5.25
|
||||
synchronized: ^3.3.0+3
|
||||
text_scroll: ^0.2.0
|
||||
timeago: ^3.7.0
|
||||
universal_io: ^2.2.2
|
||||
|
||||
@@ -3,6 +3,8 @@ version = "1.0-SNAPSHOT"
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = "1.9.22"
|
||||
ext.mozillaComponentsVersion = '131.0.3'
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
@@ -56,37 +58,36 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.mozilla.components:support-utils:131.0b1'
|
||||
implementation 'org.mozilla.components:support-ktx:131.0b1'
|
||||
implementation 'org.mozilla.components:support-webextensions:131.0b1'
|
||||
implementation 'org.mozilla.components:support-locale:131.0b1'
|
||||
implementation 'org.mozilla.components:lib-fetch-httpurlconnection:131.0b1'
|
||||
implementation 'org.mozilla.components:lib-crash:131.0b1'
|
||||
implementation 'org.mozilla.components:lib-publicsuffixlist:131.0b1'
|
||||
implementation 'org.mozilla.components:lib-dataprotect:131.0b1'
|
||||
implementation 'org.mozilla.components:concept-engine:131.0b1'
|
||||
implementation 'org.mozilla.components:concept-fetch:131.0b1'
|
||||
implementation 'org.mozilla.components:browser-engine-gecko:131.0b1'
|
||||
implementation 'org.mozilla.components:browser-state:131.0b1'
|
||||
implementation 'org.mozilla.components:browser-session-storage:131.0b1'
|
||||
implementation 'org.mozilla.components:browser-icons:131.0b1'
|
||||
//implementation 'org.mozilla.components:browser-storage-sync:131.0b1'
|
||||
implementation 'org.mozilla.components:browser-thumbnails:131.0b1'
|
||||
implementation 'org.mozilla.components:feature-addons:131.0b1'
|
||||
implementation 'org.mozilla.components:feature-app-links:131.0b1'
|
||||
implementation 'org.mozilla.components:feature-autofill:131.0b1'
|
||||
implementation 'org.mozilla.components:feature-downloads:131.0b1'
|
||||
implementation 'org.mozilla.components:feature-media:131.0b1'
|
||||
implementation 'org.mozilla.components:feature-tabs:131.0b1'
|
||||
implementation 'org.mozilla.components:feature-prompts:131.0b1'
|
||||
implementation 'org.mozilla.components:feature-session:131.0b1'
|
||||
implementation 'org.mozilla.components:feature-readerview:131.0b1'
|
||||
implementation 'org.mozilla.components:feature-privatemode:131.0b1'
|
||||
implementation 'org.mozilla.components:feature-sitepermissions:131.0b1'
|
||||
implementation 'org.mozilla.components:feature-webcompat:131.0b1'
|
||||
implementation 'org.mozilla.components:feature-webnotifications:131.0b1'
|
||||
implementation 'org.mozilla.components:service-digitalassetlinks:131.0b1'
|
||||
implementation 'org.mozilla.components:ui-widgets:131.0b1'
|
||||
implementation "org.mozilla.components:support-utils:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:support-ktx:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:support-webextensions:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:support-locale:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:lib-fetch-httpurlconnection:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:lib-crash:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:lib-publicsuffixlist:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:lib-dataprotect:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:concept-engine:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:concept-fetch:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:browser-engine-gecko:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:browser-state:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:browser-session-storage:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:browser-icons:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:browser-thumbnails:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-addons:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-app-links:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-autofill:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-downloads:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-media:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-tabs:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-prompts:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-session:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-readerview:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-privatemode:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-sitepermissions:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-webcompat:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-webnotifications:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:service-digitalassetlinks:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:ui-widgets:$mozillaComponentsVersion"
|
||||
|
||||
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||
|
||||
-16
@@ -229,22 +229,6 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
// consumeFlow(components.store) { flow ->
|
||||
// flow.mapNotNull { state -> state.findCustomTabOrSelectedTab(sessionId) }
|
||||
// .ifAnyChanged { tab ->
|
||||
// arrayOf(
|
||||
// tab.content.loading,
|
||||
// tab.content.canGoBack,
|
||||
// tab.content.canGoForward,
|
||||
// )
|
||||
// }
|
||||
// .collect {
|
||||
// binding.toolbar.invalidateActions()
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun onBackPressed(): Boolean =
|
||||
listOf(sessionFeature).any { it.onBackPressed() }
|
||||
|
||||
+3
-3
@@ -31,9 +31,9 @@ class BrowserFragment(private val context: Context) : BaseBrowserFragment(), Use
|
||||
|
||||
override fun createEngine(components: Components): EngineView {
|
||||
return components.engine.createView(context).apply {
|
||||
selectionActionDelegate = DefaultSelectionActionDelegate(
|
||||
components.selectionAction
|
||||
)
|
||||
// selectionActionDelegate = DefaultSelectionActionDelegate(
|
||||
// components.selectionAction
|
||||
// )
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+9
-1
@@ -88,6 +88,7 @@ open class DefaultComponents(
|
||||
}
|
||||
|
||||
var engineView: EngineView? = null
|
||||
var engineReportedInitialized = false
|
||||
|
||||
val preferences: SharedPreferences =
|
||||
applicationContext.getSharedPreferences(SAMPLE_BROWSER_PREFERENCES, Context.MODE_PRIVATE)
|
||||
@@ -166,6 +167,7 @@ open class DefaultComponents(
|
||||
.distinctUntilChanged()
|
||||
.collect { tabId ->
|
||||
flutterEvents.onSelectedTabChange(
|
||||
System.currentTimeMillis(),
|
||||
tabId
|
||||
) { _ -> }
|
||||
}
|
||||
@@ -181,6 +183,7 @@ open class DefaultComponents(
|
||||
.collect { tab ->
|
||||
val iconBytes = tab.content.icon?.toWebPBytes()
|
||||
flutterEvents.onIconChange(
|
||||
System.currentTimeMillis(),
|
||||
tab.id,
|
||||
iconBytes
|
||||
) { _ -> }
|
||||
@@ -195,6 +198,7 @@ open class DefaultComponents(
|
||||
.debounce { 50 }
|
||||
.collect { tab ->
|
||||
flutterEvents.onSecurityInfoStateChange(
|
||||
System.currentTimeMillis(),
|
||||
tab.id,
|
||||
SecurityInfoState(
|
||||
tab.content.securityInfo.secure,
|
||||
@@ -218,6 +222,7 @@ open class DefaultComponents(
|
||||
.debounce { 50 }
|
||||
.collect { tab ->
|
||||
flutterEvents.onReaderableStateChange(
|
||||
System.currentTimeMillis(),
|
||||
tab.id,
|
||||
ReaderableState(
|
||||
tab.readerState.readerable,
|
||||
@@ -241,6 +246,7 @@ open class DefaultComponents(
|
||||
.debounce { 50 }
|
||||
.collect { tab ->
|
||||
flutterEvents.onHistoryStateChange(
|
||||
System.currentTimeMillis(),
|
||||
tab.id,
|
||||
HistoryState(
|
||||
items = tab.content.history.items.map { item -> HistoryItem(
|
||||
@@ -259,7 +265,7 @@ open class DefaultComponents(
|
||||
flow.mapNotNull { state -> state.tabs.map {tab -> tab.id} }
|
||||
.distinctUntilChanged()
|
||||
.collect { tabs ->
|
||||
flutterEvents.onTabListChange(tabs) { _ -> }
|
||||
flutterEvents.onTabListChange(System.currentTimeMillis(), tabs) { _ -> }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,6 +286,7 @@ open class DefaultComponents(
|
||||
.collect { tab ->
|
||||
logger.info("title: ${tab.content.title} ${tab.content.url}")
|
||||
flutterEvents.onTabContentStateChange(
|
||||
System.currentTimeMillis(),
|
||||
TabContentState(
|
||||
id = tab.id,
|
||||
contextId = tab.contextId,
|
||||
@@ -303,6 +310,7 @@ open class DefaultComponents(
|
||||
.collect { tab ->
|
||||
tab.content.findResults
|
||||
flutterEvents.onFindResults(
|
||||
System.currentTimeMillis(),
|
||||
tab.id,
|
||||
tab.content.findResults.map { result -> FindResultState(
|
||||
activeMatchOrdinal = result.activeMatchOrdinal.toLong(),
|
||||
|
||||
+3
-1
@@ -40,7 +40,9 @@ private class NativeFragmentView(
|
||||
|
||||
override fun onFlutterViewAttached(flutterView: View) {
|
||||
super.onFlutterViewAttached(flutterView)
|
||||
flutterEvents.onFragmentReadyStateChange(true) { _ -> }
|
||||
|
||||
GlobalComponents.components!!.engineReportedInitialized = false;
|
||||
flutterEvents.onViewReadyStateChange(System.currentTimeMillis(),true) { _ -> }
|
||||
}
|
||||
|
||||
override fun getView(): View {
|
||||
|
||||
-2
@@ -84,8 +84,6 @@ object GlobalComponents {
|
||||
Logger.error("Failed to initialize web extension support", e)
|
||||
}
|
||||
|
||||
newComponents.tabsUseCases.addTab.invoke("https://google.com", selectTab = true)
|
||||
|
||||
_components = newComponents
|
||||
}
|
||||
}
|
||||
+12
@@ -1,9 +1,21 @@
|
||||
package eu.lensai.flutter_mozilla_components.api
|
||||
|
||||
import eu.lensai.flutter_mozilla_components.GlobalComponents
|
||||
import eu.lensai.flutter_mozilla_components.pigeons.GeckoBrowserApi
|
||||
import mozilla.components.browser.state.action.SystemAction
|
||||
import mozilla.components.feature.addons.logger
|
||||
|
||||
class GeckoBrowserApiImpl(private val showFragmentCallback: () -> Unit) : GeckoBrowserApi {
|
||||
override fun showNativeFragment() {
|
||||
showFragmentCallback.invoke();
|
||||
}
|
||||
|
||||
override fun onTrimMemory(level: Long) {
|
||||
logger.debug("onTrimMemory: $level")
|
||||
|
||||
val components = GlobalComponents.components!!
|
||||
|
||||
components.store.dispatch(SystemAction.LowMemoryAction(level.toInt()))
|
||||
components.icons.onTrimMemory(level.toInt())
|
||||
}
|
||||
}
|
||||
+44
-12
@@ -19,7 +19,13 @@ import eu.lensai.flutter_mozilla_components.pigeons.SecurityInfoState
|
||||
import eu.lensai.flutter_mozilla_components.pigeons.TabContentState
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import mozilla.components.browser.icons.BrowserIcons
|
||||
import mozilla.components.browser.icons.IconRequest
|
||||
import mozilla.components.browser.session.storage.RecoverableBrowserState
|
||||
import mozilla.components.browser.state.action.TabListAction
|
||||
import mozilla.components.browser.state.selector.findTab
|
||||
@@ -43,6 +49,7 @@ class GeckoTabsApiImpl() : GeckoTabsApi {
|
||||
private val engine: Engine by lazy { GlobalComponents.components!!.engine }
|
||||
private val state: BrowserState by lazy { GlobalComponents.components!!.store.state }
|
||||
private val thumbnailStorage: ThumbnailStorage by lazy { GlobalComponents.components!!.thumbnailStorage }
|
||||
private val icons: BrowserIcons by lazy { GlobalComponents.components!!.icons }
|
||||
private val events: GeckoStateEvents by lazy { GlobalComponents.components!!.flutterEvents }
|
||||
|
||||
private fun restoreSource(source: SourceValue ) : SessionState.Source {
|
||||
@@ -115,19 +122,27 @@ class GeckoTabsApiImpl() : GeckoTabsApi {
|
||||
onFindResults: Boolean,
|
||||
onThumbnailChange: Boolean,
|
||||
) {
|
||||
val tabs = state.tabs.map { x -> x.copy() }.toList()
|
||||
val selectedTab = state.selectedTabId
|
||||
|
||||
if(onSelectedTabChange) {
|
||||
events.onSelectedTabChange(
|
||||
state.selectedTabId
|
||||
System.currentTimeMillis(),
|
||||
selectedTab
|
||||
) { _ -> }
|
||||
}
|
||||
|
||||
if(onTabListChange) {
|
||||
events.onTabListChange(state.tabs.map {tab -> tab.id}) { _ -> }
|
||||
events.onTabListChange(
|
||||
System.currentTimeMillis(),
|
||||
tabs.map {tab -> tab.id}
|
||||
) { _ -> }
|
||||
}
|
||||
|
||||
if(onTabContentStateChange) {
|
||||
state.tabs.forEach { tab ->
|
||||
tabs.forEach { tab ->
|
||||
events.onTabContentStateChange(
|
||||
System.currentTimeMillis(),
|
||||
TabContentState(
|
||||
id = tab.id,
|
||||
contextId = tab.contextId,
|
||||
@@ -143,21 +158,35 @@ class GeckoTabsApiImpl() : GeckoTabsApi {
|
||||
}
|
||||
|
||||
if(onIconChange) {
|
||||
state.tabs.forEach { tab ->
|
||||
tabs.forEach { tab ->
|
||||
if(tab.content.icon != null) {
|
||||
val iconBytes = tab.content.icon?.toWebPBytes()
|
||||
events.onIconChange(
|
||||
System.currentTimeMillis(),
|
||||
tab.id,
|
||||
iconBytes
|
||||
) { _ -> }
|
||||
} else {
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
val result = icons.loadIcon(IconRequest(url = tab.content.url)).await()
|
||||
val iconBytes = result.bitmap.toWebPBytes()
|
||||
runOnUiThread {
|
||||
events.onIconChange(
|
||||
System.currentTimeMillis(),
|
||||
tab.id,
|
||||
iconBytes
|
||||
) { _ -> }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(onSecurityInfoStateChange) {
|
||||
state.tabs.forEach { tab ->
|
||||
val iconBytes = tab.content.icon?.toWebPBytes()
|
||||
tabs.forEach { tab ->
|
||||
events.onSecurityInfoStateChange(
|
||||
System.currentTimeMillis(),
|
||||
tab.id,
|
||||
SecurityInfoState(
|
||||
tab.content.securityInfo.secure,
|
||||
@@ -169,8 +198,9 @@ class GeckoTabsApiImpl() : GeckoTabsApi {
|
||||
}
|
||||
|
||||
if(onReaderableStateChange) {
|
||||
state.tabs.forEach { tab ->
|
||||
tabs.forEach { tab ->
|
||||
events.onReaderableStateChange(
|
||||
System.currentTimeMillis(),
|
||||
tab.id,
|
||||
ReaderableState(
|
||||
tab.readerState.readerable,
|
||||
@@ -181,8 +211,9 @@ class GeckoTabsApiImpl() : GeckoTabsApi {
|
||||
}
|
||||
|
||||
if(onHistoryStateChange) {
|
||||
state.tabs.forEach { tab ->
|
||||
tabs.forEach { tab ->
|
||||
events.onHistoryStateChange(
|
||||
System.currentTimeMillis(),
|
||||
tab.id,
|
||||
HistoryState(
|
||||
items = tab.content.history.items.map { item -> HistoryItem(
|
||||
@@ -198,8 +229,9 @@ class GeckoTabsApiImpl() : GeckoTabsApi {
|
||||
}
|
||||
|
||||
if(onFindResults) {
|
||||
state.tabs.forEach { tab ->
|
||||
tabs.forEach { tab ->
|
||||
events.onFindResults(
|
||||
System.currentTimeMillis(),
|
||||
tab.id,
|
||||
tab.content.findResults.map { result -> FindResultState(
|
||||
activeMatchOrdinal = result.activeMatchOrdinal.toLong(),
|
||||
@@ -211,13 +243,13 @@ class GeckoTabsApiImpl() : GeckoTabsApi {
|
||||
}
|
||||
|
||||
if(onThumbnailChange) {
|
||||
state.tabs.forEach { tab ->
|
||||
tabs.forEach { tab ->
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
val bitmap = thumbnailStorage.loadThumbnail(
|
||||
ImageLoadRequest(
|
||||
id = tab.id,
|
||||
//TODO: make this configurable
|
||||
size = 600,
|
||||
size = 1024,
|
||||
isPrivate = tab.content.private
|
||||
)
|
||||
).await()
|
||||
@@ -225,7 +257,7 @@ class GeckoTabsApiImpl() : GeckoTabsApi {
|
||||
if(bitmap != null) {
|
||||
val bytes = bitmap.toWebPBytes()
|
||||
runOnUiThread {
|
||||
events.onThumbnailChange(tab.id, bytes) { _ -> }
|
||||
events.onThumbnailChange(System.currentTimeMillis(), tab.id, bytes) { _ -> }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
@@ -4,6 +4,23 @@ import android.graphics.Bitmap
|
||||
import android.os.Build
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
fun Bitmap.resize(maxWidth: Int, maxHeight: Int): Bitmap {
|
||||
var width = this.width
|
||||
var height = this.height
|
||||
|
||||
val aspectRatio: Float = width.toFloat() / height.toFloat()
|
||||
|
||||
if (width > height) {
|
||||
width = maxWidth
|
||||
height = (width / aspectRatio).toInt()
|
||||
} else {
|
||||
height = maxHeight
|
||||
width = (height * aspectRatio).toInt()
|
||||
}
|
||||
|
||||
return Bitmap.createScaledBitmap(this, width, height, true)
|
||||
}
|
||||
|
||||
fun Bitmap.toWebPBytes(): ByteArray {
|
||||
val stream = ByteArrayOutputStream()
|
||||
val compressFormat = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
|
||||
+34
-4
@@ -5,16 +5,22 @@ package eu.lensai.flutter_mozilla_components.middleware
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.util.Log
|
||||
import eu.lensai.flutter_mozilla_components.GlobalComponents
|
||||
import eu.lensai.flutter_mozilla_components.ext.resize
|
||||
import eu.lensai.flutter_mozilla_components.ext.toWebPBytes
|
||||
import eu.lensai.flutter_mozilla_components.pigeons.GeckoStateEvents
|
||||
import mozilla.components.browser.state.action.BrowserAction
|
||||
import mozilla.components.browser.state.action.ContentAction
|
||||
import mozilla.components.browser.state.action.LastAccessAction
|
||||
import mozilla.components.browser.state.action.ReaderAction
|
||||
import mozilla.components.browser.state.action.TabListAction
|
||||
import mozilla.components.browser.state.state.BrowserState
|
||||
import mozilla.components.feature.addons.logger
|
||||
import mozilla.components.lib.state.Middleware
|
||||
import mozilla.components.lib.state.MiddlewareContext
|
||||
import org.mozilla.gecko.util.ThreadUtils.runOnUiThread
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
import kotlin.reflect.typeOf
|
||||
|
||||
|
||||
/**
|
||||
@@ -30,13 +36,37 @@ class FlutterEventMiddleware(private val flutterEvents: GeckoStateEvents) : Midd
|
||||
) {
|
||||
when (action) {
|
||||
is ContentAction.UpdateThumbnailAction -> {
|
||||
val bytes = action.thumbnail.toWebPBytes()
|
||||
val resized = action.thumbnail.resize(maxWidth = 640, maxHeight = 480);
|
||||
val bytes = resized.toWebPBytes()
|
||||
|
||||
runOnUiThread {
|
||||
flutterEvents.onThumbnailChange(action.sessionId, bytes) { _ -> }
|
||||
flutterEvents.onThumbnailChange(System.currentTimeMillis(), action.sessionId, bytes) { _ -> }
|
||||
}
|
||||
}
|
||||
//UpdateReaderConnectRequiredAction seems to be the only event that is called predictable
|
||||
//after a hot reload
|
||||
is ReaderAction.UpdateReaderConnectRequiredAction -> {
|
||||
if(!GlobalComponents.components!!.engineReportedInitialized) {
|
||||
runOnUiThread {
|
||||
flutterEvents.onEngineReadyStateChange(
|
||||
System.currentTimeMillis(),
|
||||
true
|
||||
) { _ -> }
|
||||
}
|
||||
|
||||
GlobalComponents.components!!.engineReportedInitialized = true
|
||||
}
|
||||
}
|
||||
is TabListAction.AddTabAction -> {
|
||||
runOnUiThread {
|
||||
flutterEvents.onTabAdded(
|
||||
System.currentTimeMillis(),
|
||||
action.tab.id
|
||||
) { _ -> }
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// no-op
|
||||
//logger.debug("Event fired: " + action.javaClass.name)
|
||||
}
|
||||
}
|
||||
next(action)
|
||||
|
||||
+74
-21
@@ -1158,6 +1158,7 @@ private open class GeckoPigeonCodec : StandardMessageCodec() {
|
||||
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
||||
interface GeckoBrowserApi {
|
||||
fun showNativeFragment()
|
||||
fun onTrimMemory(level: Long)
|
||||
|
||||
companion object {
|
||||
/** The codec used by GeckoBrowserApi. */
|
||||
@@ -1184,6 +1185,24 @@ interface GeckoBrowserApi {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
run {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoBrowserApi.onTrimMemory$separatedMessageChannelSuffix", codec)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { message, reply ->
|
||||
val args = message as List<Any?>
|
||||
val levelArg = args[0] as Long
|
||||
val wrapped: List<Any?> = try {
|
||||
api.onTrimMemory(levelArg)
|
||||
listOf(null)
|
||||
} catch (exception: Throwable) {
|
||||
wrapError(exception)
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2153,12 +2172,12 @@ class GeckoStateEvents(private val binaryMessenger: BinaryMessenger, private val
|
||||
GeckoPigeonCodec()
|
||||
}
|
||||
}
|
||||
fun onFragmentReadyStateChange(stateArg: Boolean, callback: (Result<Unit>) -> Unit)
|
||||
fun onViewReadyStateChange(timestampArg: Long, stateArg: Boolean, callback: (Result<Unit>) -> Unit)
|
||||
{
|
||||
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
|
||||
val channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onFragmentReadyStateChange$separatedMessageChannelSuffix"
|
||||
val channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onViewReadyStateChange$separatedMessageChannelSuffix"
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
|
||||
channel.send(listOf(stateArg)) {
|
||||
channel.send(listOf(timestampArg, stateArg)) {
|
||||
if (it is List<*>) {
|
||||
if (it.size > 1) {
|
||||
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
|
||||
@@ -2170,12 +2189,46 @@ class GeckoStateEvents(private val binaryMessenger: BinaryMessenger, private val
|
||||
}
|
||||
}
|
||||
}
|
||||
fun onTabListChange(tabIdsArg: List<String>, callback: (Result<Unit>) -> Unit)
|
||||
fun onEngineReadyStateChange(timestampArg: Long, stateArg: Boolean, callback: (Result<Unit>) -> Unit)
|
||||
{
|
||||
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
|
||||
val channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onEngineReadyStateChange$separatedMessageChannelSuffix"
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
|
||||
channel.send(listOf(timestampArg, stateArg)) {
|
||||
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 ""
|
||||
val channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onTabAdded$separatedMessageChannelSuffix"
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
|
||||
channel.send(listOf(timestampArg, tabIdArg)) {
|
||||
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 onTabListChange(timestampArg: Long, tabIdsArg: List<String>, callback: (Result<Unit>) -> Unit)
|
||||
{
|
||||
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
|
||||
val channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onTabListChange$separatedMessageChannelSuffix"
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
|
||||
channel.send(listOf(tabIdsArg)) {
|
||||
channel.send(listOf(timestampArg, tabIdsArg)) {
|
||||
if (it is List<*>) {
|
||||
if (it.size > 1) {
|
||||
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
|
||||
@@ -2187,12 +2240,12 @@ class GeckoStateEvents(private val binaryMessenger: BinaryMessenger, private val
|
||||
}
|
||||
}
|
||||
}
|
||||
fun onSelectedTabChange(idArg: String?, callback: (Result<Unit>) -> Unit)
|
||||
fun onSelectedTabChange(timestampArg: Long, idArg: String?, callback: (Result<Unit>) -> Unit)
|
||||
{
|
||||
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
|
||||
val channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onSelectedTabChange$separatedMessageChannelSuffix"
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
|
||||
channel.send(listOf(idArg)) {
|
||||
channel.send(listOf(timestampArg, idArg)) {
|
||||
if (it is List<*>) {
|
||||
if (it.size > 1) {
|
||||
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
|
||||
@@ -2204,12 +2257,12 @@ class GeckoStateEvents(private val binaryMessenger: BinaryMessenger, private val
|
||||
}
|
||||
}
|
||||
}
|
||||
fun onTabContentStateChange(stateArg: TabContentState, callback: (Result<Unit>) -> Unit)
|
||||
fun onTabContentStateChange(timestampArg: Long, stateArg: TabContentState, callback: (Result<Unit>) -> Unit)
|
||||
{
|
||||
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
|
||||
val channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onTabContentStateChange$separatedMessageChannelSuffix"
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
|
||||
channel.send(listOf(stateArg)) {
|
||||
channel.send(listOf(timestampArg, stateArg)) {
|
||||
if (it is List<*>) {
|
||||
if (it.size > 1) {
|
||||
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
|
||||
@@ -2221,12 +2274,12 @@ class GeckoStateEvents(private val binaryMessenger: BinaryMessenger, private val
|
||||
}
|
||||
}
|
||||
}
|
||||
fun onHistoryStateChange(idArg: String, stateArg: HistoryState, callback: (Result<Unit>) -> Unit)
|
||||
fun onHistoryStateChange(timestampArg: Long, idArg: String, stateArg: HistoryState, callback: (Result<Unit>) -> Unit)
|
||||
{
|
||||
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
|
||||
val channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onHistoryStateChange$separatedMessageChannelSuffix"
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
|
||||
channel.send(listOf(idArg, stateArg)) {
|
||||
channel.send(listOf(timestampArg, idArg, stateArg)) {
|
||||
if (it is List<*>) {
|
||||
if (it.size > 1) {
|
||||
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
|
||||
@@ -2238,12 +2291,12 @@ class GeckoStateEvents(private val binaryMessenger: BinaryMessenger, private val
|
||||
}
|
||||
}
|
||||
}
|
||||
fun onReaderableStateChange(idArg: String, stateArg: ReaderableState, callback: (Result<Unit>) -> Unit)
|
||||
fun onReaderableStateChange(timestampArg: Long, idArg: String, stateArg: ReaderableState, callback: (Result<Unit>) -> Unit)
|
||||
{
|
||||
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
|
||||
val channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onReaderableStateChange$separatedMessageChannelSuffix"
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
|
||||
channel.send(listOf(idArg, stateArg)) {
|
||||
channel.send(listOf(timestampArg, idArg, stateArg)) {
|
||||
if (it is List<*>) {
|
||||
if (it.size > 1) {
|
||||
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
|
||||
@@ -2255,12 +2308,12 @@ class GeckoStateEvents(private val binaryMessenger: BinaryMessenger, private val
|
||||
}
|
||||
}
|
||||
}
|
||||
fun onSecurityInfoStateChange(idArg: String, stateArg: SecurityInfoState, callback: (Result<Unit>) -> Unit)
|
||||
fun onSecurityInfoStateChange(timestampArg: Long, idArg: String, stateArg: SecurityInfoState, callback: (Result<Unit>) -> Unit)
|
||||
{
|
||||
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
|
||||
val channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onSecurityInfoStateChange$separatedMessageChannelSuffix"
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
|
||||
channel.send(listOf(idArg, stateArg)) {
|
||||
channel.send(listOf(timestampArg, idArg, stateArg)) {
|
||||
if (it is List<*>) {
|
||||
if (it.size > 1) {
|
||||
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
|
||||
@@ -2272,12 +2325,12 @@ class GeckoStateEvents(private val binaryMessenger: BinaryMessenger, private val
|
||||
}
|
||||
}
|
||||
}
|
||||
fun onIconChange(idArg: String, bytesArg: ByteArray?, callback: (Result<Unit>) -> Unit)
|
||||
fun onIconChange(timestampArg: Long, idArg: String, bytesArg: ByteArray?, callback: (Result<Unit>) -> Unit)
|
||||
{
|
||||
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
|
||||
val channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onIconChange$separatedMessageChannelSuffix"
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
|
||||
channel.send(listOf(idArg, bytesArg)) {
|
||||
channel.send(listOf(timestampArg, idArg, 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?)))
|
||||
@@ -2289,12 +2342,12 @@ class GeckoStateEvents(private val binaryMessenger: BinaryMessenger, private val
|
||||
}
|
||||
}
|
||||
}
|
||||
fun onThumbnailChange(idArg: String, bytesArg: ByteArray?, callback: (Result<Unit>) -> Unit)
|
||||
fun onThumbnailChange(timestampArg: Long, idArg: String, bytesArg: ByteArray?, callback: (Result<Unit>) -> Unit)
|
||||
{
|
||||
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
|
||||
val channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onThumbnailChange$separatedMessageChannelSuffix"
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
|
||||
channel.send(listOf(idArg, bytesArg)) {
|
||||
channel.send(listOf(timestampArg, idArg, 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?)))
|
||||
@@ -2306,12 +2359,12 @@ class GeckoStateEvents(private val binaryMessenger: BinaryMessenger, private val
|
||||
}
|
||||
}
|
||||
}
|
||||
fun onFindResults(idArg: String, resultsArg: List<FindResultState>, callback: (Result<Unit>) -> Unit)
|
||||
fun onFindResults(timestampArg: Long, idArg: String, resultsArg: List<FindResultState>, callback: (Result<Unit>) -> Unit)
|
||||
{
|
||||
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
|
||||
val channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onFindResults$separatedMessageChannelSuffix"
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
|
||||
channel.send(listOf(idArg, resultsArg)) {
|
||||
channel.send(listOf(timestampArg, idArg, resultsArg)) {
|
||||
if (it is List<*>) {
|
||||
if (it.size > 1) {
|
||||
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
|
||||
|
||||
+16
-1
@@ -1,5 +1,20 @@
|
||||
package eu.lensai.flutter_mozilla_components_example
|
||||
|
||||
import io.flutter.embedding.android.FlutterFragmentActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
|
||||
class MainActivity: FlutterFragmentActivity()
|
||||
class MainActivity: FlutterFragmentActivity() {
|
||||
private val CHANNEL = "me.movenext.flutter_mozilla_components/trim_memory"
|
||||
private lateinit var channel: MethodChannel
|
||||
|
||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
|
||||
}
|
||||
|
||||
override fun onTrimMemory(level: Int) {
|
||||
super.onTrimMemory(level)
|
||||
channel.invokeMethod("onTrimMemory", level)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,4 +10,8 @@ class GeckoBrowserService {
|
||||
Future<void> showNativeFragment() {
|
||||
return _api.showNativeFragment();
|
||||
}
|
||||
|
||||
Future<void> onTrimMemory(int level) {
|
||||
return _api.onTrimMemory(level);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,22 +12,31 @@ typedef ThumbnailEvent = ({String tabId, Uint8List? bytes});
|
||||
typedef FindResultsEvent = ({String tabId, List<FindResultState> results});
|
||||
|
||||
class GeckoEventService extends GeckoStateEvents {
|
||||
final _lastEventTimes = <Subject, Map<dynamic, int>>{};
|
||||
|
||||
// Stream controllers
|
||||
final _fragmentStateSubject = BehaviorSubject.seeded(false);
|
||||
final _viewStateSubject = BehaviorSubject.seeded(false);
|
||||
final _engineStateSubject = BehaviorSubject.seeded(false);
|
||||
final _tabListSubject = BehaviorSubject<List<String>>();
|
||||
final _selectedTabSubject = BehaviorSubject<String?>();
|
||||
final _tabContentSubject = BehaviorSubject<TabContentState>();
|
||||
final _historySubject = BehaviorSubject<HistoryEvent>();
|
||||
final _readerableSubject = BehaviorSubject<ReaderableEvent>();
|
||||
final _securityInfoSubject = BehaviorSubject<SecurityInfoEvent>();
|
||||
final _iconSubject = BehaviorSubject<IconEvent>();
|
||||
final _thumbnailSubject = BehaviorSubject<ThumbnailEvent>();
|
||||
final _findResultsSubject = BehaviorSubject<FindResultsEvent>();
|
||||
|
||||
final _tabContentSubject = ReplaySubject<TabContentState>();
|
||||
final _historySubject = ReplaySubject<HistoryEvent>();
|
||||
final _securityInfoSubject = ReplaySubject<SecurityInfoEvent>();
|
||||
final _readerableSubject = ReplaySubject<ReaderableEvent>();
|
||||
|
||||
final _iconSubject = PublishSubject<IconEvent>();
|
||||
final _thumbnailSubject = PublishSubject<ThumbnailEvent>();
|
||||
final _findResultsSubject = PublishSubject<FindResultsEvent>();
|
||||
|
||||
final _tabAddedSubject = PublishSubject<String>();
|
||||
|
||||
// Event streams
|
||||
Stream<bool> get fragmentReadyStateEvents => _fragmentStateSubject.stream;
|
||||
Stream<List<String>> get tabListEvents => _tabListSubject.stream;
|
||||
Stream<String?> get selectedTabEvents => _selectedTabSubject.stream;
|
||||
ValueStream<bool> get viewReadyStateEvents => _viewStateSubject.stream;
|
||||
ValueStream<bool> get engineReadyStateEvents => _engineStateSubject.stream;
|
||||
ValueStream<List<String>> get tabListEvents => _tabListSubject.stream;
|
||||
ValueStream<String?> get selectedTabEvents => _selectedTabSubject.stream;
|
||||
|
||||
Stream<TabContentState> get tabContentEvents => _tabContentSubject.stream;
|
||||
Stream<HistoryEvent> get historyEvents => _historySubject.stream;
|
||||
Stream<ReaderableEvent> get readerableEvents => _readerableSubject.stream;
|
||||
@@ -37,55 +46,124 @@ class GeckoEventService extends GeckoStateEvents {
|
||||
Stream<ThumbnailEvent> get thumbnailEvents => _thumbnailSubject.stream;
|
||||
Stream<FindResultsEvent> get findResultsEvent => _findResultsSubject.stream;
|
||||
|
||||
Stream<String> get tabAddedStream => _tabAddedSubject.stream;
|
||||
|
||||
void _addWhenMoreRecent<T>(
|
||||
Subject<T> subject,
|
||||
int timestamp,
|
||||
dynamic identifier,
|
||||
T value,
|
||||
) {
|
||||
_lastEventTimes[subject] ??= {};
|
||||
|
||||
if ((_lastEventTimes[subject]?[identifier] ?? 0) < timestamp) {
|
||||
_lastEventTimes[subject]![identifier] = timestamp;
|
||||
subject.add(value);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onFragmentReadyStateChange(bool state) {
|
||||
_fragmentStateSubject.add(state);
|
||||
void onViewReadyStateChange(int timestamp, bool state) {
|
||||
_addWhenMoreRecent(_viewStateSubject, timestamp, null, state);
|
||||
}
|
||||
|
||||
@override
|
||||
void onEngineReadyStateChange(int timestamp, bool state) {
|
||||
_addWhenMoreRecent(_engineStateSubject, timestamp, null, state);
|
||||
}
|
||||
|
||||
// Overridden methods
|
||||
@override
|
||||
void onTabListChange(List<String?> tabIds) {
|
||||
_tabListSubject.add(tabIds.nonNulls.toList());
|
||||
void onTabListChange(int timestamp, List<String?> tabIds) {
|
||||
_addWhenMoreRecent(
|
||||
_tabListSubject,
|
||||
timestamp,
|
||||
null,
|
||||
tabIds.nonNulls.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void onSelectedTabChange(String? id) {
|
||||
_selectedTabSubject.add(id);
|
||||
void onSelectedTabChange(int timestamp, String? id) {
|
||||
_addWhenMoreRecent(_selectedTabSubject, timestamp, id, id);
|
||||
}
|
||||
|
||||
@override
|
||||
void onTabContentStateChange(TabContentState state) {
|
||||
_tabContentSubject.add(state);
|
||||
void onTabContentStateChange(int timestamp, TabContentState state) {
|
||||
_addWhenMoreRecent(_tabContentSubject, timestamp, state.id, state);
|
||||
}
|
||||
|
||||
@override
|
||||
void onHistoryStateChange(String id, HistoryState state) {
|
||||
_historySubject.add((tabId: id, history: state));
|
||||
void onHistoryStateChange(int timestamp, String id, HistoryState state) {
|
||||
_addWhenMoreRecent(
|
||||
_historySubject,
|
||||
timestamp,
|
||||
id,
|
||||
(tabId: id, history: state),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void onReaderableStateChange(String id, ReaderableState state) {
|
||||
_readerableSubject.add((tabId: id, readerable: state));
|
||||
void onReaderableStateChange(
|
||||
int timestamp,
|
||||
String id,
|
||||
ReaderableState state,
|
||||
) {
|
||||
_addWhenMoreRecent(
|
||||
_readerableSubject,
|
||||
timestamp,
|
||||
id,
|
||||
(tabId: id, readerable: state),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void onSecurityInfoStateChange(String id, SecurityInfoState state) {
|
||||
_securityInfoSubject.add((tabId: id, securityInfo: state));
|
||||
void onSecurityInfoStateChange(
|
||||
int timestamp,
|
||||
String id,
|
||||
SecurityInfoState state,
|
||||
) {
|
||||
_addWhenMoreRecent(
|
||||
_securityInfoSubject,
|
||||
timestamp,
|
||||
id,
|
||||
(tabId: id, securityInfo: state),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void onIconChange(String id, Uint8List? bytes) {
|
||||
_iconSubject.add((tabId: id, bytes: bytes));
|
||||
void onIconChange(int timestamp, String id, Uint8List? bytes) {
|
||||
_addWhenMoreRecent(_iconSubject, timestamp, id, (tabId: id, bytes: bytes));
|
||||
}
|
||||
|
||||
@override
|
||||
void onThumbnailChange(String id, Uint8List? bytes) {
|
||||
_thumbnailSubject.add((tabId: id, bytes: bytes));
|
||||
void onThumbnailChange(int timestamp, String id, Uint8List? bytes) {
|
||||
_addWhenMoreRecent(
|
||||
_thumbnailSubject,
|
||||
timestamp,
|
||||
id,
|
||||
(tabId: id, bytes: bytes),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void onFindResults(String id, List<FindResultState?> results) {
|
||||
_findResultsSubject.add((tabId: id, results: results.nonNulls.toList()));
|
||||
void onFindResults(int timestamp, String id, List<FindResultState?> results) {
|
||||
_addWhenMoreRecent(
|
||||
_findResultsSubject,
|
||||
timestamp,
|
||||
id,
|
||||
(tabId: id, results: results.nonNulls.toList()),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void onTabAdded(int timestamp, String tabId) {
|
||||
_addWhenMoreRecent(
|
||||
_tabAddedSubject,
|
||||
timestamp,
|
||||
null,
|
||||
tabId,
|
||||
);
|
||||
}
|
||||
|
||||
GeckoEventService.setUp({
|
||||
@@ -109,5 +187,6 @@ class GeckoEventService extends GeckoStateEvents {
|
||||
unawaited(_iconSubject.close());
|
||||
unawaited(_thumbnailSubject.close());
|
||||
unawaited(_findResultsSubject.close());
|
||||
unawaited(_tabAddedSubject.close());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,14 +11,14 @@ class GeckoTabService {
|
||||
GeckoTabService({GeckoTabsApi? api}) : _api = api ?? _apiInstance;
|
||||
|
||||
Future<void> syncEvents({
|
||||
bool onSelectedTabChange = true,
|
||||
bool onTabListChange = true,
|
||||
bool onTabContentStateChange = true,
|
||||
bool onIconChange = true,
|
||||
bool onSecurityInfoStateChange = true,
|
||||
bool onHistoryStateChange = true,
|
||||
bool onFindResults = true,
|
||||
bool onThumbnailChange = true,
|
||||
bool onSelectedTabChange = false,
|
||||
bool onTabListChange = false,
|
||||
bool onTabContentStateChange = false,
|
||||
bool onIconChange = false,
|
||||
bool onSecurityInfoStateChange = false,
|
||||
bool onHistoryStateChange = false,
|
||||
bool onFindResults = false,
|
||||
bool onThumbnailChange = false,
|
||||
}) {
|
||||
return _api.syncEvents(
|
||||
onSelectedTabChange: onSelectedTabChange,
|
||||
|
||||
@@ -8,11 +8,35 @@ import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mozilla_components/src/domain/services/gecko_browser.dart';
|
||||
|
||||
class GeckoView extends StatelessWidget {
|
||||
class GeckoView extends StatefulWidget {
|
||||
final FutureOr<void> Function()? preInitializationStep;
|
||||
|
||||
const GeckoView({super.key, this.preInitializationStep});
|
||||
|
||||
@override
|
||||
State<GeckoView> createState() => _GeckoViewState();
|
||||
}
|
||||
|
||||
class _GeckoViewState extends State<GeckoView> {
|
||||
static const platform =
|
||||
MethodChannel('me.movenext.flutter_mozilla_components/trim_memory');
|
||||
|
||||
final browserService = GeckoBrowserService();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_setupMethodCallHandler();
|
||||
}
|
||||
|
||||
void _setupMethodCallHandler() {
|
||||
platform.setMethodCallHandler((MethodCall call) async {
|
||||
if (call.method == 'onTrimMemory') {
|
||||
await browserService.onTrimMemory(call.arguments as int);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PlatformViewLink(
|
||||
@@ -39,8 +63,14 @@ class GeckoView extends StatelessWidget {
|
||||
params.onPlatformViewCreated(value);
|
||||
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) async {
|
||||
await preInitializationStep?.call();
|
||||
await GeckoBrowserService().showNativeFragment();
|
||||
await widget.preInitializationStep?.call();
|
||||
|
||||
await Future.delayed(
|
||||
//Wait for two more frames just to be sure view has been initialized
|
||||
Duration(milliseconds: ((1000 / 60) * 2).toInt()),
|
||||
).whenComplete(() async {
|
||||
await browserService.showNativeFragment();
|
||||
});
|
||||
});
|
||||
})
|
||||
// ignore: discarded_futures
|
||||
|
||||
@@ -1187,6 +1187,28 @@ class GeckoBrowserApi {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> onTrimMemory(int level) async {
|
||||
final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoBrowserApi.onTrimMemory$pigeonVar_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: pigeonVar_binaryMessenger,
|
||||
);
|
||||
final List<Object?>? pigeonVar_replyList =
|
||||
await pigeonVar_channel.send(<Object?>[level]) as List<Object?>?;
|
||||
if (pigeonVar_replyList == null) {
|
||||
throw _createConnectionError(pigeonVar_channelName);
|
||||
} else if (pigeonVar_replyList.length > 1) {
|
||||
throw PlatformException(
|
||||
code: pigeonVar_replyList[0]! as String,
|
||||
message: pigeonVar_replyList[1] as String?,
|
||||
details: pigeonVar_replyList[2],
|
||||
);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GeckoEngineSettingsApi {
|
||||
@@ -2243,44 +2265,107 @@ class GeckoCookieApi {
|
||||
abstract class GeckoStateEvents {
|
||||
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
||||
|
||||
void onFragmentReadyStateChange(bool state);
|
||||
void onViewReadyStateChange(int timestamp, bool state);
|
||||
|
||||
void onTabListChange(List<String> tabIds);
|
||||
void onEngineReadyStateChange(int timestamp, bool state);
|
||||
|
||||
void onSelectedTabChange(String? id);
|
||||
void onTabAdded(int timestamp, String tabId);
|
||||
|
||||
void onTabContentStateChange(TabContentState state);
|
||||
void onTabListChange(int timestamp, List<String> tabIds);
|
||||
|
||||
void onHistoryStateChange(String id, HistoryState state);
|
||||
void onSelectedTabChange(int timestamp, String? id);
|
||||
|
||||
void onReaderableStateChange(String id, ReaderableState state);
|
||||
void onTabContentStateChange(int timestamp, TabContentState state);
|
||||
|
||||
void onSecurityInfoStateChange(String id, SecurityInfoState state);
|
||||
void onHistoryStateChange(int timestamp, String id, HistoryState state);
|
||||
|
||||
void onIconChange(String id, Uint8List? bytes);
|
||||
void onReaderableStateChange(int timestamp, String id, ReaderableState state);
|
||||
|
||||
void onThumbnailChange(String id, Uint8List? bytes);
|
||||
void onSecurityInfoStateChange(int timestamp, String id, SecurityInfoState state);
|
||||
|
||||
void onFindResults(String id, List<FindResultState> results);
|
||||
void onIconChange(int timestamp, String id, Uint8List? bytes);
|
||||
|
||||
void onThumbnailChange(int timestamp, String id, Uint8List? bytes);
|
||||
|
||||
void onFindResults(int timestamp, String id, List<FindResultState> results);
|
||||
|
||||
static void setUp(GeckoStateEvents? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) {
|
||||
messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||
{
|
||||
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onFragmentReadyStateChange$messageChannelSuffix', pigeonChannelCodec,
|
||||
'dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onViewReadyStateChange$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.onFragmentReadyStateChange was null.');
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onViewReadyStateChange was null.');
|
||||
final List<Object?> args = (message as List<Object?>?)!;
|
||||
final bool? arg_state = (args[0] as bool?);
|
||||
final int? arg_timestamp = (args[0] as int?);
|
||||
assert(arg_timestamp != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onViewReadyStateChange was null, expected non-null int.');
|
||||
final bool? arg_state = (args[1] as bool?);
|
||||
assert(arg_state != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onFragmentReadyStateChange was null, expected non-null bool.');
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onViewReadyStateChange was null, expected non-null bool.');
|
||||
try {
|
||||
api.onFragmentReadyStateChange(arg_state!);
|
||||
api.onViewReadyStateChange(arg_timestamp!, arg_state!);
|
||||
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.onEngineReadyStateChange$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.onEngineReadyStateChange 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.onEngineReadyStateChange was null, expected non-null int.');
|
||||
final bool? arg_state = (args[1] as bool?);
|
||||
assert(arg_state != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onEngineReadyStateChange was null, expected non-null bool.');
|
||||
try {
|
||||
api.onEngineReadyStateChange(arg_timestamp!, arg_state!);
|
||||
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,
|
||||
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.onTabAdded 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.onTabAdded was null, expected non-null int.');
|
||||
final String? arg_tabId = (args[1] as String?);
|
||||
assert(arg_tabId != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onTabAdded was null, expected non-null String.');
|
||||
try {
|
||||
api.onTabAdded(arg_timestamp!, arg_tabId!);
|
||||
return wrapResponse(empty: true);
|
||||
} on PlatformException catch (e) {
|
||||
return wrapResponse(error: e);
|
||||
@@ -2301,11 +2386,14 @@ abstract class GeckoStateEvents {
|
||||
assert(message != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onTabListChange was null.');
|
||||
final List<Object?> args = (message as List<Object?>?)!;
|
||||
final List<String>? arg_tabIds = (args[0] as List<Object?>?)?.cast<String>();
|
||||
final int? arg_timestamp = (args[0] as int?);
|
||||
assert(arg_timestamp != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onTabListChange was null, expected non-null int.');
|
||||
final List<String>? arg_tabIds = (args[1] as List<Object?>?)?.cast<String>();
|
||||
assert(arg_tabIds != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onTabListChange was null, expected non-null List<String>.');
|
||||
try {
|
||||
api.onTabListChange(arg_tabIds!);
|
||||
api.onTabListChange(arg_timestamp!, arg_tabIds!);
|
||||
return wrapResponse(empty: true);
|
||||
} on PlatformException catch (e) {
|
||||
return wrapResponse(error: e);
|
||||
@@ -2326,9 +2414,12 @@ abstract class GeckoStateEvents {
|
||||
assert(message != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onSelectedTabChange was null.');
|
||||
final List<Object?> args = (message as List<Object?>?)!;
|
||||
final String? arg_id = (args[0] as String?);
|
||||
final int? arg_timestamp = (args[0] as int?);
|
||||
assert(arg_timestamp != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onSelectedTabChange was null, expected non-null int.');
|
||||
final String? arg_id = (args[1] as String?);
|
||||
try {
|
||||
api.onSelectedTabChange(arg_id);
|
||||
api.onSelectedTabChange(arg_timestamp!, arg_id);
|
||||
return wrapResponse(empty: true);
|
||||
} on PlatformException catch (e) {
|
||||
return wrapResponse(error: e);
|
||||
@@ -2349,11 +2440,14 @@ abstract class GeckoStateEvents {
|
||||
assert(message != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onTabContentStateChange was null.');
|
||||
final List<Object?> args = (message as List<Object?>?)!;
|
||||
final TabContentState? arg_state = (args[0] as TabContentState?);
|
||||
final int? arg_timestamp = (args[0] as int?);
|
||||
assert(arg_timestamp != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onTabContentStateChange was null, expected non-null int.');
|
||||
final TabContentState? arg_state = (args[1] as TabContentState?);
|
||||
assert(arg_state != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onTabContentStateChange was null, expected non-null TabContentState.');
|
||||
try {
|
||||
api.onTabContentStateChange(arg_state!);
|
||||
api.onTabContentStateChange(arg_timestamp!, arg_state!);
|
||||
return wrapResponse(empty: true);
|
||||
} on PlatformException catch (e) {
|
||||
return wrapResponse(error: e);
|
||||
@@ -2374,14 +2468,17 @@ abstract class GeckoStateEvents {
|
||||
assert(message != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onHistoryStateChange was null.');
|
||||
final List<Object?> args = (message as List<Object?>?)!;
|
||||
final String? arg_id = (args[0] as String?);
|
||||
final int? arg_timestamp = (args[0] as int?);
|
||||
assert(arg_timestamp != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onHistoryStateChange was null, expected non-null int.');
|
||||
final String? arg_id = (args[1] as String?);
|
||||
assert(arg_id != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onHistoryStateChange was null, expected non-null String.');
|
||||
final HistoryState? arg_state = (args[1] as HistoryState?);
|
||||
final HistoryState? arg_state = (args[2] as HistoryState?);
|
||||
assert(arg_state != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onHistoryStateChange was null, expected non-null HistoryState.');
|
||||
try {
|
||||
api.onHistoryStateChange(arg_id!, arg_state!);
|
||||
api.onHistoryStateChange(arg_timestamp!, arg_id!, arg_state!);
|
||||
return wrapResponse(empty: true);
|
||||
} on PlatformException catch (e) {
|
||||
return wrapResponse(error: e);
|
||||
@@ -2402,14 +2499,17 @@ abstract class GeckoStateEvents {
|
||||
assert(message != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onReaderableStateChange was null.');
|
||||
final List<Object?> args = (message as List<Object?>?)!;
|
||||
final String? arg_id = (args[0] as String?);
|
||||
final int? arg_timestamp = (args[0] as int?);
|
||||
assert(arg_timestamp != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onReaderableStateChange was null, expected non-null int.');
|
||||
final String? arg_id = (args[1] as String?);
|
||||
assert(arg_id != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onReaderableStateChange was null, expected non-null String.');
|
||||
final ReaderableState? arg_state = (args[1] as ReaderableState?);
|
||||
final ReaderableState? arg_state = (args[2] as ReaderableState?);
|
||||
assert(arg_state != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onReaderableStateChange was null, expected non-null ReaderableState.');
|
||||
try {
|
||||
api.onReaderableStateChange(arg_id!, arg_state!);
|
||||
api.onReaderableStateChange(arg_timestamp!, arg_id!, arg_state!);
|
||||
return wrapResponse(empty: true);
|
||||
} on PlatformException catch (e) {
|
||||
return wrapResponse(error: e);
|
||||
@@ -2430,14 +2530,17 @@ abstract class GeckoStateEvents {
|
||||
assert(message != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onSecurityInfoStateChange was null.');
|
||||
final List<Object?> args = (message as List<Object?>?)!;
|
||||
final String? arg_id = (args[0] as String?);
|
||||
final int? arg_timestamp = (args[0] as int?);
|
||||
assert(arg_timestamp != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onSecurityInfoStateChange was null, expected non-null int.');
|
||||
final String? arg_id = (args[1] as String?);
|
||||
assert(arg_id != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onSecurityInfoStateChange was null, expected non-null String.');
|
||||
final SecurityInfoState? arg_state = (args[1] as SecurityInfoState?);
|
||||
final SecurityInfoState? arg_state = (args[2] as SecurityInfoState?);
|
||||
assert(arg_state != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onSecurityInfoStateChange was null, expected non-null SecurityInfoState.');
|
||||
try {
|
||||
api.onSecurityInfoStateChange(arg_id!, arg_state!);
|
||||
api.onSecurityInfoStateChange(arg_timestamp!, arg_id!, arg_state!);
|
||||
return wrapResponse(empty: true);
|
||||
} on PlatformException catch (e) {
|
||||
return wrapResponse(error: e);
|
||||
@@ -2458,12 +2561,15 @@ abstract class GeckoStateEvents {
|
||||
assert(message != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onIconChange was null.');
|
||||
final List<Object?> args = (message as List<Object?>?)!;
|
||||
final String? arg_id = (args[0] as String?);
|
||||
final int? arg_timestamp = (args[0] as int?);
|
||||
assert(arg_timestamp != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onIconChange was null, expected non-null int.');
|
||||
final String? arg_id = (args[1] as String?);
|
||||
assert(arg_id != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onIconChange was null, expected non-null String.');
|
||||
final Uint8List? arg_bytes = (args[1] as Uint8List?);
|
||||
final Uint8List? arg_bytes = (args[2] as Uint8List?);
|
||||
try {
|
||||
api.onIconChange(arg_id!, arg_bytes);
|
||||
api.onIconChange(arg_timestamp!, arg_id!, arg_bytes);
|
||||
return wrapResponse(empty: true);
|
||||
} on PlatformException catch (e) {
|
||||
return wrapResponse(error: e);
|
||||
@@ -2484,12 +2590,15 @@ abstract class GeckoStateEvents {
|
||||
assert(message != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onThumbnailChange was null.');
|
||||
final List<Object?> args = (message as List<Object?>?)!;
|
||||
final String? arg_id = (args[0] as String?);
|
||||
final int? arg_timestamp = (args[0] as int?);
|
||||
assert(arg_timestamp != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onThumbnailChange was null, expected non-null int.');
|
||||
final String? arg_id = (args[1] as String?);
|
||||
assert(arg_id != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onThumbnailChange was null, expected non-null String.');
|
||||
final Uint8List? arg_bytes = (args[1] as Uint8List?);
|
||||
final Uint8List? arg_bytes = (args[2] as Uint8List?);
|
||||
try {
|
||||
api.onThumbnailChange(arg_id!, arg_bytes);
|
||||
api.onThumbnailChange(arg_timestamp!, arg_id!, arg_bytes);
|
||||
return wrapResponse(empty: true);
|
||||
} on PlatformException catch (e) {
|
||||
return wrapResponse(error: e);
|
||||
@@ -2510,14 +2619,17 @@ abstract class GeckoStateEvents {
|
||||
assert(message != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onFindResults was null.');
|
||||
final List<Object?> args = (message as List<Object?>?)!;
|
||||
final String? arg_id = (args[0] as String?);
|
||||
final int? arg_timestamp = (args[0] as int?);
|
||||
assert(arg_timestamp != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onFindResults was null, expected non-null int.');
|
||||
final String? arg_id = (args[1] as String?);
|
||||
assert(arg_id != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onFindResults was null, expected non-null String.');
|
||||
final List<FindResultState>? arg_results = (args[1] as List<Object?>?)?.cast<FindResultState>();
|
||||
final List<FindResultState>? arg_results = (args[2] as List<Object?>?)?.cast<FindResultState>();
|
||||
assert(arg_results != null,
|
||||
'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onFindResults was null, expected non-null List<FindResultState>.');
|
||||
try {
|
||||
api.onFindResults(arg_id!, arg_results!);
|
||||
api.onFindResults(arg_timestamp!, arg_id!, arg_results!);
|
||||
return wrapResponse(empty: true);
|
||||
} on PlatformException catch (e) {
|
||||
return wrapResponse(error: e);
|
||||
|
||||
@@ -539,6 +539,7 @@ class FindResultState {
|
||||
@HostApi()
|
||||
abstract class GeckoBrowserApi {
|
||||
void showNativeFragment();
|
||||
void onTrimMemory(int level);
|
||||
}
|
||||
|
||||
@HostApi()
|
||||
@@ -782,19 +783,26 @@ abstract class GeckoCookieApi {
|
||||
|
||||
@FlutterApi()
|
||||
abstract class GeckoStateEvents {
|
||||
void onFragmentReadyStateChange(bool state);
|
||||
void onViewReadyStateChange(int timestamp, bool state);
|
||||
void onEngineReadyStateChange(int timestamp, bool state);
|
||||
|
||||
void onTabListChange(List<String> tabIds);
|
||||
void onSelectedTabChange(String? id);
|
||||
void onTabAdded(int timestamp, String tabId);
|
||||
|
||||
void onTabContentStateChange(TabContentState state);
|
||||
void onHistoryStateChange(String id, HistoryState state);
|
||||
void onReaderableStateChange(String id, ReaderableState state);
|
||||
void onSecurityInfoStateChange(String id, SecurityInfoState state);
|
||||
void onIconChange(String id, Uint8List? bytes);
|
||||
void onThumbnailChange(String id, Uint8List? bytes);
|
||||
void onTabListChange(int timestamp, List<String> tabIds);
|
||||
void onSelectedTabChange(int timestamp, String? id);
|
||||
|
||||
void onFindResults(String id, List<FindResultState> results);
|
||||
void onTabContentStateChange(int timestamp, TabContentState state);
|
||||
void onHistoryStateChange(int timestamp, String id, HistoryState state);
|
||||
void onReaderableStateChange(int timestamp, String id, ReaderableState state);
|
||||
void onSecurityInfoStateChange(
|
||||
int timestamp,
|
||||
String id,
|
||||
SecurityInfoState state,
|
||||
);
|
||||
void onIconChange(int timestamp, String id, Uint8List? bytes);
|
||||
void onThumbnailChange(int timestamp, String id, Uint8List? bytes);
|
||||
|
||||
void onFindResults(int timestamp, String id, List<FindResultState> results);
|
||||
}
|
||||
|
||||
@HostApi()
|
||||
|
||||
Reference in New Issue
Block a user