settings for readability & default search
This commit is contained in:
@@ -3,6 +3,7 @@ import 'package:lensai/features/bangs/data/models/bang_group.dart';
|
||||
import 'package:lensai/features/bangs/data/models/search_history_entry.dart';
|
||||
import 'package:lensai/features/bangs/domain/repositories/data.dart';
|
||||
import 'package:lensai/features/bangs/domain/repositories/sync.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/general_settings.dart';
|
||||
import 'package:riverpod/riverpod.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
@@ -10,9 +11,14 @@ part 'bangs.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
Stream<BangData?> defaultSearchBangData(Ref ref) {
|
||||
final trigger = ref.watch(
|
||||
generalSettingsRepositoryProvider.select(
|
||||
(value) => value.defaultSearchProvider,
|
||||
),
|
||||
);
|
||||
|
||||
final repository = ref.watch(bangDataRepositoryProvider.notifier);
|
||||
//TODO: Setting
|
||||
return repository.watchBang('lai');
|
||||
return repository.watchBang(trigger);
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'bangs.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$defaultSearchBangDataHash() =>
|
||||
r'c93026fe7b1650fe8a39bb2a8850af3ee416c306';
|
||||
r'acce519559903505f9623f3631b6f4177a396d39';
|
||||
|
||||
/// See also [defaultSearchBangData].
|
||||
@ProviderFor(defaultSearchBangData)
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/core/routing/routes.dart';
|
||||
import 'package:lensai/features/bangs/domain/providers/bangs.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/domain/providers.dart';
|
||||
import 'package:lensai/presentation/widgets/failure_widget.dart';
|
||||
|
||||
class BangCategoriesScreen extends HookConsumerWidget {
|
||||
@@ -18,7 +19,15 @@ class BangCategoriesScreen extends HookConsumerWidget {
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
await const BangSearchRoute().push(context);
|
||||
final trigger = await const BangSearchRoute().push<String?>(
|
||||
context,
|
||||
);
|
||||
|
||||
if (trigger != null) {
|
||||
ref
|
||||
.read(selectedBangTriggerProvider().notifier)
|
||||
.setTrigger(trigger);
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.search),
|
||||
),
|
||||
|
||||
@@ -7,7 +7,6 @@ import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/features/bangs/domain/providers/search.dart';
|
||||
import 'package:lensai/features/bangs/presentation/widgets/bang_details.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/domain/providers.dart';
|
||||
import 'package:lensai/features/user/domain/providers.dart';
|
||||
import 'package:lensai/presentation/hooks/listenable_callback.dart';
|
||||
import 'package:lensai/presentation/widgets/failure_widget.dart';
|
||||
@@ -70,11 +69,7 @@ class BangSearchScreen extends HookConsumerWidget {
|
||||
return BangDetails(
|
||||
bang,
|
||||
onTap: () {
|
||||
ref
|
||||
.read(selectedBangTriggerProvider().notifier)
|
||||
.setTrigger(bang.trigger);
|
||||
|
||||
context.pop();
|
||||
context.pop(bang.trigger);
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
+23
-19
@@ -20,6 +20,12 @@ class ReaderButton extends HookConsumerWidget {
|
||||
),
|
||||
);
|
||||
|
||||
final enforceReadability = ref.watch(
|
||||
generalSettingsRepositoryProvider.select(
|
||||
(value) => value.enforceReadability,
|
||||
),
|
||||
);
|
||||
|
||||
final readerabilityState = ref.watch(
|
||||
selectedTabStateProvider.select(
|
||||
(state) => state?.readerableState ?? ReaderableState.$default(),
|
||||
@@ -39,29 +45,27 @@ class ReaderButton extends HookConsumerWidget {
|
||||
|
||||
return Visibility(
|
||||
visible:
|
||||
readerabilityState.readerable &&
|
||||
(enableReadability || readerabilityState.active),
|
||||
(readerabilityState.readerable &&
|
||||
(enableReadability || readerabilityState.active)) ||
|
||||
(enforceReadability && enableReadability),
|
||||
child: readerChanging.when(
|
||||
skipLoadingOnReload: true,
|
||||
data:
|
||||
(_) => Visibility(
|
||||
visible: readerabilityState.readerable,
|
||||
child: InkWell(
|
||||
onTap:
|
||||
readerChanging.isLoading
|
||||
? null
|
||||
: () async {
|
||||
await ref
|
||||
.read(readerableScreenControllerProvider.notifier)
|
||||
.toggleReaderView(!readerabilityState.active);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 15.0,
|
||||
horizontal: 8.0,
|
||||
),
|
||||
child: icon,
|
||||
(_) => InkWell(
|
||||
onTap:
|
||||
readerChanging.isLoading
|
||||
? null
|
||||
: () async {
|
||||
await ref
|
||||
.read(readerableScreenControllerProvider.notifier)
|
||||
.toggleReaderView(!readerabilityState.active);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 15.0,
|
||||
horizontal: 8.0,
|
||||
),
|
||||
child: icon,
|
||||
),
|
||||
),
|
||||
error: (error, stackTrace) => SizedBox.shrink(),
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:lensai/core/routing/routes.dart';
|
||||
import 'package:lensai/extensions/nullable.dart';
|
||||
import 'package:lensai/features/bangs/data/models/bang_data.dart';
|
||||
import 'package:lensai/features/bangs/domain/providers/search.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/domain/providers.dart';
|
||||
import 'package:lensai/presentation/hooks/listenable_callback.dart';
|
||||
import 'package:lensai/presentation/widgets/selectable_chips.dart';
|
||||
import 'package:lensai/presentation/widgets/url_icon.dart';
|
||||
@@ -98,12 +99,18 @@ class BangChips extends HookConsumerWidget {
|
||||
onPressed: () async {
|
||||
final searchText = searchTextController?.text.trim();
|
||||
|
||||
await BangSearchRoute(
|
||||
final trigger = await BangSearchRoute(
|
||||
searchText:
|
||||
(searchText.isEmpty)
|
||||
? BangSearchRoute.emptySearchText
|
||||
: searchText!,
|
||||
).push(context);
|
||||
).push<String?>(context);
|
||||
|
||||
if (trigger != null) {
|
||||
ref
|
||||
.read(selectedBangTriggerProvider().notifier)
|
||||
.setTrigger(trigger);
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.chevron_right),
|
||||
),
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/features/settings/presentation/controllers/save_settings.dart';
|
||||
import 'package:lensai/features/settings/presentation/widgets/custom_list_tile.dart';
|
||||
import 'package:lensai/features/settings/presentation/widgets/default_search_selector.dart';
|
||||
import 'package:lensai/features/user/data/models/general_settings.dart';
|
||||
import 'package:lensai/features/user/domain/providers.dart';
|
||||
import 'package:lensai/features/user/domain/repositories/cache.dart';
|
||||
@@ -74,6 +75,24 @@ class GeneralSettingsScreen extends HookConsumerWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ListTile(
|
||||
title: Text('Default Search Provider'),
|
||||
leading: Icon(MdiIcons.cloudSearch),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 40),
|
||||
child: DefaultSearchSelector(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SwitchListTile.adaptive(
|
||||
title: const Text('Enable Reader Mode'),
|
||||
subtitle: const Text(
|
||||
@@ -90,6 +109,29 @@ class GeneralSettingsScreen extends HookConsumerWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
SwitchListTile.adaptive(
|
||||
title: const Text('Enforce Reader Mode'),
|
||||
subtitle: const Text(
|
||||
'Override readability probability of websites and always show Reader Mode capabilities even the site might not be compatible.',
|
||||
),
|
||||
secondary: const Icon(MdiIcons.bookCheck),
|
||||
value:
|
||||
generalSettings.enableReadability &&
|
||||
generalSettings.enforceReadability,
|
||||
onChanged:
|
||||
generalSettings.enableReadability
|
||||
? (value) async {
|
||||
await ref
|
||||
.read(
|
||||
saveGeneralSettingsControllerProvider.notifier,
|
||||
)
|
||||
.save(
|
||||
(currentSettings) => currentSettings.copyWith
|
||||
.enforceReadability(value),
|
||||
);
|
||||
}
|
||||
: null,
|
||||
),
|
||||
Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final size = ref.watch(
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:lensai/core/routing/routes.dart';
|
||||
import 'package:lensai/features/bangs/domain/providers/bangs.dart';
|
||||
import 'package:lensai/features/settings/presentation/controllers/save_settings.dart';
|
||||
import 'package:lensai/features/user/data/models/general_settings.dart';
|
||||
import 'package:lensai/presentation/widgets/selectable_chips.dart';
|
||||
import 'package:lensai/presentation/widgets/url_icon.dart';
|
||||
|
||||
class DefaultSearchSelector extends HookConsumerWidget {
|
||||
const DefaultSearchSelector();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final activeBang = ref.watch(defaultSearchBangDataProvider).valueOrNull;
|
||||
final availableBangs = ref.watch(frequentBangListProvider);
|
||||
|
||||
Future<void> updateSearchProvider(String trigger) async {
|
||||
await ref
|
||||
.read(saveGeneralSettingsControllerProvider.notifier)
|
||||
.save(
|
||||
(currentSettings) =>
|
||||
currentSettings.copyWith.defaultSearchProvider(trigger),
|
||||
);
|
||||
}
|
||||
|
||||
return availableBangs.when(
|
||||
skipLoadingOnReload: true,
|
||||
data: (availableBangs) {
|
||||
return SizedBox(
|
||||
height: 48,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SelectableChips(
|
||||
itemId: (bang) => bang.trigger,
|
||||
itemAvatar:
|
||||
(bang) =>
|
||||
UrlIcon([bang.getTemplateUrl('')], iconSize: 20),
|
||||
itemLabel: (bang) => Text(bang.websiteName),
|
||||
availableItems: availableBangs,
|
||||
selectedItem: activeBang,
|
||||
onSelected: (bang) async {
|
||||
await updateSearchProvider(bang.trigger);
|
||||
},
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
final trigger = await const BangSearchRoute().push<String?>(
|
||||
context,
|
||||
);
|
||||
|
||||
if (trigger != null) {
|
||||
await updateSearchProvider(trigger);
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.chevron_right),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
error: (error, stackTrace) => const SizedBox.shrink(),
|
||||
loading: () => const SizedBox(height: 48, width: double.infinity),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -24,20 +24,28 @@ enum DeleteBrowsingDataType {
|
||||
class GeneralSettings with FastEquatable {
|
||||
final ThemeMode themeMode;
|
||||
final bool enableReadability;
|
||||
final bool enforceReadability;
|
||||
final Set<DeleteBrowsingDataType>? deleteBrowsingDataOnQuit;
|
||||
final String defaultSearchProvider;
|
||||
|
||||
GeneralSettings({
|
||||
required this.themeMode,
|
||||
required this.enableReadability,
|
||||
required this.enforceReadability,
|
||||
required this.deleteBrowsingDataOnQuit,
|
||||
required this.defaultSearchProvider,
|
||||
});
|
||||
|
||||
GeneralSettings.withDefaults({
|
||||
ThemeMode? themeMode,
|
||||
bool? enableReadability,
|
||||
bool? enforceReadability,
|
||||
this.deleteBrowsingDataOnQuit,
|
||||
String? defaultSearchProvider,
|
||||
}) : themeMode = themeMode ?? ThemeMode.dark,
|
||||
enableReadability = enableReadability ?? true;
|
||||
enableReadability = enableReadability ?? true,
|
||||
enforceReadability = enforceReadability ?? false,
|
||||
defaultSearchProvider = defaultSearchProvider ?? 'lais';
|
||||
|
||||
factory GeneralSettings.fromJson(Map<String, dynamic> json) =>
|
||||
_$GeneralSettingsFromJson(json);
|
||||
@@ -48,6 +56,8 @@ class GeneralSettings with FastEquatable {
|
||||
List<Object?> get hashParameters => [
|
||||
themeMode,
|
||||
enableReadability,
|
||||
enforceReadability,
|
||||
deleteBrowsingDataOnQuit,
|
||||
defaultSearchProvider,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -11,10 +11,14 @@ abstract class _$GeneralSettingsCWProxy {
|
||||
|
||||
GeneralSettings enableReadability(bool enableReadability);
|
||||
|
||||
GeneralSettings enforceReadability(bool enforceReadability);
|
||||
|
||||
GeneralSettings deleteBrowsingDataOnQuit(
|
||||
Set<DeleteBrowsingDataType>? deleteBrowsingDataOnQuit,
|
||||
);
|
||||
|
||||
GeneralSettings defaultSearchProvider(String defaultSearchProvider);
|
||||
|
||||
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `GeneralSettings(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
|
||||
///
|
||||
/// Usage
|
||||
@@ -24,7 +28,9 @@ abstract class _$GeneralSettingsCWProxy {
|
||||
GeneralSettings call({
|
||||
ThemeMode themeMode,
|
||||
bool enableReadability,
|
||||
bool enforceReadability,
|
||||
Set<DeleteBrowsingDataType>? deleteBrowsingDataOnQuit,
|
||||
String defaultSearchProvider,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -41,11 +47,19 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
|
||||
GeneralSettings enableReadability(bool enableReadability) =>
|
||||
this(enableReadability: enableReadability);
|
||||
|
||||
@override
|
||||
GeneralSettings enforceReadability(bool enforceReadability) =>
|
||||
this(enforceReadability: enforceReadability);
|
||||
|
||||
@override
|
||||
GeneralSettings deleteBrowsingDataOnQuit(
|
||||
Set<DeleteBrowsingDataType>? deleteBrowsingDataOnQuit,
|
||||
) => this(deleteBrowsingDataOnQuit: deleteBrowsingDataOnQuit);
|
||||
|
||||
@override
|
||||
GeneralSettings defaultSearchProvider(String defaultSearchProvider) =>
|
||||
this(defaultSearchProvider: defaultSearchProvider);
|
||||
|
||||
@override
|
||||
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `GeneralSettings(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
|
||||
///
|
||||
@@ -56,7 +70,9 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
|
||||
GeneralSettings call({
|
||||
Object? themeMode = const $CopyWithPlaceholder(),
|
||||
Object? enableReadability = const $CopyWithPlaceholder(),
|
||||
Object? enforceReadability = const $CopyWithPlaceholder(),
|
||||
Object? deleteBrowsingDataOnQuit = const $CopyWithPlaceholder(),
|
||||
Object? defaultSearchProvider = const $CopyWithPlaceholder(),
|
||||
}) {
|
||||
return GeneralSettings(
|
||||
themeMode:
|
||||
@@ -69,11 +85,21 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
|
||||
? _value.enableReadability
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: enableReadability as bool,
|
||||
enforceReadability:
|
||||
enforceReadability == const $CopyWithPlaceholder()
|
||||
? _value.enforceReadability
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: enforceReadability as bool,
|
||||
deleteBrowsingDataOnQuit:
|
||||
deleteBrowsingDataOnQuit == const $CopyWithPlaceholder()
|
||||
? _value.deleteBrowsingDataOnQuit
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: deleteBrowsingDataOnQuit as Set<DeleteBrowsingDataType>?,
|
||||
defaultSearchProvider:
|
||||
defaultSearchProvider == const $CopyWithPlaceholder()
|
||||
? _value.defaultSearchProvider
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: defaultSearchProvider as String,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -92,20 +118,24 @@ GeneralSettings _$GeneralSettingsFromJson(Map<String, dynamic> json) =>
|
||||
GeneralSettings.withDefaults(
|
||||
themeMode: $enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']),
|
||||
enableReadability: json['enableReadability'] as bool?,
|
||||
enforceReadability: json['enforceReadability'] as bool?,
|
||||
deleteBrowsingDataOnQuit:
|
||||
(json['deleteBrowsingDataOnQuit'] as List<dynamic>?)
|
||||
?.map((e) => $enumDecode(_$DeleteBrowsingDataTypeEnumMap, e))
|
||||
.toSet(),
|
||||
defaultSearchProvider: json['defaultSearchProvider'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$GeneralSettingsToJson(GeneralSettings instance) =>
|
||||
<String, dynamic>{
|
||||
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
|
||||
'enableReadability': instance.enableReadability,
|
||||
'enforceReadability': instance.enforceReadability,
|
||||
'deleteBrowsingDataOnQuit':
|
||||
instance.deleteBrowsingDataOnQuit
|
||||
?.map((e) => _$DeleteBrowsingDataTypeEnumMap[e]!)
|
||||
.toList(),
|
||||
'defaultSearchProvider': instance.defaultSearchProvider,
|
||||
};
|
||||
|
||||
const _$ThemeModeEnumMap = {
|
||||
|
||||
@@ -32,9 +32,17 @@ class GeneralSettingsRepository extends _$GeneralSettingsRepository {
|
||||
DriftSqlType.bool,
|
||||
db.typeMapping,
|
||||
),
|
||||
'enforceReadability': settings['enforceReadability']?.readAs(
|
||||
DriftSqlType.bool,
|
||||
db.typeMapping,
|
||||
),
|
||||
'deleteBrowsingDataOnQuit': settings['deleteBrowsingDataOnQuit']
|
||||
?.readAs(DriftSqlType.string, db.typeMapping)
|
||||
.mapNotNull(jsonDecode),
|
||||
'defaultSearchProvider': settings['defaultSearchProvider']?.readAs(
|
||||
DriftSqlType.string,
|
||||
db.typeMapping,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'general_settings.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$generalSettingsRepositoryHash() =>
|
||||
r'001ffa37f5afe1d2d3ee2b3ce8539836af76cf89';
|
||||
r'2dcc600ccfb074fa27fae90acb3d61e10c87e884';
|
||||
|
||||
/// See also [GeneralSettingsRepository].
|
||||
@ProviderFor(GeneralSettingsRepository)
|
||||
|
||||
Reference in New Issue
Block a user