diff --git a/app/lib/features/onboarding/presentation/onboarding.dart b/app/lib/features/onboarding/presentation/onboarding.dart index 15a22ed8..c159aadc 100644 --- a/app/lib/features/onboarding/presentation/onboarding.dart +++ b/app/lib/features/onboarding/presentation/onboarding.dart @@ -7,6 +7,7 @@ import 'package:weblibre/core/providers/router.dart'; import 'package:weblibre/features/geckoview/domain/repositories/tab.dart'; import 'package:weblibre/features/onboarding/presentation/pages/abstract/i_form_page.dart'; import 'package:weblibre/features/onboarding/presentation/pages/default_search.dart'; +import 'package:weblibre/features/onboarding/presentation/pages/permissions.dart'; import 'package:weblibre/features/onboarding/presentation/pages/ublock_opt_in.dart'; import 'package:weblibre/features/onboarding/presentation/pages/welcome.dart'; import 'package:weblibre/features/user/domain/repositories/onboarding.dart'; @@ -32,6 +33,7 @@ class OnboardingScreen extends HookConsumerWidget { const WelcomePage(), const DefaultSearchPage(), UBlockOptInPage(formKey: GlobalKey()), + PermissionsPage(formKey: GlobalKey()), ]; }, [currentRevision, targetRevision]); diff --git a/app/lib/features/onboarding/presentation/pages/default_search.dart b/app/lib/features/onboarding/presentation/pages/default_search.dart index f84f49bc..69cdda7a 100644 --- a/app/lib/features/onboarding/presentation/pages/default_search.dart +++ b/app/lib/features/onboarding/presentation/pages/default_search.dart @@ -13,6 +13,8 @@ class DefaultSearchPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final theme = Theme.of(context); + final bangs = ref.watch(bangListProvider(triggers: defaultBangs)); final activeBang = ref.watch(defaultSearchBangDataProvider).valueOrNull; @@ -26,67 +28,77 @@ class DefaultSearchPage extends HookConsumerWidget { ); } - return bangs.when( - skipLoadingOnReload: true, - data: (availableBangs) { - return ListView( - children: [ - SizedBox( - height: 48, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text('Default Search:'), - if (activeBang != null) - FilterChip( - showCheckmark: false, - label: Text(activeBang.websiteName), - avatar: UrlIcon([ - activeBang.getTemplateUrl(''), - ], iconSize: 20), - selected: true, - onSelected: (value) {}, - ), - ], + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: bangs.when( + skipLoadingOnReload: true, + data: (availableBangs) { + return ListView( + children: [ + const SizedBox(height: 24), + Center( + child: Text( + 'Default Search Provider', + style: theme.textTheme.headlineMedium, + ), ), - ), - const Divider(), - Wrap( - spacing: 8.0, - children: [ - ...availableBangs.map( - (bang) => FilterChip( - showCheckmark: false, - label: Text(bang.websiteName), - avatar: UrlIcon([bang.getTemplateUrl('')], iconSize: 20), - selected: activeBang?.trigger == bang.trigger, - onSelected: (selected) async { - if (selected) { - await updateSearchProvider(bang.trigger); + const SizedBox(height: 24), + SizedBox( + height: 48, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text('Selected Provider'), + if (activeBang != null) + FilterChip( + showCheckmark: false, + label: Text(activeBang.websiteName), + avatar: UrlIcon([ + activeBang.getTemplateUrl(''), + ], iconSize: 20), + selected: true, + onSelected: (value) {}, + ), + ], + ), + ), + const Divider(), + Wrap( + spacing: 8.0, + children: [ + ...availableBangs.map( + (bang) => FilterChip( + showCheckmark: false, + label: Text(bang.websiteName), + avatar: UrlIcon([bang.getTemplateUrl('')], iconSize: 20), + selected: activeBang?.trigger == bang.trigger, + onSelected: (selected) async { + if (selected) { + await updateSearchProvider(bang.trigger); + } + }, + ), + ), + ActionChip( + label: const Text('Search more'), + avatar: const Icon(Icons.search), + onPressed: () async { + final trigger = await const BangSearchRoute() + .push(context); + + if (trigger != null) { + await updateSearchProvider(trigger); } }, ), - ), - ActionChip( - label: const Text('Search more'), - avatar: const Icon(Icons.search), - onPressed: () async { - final trigger = await const BangSearchRoute().push( - context, - ); - - if (trigger != null) { - await updateSearchProvider(trigger); - } - }, - ), - ], - ), - ], - ); - }, - error: (error, stackTrace) => const SizedBox.shrink(), - loading: () => const SizedBox(height: 48, width: double.infinity), + ], + ), + ], + ); + }, + error: (error, stackTrace) => const SizedBox.shrink(), + loading: () => const SizedBox(height: 48, width: double.infinity), + ), ); } } diff --git a/app/lib/features/onboarding/presentation/pages/permissions.dart b/app/lib/features/onboarding/presentation/pages/permissions.dart new file mode 100644 index 00000000..68c81205 --- /dev/null +++ b/app/lib/features/onboarding/presentation/pages/permissions.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:skeletonizer/skeletonizer.dart'; +import 'package:weblibre/features/onboarding/presentation/pages/abstract/i_form_page.dart'; +import 'package:weblibre/presentation/hooks/cached_future.dart'; + +class PermissionsPage extends HookConsumerWidget implements IFormPage { + @override + final GlobalKey formKey; + + const PermissionsPage({super.key, required this.formKey}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = Theme.of(context); + + final notificationPermissionEnabled = useCachedFuture( + () => Permission.notification.isGranted, + ); + + return Form( + key: formKey, + child: ListView( + children: [ + const SizedBox(height: 24), + Center( + child: Text('Permissions', style: theme.textTheme.headlineMedium), + ), + const SizedBox(height: 24), + // Padding( + // padding: const EdgeInsets.symmetric(horizontal: 16.0), + // child: const Text('WebLibre requires following permissions'), + // ), + // const SizedBox(height: 24), + Skeletonizer( + enabled: !notificationPermissionEnabled.hasData, + child: FormField( + initialValue: true, + onSaved: (newValue) async { + if (newValue == true) { + await Permission.notification.request(); + } + }, + builder: (field) => SwitchListTile( + value: field.value ?? true, + title: const Text('Notifications'), + subtitle: const Text( + 'Required to inform about download status', + ), + onChanged: (notificationPermissionEnabled.data == true) + ? null + : (value) { + field.didChange(value); + }, + ), + ), + ), + ], + ), + ); + } +} diff --git a/app/lib/features/onboarding/presentation/pages/ublock_opt_in.dart b/app/lib/features/onboarding/presentation/pages/ublock_opt_in.dart index c4fd8448..676a167d 100644 --- a/app/lib/features/onboarding/presentation/pages/ublock_opt_in.dart +++ b/app/lib/features/onboarding/presentation/pages/ublock_opt_in.dart @@ -21,10 +21,11 @@ class UBlockOptInPage extends HookConsumerWidget implements IFormPage { key: formKey, child: ListView( children: [ + const SizedBox(height: 24), SvgPicture.asset('assets/images/ublock.svg'), const SizedBox(height: 8), Center( - child: Text('uBlock Origin', style: theme.textTheme.headlineLarge), + child: Text('uBlock Origin', style: theme.textTheme.headlineMedium), ), const SizedBox(height: 24), const Padding( diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 75257d65..dcd04142 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -48,6 +48,7 @@ dependencies: package_info_plus: ^8.3.0 path: ^1.9.1 path_provider: ^2.1.5 + permission_handler: ^12.0.0+1 pretty_qr_code: ^3.4.0 riverpod: ^2.6.1 riverpod_annotation: ^2.6.1