This commit is contained in:
Fabian Freund
2025-02-16 23:21:44 +01:00
parent ddb0f14059
commit 6368e30479
81 changed files with 20308 additions and 88 deletions
@@ -4,7 +4,7 @@ import 'package:lensai/features/user/domain/repositories/engine_settings.dart';
import 'package:lensai/features/user/domain/repositories/general_settings.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'engine_settings.g.dart';
part 'engine_settings_replication.g.dart';
@Riverpod(keepAlive: true)
class EngineSettingsReplicationService
@@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'engine_settings.dart';
part of 'engine_settings_replication.dart';
// **************************************************************************
// RiverpodGenerator
@@ -0,0 +1,44 @@
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:lensai/data/models/equatable_iterable.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/providers.dart';
import 'package:lensai/features/tor/domain/services/tor_proxy.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'proxy_settings_replucation.g.dart';
@Riverpod(keepAlive: true)
class ProxySettingsReplucation extends _$ProxySettingsReplucation {
final _service = GeckoContainerProxyService();
@override
void build() {
ref.listen(
torProxyServiceProvider.select((data) => data.valueOrNull),
(previous, next) async {
if (next != null) {
await _service.setProxyPort(next);
}
},
);
ref.listen(
containersWithCountProvider.select(
(value) => EquatableCollection(
value.valueOrNull
?.where((container) => container.metadata.useProxy)
.map((container) => container.metadata.contextualIdentity)
.nonNulls
.toList(),
immutable: true,
),
),
(previous, next) async {
if (next.collection != null) {
for (final contextId in next.collection!) {
await _service.addContainerProxy(contextId);
}
}
},
);
}
}
@@ -0,0 +1,27 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'proxy_settings_replucation.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$proxySettingsReplucationHash() =>
r'08cb19e895056d4ddbf0399b23a9d159a0c86915';
/// See also [ProxySettingsReplucation].
@ProviderFor(ProxySettingsReplucation)
final proxySettingsReplucationProvider =
NotifierProvider<ProxySettingsReplucation, void>.internal(
ProxySettingsReplucation.new,
name: r'proxySettingsReplucationProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$proxySettingsReplucationHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$ProxySettingsReplucation = Notifier<void>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
@@ -33,6 +33,7 @@ import 'package:lensai/features/kagi/data/entities/modes.dart';
import 'package:lensai/presentation/hooks/draggable_scrollable_controller.dart';
import 'package:lensai/presentation/hooks/menu_controller.dart';
import 'package:lensai/presentation/hooks/overlay_portal_controller.dart';
import 'package:lensai/presentation/icons/tor_icons.dart';
import 'package:lensai/utils/ui_helper.dart' as ui_helper;
import 'package:share_plus/share_plus.dart';
@@ -418,6 +419,13 @@ class BrowserScreen extends HookConsumerWidget {
);
},
),
MenuItemButton(
onPressed: () async {
await context.push(TorProxyRoute().location);
},
leadingIcon: const Icon(TorIcons.onionAlt),
child: const Text('Tor'),
),
const Divider(),
MenuItemButton(
onPressed: () async {
@@ -10,7 +10,8 @@ import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
import 'package:lensai/features/geckoview/domain/providers/web_extensions_state.dart';
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
import 'package:lensai/features/geckoview/features/browser/domain/services/delete_browser_data.dart';
import 'package:lensai/features/geckoview/features/browser/domain/services/engine_settings.dart';
import 'package:lensai/features/geckoview/features/browser/domain/services/engine_settings_replication.dart';
import 'package:lensai/features/geckoview/features/browser/domain/services/proxy_settings_replucation.dart';
import 'package:lensai/features/geckoview/features/tabs/features/vector_store/domain/repositories/document.dart';
import 'package:lensai/features/user/domain/repositories/cache.dart';
import 'package:lensai/features/user/domain/repositories/general_settings.dart';
@@ -146,6 +147,11 @@ class _BrowserViewState extends ConsumerState<BrowserView>
engineSettingsReplicationServiceProvider,
(previous, next) {},
);
ref.listenManual(
proxySettingsReplucationProvider,
(previous, next) {},
);
}
@override
@@ -21,6 +21,7 @@ import 'package:lensai/features/geckoview/features/tabs/domain/repositories/cont
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/tab.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/tab_search.dart';
import 'package:lensai/features/geckoview/features/tabs/presentation/widgets/container_chips.dart';
import 'package:lensai/features/tor/presentation/controllers/start_tor_proxy.dart';
import 'package:lensai/presentation/hooks/listenable_callback.dart';
import 'package:lensai/presentation/widgets/speech_to_text_button.dart';
@@ -181,9 +182,16 @@ class _Tab extends HookConsumerWidget {
return ContainerChips(
selectedContainer: selectedContainer,
onSelected: (container) async {
await ref
final result = await ref
.read(selectedContainerProvider.notifier)
.setContainerId(container.id);
if (context.mounted &&
result == SetContainerResult.successHasProxy) {
await ref
.read(startProxyControllerProvider.notifier)
.maybeStartProxy(context);
}
},
onDeleted: (container) async {
ref
@@ -2005,7 +2005,8 @@ class $ContainerTableManager extends RootTableManager<
getPrefetchedDataCallback: (items) async {
return [
if (tabRefs)
await $_getPrefetchedData(
await $_getPrefetchedData<ContainerData, Container,
TabData>(
currentTable: table,
referencedTable: $ContainerReferences._tabRefsTable(db),
managerFromTypedResult: (p0) =>
@@ -53,20 +53,26 @@ class ContainerMetadata with FastEquatable {
final String? contextualIdentity;
final ContainerAuthSettings authSettings;
@JsonKey(defaultValue: false)
final bool useProxy;
ContainerMetadata({
required this.iconData,
required this.contextualIdentity,
required this.authSettings,
required this.useProxy,
});
ContainerMetadata.withDefaults({
IconData? iconData,
String? contextualIdentity,
ContainerAuthSettings? authSettings,
bool? useProxy,
}) : this(
iconData: iconData,
contextualIdentity: contextualIdentity,
authSettings: authSettings ?? ContainerAuthSettings.withDefaults(),
useProxy: useProxy ?? false,
);
factory ContainerMetadata.fromJson(Map<String, dynamic> json) =>
@@ -79,6 +85,7 @@ class ContainerMetadata with FastEquatable {
iconData,
contextualIdentity,
authSettings,
useProxy,
];
@override
@@ -90,6 +90,8 @@ abstract class _$ContainerMetadataCWProxy {
ContainerMetadata authSettings(ContainerAuthSettings authSettings);
ContainerMetadata useProxy(bool useProxy);
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `ContainerMetadata(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
///
/// Usage
@@ -100,6 +102,7 @@ abstract class _$ContainerMetadataCWProxy {
IconData? iconData,
String? contextualIdentity,
ContainerAuthSettings authSettings,
bool useProxy,
});
}
@@ -120,6 +123,9 @@ class _$ContainerMetadataCWProxyImpl implements _$ContainerMetadataCWProxy {
ContainerMetadata authSettings(ContainerAuthSettings authSettings) =>
this(authSettings: authSettings);
@override
ContainerMetadata useProxy(bool useProxy) => this(useProxy: useProxy);
@override
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `ContainerMetadata(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
@@ -132,6 +138,7 @@ class _$ContainerMetadataCWProxyImpl implements _$ContainerMetadataCWProxy {
Object? iconData = const $CopyWithPlaceholder(),
Object? contextualIdentity = const $CopyWithPlaceholder(),
Object? authSettings = const $CopyWithPlaceholder(),
Object? useProxy = const $CopyWithPlaceholder(),
}) {
return ContainerMetadata(
iconData: iconData == const $CopyWithPlaceholder()
@@ -146,6 +153,10 @@ class _$ContainerMetadataCWProxyImpl implements _$ContainerMetadataCWProxy {
? _value.authSettings
// ignore: cast_nullable_to_non_nullable
: authSettings as ContainerAuthSettings,
useProxy: useProxy == const $CopyWithPlaceholder()
? _value.useProxy
// ignore: cast_nullable_to_non_nullable
: useProxy as bool,
);
}
}
@@ -271,6 +282,7 @@ ContainerMetadata _$ContainerMetadataFromJson(Map<String, dynamic> json) =>
? null
: ContainerAuthSettings.fromJson(
json['authSettings'] as Map<String, dynamic>),
useProxy: json['useProxy'] as bool? ?? false,
);
Map<String, dynamic> _$ContainerMetadataToJson(ContainerMetadata instance) =>
@@ -279,6 +291,7 @@ Map<String, dynamic> _$ContainerMetadataToJson(ContainerMetadata instance) =>
instance.iconData, const IconDataJsonConverter().toJson),
'contextualIdentity': instance.contextualIdentity,
'authSettings': instance.authSettings,
'useProxy': instance.useProxy,
};
Value? _$JsonConverterFromJson<Json, Value>(
@@ -8,6 +8,12 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'selected_container.g.dart';
enum SetContainerResult {
failed,
success,
successHasProxy,
}
@Riverpod(keepAlive: true)
class SelectedContainer extends _$SelectedContainer {
Future<ContainerData?> fetchData() async {
@@ -20,12 +26,13 @@ class SelectedContainer extends _$SelectedContainer {
return null;
}
Future<void> setContainerId(String id) async {
Future<SetContainerResult> setContainerId(String id) async {
final container = await ref
.read(containerRepositoryProvider.notifier)
.getContainerData(id);
if (container != null) {
var passAuth = false;
if (container.metadata.authSettings.authenticationRequired) {
final authResult = await ref
.read(localAuthenticationServiceProvider.notifier)
@@ -38,20 +45,24 @@ class SelectedContainer extends _$SelectedContainer {
);
if (authResult) {
state = id;
passAuth = true;
}
} else {
passAuth = true;
}
if (passAuth) {
state = id;
if (container.metadata.useProxy) {
return SetContainerResult.successHasProxy;
}
return SetContainerResult.success;
}
}
}
Future<void> toggleContainer(String id) async {
if (state == id) {
clearContainer();
} else {
await setContainerId(id);
}
return SetContainerResult.failed;
}
void clearContainer() {
@@ -25,7 +25,7 @@ final selectedContainerDataProvider =
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef SelectedContainerDataRef = AutoDisposeStreamProviderRef<ContainerData?>;
String _$selectedContainerHash() => r'753f30646c4992981ec538dcfd33fa23aa1f673d';
String _$selectedContainerHash() => r'f3dab491337396b8d56daed37a0d85c85aa8d64a';
/// See also [SelectedContainer].
@ProviderFor(SelectedContainer)
@@ -1,5 +1,4 @@
import 'package:collection/collection.dart';
import 'package:cross_cache/cross_cache.dart';
import 'package:flutter/material.dart';
import 'package:flutter_chat_core/flutter_chat_core.dart';
import 'package:flutter_chat_ui/flutter_chat_ui.dart';
@@ -23,7 +22,7 @@ class TabQaChat extends HookConsumerWidget {
final chatBackend = ref.watch(chatBackendProvider(chatId).notifier);
final chatController = ref.watch(chatControllerProvider(chatId));
final crossCache = useMemoized(() => CrossCache());
// final crossCache = useMemoized(() => CrossCache());
final chatScrollController = scrollController ?? useScrollController();
useOnInitialization(() async {
@@ -59,7 +58,7 @@ class TabQaChat extends HookConsumerWidget {
),
),
chatController: chatController,
crossCache: crossCache,
// crossCache: crossCache,
scrollController: chatScrollController,
onMessageSend: (text) async {
await chatBackend.processQAMessage(text);
@@ -4,11 +4,11 @@ import 'package:flutter_material_design_icons/flutter_material_design_icons.dart
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/core/uuid.dart';
import 'package:lensai/extensions/nullable.dart';
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/container.dart';
import 'package:lensai/features/geckoview/features/tabs/presentation/widgets/color_picker_dialog.dart';
import 'package:lensai/features/user/domain/services/local_authentication.dart';
import 'package:lensai/presentation/icons/tor_icons.dart';
enum _DialogMode { create, edit }
@@ -66,6 +66,7 @@ class ContainerEditScreen extends HookConsumerWidget {
final contextualIdentity =
useState(initialContainer.metadata.contextualIdentity);
final authSettings = useState(initialContainer.metadata.authSettings);
final useProxy = useState(initialContainer.metadata.useProxy);
final textController =
useTextEditingController(text: initialContainer.name);
@@ -85,17 +86,11 @@ class ContainerEditScreen extends HookConsumerWidget {
final container = initialContainer.copyWith(
name: name.isNotEmpty ? name : null,
color: selectedColor.value,
metadata: initialContainer.metadata.mapNotNull(
(metadata) => metadata.copyWith(
contextualIdentity: contextualIdentity.value,
authSettings: authSettings.value,
),
) ??
ContainerMetadata(
iconData: null,
contextualIdentity: contextualIdentity.value,
authSettings: authSettings.value,
),
metadata: initialContainer.metadata.copyWith(
contextualIdentity: contextualIdentity.value,
authSettings: authSettings.value,
useProxy: useProxy.value && contextualIdentity.value != null,
),
);
//Check for permissions, when auth is set or getting set
@@ -187,9 +182,35 @@ class ContainerEditScreen extends HookConsumerWidget {
.metadata.contextualIdentity ??
uuid.v4()
: null;
if (!value && useProxy.value) {
useProxy.value = false;
}
}
: null,
),
SwitchListTile.adaptive(
value: useProxy.value,
title: const Text('Use Tor Proxy'),
secondary: const Icon(TorIcons.onionAlt),
contentPadding: EdgeInsets.zero,
onChanged: switch (_mode) {
_DialogMode.create => (value) {
if (value && contextualIdentity.value == null) {
contextualIdentity.value = initialContainer
.metadata.contextualIdentity ??
uuid.v4();
}
useProxy.value = value;
},
_DialogMode.edit => (contextualIdentity.value != null)
? (value) {
useProxy.value = value;
}
: null,
},
),
SwitchListTile.adaptive(
value: authSettings.value.authenticationRequired,
title: const Text('Require Authentication'),
@@ -10,6 +10,7 @@ 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_list_tile.dart';
import 'package:lensai/features/tor/presentation/controllers/start_tor_proxy.dart';
import 'package:skeletonizer/skeletonizer.dart';
class ContainerListScreen extends HookConsumerWidget {
@@ -62,9 +63,23 @@ class ContainerListScreen extends HookConsumerWidget {
container,
isSelected: container.id == selectedContainer,
onTap: () async {
await ref
.read(selectedContainerProvider.notifier)
.toggleContainer(container.id);
if (container.id == selectedContainer) {
ref
.read(selectedContainerProvider.notifier)
.clearContainer();
} else {
final result = await ref
.read(selectedContainerProvider.notifier)
.setContainerId(container.id);
if (context.mounted &&
result ==
SetContainerResult.successHasProxy) {
await ref
.read(startProxyControllerProvider.notifier)
.maybeStartProxy(context);
}
}
},
),
);
@@ -32,7 +32,7 @@ class _MaterialPickerState extends State<MaterialPicker> {
void initState() {
for (final colors in colorTypes) {
shadingTypes(colors).forEach((Map<Color, String> color) {
if (widget.pickerColor.value == color.keys.first.value) {
if (widget.pickerColor.toARGB32() == color.keys.first.toARGB32()) {
return setState(() {
_currentColorType = colors;
_currentShading = color.keys.first;