Merge branch 'main' into tor_proxy
This commit is contained in:
@@ -77,7 +77,7 @@ jobs:
|
|||||||
- uses: subosito/flutter-action@v2
|
- uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
channel: stable
|
channel: stable
|
||||||
flutter-version: 3.35.1
|
flutter-version: 3.35.3
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
- name: Setup Flutter dependencies
|
- name: Setup Flutter dependencies
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
## 0.9.22
|
||||||
|
### GeckoView 142.0
|
||||||
|
|
||||||
|
* Added setting to use third party CA certificates
|
||||||
|
* Added setting to control tab bar swipe behavior
|
||||||
|
* Downgraded AGP for F-Droid compatibility
|
||||||
|
|
||||||
## 0.9.21
|
## 0.9.21
|
||||||
### GeckoView 142.0
|
### GeckoView 142.0
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,10 @@ part of 'routes.dart';
|
|||||||
name: 'TabTreeRoute',
|
name: 'TabTreeRoute',
|
||||||
path: 'tab_tree/:rootTabId',
|
path: 'tab_tree/:rootTabId',
|
||||||
),
|
),
|
||||||
|
TypedGoRoute<OpenSharedContentRoute>(
|
||||||
|
name: 'OpenSharedContentRoute',
|
||||||
|
path: 'open_content',
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
class BrowserRoute extends GoRouteData with _$BrowserRoute {
|
class BrowserRoute extends GoRouteData with _$BrowserRoute {
|
||||||
@@ -75,9 +79,12 @@ class SearchRoute extends GoRouteData with _$SearchRoute {
|
|||||||
//This should be nullable but isnt allowed by go_router
|
//This should be nullable but isnt allowed by go_router
|
||||||
final String searchText;
|
final String searchText;
|
||||||
|
|
||||||
|
final bool $extra;
|
||||||
|
|
||||||
const SearchRoute({
|
const SearchRoute({
|
||||||
required this.tabType,
|
required this.tabType,
|
||||||
this.searchText = SearchRoute.emptySearchText,
|
this.searchText = SearchRoute.emptySearchText,
|
||||||
|
this.$extra = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -87,6 +94,7 @@ class SearchRoute extends GoRouteData with _$SearchRoute {
|
|||||||
initialSearchText: (searchText.isEmpty || searchText == emptySearchText)
|
initialSearchText: (searchText.isEmpty || searchText == emptySearchText)
|
||||||
? null
|
? null
|
||||||
: searchText,
|
: searchText,
|
||||||
|
launchedFromIntent: $extra,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -159,3 +167,14 @@ class TabTreeRoute extends GoRouteData with _$TabTreeRoute {
|
|||||||
return DialogPage(builder: (_) => TabTreeDialog(rootTabId));
|
return DialogPage(builder: (_) => TabTreeDialog(rootTabId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class OpenSharedContentRoute extends GoRouteData with _$OpenSharedContentRoute {
|
||||||
|
final Uri $extra;
|
||||||
|
|
||||||
|
const OpenSharedContentRoute(this.$extra);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Page<void> buildPage(BuildContext context, GoRouterState state) {
|
||||||
|
return DialogPage(builder: (_) => OpenSharedContent(sharedUrl: $extra));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import 'package:weblibre/features/about/presentation/screens/about.dart';
|
|||||||
import 'package:weblibre/features/bangs/presentation/screens/categories.dart';
|
import 'package:weblibre/features/bangs/presentation/screens/categories.dart';
|
||||||
import 'package:weblibre/features/bangs/presentation/screens/list.dart';
|
import 'package:weblibre/features/bangs/presentation/screens/list.dart';
|
||||||
import 'package:weblibre/features/bangs/presentation/screens/search.dart';
|
import 'package:weblibre/features/bangs/presentation/screens/search.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/open_shared_content.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/tab_tree.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/tab_tree.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/screens/browser.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/screens/browser.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/contextmenu/extensions/hit_result.dart';
|
import 'package:weblibre/features/geckoview/features/contextmenu/extensions/hit_result.dart';
|
||||||
|
|||||||
@@ -333,6 +333,12 @@ RouteBase get $browserRoute => GoRouteData.$route(
|
|||||||
|
|
||||||
factory: _$TabTreeRoute._fromState,
|
factory: _$TabTreeRoute._fromState,
|
||||||
),
|
),
|
||||||
|
GoRouteData.$route(
|
||||||
|
path: 'open_content',
|
||||||
|
name: 'OpenSharedContentRoute',
|
||||||
|
|
||||||
|
factory: _$OpenSharedContentRoute._fromState,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -361,6 +367,7 @@ mixin _$SearchRoute on GoRouteData {
|
|||||||
tabType: _$TabTypeEnumMap._$fromName(state.pathParameters['tabType']!)!,
|
tabType: _$TabTypeEnumMap._$fromName(state.pathParameters['tabType']!)!,
|
||||||
searchText:
|
searchText:
|
||||||
state.pathParameters['searchText'] ?? SearchRoute.emptySearchText,
|
state.pathParameters['searchText'] ?? SearchRoute.emptySearchText,
|
||||||
|
$extra: state.extra as bool,
|
||||||
);
|
);
|
||||||
|
|
||||||
SearchRoute get _self => this as SearchRoute;
|
SearchRoute get _self => this as SearchRoute;
|
||||||
@@ -371,17 +378,19 @@ mixin _$SearchRoute on GoRouteData {
|
|||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void go(BuildContext context) => context.go(location);
|
void go(BuildContext context) => context.go(location, extra: _self.$extra);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
|
Future<T?> push<T>(BuildContext context) =>
|
||||||
|
context.push<T>(location, extra: _self.$extra);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void pushReplacement(BuildContext context) =>
|
void pushReplacement(BuildContext context) =>
|
||||||
context.pushReplacement(location);
|
context.pushReplacement(location, extra: _self.$extra);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void replace(BuildContext context) => context.replace(location);
|
void replace(BuildContext context) =>
|
||||||
|
context.replace(location, extra: _self.$extra);
|
||||||
}
|
}
|
||||||
|
|
||||||
const _$TabTypeEnumMap = {
|
const _$TabTypeEnumMap = {
|
||||||
@@ -552,6 +561,31 @@ mixin _$TabTreeRoute on GoRouteData {
|
|||||||
void replace(BuildContext context) => context.replace(location);
|
void replace(BuildContext context) => context.replace(location);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mixin _$OpenSharedContentRoute on GoRouteData {
|
||||||
|
static OpenSharedContentRoute _fromState(GoRouterState state) =>
|
||||||
|
OpenSharedContentRoute(state.extra as Uri);
|
||||||
|
|
||||||
|
OpenSharedContentRoute get _self => this as OpenSharedContentRoute;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get location => GoRouteData.$location('/open_content');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void go(BuildContext context) => context.go(location, extra: _self.$extra);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<T?> push<T>(BuildContext context) =>
|
||||||
|
context.push<T>(location, extra: _self.$extra);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void pushReplacement(BuildContext context) =>
|
||||||
|
context.pushReplacement(location, extra: _self.$extra);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void replace(BuildContext context) =>
|
||||||
|
context.replace(location, extra: _self.$extra);
|
||||||
|
}
|
||||||
|
|
||||||
extension<T extends Enum> on Map<T, String> {
|
extension<T extends Enum> on Map<T, String> {
|
||||||
T? _$fromName(String? value) =>
|
T? _$fromName(String? value) =>
|
||||||
entries.where((element) => element.value == value).firstOrNull?.key;
|
entries.where((element) => element.value == value).firstOrNull?.key;
|
||||||
|
|||||||
@@ -25,21 +25,16 @@ import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
|||||||
import 'package:nullability/nullability.dart';
|
import 'package:nullability/nullability.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:weblibre/core/logger.dart';
|
import 'package:weblibre/core/logger.dart';
|
||||||
import 'package:weblibre/core/routing/routes.dart';
|
|
||||||
import 'package:weblibre/features/bangs/domain/providers/bangs.dart';
|
|
||||||
import 'package:weblibre/features/geckoview/domain/entities/states/tab.dart';
|
import 'package:weblibre/features/geckoview/domain/entities/states/tab.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/providers.dart';
|
import 'package:weblibre/features/geckoview/domain/providers.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/providers/selected_tab.dart';
|
import 'package:weblibre/features/geckoview/domain/providers/selected_tab.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/providers/tab_list.dart';
|
import 'package:weblibre/features/geckoview/domain/providers/tab_list.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/domain/providers.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/domain/providers/intent.dart';
|
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/data/providers.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/data/providers.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/domain/providers/selected_container.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/domain/providers/selected_container.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/container.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/container.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab.dart';
|
||||||
import 'package:weblibre/features/share_intent/domain/entities/shared_content.dart';
|
|
||||||
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
|
|
||||||
import 'package:weblibre/utils/debouncer.dart';
|
import 'package:weblibre/utils/debouncer.dart';
|
||||||
|
|
||||||
part 'tab.g.dart';
|
part 'tab.g.dart';
|
||||||
@@ -68,12 +63,16 @@ class TabRepository extends _$TabRepository {
|
|||||||
required bool private,
|
required bool private,
|
||||||
HistoryMetadataKey? historyMetadata,
|
HistoryMetadataKey? historyMetadata,
|
||||||
Map<String, String>? additionalHeaders,
|
Map<String, String>? additionalHeaders,
|
||||||
|
Value<ContainerData?>? container,
|
||||||
|
bool launchedFromIntent = false,
|
||||||
}) async {
|
}) async {
|
||||||
final selectedContainer = await ref
|
final assingedContainer =
|
||||||
.read(selectedContainerProvider.notifier)
|
container ??
|
||||||
.fetchData();
|
Value<ContainerData?>(
|
||||||
|
await ref.read(selectedContainerProvider.notifier).fetchData(),
|
||||||
|
);
|
||||||
|
|
||||||
return ref
|
final newTabId = await ref
|
||||||
.read(tabDatabaseProvider)
|
.read(tabDatabaseProvider)
|
||||||
.tabDao
|
.tabDao
|
||||||
.upsertContainerTabTransactional(
|
.upsertContainerTabTransactional(
|
||||||
@@ -84,7 +83,7 @@ class TabRepository extends _$TabRepository {
|
|||||||
startLoading: startLoading,
|
startLoading: startLoading,
|
||||||
parentId: parentId,
|
parentId: parentId,
|
||||||
flags: flags,
|
flags: flags,
|
||||||
contextId: selectedContainer?.metadata.contextualIdentity,
|
contextId: assingedContainer.value?.metadata.contextualIdentity,
|
||||||
source: source,
|
source: source,
|
||||||
private: private,
|
private: private,
|
||||||
historyMetadata: historyMetadata,
|
historyMetadata: historyMetadata,
|
||||||
@@ -92,8 +91,14 @@ class TabRepository extends _$TabRepository {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
parentId: Value(parentId),
|
parentId: Value(parentId),
|
||||||
containerId: Value(selectedContainer?.id),
|
containerId: Value(assingedContainer.value?.id),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (launchedFromIntent) {
|
||||||
|
_tabFromIntent.add(newTabId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newTabId;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> duplicateTab({
|
Future<String> duplicateTab({
|
||||||
@@ -371,45 +376,6 @@ class TabRepository extends _$TabRepository {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
ref.listen(
|
|
||||||
fireImmediately: true,
|
|
||||||
engineBoundIntentStreamProvider,
|
|
||||||
(previous, next) {
|
|
||||||
next.whenData((value) async {
|
|
||||||
final isPrivate =
|
|
||||||
ref
|
|
||||||
.read(generalSettingsWithDefaultsProvider)
|
|
||||||
.defaultIntentTabType ==
|
|
||||||
TabType.private;
|
|
||||||
|
|
||||||
switch (value) {
|
|
||||||
case SharedUrl():
|
|
||||||
_tabFromIntent.add(
|
|
||||||
await addTab(url: value.url, private: isPrivate),
|
|
||||||
);
|
|
||||||
case SharedText():
|
|
||||||
final defaultSearchBang =
|
|
||||||
ref.read(selectedBangDataProvider()) ??
|
|
||||||
await ref.read(defaultSearchBangDataProvider.future);
|
|
||||||
|
|
||||||
_tabFromIntent.add(
|
|
||||||
await addTab(
|
|
||||||
url: defaultSearchBang?.getTemplateUrl(value.text),
|
|
||||||
private: isPrivate,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onError: (error, stackTrace) {
|
|
||||||
logger.e(
|
|
||||||
'Error listening to engineBoundIntentStreamProvider',
|
|
||||||
error: error,
|
|
||||||
stackTrace: stackTrace,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
ref.onDispose(() async {
|
ref.onDispose(() async {
|
||||||
tabStateDebouncer.dispose();
|
tabStateDebouncer.dispose();
|
||||||
await tabAddedSub.cancel();
|
await tabAddedSub.cancel();
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ part of 'tab.dart';
|
|||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$tabRepositoryHash() => r'4f562c1456eadcda7986ea2048667dd083f37618';
|
String _$tabRepositoryHash() => r'45af26417881bca38973cf702073b1c7857f1cfa';
|
||||||
|
|
||||||
/// See also [TabRepository].
|
/// See also [TabRepository].
|
||||||
@ProviderFor(TabRepository)
|
@ProviderFor(TabRepository)
|
||||||
|
|||||||
+99
@@ -0,0 +1,99 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/container_chips.dart';
|
||||||
|
import 'package:weblibre/utils/form_validators.dart';
|
||||||
|
|
||||||
|
class OpenSharedContent extends HookConsumerWidget {
|
||||||
|
final Uri sharedUrl;
|
||||||
|
|
||||||
|
const OpenSharedContent({required this.sharedUrl});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final formKey = useMemoized(() => GlobalKey<FormState>());
|
||||||
|
final textController = useTextEditingController(text: sharedUrl.toString());
|
||||||
|
|
||||||
|
final selectedContainer = useState<ContainerData?>(null);
|
||||||
|
|
||||||
|
Future<void> openTab(bool isPrivate) async {
|
||||||
|
if (formKey.currentState?.validate() == true) {
|
||||||
|
await ref
|
||||||
|
.read(tabRepositoryProvider.notifier)
|
||||||
|
.addTab(
|
||||||
|
url: Uri.parse(textController.text),
|
||||||
|
private: isPrivate,
|
||||||
|
container: Value(selectedContainer.value),
|
||||||
|
launchedFromIntent: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Form(
|
||||||
|
key: formKey,
|
||||||
|
child: SimpleDialog(
|
||||||
|
title: const Text('Open URL'),
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsetsDirectional.symmetric(horizontal: 16.0),
|
||||||
|
child: SizedBox(
|
||||||
|
height: 48,
|
||||||
|
width: double.maxFinite,
|
||||||
|
child: ContainerChips(
|
||||||
|
displayMenu: false,
|
||||||
|
selectedContainer: selectedContainer.value,
|
||||||
|
onSelected: (container) {
|
||||||
|
selectedContainer.value = container;
|
||||||
|
},
|
||||||
|
onDeleted: (container) {
|
||||||
|
selectedContainer.value = null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsetsDirectional.symmetric(horizontal: 16.0),
|
||||||
|
child: TextFormField(
|
||||||
|
controller: textController,
|
||||||
|
keyboardType: TextInputType.url,
|
||||||
|
minLines: 1,
|
||||||
|
maxLines: 10,
|
||||||
|
validator: (value) {
|
||||||
|
return validateUrl(
|
||||||
|
value,
|
||||||
|
onlyHttpProtocol: true,
|
||||||
|
eagerParsing: false,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: const Text('Open Regular Tab'),
|
||||||
|
leading: const Icon(MdiIcons.tab),
|
||||||
|
onTap: () async {
|
||||||
|
await openTab(false);
|
||||||
|
if (context.mounted) {
|
||||||
|
context.pop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: const Text('Open Private Tab'),
|
||||||
|
leading: const Icon(MdiIcons.tabUnselected),
|
||||||
|
onTap: () async {
|
||||||
|
await openTab(true);
|
||||||
|
if (context.mounted) {
|
||||||
|
context.pop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
@@ -17,6 +17,7 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
import 'package:drift/drift.dart' show Value;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||||
@@ -242,6 +243,7 @@ class BrowserBottomAppBar extends HookConsumerWidget {
|
|||||||
.addTab(
|
.addTab(
|
||||||
url: ref.read(docsUriProvider),
|
url: ref.read(docsUriProvider),
|
||||||
private: isPrivate,
|
private: isPrivate,
|
||||||
|
container: const Value(null),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
leadingIcon: const Icon(Icons.help),
|
leadingIcon: const Icon(Icons.help),
|
||||||
@@ -301,6 +303,7 @@ class BrowserBottomAppBar extends HookConsumerWidget {
|
|||||||
.addTab(
|
.addTab(
|
||||||
url: Uri.parse('https://addons.mozilla.org'),
|
url: Uri.parse('https://addons.mozilla.org'),
|
||||||
private: isPrivate,
|
private: isPrivate,
|
||||||
|
container: const Value(null),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
leadingIcon: const Icon(MdiIcons.puzzlePlus),
|
leadingIcon: const Icon(MdiIcons.puzzlePlus),
|
||||||
|
|||||||
+31
-21
@@ -86,6 +86,8 @@ class ViewTabSheetWidget extends HookConsumerWidget {
|
|||||||
final scrolledTo = useRef(0.0);
|
final scrolledTo = useRef(0.0);
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
final header = headerKey.currentContext?.findRenderObject();
|
final header = headerKey.currentContext?.findRenderObject();
|
||||||
final text = textFieldKey.currentContext?.findRenderObject();
|
final text = textFieldKey.currentContext?.findRenderObject();
|
||||||
|
|
||||||
@@ -96,14 +98,16 @@ class ViewTabSheetWidget extends HookConsumerWidget {
|
|||||||
|
|
||||||
final relative = totalHeight / MediaQuery.of(context).size.height;
|
final relative = totalHeight / MediaQuery.of(context).size.height;
|
||||||
|
|
||||||
if (draggableScrollableController.size < relative &&
|
if (relative >= 0 && relative <= 1) {
|
||||||
relative > scrolledTo.value) {
|
if (draggableScrollableController.size < relative &&
|
||||||
await draggableScrollableController.animateTo(
|
relative > scrolledTo.value) {
|
||||||
relative,
|
await draggableScrollableController.animateTo(
|
||||||
duration: const Duration(milliseconds: 150),
|
relative,
|
||||||
curve: Curves.easeInOut,
|
duration: const Duration(milliseconds: 150),
|
||||||
);
|
curve: Curves.easeInOut,
|
||||||
scrolledTo.value = relative;
|
);
|
||||||
|
scrolledTo.value = relative;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,22 +117,28 @@ class ViewTabSheetWidget extends HookConsumerWidget {
|
|||||||
});
|
});
|
||||||
|
|
||||||
final bottomInsets = useRef(0.0);
|
final bottomInsets = useRef(0.0);
|
||||||
useEffect(() {
|
useEffect(
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
() {
|
||||||
final diff =
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
((MediaQuery.of(context).viewInsets.bottom / 2) /
|
final diff =
|
||||||
MediaQuery.of(context).size.height) -
|
((MediaQuery.of(context).viewInsets.bottom / 2) /
|
||||||
bottomInsets.value;
|
MediaQuery.of(context).size.height) -
|
||||||
|
bottomInsets.value;
|
||||||
|
|
||||||
draggableScrollableController.jumpTo(
|
draggableScrollableController.jumpTo(
|
||||||
draggableScrollableController.size + diff,
|
draggableScrollableController.size + diff,
|
||||||
);
|
);
|
||||||
|
|
||||||
bottomInsets.value += diff;
|
bottomInsets.value += diff;
|
||||||
});
|
});
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}, [MediaQuery.of(context).viewInsets.bottom]);
|
},
|
||||||
|
[
|
||||||
|
MediaQuery.of(context).viewInsets.bottom,
|
||||||
|
MediaQuery.of(context).size.height,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
return NestedScrollView(
|
return NestedScrollView(
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ class PreferenceSettingGroup with FastEquatable {
|
|||||||
(setting) => setting.requireUserOptIn || setting.isActive,
|
(setting) => setting.requireUserOptIn || setting.isActive,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
bool get showMasterSwitch =>
|
||||||
|
settings.values.length >
|
||||||
|
settings.values.where((setting) => setting.requireUserOptIn).length;
|
||||||
|
|
||||||
bool get isPartlyActive => settings.values.any((setting) => setting.isActive);
|
bool get isPartlyActive => settings.values.any((setting) => setting.isActive);
|
||||||
|
|
||||||
bool get hasInactiveOptional => settings.values.any(
|
bool get hasInactiveOptional => settings.values.any(
|
||||||
|
|||||||
@@ -44,8 +44,13 @@ import 'package:weblibre/utils/uri_parser.dart' as uri_parser;
|
|||||||
class SearchScreen extends HookConsumerWidget {
|
class SearchScreen extends HookConsumerWidget {
|
||||||
final String? initialSearchText;
|
final String? initialSearchText;
|
||||||
final TabType tabType;
|
final TabType tabType;
|
||||||
|
final bool launchedFromIntent;
|
||||||
|
|
||||||
const SearchScreen({required this.initialSearchText, required this.tabType});
|
const SearchScreen({
|
||||||
|
required this.initialSearchText,
|
||||||
|
required this.tabType,
|
||||||
|
this.launchedFromIntent = false,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@@ -109,6 +114,7 @@ class SearchScreen extends HookConsumerWidget {
|
|||||||
parentId: (selectedTabType.value == TabType.child)
|
parentId: (selectedTabType.value == TabType.child)
|
||||||
? ref.read(selectedTabProvider)
|
? ref.read(selectedTabProvider)
|
||||||
: null,
|
: null,
|
||||||
|
launchedFromIntent: launchedFromIntent,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
@@ -212,6 +218,7 @@ class SearchScreen extends HookConsumerWidget {
|
|||||||
(selectedTabType.value == TabType.child)
|
(selectedTabType.value == TabType.child)
|
||||||
? ref.read(selectedTabProvider)
|
? ref.read(selectedTabProvider)
|
||||||
: null,
|
: null,
|
||||||
|
launchedFromIntent: launchedFromIntent,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
|
|||||||
@@ -1256,28 +1256,28 @@ abstract class _$TabDatabase extends GeneratedDatabase {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Selectable<String> previousTabByTimestamp({required String tabId}) {
|
Selectable<String?> previousTabByTimestamp({required String tabId}) {
|
||||||
return customSelect(
|
return customSelect(
|
||||||
'WITH ranked_tabs AS (SELECT id, timestamp, LAG(id)OVER (ORDER BY timestamp RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS) AS prev_tab_id FROM tab) SELECT prev_tab_id FROM ranked_tabs WHERE id = ?1',
|
'WITH ranked_tabs AS (SELECT id, timestamp, LAG(id)OVER (ORDER BY timestamp RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS) AS prev_tab_id FROM tab) SELECT prev_tab_id FROM ranked_tabs WHERE id = ?1',
|
||||||
variables: [Variable<String>(tabId)],
|
variables: [Variable<String>(tabId)],
|
||||||
readsFrom: {tab},
|
readsFrom: {tab},
|
||||||
).map((QueryRow row) => row.read<String>('prev_tab_id'));
|
).map((QueryRow row) => row.readNullable<String>('prev_tab_id'));
|
||||||
}
|
}
|
||||||
|
|
||||||
Selectable<String> previousTabByOrderKey({required String tabId}) {
|
Selectable<String?> previousTabByOrderKey({required String tabId}) {
|
||||||
return customSelect(
|
return customSelect(
|
||||||
'WITH ranked_tabs AS (SELECT id, order_key, LAG(id)OVER (ORDER BY order_key RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS) AS prev_tab_id FROM tab) SELECT prev_tab_id FROM ranked_tabs WHERE id = ?1',
|
'WITH ranked_tabs AS (SELECT id, order_key, LAG(id)OVER (ORDER BY order_key RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS) AS prev_tab_id FROM tab) SELECT prev_tab_id FROM ranked_tabs WHERE id = ?1',
|
||||||
variables: [Variable<String>(tabId)],
|
variables: [Variable<String>(tabId)],
|
||||||
readsFrom: {tab},
|
readsFrom: {tab},
|
||||||
).map((QueryRow row) => row.read<String>('prev_tab_id'));
|
).map((QueryRow row) => row.readNullable<String>('prev_tab_id'));
|
||||||
}
|
}
|
||||||
|
|
||||||
Selectable<String> nextTabByOrderKey({required String tabId}) {
|
Selectable<String?> nextTabByOrderKey({required String tabId}) {
|
||||||
return customSelect(
|
return customSelect(
|
||||||
'WITH ranked_tabs AS (SELECT id, order_key, LEAD(id)OVER (ORDER BY order_key RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS) AS next_tab_id FROM tab) SELECT next_tab_id FROM ranked_tabs WHERE id = ?1',
|
'WITH ranked_tabs AS (SELECT id, order_key, LEAD(id)OVER (ORDER BY order_key RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS) AS next_tab_id FROM tab) SELECT next_tab_id FROM ranked_tabs WHERE id = ?1',
|
||||||
variables: [Variable<String>(tabId)],
|
variables: [Variable<String>(tabId)],
|
||||||
readsFrom: {tab},
|
readsFrom: {tab},
|
||||||
).map((QueryRow row) => row.read<String>('next_tab_id'));
|
).map((QueryRow row) => row.readNullable<String>('next_tab_id'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
import 'package:drift/drift.dart' show Value;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
@@ -147,6 +148,7 @@ class OnboardingScreen extends HookConsumerWidget {
|
|||||||
.addTab(
|
.addTab(
|
||||||
url: ref.read(docsUriProvider),
|
url: ref.read(docsUriProvider),
|
||||||
private: false,
|
private: false,
|
||||||
|
container: const Value(null),
|
||||||
);
|
);
|
||||||
|
|
||||||
ref.invalidate(routerProvider);
|
ref.invalidate(routerProvider);
|
||||||
|
|||||||
@@ -175,17 +175,22 @@ class GeneralSettingsScreen extends HookConsumerWidget {
|
|||||||
showSelectedIcon: false,
|
showSelectedIcon: false,
|
||||||
segments: const [
|
segments: const [
|
||||||
ButtonSegment(
|
ButtonSegment(
|
||||||
value: TabType.regular,
|
value: TabIntentOpenSetting.ask,
|
||||||
|
label: Text('Prompt'),
|
||||||
|
icon: Icon(MdiIcons.messageQuestion),
|
||||||
|
),
|
||||||
|
ButtonSegment(
|
||||||
|
value: TabIntentOpenSetting.regular,
|
||||||
label: Text('Regular'),
|
label: Text('Regular'),
|
||||||
icon: Icon(MdiIcons.tab),
|
icon: Icon(MdiIcons.tab),
|
||||||
),
|
),
|
||||||
ButtonSegment(
|
ButtonSegment(
|
||||||
value: TabType.private,
|
value: TabIntentOpenSetting.private,
|
||||||
label: Text('Private'),
|
label: Text('Private'),
|
||||||
icon: Icon(MdiIcons.tabUnselected),
|
icon: Icon(MdiIcons.tabUnselected),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
selected: {generalSettings.defaultIntentTabType},
|
selected: {generalSettings.tabIntentOpenSetting},
|
||||||
onSelectionChanged: (value) async {
|
onSelectionChanged: (value) async {
|
||||||
await ref
|
await ref
|
||||||
.read(
|
.read(
|
||||||
@@ -193,15 +198,16 @@ class GeneralSettingsScreen extends HookConsumerWidget {
|
|||||||
)
|
)
|
||||||
.save(
|
.save(
|
||||||
(currentSettings) => currentSettings.copyWith
|
(currentSettings) => currentSettings.copyWith
|
||||||
.defaultIntentTabType(value.first),
|
.tabIntentOpenSetting(value.first),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
style: switch (generalSettings.defaultIntentTabType) {
|
style: switch (generalSettings.tabIntentOpenSetting) {
|
||||||
TabType.regular => null,
|
TabIntentOpenSetting.regular => null,
|
||||||
TabType.private => SegmentedButton.styleFrom(
|
TabIntentOpenSetting.private =>
|
||||||
selectedBackgroundColor: const Color(0x648000D7),
|
SegmentedButton.styleFrom(
|
||||||
),
|
selectedBackgroundColor: const Color(0x648000D7),
|
||||||
TabType.child => null,
|
),
|
||||||
|
TabIntentOpenSetting.ask => null,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -47,46 +47,47 @@ class WebEngineHardeningGroupScreen extends HookConsumerWidget {
|
|||||||
data: (group) {
|
data: (group) {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
if (group.showMasterSwitch)
|
||||||
padding: const EdgeInsets.all(8.0),
|
Padding(
|
||||||
child: Card(
|
padding: const EdgeInsets.all(8.0),
|
||||||
color: theme.colorScheme.primaryContainer,
|
child: Card(
|
||||||
child: Padding(
|
color: theme.colorScheme.primaryContainer,
|
||||||
padding: const EdgeInsets.all(8.0),
|
child: Padding(
|
||||||
child: SwitchListTile(
|
padding: const EdgeInsets.all(8.0),
|
||||||
value: group.isActiveOrOptional,
|
child: SwitchListTile(
|
||||||
title: Text(
|
value: group.isActiveOrOptional,
|
||||||
groupName,
|
title: Text(
|
||||||
style: TextStyle(
|
groupName,
|
||||||
color: theme.colorScheme.onPrimaryContainer,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
subtitle: group.description.mapNotNull(
|
|
||||||
(description) => Text(
|
|
||||||
description,
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: theme.colorScheme.onPrimaryContainer,
|
color: theme.colorScheme.onPrimaryContainer,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
subtitle: group.description.mapNotNull(
|
||||||
onChanged: (value) async {
|
(description) => Text(
|
||||||
final notifier = ref.read(
|
description,
|
||||||
preferenceSettingsGroupRepositoryProvider(
|
style: TextStyle(
|
||||||
PreferencePartition.user,
|
color: theme.colorScheme.onPrimaryContainer,
|
||||||
groupName,
|
),
|
||||||
).notifier,
|
),
|
||||||
);
|
),
|
||||||
|
onChanged: (value) async {
|
||||||
|
final notifier = ref.read(
|
||||||
|
preferenceSettingsGroupRepositoryProvider(
|
||||||
|
PreferencePartition.user,
|
||||||
|
groupName,
|
||||||
|
).notifier,
|
||||||
|
);
|
||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
await notifier.apply();
|
await notifier.apply();
|
||||||
} else {
|
} else {
|
||||||
await notifier.reset();
|
await notifier.reset();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView(
|
child: ListView(
|
||||||
children: group.settings.entries.map((setting) {
|
children: group.settings.entries.map((setting) {
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ const _fallbackAutocompleteProvider = SearchSuggestionProviders.none;
|
|||||||
|
|
||||||
enum TabBarSwipeAction { switchLastOpened, navigateOrderedTabs }
|
enum TabBarSwipeAction { switchLastOpened, navigateOrderedTabs }
|
||||||
|
|
||||||
|
enum TabIntentOpenSetting { regular, private, ask }
|
||||||
|
|
||||||
enum DeleteBrowsingDataType {
|
enum DeleteBrowsingDataType {
|
||||||
tabs('Open tabs'),
|
tabs('Open tabs'),
|
||||||
history('Browsing history'),
|
history('Browsing history'),
|
||||||
@@ -58,7 +60,7 @@ class GeneralSettings with FastEquatable {
|
|||||||
final bool showExtensionShortcut;
|
final bool showExtensionShortcut;
|
||||||
final bool enableLocalAiFeatures;
|
final bool enableLocalAiFeatures;
|
||||||
final TabType defaultCreateTabType;
|
final TabType defaultCreateTabType;
|
||||||
final TabType defaultIntentTabType;
|
final TabIntentOpenSetting tabIntentOpenSetting;
|
||||||
final bool autoHideTabBar;
|
final bool autoHideTabBar;
|
||||||
final TabBarSwipeAction tabBarSwipeAction;
|
final TabBarSwipeAction tabBarSwipeAction;
|
||||||
|
|
||||||
@@ -73,7 +75,7 @@ class GeneralSettings with FastEquatable {
|
|||||||
required this.showExtensionShortcut,
|
required this.showExtensionShortcut,
|
||||||
required this.enableLocalAiFeatures,
|
required this.enableLocalAiFeatures,
|
||||||
required this.defaultCreateTabType,
|
required this.defaultCreateTabType,
|
||||||
required this.defaultIntentTabType,
|
required this.tabIntentOpenSetting,
|
||||||
required this.autoHideTabBar,
|
required this.autoHideTabBar,
|
||||||
required this.tabBarSwipeAction,
|
required this.tabBarSwipeAction,
|
||||||
});
|
});
|
||||||
@@ -89,7 +91,7 @@ class GeneralSettings with FastEquatable {
|
|||||||
bool? showExtensionShortcut,
|
bool? showExtensionShortcut,
|
||||||
bool? enableLocalAiFeatures,
|
bool? enableLocalAiFeatures,
|
||||||
TabType? defaultCreateTabType,
|
TabType? defaultCreateTabType,
|
||||||
TabType? defaultIntentTabType,
|
TabIntentOpenSetting? tabIntentOpenSetting,
|
||||||
bool? autoHideTabBar,
|
bool? autoHideTabBar,
|
||||||
TabBarSwipeAction? tabBarSwipeAction,
|
TabBarSwipeAction? tabBarSwipeAction,
|
||||||
}) : themeMode = themeMode ?? ThemeMode.dark,
|
}) : themeMode = themeMode ?? ThemeMode.dark,
|
||||||
@@ -102,7 +104,7 @@ class GeneralSettings with FastEquatable {
|
|||||||
showExtensionShortcut = showExtensionShortcut ?? false,
|
showExtensionShortcut = showExtensionShortcut ?? false,
|
||||||
enableLocalAiFeatures = enableLocalAiFeatures ?? true,
|
enableLocalAiFeatures = enableLocalAiFeatures ?? true,
|
||||||
defaultCreateTabType = defaultCreateTabType ?? TabType.regular,
|
defaultCreateTabType = defaultCreateTabType ?? TabType.regular,
|
||||||
defaultIntentTabType = defaultIntentTabType ?? TabType.regular,
|
tabIntentOpenSetting = tabIntentOpenSetting ?? TabIntentOpenSetting.ask,
|
||||||
autoHideTabBar = autoHideTabBar ?? true,
|
autoHideTabBar = autoHideTabBar ?? true,
|
||||||
tabBarSwipeAction =
|
tabBarSwipeAction =
|
||||||
tabBarSwipeAction ?? TabBarSwipeAction.switchLastOpened;
|
tabBarSwipeAction ?? TabBarSwipeAction.switchLastOpened;
|
||||||
@@ -124,7 +126,7 @@ class GeneralSettings with FastEquatable {
|
|||||||
showExtensionShortcut,
|
showExtensionShortcut,
|
||||||
enableLocalAiFeatures,
|
enableLocalAiFeatures,
|
||||||
defaultCreateTabType,
|
defaultCreateTabType,
|
||||||
defaultIntentTabType,
|
tabIntentOpenSetting,
|
||||||
autoHideTabBar,
|
autoHideTabBar,
|
||||||
tabBarSwipeAction,
|
tabBarSwipeAction,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -31,7 +31,9 @@ abstract class _$GeneralSettingsCWProxy {
|
|||||||
|
|
||||||
GeneralSettings defaultCreateTabType(TabType defaultCreateTabType);
|
GeneralSettings defaultCreateTabType(TabType defaultCreateTabType);
|
||||||
|
|
||||||
GeneralSettings defaultIntentTabType(TabType defaultIntentTabType);
|
GeneralSettings tabIntentOpenSetting(
|
||||||
|
TabIntentOpenSetting tabIntentOpenSetting,
|
||||||
|
);
|
||||||
|
|
||||||
GeneralSettings autoHideTabBar(bool autoHideTabBar);
|
GeneralSettings autoHideTabBar(bool autoHideTabBar);
|
||||||
|
|
||||||
@@ -54,7 +56,7 @@ abstract class _$GeneralSettingsCWProxy {
|
|||||||
bool showExtensionShortcut,
|
bool showExtensionShortcut,
|
||||||
bool enableLocalAiFeatures,
|
bool enableLocalAiFeatures,
|
||||||
TabType defaultCreateTabType,
|
TabType defaultCreateTabType,
|
||||||
TabType defaultIntentTabType,
|
TabIntentOpenSetting tabIntentOpenSetting,
|
||||||
bool autoHideTabBar,
|
bool autoHideTabBar,
|
||||||
TabBarSwipeAction tabBarSwipeAction,
|
TabBarSwipeAction tabBarSwipeAction,
|
||||||
});
|
});
|
||||||
@@ -108,8 +110,9 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
|
|||||||
this(defaultCreateTabType: defaultCreateTabType);
|
this(defaultCreateTabType: defaultCreateTabType);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
GeneralSettings defaultIntentTabType(TabType defaultIntentTabType) =>
|
GeneralSettings tabIntentOpenSetting(
|
||||||
this(defaultIntentTabType: defaultIntentTabType);
|
TabIntentOpenSetting tabIntentOpenSetting,
|
||||||
|
) => this(tabIntentOpenSetting: tabIntentOpenSetting);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
GeneralSettings autoHideTabBar(bool autoHideTabBar) =>
|
GeneralSettings autoHideTabBar(bool autoHideTabBar) =>
|
||||||
@@ -137,7 +140,7 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
|
|||||||
Object? showExtensionShortcut = const $CopyWithPlaceholder(),
|
Object? showExtensionShortcut = const $CopyWithPlaceholder(),
|
||||||
Object? enableLocalAiFeatures = const $CopyWithPlaceholder(),
|
Object? enableLocalAiFeatures = const $CopyWithPlaceholder(),
|
||||||
Object? defaultCreateTabType = const $CopyWithPlaceholder(),
|
Object? defaultCreateTabType = const $CopyWithPlaceholder(),
|
||||||
Object? defaultIntentTabType = const $CopyWithPlaceholder(),
|
Object? tabIntentOpenSetting = const $CopyWithPlaceholder(),
|
||||||
Object? autoHideTabBar = const $CopyWithPlaceholder(),
|
Object? autoHideTabBar = const $CopyWithPlaceholder(),
|
||||||
Object? tabBarSwipeAction = const $CopyWithPlaceholder(),
|
Object? tabBarSwipeAction = const $CopyWithPlaceholder(),
|
||||||
}) {
|
}) {
|
||||||
@@ -188,10 +191,10 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
|
|||||||
? _value.defaultCreateTabType
|
? _value.defaultCreateTabType
|
||||||
// ignore: cast_nullable_to_non_nullable
|
// ignore: cast_nullable_to_non_nullable
|
||||||
: defaultCreateTabType as TabType,
|
: defaultCreateTabType as TabType,
|
||||||
defaultIntentTabType: defaultIntentTabType == const $CopyWithPlaceholder()
|
tabIntentOpenSetting: tabIntentOpenSetting == const $CopyWithPlaceholder()
|
||||||
? _value.defaultIntentTabType
|
? _value.tabIntentOpenSetting
|
||||||
// ignore: cast_nullable_to_non_nullable
|
// ignore: cast_nullable_to_non_nullable
|
||||||
: defaultIntentTabType as TabType,
|
: tabIntentOpenSetting as TabIntentOpenSetting,
|
||||||
autoHideTabBar: autoHideTabBar == const $CopyWithPlaceholder()
|
autoHideTabBar: autoHideTabBar == const $CopyWithPlaceholder()
|
||||||
? _value.autoHideTabBar
|
? _value.autoHideTabBar
|
||||||
// ignore: cast_nullable_to_non_nullable
|
// ignore: cast_nullable_to_non_nullable
|
||||||
@@ -235,9 +238,9 @@ GeneralSettings _$GeneralSettingsFromJson(Map<String, dynamic> json) =>
|
|||||||
_$TabTypeEnumMap,
|
_$TabTypeEnumMap,
|
||||||
json['defaultCreateTabType'],
|
json['defaultCreateTabType'],
|
||||||
),
|
),
|
||||||
defaultIntentTabType: $enumDecodeNullable(
|
tabIntentOpenSetting: $enumDecodeNullable(
|
||||||
_$TabTypeEnumMap,
|
_$TabIntentOpenSettingEnumMap,
|
||||||
json['defaultIntentTabType'],
|
json['tabIntentOpenSetting'],
|
||||||
),
|
),
|
||||||
autoHideTabBar: json['autoHideTabBar'] as bool?,
|
autoHideTabBar: json['autoHideTabBar'] as bool?,
|
||||||
tabBarSwipeAction: $enumDecodeNullable(
|
tabBarSwipeAction: $enumDecodeNullable(
|
||||||
@@ -297,6 +300,12 @@ const _$TabTypeEnumMap = {
|
|||||||
TabType.child: 'child',
|
TabType.child: 'child',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const _$TabIntentOpenSettingEnumMap = {
|
||||||
|
TabIntentOpenSetting.regular: 'regular',
|
||||||
|
TabIntentOpenSetting.private: 'private',
|
||||||
|
TabIntentOpenSetting.ask: 'ask',
|
||||||
|
};
|
||||||
|
|
||||||
const _$TabBarSwipeActionEnumMap = {
|
const _$TabBarSwipeActionEnumMap = {
|
||||||
TabBarSwipeAction.switchLastOpened: 'switchLastOpened',
|
TabBarSwipeAction.switchLastOpened: 'switchLastOpened',
|
||||||
TabBarSwipeAction.navigateOrderedTabs: 'navigateOrderedTabs',
|
TabBarSwipeAction.navigateOrderedTabs: 'navigateOrderedTabs',
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ class GeneralSettingsRepository extends _$GeneralSettingsRepository {
|
|||||||
DriftSqlType.string,
|
DriftSqlType.string,
|
||||||
db.typeMapping,
|
db.typeMapping,
|
||||||
),
|
),
|
||||||
'defaultIntentTabType': settings['defaultIntentTabType']?.readAs(
|
'tabIntentOpenSetting': settings['tabIntentOpenSetting']?.readAs(
|
||||||
DriftSqlType.string,
|
DriftSqlType.string,
|
||||||
db.typeMapping,
|
db.typeMapping,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -53,7 +53,11 @@ class AddFeedDialog extends HookConsumerWidget {
|
|||||||
controller: textController,
|
controller: textController,
|
||||||
keyboardType: TextInputType.url,
|
keyboardType: TextInputType.url,
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
return validateUrl(value, onlyHttpProtocol: true);
|
return validateUrl(
|
||||||
|
value,
|
||||||
|
onlyHttpProtocol: true,
|
||||||
|
eagerParsing: false,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:drift/drift.dart' show Value;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||||
@@ -205,6 +206,7 @@ class FeedArticleScreen extends HookConsumerWidget {
|
|||||||
.addTab(
|
.addTab(
|
||||||
url: articleLink.uri,
|
url: articleLink.uri,
|
||||||
private: isPrivate,
|
private: isPrivate,
|
||||||
|
container: const Value(null),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
|
|||||||
@@ -233,6 +233,7 @@ class _FeedEditContent extends HookConsumerWidget {
|
|||||||
value,
|
value,
|
||||||
onlyHttpProtocol: true,
|
onlyHttpProtocol: true,
|
||||||
required: false,
|
required: false,
|
||||||
|
eagerParsing: false,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -249,6 +250,7 @@ class _FeedEditContent extends HookConsumerWidget {
|
|||||||
value,
|
value,
|
||||||
onlyHttpProtocol: true,
|
onlyHttpProtocol: true,
|
||||||
required: false,
|
required: false,
|
||||||
|
eagerParsing: false,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -269,7 +271,11 @@ class _FeedEditContent extends HookConsumerWidget {
|
|||||||
controller: urlTextController,
|
controller: urlTextController,
|
||||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
return validateUrl(value, onlyHttpProtocol: true);
|
return validateUrl(
|
||||||
|
value,
|
||||||
|
onlyHttpProtocol: true,
|
||||||
|
eagerParsing: false,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -19,8 +19,18 @@
|
|||||||
*/
|
*/
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:weblibre/core/logger.dart';
|
||||||
import 'package:weblibre/core/providers/router.dart';
|
import 'package:weblibre/core/providers/router.dart';
|
||||||
|
import 'package:weblibre/core/routing/routes.dart';
|
||||||
import 'package:weblibre/domain/services/app_initialization.dart';
|
import 'package:weblibre/domain/services/app_initialization.dart';
|
||||||
|
import 'package:weblibre/features/bangs/domain/providers/bangs.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/features/browser/domain/providers.dart';
|
||||||
|
import 'package:weblibre/features/geckoview/features/browser/domain/providers/intent.dart';
|
||||||
|
import 'package:weblibre/features/share_intent/domain/entities/shared_content.dart';
|
||||||
|
import 'package:weblibre/features/user/data/models/general_settings.dart';
|
||||||
|
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
|
||||||
import 'package:weblibre/presentation/widgets/failure_widget.dart';
|
import 'package:weblibre/presentation/widgets/failure_widget.dart';
|
||||||
|
|
||||||
class MainApp extends HookConsumerWidget {
|
class MainApp extends HookConsumerWidget {
|
||||||
@@ -40,6 +50,71 @@ class MainApp extends HookConsumerWidget {
|
|||||||
final initializationResult = ref.watch(appInitializationServiceProvider);
|
final initializationResult = ref.watch(appInitializationServiceProvider);
|
||||||
final router = ref.watch(routerProvider);
|
final router = ref.watch(routerProvider);
|
||||||
|
|
||||||
|
ref.listen(
|
||||||
|
engineBoundIntentStreamProvider,
|
||||||
|
(previous, next) {
|
||||||
|
next.whenData((sharedContent) async {
|
||||||
|
final router = await ref.read(routerProvider.future);
|
||||||
|
final settings = ref.read(generalSettingsWithDefaultsProvider);
|
||||||
|
|
||||||
|
switch (settings.tabIntentOpenSetting) {
|
||||||
|
case TabIntentOpenSetting.regular:
|
||||||
|
case TabIntentOpenSetting.private:
|
||||||
|
switch (sharedContent) {
|
||||||
|
case SharedUrl():
|
||||||
|
await ref
|
||||||
|
.read(tabRepositoryProvider.notifier)
|
||||||
|
.addTab(
|
||||||
|
url: sharedContent.url,
|
||||||
|
private:
|
||||||
|
settings.tabIntentOpenSetting ==
|
||||||
|
TabIntentOpenSetting.private,
|
||||||
|
launchedFromIntent: true,
|
||||||
|
);
|
||||||
|
case SharedText():
|
||||||
|
final defaultSearchBang =
|
||||||
|
ref.read(selectedBangDataProvider()) ??
|
||||||
|
await ref.read(defaultSearchBangDataProvider.future);
|
||||||
|
|
||||||
|
await ref
|
||||||
|
.read(tabRepositoryProvider.notifier)
|
||||||
|
.addTab(
|
||||||
|
url: defaultSearchBang?.getTemplateUrl(
|
||||||
|
sharedContent.text,
|
||||||
|
),
|
||||||
|
private:
|
||||||
|
settings.tabIntentOpenSetting ==
|
||||||
|
TabIntentOpenSetting.private,
|
||||||
|
launchedFromIntent: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case TabIntentOpenSetting.ask:
|
||||||
|
switch (sharedContent) {
|
||||||
|
case SharedUrl():
|
||||||
|
final route = OpenSharedContentRoute(sharedContent.url);
|
||||||
|
await router.push(route.location, extra: route.$extra);
|
||||||
|
case SharedText():
|
||||||
|
final route = SearchRoute(
|
||||||
|
tabType:
|
||||||
|
ref.read(selectedTabTypeProvider) ??
|
||||||
|
settings.defaultCreateTabType,
|
||||||
|
searchText: sharedContent.text,
|
||||||
|
$extra: true, //launched from intent
|
||||||
|
);
|
||||||
|
await router.push(route.location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: (error, stackTrace) {
|
||||||
|
logger.e(
|
||||||
|
'Error listening to engineBoundIntentStreamProvider',
|
||||||
|
error: error,
|
||||||
|
stackTrace: stackTrace,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
return initializationResult.fold(
|
return initializationResult.fold(
|
||||||
(initializationState) {
|
(initializationState) {
|
||||||
if (!initializationState.initialized) {
|
if (!initializationState.initialized) {
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ class AutoSuggestTextField extends HookWidget {
|
|||||||
final bool? enableIMEPersonalizedLearning;
|
final bool? enableIMEPersonalizedLearning;
|
||||||
final TapRegionCallback? onTapOutside;
|
final TapRegionCallback? onTapOutside;
|
||||||
final VoidCallback? onTap;
|
final VoidCallback? onTap;
|
||||||
|
final bool autocorrect;
|
||||||
|
|
||||||
const AutoSuggestTextField({
|
const AutoSuggestTextField({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -77,6 +78,7 @@ class AutoSuggestTextField extends HookWidget {
|
|||||||
this.enableIMEPersonalizedLearning = true,
|
this.enableIMEPersonalizedLearning = true,
|
||||||
this.onTapOutside,
|
this.onTapOutside,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
|
this.autocorrect = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
bool _suggestionHasMatch() =>
|
bool _suggestionHasMatch() =>
|
||||||
@@ -216,6 +218,7 @@ class AutoSuggestTextField extends HookWidget {
|
|||||||
onChanged: onChanged,
|
onChanged: onChanged,
|
||||||
onEditingComplete: onEditingComplete,
|
onEditingComplete: onEditingComplete,
|
||||||
validator: validator,
|
validator: validator,
|
||||||
|
autocorrect: autocorrect,
|
||||||
onFieldSubmitted: onSubmitted.mapNotNull(
|
onFieldSubmitted: onSubmitted.mapNotNull(
|
||||||
(onSubmitted) => (value) {
|
(onSubmitted) => (value) {
|
||||||
if (_suggestionHasMatch()) {
|
if (_suggestionHasMatch()) {
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ import 'package:weblibre/utils/uri_parser.dart' as uri_parser;
|
|||||||
|
|
||||||
String? validateUrl(
|
String? validateUrl(
|
||||||
String? value, {
|
String? value, {
|
||||||
|
required bool eagerParsing,
|
||||||
bool requireAuthority = true,
|
bool requireAuthority = true,
|
||||||
bool eagerParsing = true,
|
|
||||||
bool onlyHttpProtocol = false,
|
bool onlyHttpProtocol = false,
|
||||||
bool required = true,
|
bool required = true,
|
||||||
}) {
|
}) {
|
||||||
|
|||||||
@@ -116,6 +116,8 @@ void showTabSwitchMessage(
|
|||||||
String? tabName,
|
String? tabName,
|
||||||
void Function()? onSwitch,
|
void Function()? onSwitch,
|
||||||
}) {
|
}) {
|
||||||
|
ScaffoldMessenger.of(context).clearSnackBars();
|
||||||
|
|
||||||
final message = switch (tabName.whenNotEmpty) {
|
final message = switch (tabName.whenNotEmpty) {
|
||||||
String() => "New tab '$tabName' opened",
|
String() => "New tab '$tabName' opened",
|
||||||
null => 'New tab opened',
|
null => 'New tab opened',
|
||||||
@@ -155,6 +157,8 @@ void showTabUndoClose(
|
|||||||
int count = 1,
|
int count = 1,
|
||||||
Duration duration = const Duration(seconds: 2),
|
Duration duration = const Duration(seconds: 2),
|
||||||
}) {
|
}) {
|
||||||
|
ScaffoldMessenger.of(context).clearSnackBars();
|
||||||
|
|
||||||
final snackBar = SnackBar(
|
final snackBar = SnackBar(
|
||||||
content: (count > 1)
|
content: (count > 1)
|
||||||
? Text('$count Tabs closed')
|
? Text('$count Tabs closed')
|
||||||
|
|||||||
@@ -28,10 +28,21 @@ Uri? tryParseUrl(String? input, {bool eagerParsing = false}) {
|
|||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
if (uri.authority.isEmpty && eagerParsing) {
|
if (uri.authority.isEmpty && eagerParsing) {
|
||||||
if (uri.pathSegments.isNotEmpty) {
|
if (uri.pathSegments.isNotEmpty) {
|
||||||
if (_domainRegex.hasMatch(uri.pathSegments.first)) {
|
int? port;
|
||||||
uri = Uri.tryParse('https://$input');
|
var firstSegment = uri.pathSegments.first;
|
||||||
} else if (InternetAddress.tryParse(uri.pathSegments.first) != null) {
|
|
||||||
uri = Uri.tryParse('https://$input');
|
//When there is no scheme while aprsing, the port becomse the first segment because : is treated as delimeter
|
||||||
|
if (int.tryParse(firstSegment) case final int segmentPort) {
|
||||||
|
port = segmentPort;
|
||||||
|
firstSegment = uri.scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_domainRegex.hasMatch(firstSegment)) {
|
||||||
|
uri = Uri.tryParse('https://$input')?.replace(port: port);
|
||||||
|
} else if (firstSegment == 'localhost') {
|
||||||
|
uri = Uri.tryParse('https://$input')?.replace(port: port);
|
||||||
|
} else if (InternetAddress.tryParse(firstSegment) != null) {
|
||||||
|
uri = Uri.tryParse('https://$input')?.replace(port: port);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-9
@@ -2,7 +2,7 @@ name: weblibre
|
|||||||
description: "The Privacy-Focused & AI-Powered Research Browser"
|
description: "The Privacy-Focused & AI-Powered Research Browser"
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
resolution: workspace
|
resolution: workspace
|
||||||
version: 0.9.22-alpha-2+25
|
version: 0.9.23-alpha-1+26
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.8.0 <4.0.0'
|
sdk: '>=3.8.0 <4.0.0'
|
||||||
@@ -28,9 +28,9 @@ dependencies:
|
|||||||
flutter_reorderable_grid_view: ^5.5.1
|
flutter_reorderable_grid_view: ^5.5.1
|
||||||
flutter_secure_storage: ^10.0.0-beta.4
|
flutter_secure_storage: ^10.0.0-beta.4
|
||||||
flutter_slidable: ^4.0.1
|
flutter_slidable: ^4.0.1
|
||||||
flutter_svg: ^2.2.0
|
flutter_svg: ^2.2.1
|
||||||
go_router: ^16.2.0
|
go_router: ^16.2.1
|
||||||
google_fonts: ^6.3.0
|
google_fonts: ^6.3.1
|
||||||
graphview: ^1.2.0
|
graphview: ^1.2.0
|
||||||
home_widget: ^0.8.0
|
home_widget: ^0.8.0
|
||||||
hooks_riverpod: ^2.6.1
|
hooks_riverpod: ^2.6.1
|
||||||
@@ -64,17 +64,14 @@ dependencies:
|
|||||||
skeletonizer: ^2.1.0+1
|
skeletonizer: ^2.1.0+1
|
||||||
sliver_tools: ^0.2.12
|
sliver_tools: ^0.2.12
|
||||||
smooth_page_indicator: ^1.2.1
|
smooth_page_indicator: ^1.2.1
|
||||||
socks5_proxy:
|
socks5_proxy: ^2.1.1
|
||||||
git:
|
|
||||||
url: https://github.com/sylvieon/socks_dart.git
|
|
||||||
ref: 04550bc08f42cc1a35f1e870f651b93a4f9aceab
|
|
||||||
speech_to_text_google_dialog:
|
speech_to_text_google_dialog:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/FaFre/speech_to_text_google_dialog.git
|
url: https://github.com/FaFre/speech_to_text_google_dialog.git
|
||||||
sqlite3: ^2.9.0
|
sqlite3: ^2.9.0
|
||||||
sqlite3_flutter_libs: ^0.5.39
|
sqlite3_flutter_libs: ^0.5.39
|
||||||
synchronized: ^3.4.0
|
synchronized: ^3.4.0
|
||||||
text_scroll: ^0.2.0
|
text_scroll: ^0.2.1
|
||||||
timeago: ^3.7.1
|
timeago: ^3.7.1
|
||||||
tor:
|
tor:
|
||||||
path: /home/fafre/development/repos/tor
|
path: /home/fafre/development/repos/tor
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
GeckoView 142.0
|
||||||
|
|
||||||
|
* Added setting to use third party CA certificates
|
||||||
|
* Added setting to control tab bar swipe behavior
|
||||||
|
* Downgraded AGP for F-Droid compatibility
|
||||||
@@ -3,7 +3,7 @@ version = "1.0-SNAPSHOT"
|
|||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = "2.1.20"
|
ext.kotlin_version = "2.1.20"
|
||||||
ext.mozillaComponentsVersion = '142.0'
|
ext.mozillaComponentsVersion = '142.0.1'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
|
|||||||
+2
@@ -16,6 +16,7 @@ const SMART_TAB_GROUPING_CONFIG = {
|
|||||||
taskName: ML_TASK_FEATURE_EXTRACTION,
|
taskName: ML_TASK_FEATURE_EXTRACTION,
|
||||||
featureId: "smart-tab-embedding",
|
featureId: "smart-tab-embedding",
|
||||||
backend: "onnx",
|
backend: "onnx",
|
||||||
|
fallbackBackend: "onnx",
|
||||||
},
|
},
|
||||||
topicGeneration: {
|
topicGeneration: {
|
||||||
dtype: "q8",
|
dtype: "q8",
|
||||||
@@ -23,6 +24,7 @@ const SMART_TAB_GROUPING_CONFIG = {
|
|||||||
taskName: ML_TASK_TEXT2TEXT,
|
taskName: ML_TASK_TEXT2TEXT,
|
||||||
featureId: "smart-tab-topic",
|
featureId: "smart-tab-topic",
|
||||||
backend: "onnx",
|
backend: "onnx",
|
||||||
|
fallbackBackend: "onnx",
|
||||||
},
|
},
|
||||||
// dataConfig: {
|
// dataConfig: {
|
||||||
// titleKey: "label",
|
// titleKey: "label",
|
||||||
|
|||||||
+41
@@ -16,6 +16,7 @@ import android.view.LayoutInflater
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
|
import androidx.activity.result.ActivityResult
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.annotation.CallSuper
|
import androidx.annotation.CallSuper
|
||||||
@@ -34,6 +35,7 @@ import mozilla.components.feature.downloads.temporary.ShareResourceFeature
|
|||||||
import mozilla.components.feature.media.fullscreen.MediaSessionFullscreenFeature
|
import mozilla.components.feature.media.fullscreen.MediaSessionFullscreenFeature
|
||||||
import mozilla.components.feature.privatemode.feature.SecureWindowFeature
|
import mozilla.components.feature.privatemode.feature.SecureWindowFeature
|
||||||
import mozilla.components.feature.prompts.PromptFeature
|
import mozilla.components.feature.prompts.PromptFeature
|
||||||
|
import mozilla.components.feature.prompts.file.AndroidPhotoPicker
|
||||||
import mozilla.components.feature.session.FullScreenFeature
|
import mozilla.components.feature.session.FullScreenFeature
|
||||||
import mozilla.components.feature.session.PictureInPictureFeature
|
import mozilla.components.feature.session.PictureInPictureFeature
|
||||||
import mozilla.components.feature.session.SessionFeature
|
import mozilla.components.feature.session.SessionFeature
|
||||||
@@ -43,6 +45,7 @@ import mozilla.components.feature.sitepermissions.SitePermissionsRules
|
|||||||
import mozilla.components.feature.sitepermissions.SitePermissionsRules.AutoplayAction
|
import mozilla.components.feature.sitepermissions.SitePermissionsRules.AutoplayAction
|
||||||
import mozilla.components.feature.webauthn.WebAuthnFeature
|
import mozilla.components.feature.webauthn.WebAuthnFeature
|
||||||
import mozilla.components.support.base.feature.ActivityResultHandler
|
import mozilla.components.support.base.feature.ActivityResultHandler
|
||||||
|
import mozilla.components.support.base.feature.PermissionsFeature
|
||||||
import mozilla.components.support.base.feature.UserInteractionHandler
|
import mozilla.components.support.base.feature.UserInteractionHandler
|
||||||
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
|
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
|
||||||
import mozilla.components.support.base.log.logger.Logger
|
import mozilla.components.support.base.log.logger.Logger
|
||||||
@@ -74,6 +77,20 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
|
|||||||
|
|
||||||
private var pictureInPictureFeature: PictureInPictureFeature? = null
|
private var pictureInPictureFeature: PictureInPictureFeature? = null
|
||||||
|
|
||||||
|
// Registers a photo picker activity launcher in single-select mode.
|
||||||
|
private val singleMediaPicker =
|
||||||
|
AndroidPhotoPicker.singleMediaPicker(
|
||||||
|
{ this },
|
||||||
|
{ promptFeature.get() },
|
||||||
|
)
|
||||||
|
|
||||||
|
// Registers a photo picker activity launcher in multi-select mode.
|
||||||
|
private val multipleMediaPicker =
|
||||||
|
AndroidPhotoPicker.multipleMediaPicker(
|
||||||
|
{ this },
|
||||||
|
{ promptFeature.get() },
|
||||||
|
)
|
||||||
|
|
||||||
private val sessionId: String?
|
private val sessionId: String?
|
||||||
get() = arguments?.getString(SESSION_ID_KEY)
|
get() = arguments?.getString(SESSION_ID_KEY)
|
||||||
|
|
||||||
@@ -224,6 +241,11 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
|
|||||||
onNeedToRequestPermissions = { permissions ->
|
onNeedToRequestPermissions = { permissions ->
|
||||||
requestPromptsPermissionsLauncher.launch(permissions)
|
requestPromptsPermissionsLauncher.launch(permissions)
|
||||||
},
|
},
|
||||||
|
androidPhotoPicker = AndroidPhotoPicker(
|
||||||
|
requireContext(),
|
||||||
|
singleMediaPicker,
|
||||||
|
multipleMediaPicker,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
owner = this,
|
owner = this,
|
||||||
view = view,
|
view = view,
|
||||||
@@ -392,6 +414,21 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("OVERRIDE_DEPRECATION")
|
||||||
|
final override fun onRequestPermissionsResult(
|
||||||
|
requestCode: Int,
|
||||||
|
permissions: Array<String>,
|
||||||
|
grantResults: IntArray,
|
||||||
|
) {
|
||||||
|
val feature: PermissionsFeature? = when (requestCode) {
|
||||||
|
REQUEST_CODE_DOWNLOAD_PERMISSIONS -> downloadsFeature.get()
|
||||||
|
REQUEST_CODE_PROMPT_PERMISSIONS -> promptFeature.get()
|
||||||
|
REQUEST_CODE_APP_PERMISSIONS -> sitePermissionsFeature.get()
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
feature?.onPermissionsResult(permissions, grantResults)
|
||||||
|
}
|
||||||
|
|
||||||
@CallSuper
|
@CallSuper
|
||||||
override fun onActivityResult(requestCode: Int, data: Intent?, resultCode: Int): Boolean {
|
override fun onActivityResult(requestCode: Int, data: Intent?, resultCode: Int): Boolean {
|
||||||
return activityResultHandler.any { it.onActivityResult(requestCode, data, resultCode) }
|
return activityResultHandler.any { it.onActivityResult(requestCode, data, resultCode) }
|
||||||
@@ -400,6 +437,10 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
|
|||||||
companion object {
|
companion object {
|
||||||
private const val SESSION_ID_KEY = "session_id"
|
private const val SESSION_ID_KEY = "session_id"
|
||||||
|
|
||||||
|
private const val REQUEST_CODE_DOWNLOAD_PERMISSIONS = 1
|
||||||
|
private const val REQUEST_CODE_PROMPT_PERMISSIONS = 2
|
||||||
|
private const val REQUEST_CODE_APP_PERMISSIONS = 3
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
protected fun Bundle.putSessionId(sessionId: String?) {
|
protected fun Bundle.putSessionId(sessionId: String?) {
|
||||||
putString(SESSION_ID_KEY, sessionId)
|
putString(SESSION_ID_KEY, sessionId)
|
||||||
|
|||||||
+7
@@ -10,6 +10,7 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.annotation.CallSuper
|
||||||
import eu.weblibre.flutter_mozilla_components.addons.WebExtensionActionPopupActivity
|
import eu.weblibre.flutter_mozilla_components.addons.WebExtensionActionPopupActivity
|
||||||
import eu.weblibre.flutter_mozilla_components.feature.ReadabilityExtractFeature
|
import eu.weblibre.flutter_mozilla_components.feature.ReadabilityExtractFeature
|
||||||
import eu.weblibre.flutter_mozilla_components.feature.WebExtensionToolbarFeature
|
import eu.weblibre.flutter_mozilla_components.feature.WebExtensionToolbarFeature
|
||||||
@@ -39,6 +40,12 @@ class BrowserFragment() : BaseBrowserFragment(), UserInteractionHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("Deprecated in Java")
|
||||||
|
@CallSuper
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
super<BaseBrowserFragment>.onActivityResult(requestCode, data, resultCode)
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("LongMethod")
|
@Suppress("LongMethod")
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|||||||
+1
-1
@@ -70,7 +70,7 @@ object EngineProvider {
|
|||||||
builder.aboutConfigEnabled(true)
|
builder.aboutConfigEnabled(true)
|
||||||
builder.extensionsProcessEnabled(true)
|
builder.extensionsProcessEnabled(true)
|
||||||
builder.extensionsWebAPIEnabled(true)
|
builder.extensionsWebAPIEnabled(true)
|
||||||
builder.debugLogging(components.logLevel == Log.Priority.DEBUG)
|
//builder.debugLogging(components.logLevel == Log.Priority.DEBUG)
|
||||||
builder.consoleOutput(components.logLevel == Log.Priority.DEBUG)
|
builder.consoleOutput(components.logLevel == Log.Priority.DEBUG)
|
||||||
builder.contentBlocking(contentBlocking.build())
|
builder.contentBlocking(contentBlocking.build())
|
||||||
|
|
||||||
|
|||||||
+6
-1
@@ -46,8 +46,11 @@ import mozilla.components.feature.addons.update.DefaultAddonUpdater
|
|||||||
import mozilla.components.feature.customtabs.store.CustomTabsServiceStore
|
import mozilla.components.feature.customtabs.store.CustomTabsServiceStore
|
||||||
import mozilla.components.feature.downloads.DownloadMiddleware
|
import mozilla.components.feature.downloads.DownloadMiddleware
|
||||||
import mozilla.components.feature.media.MediaSessionFeature
|
import mozilla.components.feature.media.MediaSessionFeature
|
||||||
|
import mozilla.components.feature.media.middleware.LastMediaAccessMiddleware
|
||||||
import mozilla.components.feature.media.middleware.RecordingDevicesMiddleware
|
import mozilla.components.feature.media.middleware.RecordingDevicesMiddleware
|
||||||
|
import mozilla.components.feature.prompts.PromptMiddleware
|
||||||
import mozilla.components.feature.prompts.file.FileUploadsDirCleaner
|
import mozilla.components.feature.prompts.file.FileUploadsDirCleaner
|
||||||
|
import mozilla.components.feature.prompts.file.FileUploadsDirCleanerMiddleware
|
||||||
import mozilla.components.feature.readerview.ReaderViewMiddleware
|
import mozilla.components.feature.readerview.ReaderViewMiddleware
|
||||||
import mozilla.components.feature.session.HistoryDelegate
|
import mozilla.components.feature.session.HistoryDelegate
|
||||||
import mozilla.components.feature.session.middleware.LastAccessMiddleware
|
import mozilla.components.feature.session.middleware.LastAccessMiddleware
|
||||||
@@ -166,9 +169,11 @@ class Core(private val context: Context,
|
|||||||
ReaderViewMiddleware(),
|
ReaderViewMiddleware(),
|
||||||
UndoMiddleware(),
|
UndoMiddleware(),
|
||||||
LastAccessMiddleware(),
|
LastAccessMiddleware(),
|
||||||
// PromptMiddleware(),
|
|
||||||
SessionPrioritizationMiddleware(),
|
SessionPrioritizationMiddleware(),
|
||||||
RecordingDevicesMiddleware(context, components.notificationsDelegate),
|
RecordingDevicesMiddleware(context, components.notificationsDelegate),
|
||||||
|
PromptMiddleware(),
|
||||||
|
FileUploadsDirCleanerMiddleware(fileUploadsDirCleaner),
|
||||||
|
LastMediaAccessMiddleware(),
|
||||||
) + EngineMiddleware.create(
|
) + EngineMiddleware.create(
|
||||||
engine,
|
engine,
|
||||||
// We are disabling automatic suspending of engine sessions under memory pressure.
|
// We are disabling automatic suspending of engine sessions under memory pressure.
|
||||||
|
|||||||
Reference in New Issue
Block a user