settings search

This commit is contained in:
Thibault Deckers 2022-04-27 11:14:46 +09:00
parent 9747ea98bc
commit 21938ab1b1
26 changed files with 1014 additions and 632 deletions

View file

@ -588,6 +588,8 @@
"settingsSystemDefault": "System",
"settingsDefault": "Default",
"settingsSearchFieldLabel": "Search settings",
"settingsSearchEmpty": "No matching setting",
"settingsActionExport": "Export",
"settingsActionImport": "Import",
@ -616,6 +618,8 @@
"settingsNavigationDrawerAddAlbum": "Add album",
"settingsSectionThumbnails": "Thumbnails",
"settingsThumbnailOverlayTile": "Overlay",
"settingsThumbnailOverlayTitle": "Overlay",
"settingsThumbnailShowFavouriteIcon": "Show favorite icon",
"settingsThumbnailShowLocationIcon": "Show location icon",
"settingsThumbnailShowMotionPhotoIcon": "Show motion photo icon",

View file

@ -1,45 +1,57 @@
import 'dart:async';
import 'package:aves/model/settings/enums/accessibility_animations.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/colors.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
import 'package:aves/widgets/settings/accessibility/time_to_take_action.dart';
import 'package:aves/widgets/settings/common/tile_leading.dart';
import 'package:aves/widgets/settings/common/tiles.dart';
import 'package:aves/widgets/settings/settings_definition.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class AccessibilitySection extends StatelessWidget {
final ValueNotifier<String?> expandedNotifier;
const AccessibilitySection({
Key? key,
required this.expandedNotifier,
}) : super(key: key);
class AccessibilitySection extends SettingsSection {
@override
String get key => 'accessibility';
@override
Widget build(BuildContext context) {
return AvesExpansionTile(
leading: SettingsTileLeading(
Widget icon(BuildContext context) => SettingsTileLeading(
icon: AIcons.accessibility,
color: context.select<AvesColorsData, Color>((v) => v.accessibility),
),
title: context.l10n.settingsSectionAccessibility,
expandedNotifier: expandedNotifier,
showHighlight: false,
children: [
SettingsSelectionListTile<AccessibilityAnimations>(
values: AccessibilityAnimations.values,
getName: (context, v) => v.getName(context),
selector: (context, s) => s.accessibilityAnimations,
onSelection: (v) => settings.accessibilityAnimations = v,
tileTitle: context.l10n.settingsRemoveAnimationsTile,
dialogTitle: context.l10n.settingsRemoveAnimationsTitle,
),
const TimeToTakeActionTile(),
],
);
}
);
@override
String title(BuildContext context) => context.l10n.settingsSectionAccessibility;
@override
FutureOr<List<SettingsTile>> tiles(BuildContext context) => [
SettingsTileAccessibilityAnimations(),
SettingsTileAccessibilityTimeToTakeAction(),
];
}
class SettingsTileAccessibilityAnimations extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsRemoveAnimationsTile;
@override
Widget build(BuildContext context) => SettingsSelectionListTile<AccessibilityAnimations>(
values: AccessibilityAnimations.values,
getName: (context, v) => v.getName(context),
selector: (context, s) => s.accessibilityAnimations,
onSelection: (v) => settings.accessibilityAnimations = v,
tileTitle: title(context),
dialogTitle: context.l10n.settingsRemoveAnimationsTitle,
);
}
class SettingsTileAccessibilityTimeToTakeAction extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsTimeToTakeActionTile;
@override
Widget build(BuildContext context) => const TimeToTakeActionTile();
}

View file

@ -4,6 +4,34 @@ import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class SettingsSubPageTile extends StatelessWidget {
final String title, routeName;
final WidgetBuilder builder;
const SettingsSubPageTile({
Key? key,
required this.title,
required this.routeName,
required this.builder,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListTile(
title: Text(title),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
settings: RouteSettings(name: routeName),
builder: builder,
),
);
},
);
}
}
class SettingsSwitchListTile extends StatelessWidget {
final bool Function(BuildContext, Settings) selector;
final ValueChanged<bool> onChanged;

View file

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:aves/model/settings/enums/display_refresh_rate_mode.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/enums/theme_brightness.dart';
@ -5,53 +7,71 @@ import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/colors.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
import 'package:aves/widgets/settings/common/tile_leading.dart';
import 'package:aves/widgets/settings/common/tiles.dart';
import 'package:aves/widgets/settings/settings_definition.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class DisplaySection extends StatelessWidget {
final ValueNotifier<String?> expandedNotifier;
const DisplaySection({
Key? key,
required this.expandedNotifier,
}) : super(key: key);
class DisplaySection extends SettingsSection {
@override
String get key => 'display';
@override
Widget build(BuildContext context) {
return AvesExpansionTile(
leading: SettingsTileLeading(
Widget icon(BuildContext context) => SettingsTileLeading(
icon: AIcons.display,
color: context.select<AvesColorsData, Color>((v) => v.display),
),
title: context.l10n.settingsSectionDisplay,
expandedNotifier: expandedNotifier,
showHighlight: false,
children: [
SettingsSelectionListTile<AvesThemeBrightness>(
values: AvesThemeBrightness.values,
getName: (context, v) => v.getName(context),
selector: (context, s) => s.themeBrightness,
onSelection: (v) => settings.themeBrightness = v,
tileTitle: context.l10n.settingsThemeBrightness,
dialogTitle: context.l10n.settingsThemeBrightness,
),
SettingsSwitchListTile(
selector: (context, s) => s.themeColorMode == AvesThemeColorMode.polychrome,
onChanged: (v) => settings.themeColorMode = v ? AvesThemeColorMode.polychrome : AvesThemeColorMode.monochrome,
title: context.l10n.settingsThemeColorHighlights,
),
SettingsSelectionListTile<DisplayRefreshRateMode>(
values: DisplayRefreshRateMode.values,
getName: (context, v) => v.getName(context),
selector: (context, s) => s.displayRefreshRateMode,
onSelection: (v) => settings.displayRefreshRateMode = v,
tileTitle: context.l10n.settingsDisplayRefreshRateModeTile,
dialogTitle: context.l10n.settingsDisplayRefreshRateModeTitle,
),
],
);
}
);
@override
String title(BuildContext context) => context.l10n.settingsSectionDisplay;
@override
FutureOr<List<SettingsTile>> tiles(BuildContext context) => [
SettingsTileDisplayThemeBrightness(),
SettingsTileDisplayThemeColorMode(),
SettingsTileDisplayDisplayRefreshRateMode(),
];
}
class SettingsTileDisplayThemeBrightness extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsThemeBrightness;
@override
Widget build(BuildContext context) => SettingsSelectionListTile<AvesThemeBrightness>(
values: AvesThemeBrightness.values,
getName: (context, v) => v.getName(context),
selector: (context, s) => s.themeBrightness,
onSelection: (v) => settings.themeBrightness = v,
tileTitle: title(context),
dialogTitle: context.l10n.settingsThemeBrightness,
);
}
class SettingsTileDisplayThemeColorMode extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsThemeColorHighlights;
@override
Widget build(BuildContext context) => SettingsSwitchListTile(
selector: (context, s) => s.themeColorMode == AvesThemeColorMode.polychrome,
onChanged: (v) => settings.themeColorMode = v ? AvesThemeColorMode.polychrome : AvesThemeColorMode.monochrome,
title: title(context),
);
}
class SettingsTileDisplayDisplayRefreshRateMode extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsDisplayRefreshRateModeTile;
@override
Widget build(BuildContext context) => SettingsSelectionListTile<DisplayRefreshRateMode>(
values: DisplayRefreshRateMode.values,
getName: (context, v) => v.getName(context),
selector: (context, s) => s.displayRefreshRateMode,
onSelection: (v) => settings.displayRefreshRateMode = v,
tileTitle: title(context),
dialogTitle: context.l10n.settingsDisplayRefreshRateModeTitle,
);
}

View file

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:aves/model/settings/enums/coordinate_format.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/enums/unit_system.dart';
@ -6,57 +8,69 @@ import 'package:aves/theme/colors.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
import 'package:aves/widgets/settings/common/tile_leading.dart';
import 'package:aves/widgets/settings/common/tiles.dart';
import 'package:aves/widgets/settings/language/locale.dart';
import 'package:aves/widgets/settings/settings_definition.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class LanguageSection extends StatelessWidget {
final ValueNotifier<String?> expandedNotifier;
const LanguageSection({
Key? key,
required this.expandedNotifier,
}) : super(key: key);
class LanguageSection extends SettingsSection {
@override
String get key => 'language';
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
return AvesExpansionTile(
// key is expected by test driver
key: const Key('section-language'),
// use a fixed value instead of the title to identify this expansion tile
// so that the tile state is kept when the language is modified
value: 'language',
leading: SettingsTileLeading(
Widget icon(BuildContext context) => SettingsTileLeading(
icon: AIcons.language,
color: context.select<AvesColorsData, Color>((v) => v.language),
),
title: l10n.settingsSectionLanguage,
expandedNotifier: expandedNotifier,
showHighlight: false,
children: [
const LocaleTile(),
SettingsSelectionListTile<CoordinateFormat>(
values: CoordinateFormat.values,
getName: (context, v) => v.getName(context),
selector: (context, s) => s.coordinateFormat,
onSelection: (v) => settings.coordinateFormat = v,
tileTitle: l10n.settingsCoordinateFormatTile,
dialogTitle: l10n.settingsCoordinateFormatTitle,
optionSubtitleBuilder: (value) => value.format(l10n, Constants.pointNemo),
),
SettingsSelectionListTile<UnitSystem>(
values: UnitSystem.values,
getName: (context, v) => v.getName(context),
selector: (context, s) => s.unitSystem,
onSelection: (v) => settings.unitSystem = v,
tileTitle: l10n.settingsUnitSystemTile,
dialogTitle: l10n.settingsUnitSystemTitle,
),
],
);
}
);
@override
String title(BuildContext context) => context.l10n.settingsSectionLanguage;
@override
FutureOr<List<SettingsTile>> tiles(BuildContext context) => [
SettingsTileLanguageLocale(),
SettingsTileLanguageCoordinateFormat(),
SettingsTileLanguageUnitSystem(),
];
}
class SettingsTileLanguageLocale extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsLanguage;
@override
Widget build(BuildContext context) => const LocaleTile();
}
class SettingsTileLanguageCoordinateFormat extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsCoordinateFormatTile;
@override
Widget build(BuildContext context) => SettingsSelectionListTile<CoordinateFormat>(
values: CoordinateFormat.values,
getName: (context, v) => v.getName(context),
selector: (context, s) => s.coordinateFormat,
onSelection: (v) => settings.coordinateFormat = v,
tileTitle: title(context),
dialogTitle: context.l10n.settingsCoordinateFormatTitle,
optionSubtitleBuilder: (value) => value.format(context.l10n, Constants.pointNemo),
);
}
class SettingsTileLanguageUnitSystem extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsUnitSystemTile;
@override
Widget build(BuildContext context) => SettingsSelectionListTile<UnitSystem>(
values: UnitSystem.values,
getName: (context, v) => v.getName(context),
selector: (context, s) => s.unitSystem,
onSelection: (v) => settings.unitSystem = v,
tileTitle: title(context),
dialogTitle: context.l10n.settingsUnitSystemTitle,
);
}

View file

@ -3,26 +3,6 @@ import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/settings/common/tiles.dart';
import 'package:flutter/material.dart';
class ConfirmationDialogTile extends StatelessWidget {
const ConfirmationDialogTile({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListTile(
title: Text(context.l10n.settingsConfirmationDialogTile),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
settings: const RouteSettings(name: ConfirmationDialogPage.routeName),
builder: (context) => const ConfirmationDialogPage(),
),
);
},
);
}
}
class ConfirmationDialogPage extends StatelessWidget {
static const routeName = '/settings/navigation_confirmation';

View file

@ -13,26 +13,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:tuple/tuple.dart';
class NavigationDrawerTile extends StatelessWidget {
const NavigationDrawerTile({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListTile(
title: Text(context.l10n.settingsNavigationDrawerTile),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
settings: const RouteSettings(name: NavigationDrawerEditorPage.routeName),
builder: (context) => const NavigationDrawerEditorPage(),
),
);
},
);
}
}
class NavigationDrawerEditorPage extends StatefulWidget {
static const routeName = '/settings/navigation_drawer';

View file

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/enums/home_page.dart';
import 'package:aves/model/settings/enums/screen_on.dart';
@ -5,57 +7,99 @@ import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/colors.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
import 'package:aves/widgets/settings/common/tile_leading.dart';
import 'package:aves/widgets/settings/common/tiles.dart';
import 'package:aves/widgets/settings/navigation/confirmation_dialogs.dart';
import 'package:aves/widgets/settings/navigation/drawer.dart';
import 'package:aves/widgets/settings/settings_definition.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class NavigationSection extends StatelessWidget {
final ValueNotifier<String?> expandedNotifier;
const NavigationSection({
Key? key,
required this.expandedNotifier,
}) : super(key: key);
class NavigationSection extends SettingsSection {
@override
String get key => 'navigation';
@override
Widget build(BuildContext context) {
return AvesExpansionTile(
leading: SettingsTileLeading(
Widget icon(BuildContext context) => SettingsTileLeading(
icon: AIcons.home,
color: context.select<AvesColorsData, Color>((v) => v.navigation),
),
title: context.l10n.settingsSectionNavigation,
expandedNotifier: expandedNotifier,
showHighlight: false,
children: [
SettingsSelectionListTile<HomePageSetting>(
values: HomePageSetting.values,
getName: (context, v) => v.getName(context),
selector: (context, s) => s.homePage,
onSelection: (v) => settings.homePage = v,
tileTitle: context.l10n.settingsHome,
dialogTitle: context.l10n.settingsHome,
),
const NavigationDrawerTile(),
const ConfirmationDialogTile(),
SettingsSelectionListTile<KeepScreenOn>(
values: KeepScreenOn.values,
getName: (context, v) => v.getName(context),
selector: (context, s) => s.keepScreenOn,
onSelection: (v) => settings.keepScreenOn = v,
tileTitle: context.l10n.settingsKeepScreenOnTile,
dialogTitle: context.l10n.settingsKeepScreenOnTitle,
),
SettingsSwitchListTile(
selector: (context, s) => s.mustBackTwiceToExit,
onChanged: (v) => settings.mustBackTwiceToExit = v,
title: context.l10n.settingsDoubleBackExit,
),
],
);
}
);
@override
String title(BuildContext context) => context.l10n.settingsSectionNavigation;
@override
FutureOr<List<SettingsTile>> tiles(BuildContext context) => [
SettingsTileNavigationHomePage(),
SettingsTileNavigationDrawer(),
SettingsTileNavigationConfirmationDialog(),
SettingsTileNavigationKeepScreenOn(),
SettingsTileNavigationDoubleBackExit(),
];
}
class SettingsTileNavigationHomePage extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsHome;
@override
Widget build(BuildContext context) => SettingsSelectionListTile<HomePageSetting>(
values: HomePageSetting.values,
getName: (context, v) => v.getName(context),
selector: (context, s) => s.homePage,
onSelection: (v) => settings.homePage = v,
tileTitle: title(context),
dialogTitle: context.l10n.settingsHome,
);
}
class SettingsTileNavigationDrawer extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsNavigationDrawerTile;
@override
Widget build(BuildContext context) => SettingsSubPageTile(
title: title(context),
routeName: NavigationDrawerEditorPage.routeName,
builder: (context) => const NavigationDrawerEditorPage(),
);
}
class SettingsTileNavigationConfirmationDialog extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsConfirmationDialogTile;
@override
Widget build(BuildContext context) => SettingsSubPageTile(
title: title(context),
routeName: ConfirmationDialogPage.routeName,
builder: (context) => const ConfirmationDialogPage(),
);
}
class SettingsTileNavigationKeepScreenOn extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsKeepScreenOnTile;
@override
Widget build(BuildContext context) => SettingsSelectionListTile<KeepScreenOn>(
values: KeepScreenOn.values,
getName: (context, v) => v.getName(context),
selector: (context, s) => s.keepScreenOn,
onSelection: (v) => settings.keepScreenOn = v,
tileTitle: title(context),
dialogTitle: context.l10n.settingsKeepScreenOnTitle,
);
}
class SettingsTileNavigationDoubleBackExit extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsDoubleBackExit;
@override
Widget build(BuildContext context) => SettingsSwitchListTile(
selector: (context, s) => s.mustBackTwiceToExit,
onChanged: (v) => settings.mustBackTwiceToExit = v,
title: title(context),
);
}

View file

@ -5,26 +5,6 @@ import 'package:aves/widgets/common/identity/empty.dart';
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
import 'package:flutter/material.dart';
class StorageAccessTile extends StatelessWidget {
const StorageAccessTile({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListTile(
title: Text(context.l10n.settingsStorageAccessTile),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
settings: const RouteSettings(name: StorageAccessPage.routeName),
builder: (context) => const StorageAccessPage(),
),
);
},
);
}
}
class StorageAccessPage extends StatefulWidget {
static const routeName = '/settings/storage_access';

View file

@ -14,26 +14,6 @@ import 'package:flutter/scheduler.dart';
import 'package:provider/provider.dart';
import 'package:tuple/tuple.dart';
class HiddenItemsTile extends StatelessWidget {
const HiddenItemsTile({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListTile(
title: Text(context.l10n.settingsHiddenItemsTile),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
settings: const RouteSettings(name: HiddenItemsPage.routeName),
builder: (context) => const HiddenItemsPage(),
),
);
},
);
}
}
class HiddenItemsPage extends StatelessWidget {
static const routeName = '/settings/hidden_items';

View file

@ -1,74 +1,126 @@
import 'dart:async';
import 'package:aves/app_flavor.dart';
import 'package:aves/model/device.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/colors.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
import 'package:aves/widgets/settings/common/tile_leading.dart';
import 'package:aves/widgets/settings/common/tiles.dart';
import 'package:aves/widgets/settings/privacy/access_grants.dart';
import 'package:aves/widgets/settings/privacy/hidden_items.dart';
import 'package:aves/widgets/settings/settings_definition.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class PrivacySection extends StatelessWidget {
final ValueNotifier<String?> expandedNotifier;
const PrivacySection({
Key? key,
required this.expandedNotifier,
}) : super(key: key);
class PrivacySection extends SettingsSection {
@override
String get key => 'privacy';
@override
Widget build(BuildContext context) {
final canEnableErrorReporting = context.select<AppFlavor, bool>((v) => v.canEnableErrorReporting);
return AvesExpansionTile(
leading: SettingsTileLeading(
Widget icon(BuildContext context) => SettingsTileLeading(
icon: AIcons.privacy,
color: context.select<AvesColorsData, Color>((v) => v.privacy),
),
title: context.l10n.settingsSectionPrivacy,
expandedNotifier: expandedNotifier,
showHighlight: false,
children: [
SettingsSwitchListTile(
selector: (context, s) => s.isInstalledAppAccessAllowed,
onChanged: (v) => settings.isInstalledAppAccessAllowed = v,
title: context.l10n.settingsAllowInstalledAppAccess,
subtitle: context.l10n.settingsAllowInstalledAppAccessSubtitle,
),
if (canEnableErrorReporting)
SettingsSwitchListTile(
selector: (context, s) => s.isErrorReportingAllowed,
onChanged: (v) => settings.isErrorReportingAllowed = v,
title: context.l10n.settingsAllowErrorReporting,
),
SettingsSwitchListTile(
selector: (context, s) => s.saveSearchHistory,
onChanged: (v) {
settings.saveSearchHistory = v;
if (!v) {
settings.searchHistory = [];
}
},
title: context.l10n.settingsSaveSearchHistory,
),
SettingsSwitchListTile(
selector: (context, s) => s.enableBin,
onChanged: (v) {
settings.enableBin = v;
if (!v) {
settings.searchHistory = [];
}
},
title: context.l10n.settingsEnableBin,
subtitle: context.l10n.settingsEnableBinSubtitle,
),
const HiddenItemsTile(),
if (device.canGrantDirectoryAccess) const StorageAccessTile(),
],
);
);
@override
String title(BuildContext context) => context.l10n.settingsSectionPrivacy;
@override
FutureOr<List<SettingsTile>> tiles(BuildContext context) async {
final canEnableErrorReporting = context.select<AppFlavor, bool>((v) => v.canEnableErrorReporting);
return [
SettingsTilePrivacyAllowInstalledAppAccess(),
if (canEnableErrorReporting) SettingsTilePrivacyAllowErrorReporting(),
SettingsTilePrivacySaveSearchHistory(),
SettingsTilePrivacyEnableBin(),
SettingsTilePrivacyHiddenItems(),
if (device.canGrantDirectoryAccess) SettingsTilePrivacyStorageAccess(),
];
}
}
class SettingsTilePrivacyAllowInstalledAppAccess extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsAllowInstalledAppAccess;
@override
Widget build(BuildContext context) => SettingsSwitchListTile(
selector: (context, s) => s.isInstalledAppAccessAllowed,
onChanged: (v) => settings.isInstalledAppAccessAllowed = v,
title: title(context),
subtitle: context.l10n.settingsAllowInstalledAppAccessSubtitle,
);
}
class SettingsTilePrivacyAllowErrorReporting extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsAllowErrorReporting;
@override
Widget build(BuildContext context) => SettingsSwitchListTile(
selector: (context, s) => s.isErrorReportingAllowed,
onChanged: (v) => settings.isErrorReportingAllowed = v,
title: title(context),
);
}
class SettingsTilePrivacySaveSearchHistory extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsSaveSearchHistory;
@override
Widget build(BuildContext context) => SettingsSwitchListTile(
selector: (context, s) => s.saveSearchHistory,
onChanged: (v) {
settings.saveSearchHistory = v;
if (!v) {
settings.searchHistory = [];
}
},
title: title(context),
);
}
class SettingsTilePrivacyEnableBin extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsEnableBin;
@override
Widget build(BuildContext context) => SettingsSwitchListTile(
selector: (context, s) => s.enableBin,
onChanged: (v) {
settings.enableBin = v;
if (!v) {
settings.searchHistory = [];
}
},
title: title(context),
subtitle: context.l10n.settingsEnableBinSubtitle,
);
}
class SettingsTilePrivacyHiddenItems extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsHiddenItemsTile;
@override
Widget build(BuildContext context) => SettingsSubPageTile(
title: title(context),
routeName: HiddenItemsPage.routeName,
builder: (context) => const HiddenItemsPage(),
);
}
class SettingsTilePrivacyStorageAccess extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsStorageAccessTile;
@override
Widget build(BuildContext context) => SettingsSubPageTile(
title: title(context),
routeName: StorageAccessPage.routeName,
builder: (context) => const StorageAccessPage(),
);
}

View file

@ -0,0 +1,43 @@
import 'dart:async';
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
import 'package:flutter/widgets.dart';
abstract class SettingsSection {
String get key;
Widget icon(BuildContext context);
String title(BuildContext context);
FutureOr<List<SettingsTile>> tiles(BuildContext context);
Widget build(BuildContext context, ValueNotifier<String?> expandedNotifier) {
return FutureBuilder<List<SettingsTile>>(
future: Future.value(tiles(context)),
builder: (context, snapshot) {
final tiles = snapshot.data;
if (tiles == null) return const SizedBox();
return AvesExpansionTile(
// key is expected by test driver
key: Key('section-$key'),
// use a fixed value instead of the title to identify this expansion tile
// so that the tile state is kept when the language is modified
value: key,
leading: icon(context),
title: title(context),
expandedNotifier: expandedNotifier,
showHighlight: false,
children: tiles.map((v) => v.build(context)).toList(),
);
},
);
}
}
abstract class SettingsTile {
String title(BuildContext context);
Widget build(BuildContext context);
}

View file

@ -9,6 +9,7 @@ import 'package:aves/services/common/services.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/action_mixins/feedback.dart';
import 'package:aves/widgets/common/app_bar_title.dart';
import 'package:aves/widgets/common/basic/insets.dart';
import 'package:aves/widgets/common/basic/menu.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
@ -21,6 +22,8 @@ import 'package:aves/widgets/settings/display/display.dart';
import 'package:aves/widgets/settings/language/language.dart';
import 'package:aves/widgets/settings/navigation/navigation.dart';
import 'package:aves/widgets/settings/privacy/privacy.dart';
import 'package:aves/widgets/settings/settings_definition.dart';
import 'package:aves/widgets/settings/settings_search.dart';
import 'package:aves/widgets/settings/thumbnails/thumbnails.dart';
import 'package:aves/widgets/settings/video/video.dart';
import 'package:aves/widgets/settings/viewer/viewer.dart';
@ -43,6 +46,17 @@ class SettingsPage extends StatefulWidget {
class _SettingsPageState extends State<SettingsPage> with FeedbackMixin {
final ValueNotifier<String?> _expandedNotifier = ValueNotifier(null);
static final List<SettingsSection> sections = [
NavigationSection(),
ThumbnailsSection(),
ViewerSection(),
VideoSection(),
PrivacySection(),
AccessibilitySection(),
DisplaySection(),
LanguageSection(),
];
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
@ -50,8 +64,16 @@ class _SettingsPageState extends State<SettingsPage> with FeedbackMixin {
return MediaQueryDataProvider(
child: Scaffold(
appBar: AppBar(
title: Text(context.l10n.settingsPageTitle),
title: InteractiveAppBarTitle(
onTap: () => _goToSearch(context),
child: Text(context.l10n.settingsPageTitle),
),
actions: [
IconButton(
icon: const Icon(AIcons.search),
onPressed: () => _goToSearch(context),
tooltip: MaterialLocalizations.of(context).searchFieldLabel,
),
MenuIconTheme(
child: PopupMenuButton<SettingsAction>(
itemBuilder: (context) {
@ -74,6 +96,7 @@ class _SettingsPageState extends State<SettingsPage> with FeedbackMixin {
),
),
],
titleSpacing: 0,
),
body: GestureAreaProtectorStack(
child: SafeArea(
@ -100,16 +123,7 @@ class _SettingsPageState extends State<SettingsPage> with FeedbackMixin {
child: child,
),
),
children: [
NavigationSection(expandedNotifier: _expandedNotifier),
ThumbnailsSection(expandedNotifier: _expandedNotifier),
ViewerSection(expandedNotifier: _expandedNotifier),
VideoSection(expandedNotifier: _expandedNotifier),
PrivacySection(expandedNotifier: _expandedNotifier),
AccessibilitySection(expandedNotifier: _expandedNotifier),
DisplaySection(expandedNotifier: _expandedNotifier),
LanguageSection(expandedNotifier: _expandedNotifier),
],
children: sections.map((v) => v.build(context, _expandedNotifier)).toList(),
),
);
}),
@ -206,4 +220,14 @@ class _SettingsPageState extends State<SettingsPage> with FeedbackMixin {
break;
}
}
void _goToSearch(BuildContext context) {
showSearch(
context: context,
delegate: SettingsSearchDelegate(
searchFieldLabel: context.l10n.settingsSearchFieldLabel,
sections: sections,
),
);
}
}

View file

@ -0,0 +1,114 @@
import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/animated_icons_fix.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/empty.dart';
import 'package:aves/widgets/common/identity/highlight_title.dart';
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
import 'package:aves/widgets/settings/settings_definition.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
class SettingsSearchDelegate extends SearchDelegate {
final List<SettingsSection> sections;
SettingsSearchDelegate({
required String searchFieldLabel,
required this.sections,
}) : super(
searchFieldLabel: searchFieldLabel,
);
@override
Widget buildLeading(BuildContext context) {
return IconButton(
// TODO TLAD [rtl] replace to regular `AnimatedIcon` when this is fixed: https://github.com/flutter/flutter/issues/60521
icon: AnimatedIconFixIssue60521(
icon: AnimatedIconsFixIssue60521.menu_arrow,
progress: transitionAnimation,
),
onPressed: () => Navigator.pop(context),
tooltip: MaterialLocalizations.of(context).backButtonTooltip,
);
}
@override
List<Widget> buildActions(BuildContext context) {
return [
if (query.isNotEmpty)
IconButton(
icon: const Icon(AIcons.clear),
onPressed: () {
query = '';
showSuggestions(context);
},
tooltip: context.l10n.clearTooltip,
),
];
}
@override
Widget buildSuggestions(BuildContext context) => const SizedBox();
@override
Widget buildResults(BuildContext context) {
if (query.isEmpty) {
showSuggestions(context);
return const SizedBox();
}
final upQuery = query.toUpperCase().trim();
bool testKey(String key) => key.toUpperCase().contains(upQuery);
final loader = Future.wait(sections.map((section) async {
final allTiles = await section.tiles(context);
final filteredTiles = testKey(section.title(context)) ? allTiles : allTiles.where((v) => testKey(v.title(context))).toList();
if (filteredTiles.isEmpty) return null;
return (context) {
return <Widget>[
Padding(
// match header layout in Settings page
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 13),
child: Row(
children: [
section.icon(context),
const SizedBox(width: 8),
Expanded(
child: HighlightTitle(
title: section.title(context),
showHighlight: false,
),
),
],
),
),
...filteredTiles.map((v) => v.build(context)),
];
};
}));
return MediaQueryDataProvider(
child: SafeArea(
child: FutureBuilder<List<List<Widget> Function(BuildContext)?>>(
future: loader,
builder: (context, snapshot) {
final loaders = snapshot.data;
if (loaders == null) return const SizedBox();
final children = loaders.whereNotNull().expand((builder) => builder(context)).toList();
return children.isEmpty
? EmptyContent(
icon: AIcons.settings,
text: context.l10n.settingsSearchEmpty,
)
: ListView(
padding: const EdgeInsets.all(8),
children: children,
);
},
),
),
);
}
}

View file

@ -6,26 +6,6 @@ import 'package:aves/widgets/settings/common/quick_actions/editor_page.dart';
import 'package:flutter/material.dart';
import 'package:tuple/tuple.dart';
class CollectionActionsTile extends StatelessWidget {
const CollectionActionsTile({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListTile(
title: Text(context.l10n.settingsCollectionQuickActionsTile),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
settings: const RouteSettings(name: CollectionActionEditorPage.routeName),
builder: (context) => const CollectionActionEditorPage(),
),
);
},
);
}
}
class CollectionActionEditorPage extends StatelessWidget {
static const routeName = '/settings/collection_actions';

View file

@ -0,0 +1,93 @@
import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/colors.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/aves_icons.dart';
import 'package:aves/widgets/settings/common/tiles.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class ThumbnailOverlayPage extends StatelessWidget {
static const routeName = '/settings/thumbnail_overlay';
const ThumbnailOverlayPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final iconSize = IconTheme.of(context).size! * MediaQuery.textScaleFactorOf(context);
final iconColor = context.select<AvesColorsData, Color>((v) => v.neutral);
return Scaffold(
appBar: AppBar(
title: Text(context.l10n.settingsThumbnailOverlayTitle),
),
body: SafeArea(
child: ListView(
children: [
SettingsSwitchListTile(
selector: (context, s) => s.showThumbnailFavourite,
onChanged: (v) => settings.showThumbnailFavourite = v,
title: context.l10n.settingsThumbnailShowFavouriteIcon,
trailing: Padding(
padding: EdgeInsets.symmetric(horizontal: iconSize * (1 - FavouriteIcon.scale) / 2),
child: Icon(
AIcons.favourite,
size: iconSize * FavouriteIcon.scale,
color: iconColor,
),
),
),
SettingsSwitchListTile(
selector: (context, s) => s.showThumbnailLocation,
onChanged: (v) => settings.showThumbnailLocation = v,
title: context.l10n.settingsThumbnailShowLocationIcon,
trailing: Icon(
AIcons.location,
size: iconSize,
color: iconColor,
),
),
SettingsSwitchListTile(
selector: (context, s) => s.showThumbnailMotionPhoto,
onChanged: (v) => settings.showThumbnailMotionPhoto = v,
title: context.l10n.settingsThumbnailShowMotionPhotoIcon,
trailing: Padding(
padding: EdgeInsets.symmetric(horizontal: iconSize * (1 - MotionPhotoIcon.scale) / 2),
child: Icon(
AIcons.motionPhoto,
size: iconSize * MotionPhotoIcon.scale,
color: iconColor,
),
),
),
SettingsSwitchListTile(
selector: (context, s) => s.showThumbnailRating,
onChanged: (v) => settings.showThumbnailRating = v,
title: context.l10n.settingsThumbnailShowRating,
trailing: Icon(
AIcons.rating,
size: iconSize,
color: iconColor,
),
),
SettingsSwitchListTile(
selector: (context, s) => s.showThumbnailRaw,
onChanged: (v) => settings.showThumbnailRaw = v,
title: context.l10n.settingsThumbnailShowRawIcon,
trailing: Icon(
AIcons.raw,
size: iconSize,
color: iconColor,
),
),
SettingsSwitchListTile(
selector: (context, s) => s.showThumbnailVideoDuration,
onChanged: (v) => settings.showThumbnailVideoDuration = v,
title: context.l10n.settingsThumbnailShowVideoDuration,
),
],
),
),
);
}
}

View file

@ -1,99 +1,54 @@
import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/colors.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
import 'package:aves/widgets/common/identity/aves_icons.dart';
import 'package:aves/widgets/settings/common/tile_leading.dart';
import 'package:aves/widgets/settings/common/tiles.dart';
import 'package:aves/widgets/settings/settings_definition.dart';
import 'package:aves/widgets/settings/thumbnails/collection_actions_editor.dart';
import 'package:aves/widgets/settings/thumbnails/overlay.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class ThumbnailsSection extends StatelessWidget {
final ValueNotifier<String?> expandedNotifier;
const ThumbnailsSection({
Key? key,
required this.expandedNotifier,
}) : super(key: key);
class ThumbnailsSection extends SettingsSection {
@override
String get key => 'thumbnails';
@override
Widget build(BuildContext context) {
final iconSize = IconTheme.of(context).size! * MediaQuery.textScaleFactorOf(context);
final iconColor = context.select<AvesColorsData, Color>((v) => v.neutral);
return AvesExpansionTile(
leading: SettingsTileLeading(
Widget icon(BuildContext context) => SettingsTileLeading(
icon: AIcons.grid,
color: context.select<AvesColorsData, Color>((v) => v.thumbnails),
),
title: context.l10n.settingsSectionThumbnails,
expandedNotifier: expandedNotifier,
showHighlight: false,
children: [
const CollectionActionsTile(),
SettingsSwitchListTile(
selector: (context, s) => s.showThumbnailFavourite,
onChanged: (v) => settings.showThumbnailFavourite = v,
title: context.l10n.settingsThumbnailShowFavouriteIcon,
trailing: Padding(
padding: EdgeInsets.symmetric(horizontal: iconSize * (1 - FavouriteIcon.scale) / 2),
child: Icon(
AIcons.favourite,
size: iconSize * FavouriteIcon.scale,
color: iconColor,
),
),
),
SettingsSwitchListTile(
selector: (context, s) => s.showThumbnailLocation,
onChanged: (v) => settings.showThumbnailLocation = v,
title: context.l10n.settingsThumbnailShowLocationIcon,
trailing: Icon(
AIcons.location,
size: iconSize,
color: iconColor,
),
),
SettingsSwitchListTile(
selector: (context, s) => s.showThumbnailMotionPhoto,
onChanged: (v) => settings.showThumbnailMotionPhoto = v,
title: context.l10n.settingsThumbnailShowMotionPhotoIcon,
trailing: Padding(
padding: EdgeInsets.symmetric(horizontal: iconSize * (1 - MotionPhotoIcon.scale) / 2),
child: Icon(
AIcons.motionPhoto,
size: iconSize * MotionPhotoIcon.scale,
color: iconColor,
),
),
),
SettingsSwitchListTile(
selector: (context, s) => s.showThumbnailRating,
onChanged: (v) => settings.showThumbnailRating = v,
title: context.l10n.settingsThumbnailShowRating,
trailing: Icon(
AIcons.rating,
size: iconSize,
color: iconColor,
),
),
SettingsSwitchListTile(
selector: (context, s) => s.showThumbnailRaw,
onChanged: (v) => settings.showThumbnailRaw = v,
title: context.l10n.settingsThumbnailShowRawIcon,
trailing: Icon(
AIcons.raw,
size: iconSize,
color: iconColor,
),
),
SettingsSwitchListTile(
selector: (context, s) => s.showThumbnailVideoDuration,
onChanged: (v) => settings.showThumbnailVideoDuration = v,
title: context.l10n.settingsThumbnailShowVideoDuration,
),
],
);
}
);
@override
String title(BuildContext context) => context.l10n.settingsSectionThumbnails;
@override
List<SettingsTile> tiles(BuildContext context) => [
SettingsTileCollectionQuickActions(),
SettingsTileThumbnailOverlay(),
];
}
class SettingsTileCollectionQuickActions extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsCollectionQuickActionsTile;
@override
Widget build(BuildContext context) => SettingsSubPageTile(
title: title(context),
routeName: CollectionActionEditorPage.routeName,
builder: (context) => const CollectionActionEditorPage(),
);
}
class SettingsTileThumbnailOverlay extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsThumbnailOverlayTile;
@override
Widget build(BuildContext context) => SettingsSubPageTile(
title: title(context),
routeName: ThumbnailOverlayPage.routeName,
builder: (context) => const ThumbnailOverlayPage(),
);
}

View file

@ -5,26 +5,6 @@ import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/settings/common/tiles.dart';
import 'package:flutter/material.dart';
class VideoControlsTile extends StatelessWidget {
const VideoControlsTile({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListTile(
title: Text(context.l10n.settingsVideoControlsTile),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
settings: const RouteSettings(name: VideoControlsPage.routeName),
builder: (context) => const VideoControlsPage(),
),
);
},
);
}
}
class VideoControlsPage extends StatelessWidget {
static const routeName = '/settings/video/controls';

View file

@ -7,26 +7,6 @@ import 'package:aves/widgets/settings/video/subtitle_sample.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class SubtitleThemeTile extends StatelessWidget {
const SubtitleThemeTile({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListTile(
title: Text(context.l10n.settingsSubtitleThemeTile),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
settings: const RouteSettings(name: SubtitleThemePage.routeName),
builder: (context) => const SubtitleThemePage(),
),
);
},
);
}
}
class SubtitleThemePage extends StatelessWidget {
static const routeName = '/settings/video/subtitle_theme';

View file

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:aves/model/filters/mime.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/enums/video_loop_mode.dart';
@ -5,100 +7,117 @@ import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/colors.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
import 'package:aves/widgets/settings/common/tile_leading.dart';
import 'package:aves/widgets/settings/common/tiles.dart';
import 'package:aves/widgets/settings/settings_definition.dart';
import 'package:aves/widgets/settings/video/controls.dart';
import 'package:aves/widgets/settings/video/subtitle_theme.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class VideoSection extends StatelessWidget {
final ValueNotifier<String?>? expandedNotifier;
class VideoSection extends SettingsSection {
final bool standalonePage;
const VideoSection({
Key? key,
this.expandedNotifier,
VideoSection({
this.standalonePage = false,
}) : super(key: key);
});
@override
Widget build(BuildContext context) {
final children = [
if (!standalonePage)
SettingsSwitchListTile(
selector: (context, s) => !s.hiddenFilters.contains(MimeFilter.video),
onChanged: (v) => settings.changeFilterVisibility({MimeFilter.video}, v),
title: context.l10n.settingsVideoShowVideos,
),
SettingsSwitchListTile(
String get key => 'video';
@override
Widget icon(BuildContext context) => SettingsTileLeading(
icon: AIcons.video,
color: context.select<AvesColorsData, Color>((v) => v.video),
);
@override
String title(BuildContext context) => context.l10n.settingsSectionVideo;
@override
FutureOr<List<SettingsTile>> tiles(BuildContext context) async {
return [
if (!standalonePage) SettingsTileVideoShowVideos(),
SettingsTileVideoEnableHardwareAcceleration(),
SettingsTileVideoEnableAutoPlay(),
SettingsTileVideoLoopMode(),
SettingsTileVideoControls(),
SettingsTileVideoSubtitleTheme(),
];
}
}
class SettingsTileVideoShowVideos extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsVideoShowVideos;
@override
Widget build(BuildContext context) => SettingsSwitchListTile(
selector: (context, s) => !s.hiddenFilters.contains(MimeFilter.video),
onChanged: (v) => settings.changeFilterVisibility({MimeFilter.video}, v),
title: title(context),
);
}
class SettingsTileVideoEnableHardwareAcceleration extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsVideoEnableHardwareAcceleration;
@override
Widget build(BuildContext context) => SettingsSwitchListTile(
selector: (context, s) => s.enableVideoHardwareAcceleration,
onChanged: (v) => settings.enableVideoHardwareAcceleration = v,
title: context.l10n.settingsVideoEnableHardwareAcceleration,
),
SettingsSwitchListTile(
title: title(context),
);
}
class SettingsTileVideoEnableAutoPlay extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsVideoEnableAutoPlay;
@override
Widget build(BuildContext context) => SettingsSwitchListTile(
selector: (context, s) => s.enableVideoAutoPlay,
onChanged: (v) => settings.enableVideoAutoPlay = v,
title: context.l10n.settingsVideoEnableAutoPlay,
),
SettingsSelectionListTile<VideoLoopMode>(
title: title(context),
);
}
class SettingsTileVideoLoopMode extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsVideoLoopModeTile;
@override
Widget build(BuildContext context) => SettingsSelectionListTile<VideoLoopMode>(
values: VideoLoopMode.values,
getName: (context, v) => v.getName(context),
selector: (context, s) => s.videoLoopMode,
onSelection: (v) => settings.videoLoopMode = v,
tileTitle: context.l10n.settingsVideoLoopModeTile,
tileTitle: title(context),
dialogTitle: context.l10n.settingsVideoLoopModeTitle,
),
const VideoControlsTile(),
const SubtitleThemeTile(),
];
return standalonePage
? ListView(
children: children,
)
: AvesExpansionTile(
leading: SettingsTileLeading(
icon: AIcons.video,
color: context.select<AvesColorsData, Color>((v) => v.video),
),
title: context.l10n.settingsSectionVideo,
expandedNotifier: expandedNotifier,
showHighlight: false,
children: children,
);
}
);
}
class VideoSettingsPage extends StatelessWidget {
static const routeName = '/settings/video';
const VideoSettingsPage({Key? key}) : super(key: key);
class SettingsTileVideoControls extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsVideoControlsTile;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return MediaQueryDataProvider(
child: Scaffold(
appBar: AppBar(
title: Text(context.l10n.settingsVideoPageTitle),
),
body: Theme(
data: theme.copyWith(
textTheme: theme.textTheme.copyWith(
// dense style font for tile subtitles, without modifying title font
bodyText2: const TextStyle(fontSize: 12),
),
),
child: const SafeArea(
child: VideoSection(
standalonePage: true,
),
),
),
),
);
}
Widget build(BuildContext context) => SettingsSubPageTile(
title: title(context),
routeName: VideoControlsPage.routeName,
builder: (context) => const VideoControlsPage(),
);
}
class SettingsTileVideoSubtitleTheme extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsSubtitleThemeTile;
@override
Widget build(BuildContext context) => SettingsSubPageTile(
title: title(context),
routeName: SubtitleThemePage.routeName,
builder: (context) => const SubtitleThemePage(),
);
}

View file

@ -0,0 +1,51 @@
import 'dart:async';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
import 'package:aves/widgets/settings/settings_definition.dart';
import 'package:aves/widgets/settings/video/video.dart';
import 'package:flutter/material.dart';
class VideoSettingsPage extends StatefulWidget {
static const routeName = '/settings/video';
const VideoSettingsPage({Key? key}) : super(key: key);
@override
State<VideoSettingsPage> createState() => _VideoSettingsPageState();
}
class _VideoSettingsPageState extends State<VideoSettingsPage> {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return MediaQueryDataProvider(
child: Scaffold(
appBar: AppBar(
title: Text(context.l10n.settingsVideoPageTitle),
),
body: Theme(
data: theme.copyWith(
textTheme: theme.textTheme.copyWith(
// dense style font for tile subtitles, without modifying title font
bodyText2: const TextStyle(fontSize: 12),
),
),
child: SafeArea(
child: FutureBuilder<List<SettingsTile>>(
future: Future.value(VideoSection(standalonePage: true).tiles(context)),
builder: (context, snapshot) {
final tiles = snapshot.data;
if (tiles == null) return const SizedBox();
return ListView(
children: tiles.map((v) => v.build(context)).toList(),
);
},
),
),
),
),
);
}
}

View file

@ -5,26 +5,6 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:tuple/tuple.dart';
class ViewerOverlayTile extends StatelessWidget {
const ViewerOverlayTile({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListTile(
title: Text(context.l10n.settingsViewerOverlayTile),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
settings: const RouteSettings(name: ViewerOverlayPage.routeName),
builder: (context) => const ViewerOverlayPage(),
),
);
},
);
}
}
class ViewerOverlayPage extends StatelessWidget {
static const routeName = '/settings/viewer_overlay';

View file

@ -1,95 +1,120 @@
import 'dart:async';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/theme/colors.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
import 'package:aves/widgets/settings/common/tile_leading.dart';
import 'package:aves/widgets/settings/common/tiles.dart';
import 'package:aves/widgets/settings/settings_definition.dart';
import 'package:aves/widgets/settings/viewer/entry_background.dart';
import 'package:aves/widgets/settings/viewer/overlay.dart';
import 'package:aves/widgets/settings/viewer/viewer_actions_editor.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class ViewerSection extends StatelessWidget {
final ValueNotifier<String?> expandedNotifier;
const ViewerSection({
Key? key,
required this.expandedNotifier,
}) : super(key: key);
class ViewerSection extends SettingsSection {
@override
String get key => 'viewer';
@override
Widget build(BuildContext context) {
return AvesExpansionTile(
leading: SettingsTileLeading(
Widget icon(BuildContext context) => SettingsTileLeading(
icon: AIcons.image,
color: context.select<AvesColorsData, Color>((v) => v.image),
),
title: context.l10n.settingsSectionViewer,
expandedNotifier: expandedNotifier,
showHighlight: false,
children: [
const ViewerActionsTile(),
const ViewerOverlayTile(),
const _CutoutModeSwitch(),
SettingsSwitchListTile(
selector: (context, s) => s.viewerMaxBrightness,
onChanged: (v) => settings.viewerMaxBrightness = v,
title: context.l10n.settingsViewerMaximumBrightness,
),
SettingsSwitchListTile(
selector: (context, s) => s.enableMotionPhotoAutoPlay,
onChanged: (v) => settings.enableMotionPhotoAutoPlay = v,
title: context.l10n.settingsMotionPhotoAutoPlay,
),
Selector<Settings, EntryBackground>(
selector: (context, s) => s.imageBackground,
builder: (context, current, child) => ListTile(
title: Text(context.l10n.settingsImageBackground),
trailing: EntryBackgroundSelector(
getter: () => current,
setter: (value) => settings.imageBackground = value,
),
);
@override
String title(BuildContext context) => context.l10n.settingsSectionViewer;
@override
FutureOr<List<SettingsTile>> tiles(BuildContext context) async {
final canSetCutoutMode = await windowService.canSetCutoutMode();
return [
SettingsTileViewerQuickActions(),
SettingsTileViewerOverlay(),
if (canSetCutoutMode) SettingsTileViewerCutoutMode(),
SettingsTileViewerMaxBrightness(),
SettingsTileViewerMotionPhotoAutoPlay(),
SettingsTileViewerImageBackground(),
];
}
}
class SettingsTileViewerQuickActions extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsViewerQuickActionsTile;
@override
Widget build(BuildContext context) => SettingsSubPageTile(
title: title(context),
routeName: ViewerActionEditorPage.routeName,
builder: (context) => const ViewerActionEditorPage(),
);
}
class SettingsTileViewerOverlay extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsViewerOverlayTile;
@override
Widget build(BuildContext context) => SettingsSubPageTile(
title: title(context),
routeName: ViewerOverlayPage.routeName,
builder: (context) => const ViewerOverlayPage(),
);
}
class SettingsTileViewerCutoutMode extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsViewerUseCutout;
@override
Widget build(BuildContext context) => SettingsSwitchListTile(
selector: (context, s) => s.viewerUseCutout,
onChanged: (v) => settings.viewerUseCutout = v,
title: title(context),
);
}
class SettingsTileViewerMaxBrightness extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsViewerMaximumBrightness;
@override
Widget build(BuildContext context) => SettingsSwitchListTile(
selector: (context, s) => s.viewerMaxBrightness,
onChanged: (v) => settings.viewerMaxBrightness = v,
title: title(context),
);
}
class SettingsTileViewerMotionPhotoAutoPlay extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsMotionPhotoAutoPlay;
@override
Widget build(BuildContext context) => SettingsSwitchListTile(
selector: (context, s) => s.enableMotionPhotoAutoPlay,
onChanged: (v) => settings.enableMotionPhotoAutoPlay = v,
title: title(context),
);
}
class SettingsTileViewerImageBackground extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsImageBackground;
@override
Widget build(BuildContext context) => Selector<Settings, EntryBackground>(
selector: (context, s) => s.imageBackground,
builder: (context, current, child) => ListTile(
title: Text(title(context)),
trailing: EntryBackgroundSelector(
getter: () => current,
setter: (value) => settings.imageBackground = value,
),
),
],
);
}
}
class _CutoutModeSwitch extends StatefulWidget {
const _CutoutModeSwitch({Key? key}) : super(key: key);
@override
State<_CutoutModeSwitch> createState() => _CutoutModeSwitchState();
}
class _CutoutModeSwitchState extends State<_CutoutModeSwitch> {
late Future<bool> _canSet;
@override
void initState() {
super.initState();
_canSet = windowService.canSetCutoutMode();
}
@override
Widget build(BuildContext context) {
return FutureBuilder<bool>(
future: _canSet,
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data!) {
return SettingsSwitchListTile(
selector: (context, s) => s.viewerUseCutout,
onChanged: (v) => settings.viewerUseCutout = v,
title: context.l10n.settingsViewerUseCutout,
);
}
return const SizedBox.shrink();
},
);
}
);
}

View file

@ -4,26 +4,6 @@ import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/settings/common/quick_actions/editor_page.dart';
import 'package:flutter/material.dart';
class ViewerActionsTile extends StatelessWidget {
const ViewerActionsTile({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListTile(
title: Text(context.l10n.settingsViewerQuickActionsTile),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
settings: const RouteSettings(name: ViewerActionEditorPage.routeName),
builder: (context) => const ViewerActionEditorPage(),
),
);
},
);
}
}
class ViewerActionEditorPage extends StatelessWidget {
static const routeName = '/settings/viewer_actions';

View file

@ -15,7 +15,7 @@ import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/dialogs/aves_dialog.dart';
import 'package:aves/widgets/dialogs/video_speed_dialog.dart';
import 'package:aves/widgets/dialogs/video_stream_selection_dialog.dart';
import 'package:aves/widgets/settings/video/video.dart';
import 'package:aves/widgets/settings/video/video_settings_page.dart';
import 'package:aves/widgets/viewer/overlay/notifications.dart';
import 'package:aves/widgets/viewer/video/controller.dart';
import 'package:collection/collection.dart';

View file

@ -1,4 +1,11 @@
{
"de": [
"settingsSearchFieldLabel",
"settingsSearchEmpty",
"settingsThumbnailOverlayTile",
"settingsThumbnailOverlayTitle"
],
"es": [
"entryActionShowGeoTiffOnMap",
"entryActionConvertMotionPhotoToStillImage",
@ -9,7 +16,32 @@
"coverDialogTabApp",
"coverDialogTabColor",
"appPickDialogTitle",
"appPickDialogNone"
"appPickDialogNone",
"settingsSearchFieldLabel",
"settingsSearchEmpty",
"settingsThumbnailOverlayTile",
"settingsThumbnailOverlayTitle"
],
"fr": [
"settingsSearchFieldLabel",
"settingsSearchEmpty",
"settingsThumbnailOverlayTile",
"settingsThumbnailOverlayTitle"
],
"id": [
"settingsSearchFieldLabel",
"settingsSearchEmpty",
"settingsThumbnailOverlayTile",
"settingsThumbnailOverlayTitle"
],
"it": [
"settingsSearchFieldLabel",
"settingsSearchEmpty",
"settingsThumbnailOverlayTile",
"settingsThumbnailOverlayTitle"
],
"ja": [
@ -22,6 +54,38 @@
"coverDialogTabApp",
"coverDialogTabColor",
"appPickDialogTitle",
"appPickDialogNone"
"appPickDialogNone",
"settingsSearchFieldLabel",
"settingsSearchEmpty",
"settingsThumbnailOverlayTile",
"settingsThumbnailOverlayTitle"
],
"ko": [
"settingsSearchFieldLabel",
"settingsSearchEmpty",
"settingsThumbnailOverlayTile",
"settingsThumbnailOverlayTitle"
],
"pt": [
"settingsSearchFieldLabel",
"settingsSearchEmpty",
"settingsThumbnailOverlayTile",
"settingsThumbnailOverlayTitle"
],
"ru": [
"settingsSearchFieldLabel",
"settingsSearchEmpty",
"settingsThumbnailOverlayTile",
"settingsThumbnailOverlayTitle"
],
"zh": [
"settingsSearchFieldLabel",
"settingsSearchEmpty",
"settingsThumbnailOverlayTile",
"settingsThumbnailOverlayTitle"
]
}