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", "settingsSystemDefault": "System",
"settingsDefault": "Default", "settingsDefault": "Default",
"settingsSearchFieldLabel": "Search settings",
"settingsSearchEmpty": "No matching setting",
"settingsActionExport": "Export", "settingsActionExport": "Export",
"settingsActionImport": "Import", "settingsActionImport": "Import",
@ -616,6 +618,8 @@
"settingsNavigationDrawerAddAlbum": "Add album", "settingsNavigationDrawerAddAlbum": "Add album",
"settingsSectionThumbnails": "Thumbnails", "settingsSectionThumbnails": "Thumbnails",
"settingsThumbnailOverlayTile": "Overlay",
"settingsThumbnailOverlayTitle": "Overlay",
"settingsThumbnailShowFavouriteIcon": "Show favorite icon", "settingsThumbnailShowFavouriteIcon": "Show favorite icon",
"settingsThumbnailShowLocationIcon": "Show location icon", "settingsThumbnailShowLocationIcon": "Show location icon",
"settingsThumbnailShowMotionPhotoIcon": "Show motion photo 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/accessibility_animations.dart';
import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/colors.dart'; import 'package:aves/theme/colors.dart';
import 'package:aves/theme/icons.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/extensions/build_context.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/accessibility/time_to_take_action.dart';
import 'package:aves/widgets/settings/common/tile_leading.dart'; import 'package:aves/widgets/settings/common/tile_leading.dart';
import 'package:aves/widgets/settings/common/tiles.dart'; import 'package:aves/widgets/settings/common/tiles.dart';
import 'package:aves/widgets/settings/settings_definition.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class AccessibilitySection extends StatelessWidget { class AccessibilitySection extends SettingsSection {
final ValueNotifier<String?> expandedNotifier; @override
String get key => 'accessibility';
const AccessibilitySection({
Key? key,
required this.expandedNotifier,
}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget icon(BuildContext context) => SettingsTileLeading(
return AvesExpansionTile(
leading: SettingsTileLeading(
icon: AIcons.accessibility, icon: AIcons.accessibility,
color: context.select<AvesColorsData, Color>((v) => v.accessibility), color: context.select<AvesColorsData, Color>((v) => v.accessibility),
), );
title: context.l10n.settingsSectionAccessibility,
expandedNotifier: expandedNotifier, @override
showHighlight: false, String title(BuildContext context) => context.l10n.settingsSectionAccessibility;
children: [
SettingsSelectionListTile<AccessibilityAnimations>( @override
values: AccessibilityAnimations.values, FutureOr<List<SettingsTile>> tiles(BuildContext context) => [
getName: (context, v) => v.getName(context), SettingsTileAccessibilityAnimations(),
selector: (context, s) => s.accessibilityAnimations, SettingsTileAccessibilityTimeToTakeAction(),
onSelection: (v) => settings.accessibilityAnimations = v, ];
tileTitle: context.l10n.settingsRemoveAnimationsTile, }
dialogTitle: context.l10n.settingsRemoveAnimationsTitle,
), class SettingsTileAccessibilityAnimations extends SettingsTile {
const TimeToTakeActionTile(), @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:flutter/material.dart';
import 'package:provider/provider.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 { class SettingsSwitchListTile extends StatelessWidget {
final bool Function(BuildContext, Settings) selector; final bool Function(BuildContext, Settings) selector;
final ValueChanged<bool> onChanged; 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/display_refresh_rate_mode.dart';
import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/enums/theme_brightness.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/colors.dart';
import 'package:aves/theme/icons.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/extensions/build_context.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/tile_leading.dart';
import 'package:aves/widgets/settings/common/tiles.dart'; import 'package:aves/widgets/settings/common/tiles.dart';
import 'package:aves/widgets/settings/settings_definition.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class DisplaySection extends StatelessWidget { class DisplaySection extends SettingsSection {
final ValueNotifier<String?> expandedNotifier; @override
String get key => 'display';
const DisplaySection({
Key? key,
required this.expandedNotifier,
}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget icon(BuildContext context) => SettingsTileLeading(
return AvesExpansionTile(
leading: SettingsTileLeading(
icon: AIcons.display, icon: AIcons.display,
color: context.select<AvesColorsData, Color>((v) => v.display), color: context.select<AvesColorsData, Color>((v) => v.display),
), );
title: context.l10n.settingsSectionDisplay,
expandedNotifier: expandedNotifier, @override
showHighlight: false, String title(BuildContext context) => context.l10n.settingsSectionDisplay;
children: [
SettingsSelectionListTile<AvesThemeBrightness>( @override
values: AvesThemeBrightness.values, FutureOr<List<SettingsTile>> tiles(BuildContext context) => [
getName: (context, v) => v.getName(context), SettingsTileDisplayThemeBrightness(),
selector: (context, s) => s.themeBrightness, SettingsTileDisplayThemeColorMode(),
onSelection: (v) => settings.themeBrightness = v, SettingsTileDisplayDisplayRefreshRateMode(),
tileTitle: context.l10n.settingsThemeBrightness, ];
dialogTitle: context.l10n.settingsThemeBrightness, }
),
SettingsSwitchListTile( class SettingsTileDisplayThemeBrightness extends SettingsTile {
selector: (context, s) => s.themeColorMode == AvesThemeColorMode.polychrome, @override
onChanged: (v) => settings.themeColorMode = v ? AvesThemeColorMode.polychrome : AvesThemeColorMode.monochrome, String title(BuildContext context) => context.l10n.settingsThemeBrightness;
title: context.l10n.settingsThemeColorHighlights,
), @override
SettingsSelectionListTile<DisplayRefreshRateMode>( Widget build(BuildContext context) => SettingsSelectionListTile<AvesThemeBrightness>(
values: DisplayRefreshRateMode.values, values: AvesThemeBrightness.values,
getName: (context, v) => v.getName(context), getName: (context, v) => v.getName(context),
selector: (context, s) => s.displayRefreshRateMode, selector: (context, s) => s.themeBrightness,
onSelection: (v) => settings.displayRefreshRateMode = v, onSelection: (v) => settings.themeBrightness = v,
tileTitle: context.l10n.settingsDisplayRefreshRateModeTile, tileTitle: title(context),
dialogTitle: context.l10n.settingsDisplayRefreshRateModeTitle, 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/coordinate_format.dart';
import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/enums/unit_system.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/theme/icons.dart';
import 'package:aves/utils/constants.dart'; import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/common/extensions/build_context.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/tile_leading.dart';
import 'package:aves/widgets/settings/common/tiles.dart'; import 'package:aves/widgets/settings/common/tiles.dart';
import 'package:aves/widgets/settings/language/locale.dart'; import 'package:aves/widgets/settings/language/locale.dart';
import 'package:aves/widgets/settings/settings_definition.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class LanguageSection extends StatelessWidget { class LanguageSection extends SettingsSection {
final ValueNotifier<String?> expandedNotifier; @override
String get key => 'language';
const LanguageSection({
Key? key,
required this.expandedNotifier,
}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget icon(BuildContext context) => SettingsTileLeading(
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(
icon: AIcons.language, icon: AIcons.language,
color: context.select<AvesColorsData, Color>((v) => v.language), color: context.select<AvesColorsData, Color>((v) => v.language),
), );
title: l10n.settingsSectionLanguage,
expandedNotifier: expandedNotifier, @override
showHighlight: false, String title(BuildContext context) => context.l10n.settingsSectionLanguage;
children: [
const LocaleTile(), @override
SettingsSelectionListTile<CoordinateFormat>( FutureOr<List<SettingsTile>> tiles(BuildContext context) => [
values: CoordinateFormat.values, SettingsTileLanguageLocale(),
getName: (context, v) => v.getName(context), SettingsTileLanguageCoordinateFormat(),
selector: (context, s) => s.coordinateFormat, SettingsTileLanguageUnitSystem(),
onSelection: (v) => settings.coordinateFormat = v, ];
tileTitle: l10n.settingsCoordinateFormatTile, }
dialogTitle: l10n.settingsCoordinateFormatTitle,
optionSubtitleBuilder: (value) => value.format(l10n, Constants.pointNemo), class SettingsTileLanguageLocale extends SettingsTile {
), @override
SettingsSelectionListTile<UnitSystem>( String title(BuildContext context) => context.l10n.settingsLanguage;
values: UnitSystem.values,
getName: (context, v) => v.getName(context), @override
selector: (context, s) => s.unitSystem, Widget build(BuildContext context) => const LocaleTile();
onSelection: (v) => settings.unitSystem = v, }
tileTitle: l10n.settingsUnitSystemTile,
dialogTitle: l10n.settingsUnitSystemTitle, 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:aves/widgets/settings/common/tiles.dart';
import 'package:flutter/material.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 { class ConfirmationDialogPage extends StatelessWidget {
static const routeName = '/settings/navigation_confirmation'; static const routeName = '/settings/navigation_confirmation';

View file

@ -13,26 +13,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:tuple/tuple.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 { class NavigationDrawerEditorPage extends StatefulWidget {
static const routeName = '/settings/navigation_drawer'; 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/enums.dart';
import 'package:aves/model/settings/enums/home_page.dart'; import 'package:aves/model/settings/enums/home_page.dart';
import 'package:aves/model/settings/enums/screen_on.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/colors.dart';
import 'package:aves/theme/icons.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/extensions/build_context.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/tile_leading.dart';
import 'package:aves/widgets/settings/common/tiles.dart'; import 'package:aves/widgets/settings/common/tiles.dart';
import 'package:aves/widgets/settings/navigation/confirmation_dialogs.dart'; import 'package:aves/widgets/settings/navigation/confirmation_dialogs.dart';
import 'package:aves/widgets/settings/navigation/drawer.dart'; import 'package:aves/widgets/settings/navigation/drawer.dart';
import 'package:aves/widgets/settings/settings_definition.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class NavigationSection extends StatelessWidget { class NavigationSection extends SettingsSection {
final ValueNotifier<String?> expandedNotifier; @override
String get key => 'navigation';
const NavigationSection({
Key? key,
required this.expandedNotifier,
}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget icon(BuildContext context) => SettingsTileLeading(
return AvesExpansionTile(
leading: SettingsTileLeading(
icon: AIcons.home, icon: AIcons.home,
color: context.select<AvesColorsData, Color>((v) => v.navigation), color: context.select<AvesColorsData, Color>((v) => v.navigation),
), );
title: context.l10n.settingsSectionNavigation,
expandedNotifier: expandedNotifier, @override
showHighlight: false, String title(BuildContext context) => context.l10n.settingsSectionNavigation;
children: [
SettingsSelectionListTile<HomePageSetting>( @override
values: HomePageSetting.values, FutureOr<List<SettingsTile>> tiles(BuildContext context) => [
getName: (context, v) => v.getName(context), SettingsTileNavigationHomePage(),
selector: (context, s) => s.homePage, SettingsTileNavigationDrawer(),
onSelection: (v) => settings.homePage = v, SettingsTileNavigationConfirmationDialog(),
tileTitle: context.l10n.settingsHome, SettingsTileNavigationKeepScreenOn(),
dialogTitle: context.l10n.settingsHome, SettingsTileNavigationDoubleBackExit(),
), ];
const NavigationDrawerTile(), }
const ConfirmationDialogTile(),
SettingsSelectionListTile<KeepScreenOn>( class SettingsTileNavigationHomePage extends SettingsTile {
values: KeepScreenOn.values, @override
getName: (context, v) => v.getName(context), String title(BuildContext context) => context.l10n.settingsHome;
selector: (context, s) => s.keepScreenOn,
onSelection: (v) => settings.keepScreenOn = v, @override
tileTitle: context.l10n.settingsKeepScreenOnTile, Widget build(BuildContext context) => SettingsSelectionListTile<HomePageSetting>(
dialogTitle: context.l10n.settingsKeepScreenOnTitle, values: HomePageSetting.values,
), getName: (context, v) => v.getName(context),
SettingsSwitchListTile( selector: (context, s) => s.homePage,
selector: (context, s) => s.mustBackTwiceToExit, onSelection: (v) => settings.homePage = v,
onChanged: (v) => settings.mustBackTwiceToExit = v, tileTitle: title(context),
title: context.l10n.settingsDoubleBackExit, 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:aves/widgets/common/providers/media_query_data_provider.dart';
import 'package:flutter/material.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 { class StorageAccessPage extends StatefulWidget {
static const routeName = '/settings/storage_access'; static const routeName = '/settings/storage_access';

View file

@ -14,26 +14,6 @@ import 'package:flutter/scheduler.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:tuple/tuple.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 { class HiddenItemsPage extends StatelessWidget {
static const routeName = '/settings/hidden_items'; static const routeName = '/settings/hidden_items';

View file

@ -1,74 +1,126 @@
import 'dart:async';
import 'package:aves/app_flavor.dart'; import 'package:aves/app_flavor.dart';
import 'package:aves/model/device.dart'; import 'package:aves/model/device.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/colors.dart'; import 'package:aves/theme/colors.dart';
import 'package:aves/theme/icons.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/extensions/build_context.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/tile_leading.dart';
import 'package:aves/widgets/settings/common/tiles.dart'; import 'package:aves/widgets/settings/common/tiles.dart';
import 'package:aves/widgets/settings/privacy/access_grants.dart'; import 'package:aves/widgets/settings/privacy/access_grants.dart';
import 'package:aves/widgets/settings/privacy/hidden_items.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:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class PrivacySection extends StatelessWidget { class PrivacySection extends SettingsSection {
final ValueNotifier<String?> expandedNotifier; @override
String get key => 'privacy';
const PrivacySection({
Key? key,
required this.expandedNotifier,
}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget icon(BuildContext context) => SettingsTileLeading(
final canEnableErrorReporting = context.select<AppFlavor, bool>((v) => v.canEnableErrorReporting);
return AvesExpansionTile(
leading: SettingsTileLeading(
icon: AIcons.privacy, icon: AIcons.privacy,
color: context.select<AvesColorsData, Color>((v) => v.privacy), color: context.select<AvesColorsData, Color>((v) => v.privacy),
), );
title: context.l10n.settingsSectionPrivacy,
expandedNotifier: expandedNotifier, @override
showHighlight: false, String title(BuildContext context) => context.l10n.settingsSectionPrivacy;
children: [
SettingsSwitchListTile( @override
selector: (context, s) => s.isInstalledAppAccessAllowed, FutureOr<List<SettingsTile>> tiles(BuildContext context) async {
onChanged: (v) => settings.isInstalledAppAccessAllowed = v, final canEnableErrorReporting = context.select<AppFlavor, bool>((v) => v.canEnableErrorReporting);
title: context.l10n.settingsAllowInstalledAppAccess, return [
subtitle: context.l10n.settingsAllowInstalledAppAccessSubtitle, SettingsTilePrivacyAllowInstalledAppAccess(),
), if (canEnableErrorReporting) SettingsTilePrivacyAllowErrorReporting(),
if (canEnableErrorReporting) SettingsTilePrivacySaveSearchHistory(),
SettingsSwitchListTile( SettingsTilePrivacyEnableBin(),
selector: (context, s) => s.isErrorReportingAllowed, SettingsTilePrivacyHiddenItems(),
onChanged: (v) => settings.isErrorReportingAllowed = v, if (device.canGrantDirectoryAccess) SettingsTilePrivacyStorageAccess(),
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(),
],
);
} }
} }
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/durations.dart';
import 'package:aves/theme/icons.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/action_mixins/feedback.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/insets.dart';
import 'package:aves/widgets/common/basic/menu.dart'; import 'package:aves/widgets/common/basic/menu.dart';
import 'package:aves/widgets/common/extensions/build_context.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/language/language.dart';
import 'package:aves/widgets/settings/navigation/navigation.dart'; import 'package:aves/widgets/settings/navigation/navigation.dart';
import 'package:aves/widgets/settings/privacy/privacy.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/thumbnails/thumbnails.dart';
import 'package:aves/widgets/settings/video/video.dart'; import 'package:aves/widgets/settings/video/video.dart';
import 'package:aves/widgets/settings/viewer/viewer.dart'; import 'package:aves/widgets/settings/viewer/viewer.dart';
@ -43,6 +46,17 @@ class SettingsPage extends StatefulWidget {
class _SettingsPageState extends State<SettingsPage> with FeedbackMixin { class _SettingsPageState extends State<SettingsPage> with FeedbackMixin {
final ValueNotifier<String?> _expandedNotifier = ValueNotifier(null); final ValueNotifier<String?> _expandedNotifier = ValueNotifier(null);
static final List<SettingsSection> sections = [
NavigationSection(),
ThumbnailsSection(),
ViewerSection(),
VideoSection(),
PrivacySection(),
AccessibilitySection(),
DisplaySection(),
LanguageSection(),
];
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
@ -50,8 +64,16 @@ class _SettingsPageState extends State<SettingsPage> with FeedbackMixin {
return MediaQueryDataProvider( return MediaQueryDataProvider(
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(context.l10n.settingsPageTitle), title: InteractiveAppBarTitle(
onTap: () => _goToSearch(context),
child: Text(context.l10n.settingsPageTitle),
),
actions: [ actions: [
IconButton(
icon: const Icon(AIcons.search),
onPressed: () => _goToSearch(context),
tooltip: MaterialLocalizations.of(context).searchFieldLabel,
),
MenuIconTheme( MenuIconTheme(
child: PopupMenuButton<SettingsAction>( child: PopupMenuButton<SettingsAction>(
itemBuilder: (context) { itemBuilder: (context) {
@ -74,6 +96,7 @@ class _SettingsPageState extends State<SettingsPage> with FeedbackMixin {
), ),
), ),
], ],
titleSpacing: 0,
), ),
body: GestureAreaProtectorStack( body: GestureAreaProtectorStack(
child: SafeArea( child: SafeArea(
@ -100,16 +123,7 @@ class _SettingsPageState extends State<SettingsPage> with FeedbackMixin {
child: child, child: child,
), ),
), ),
children: [ children: sections.map((v) => v.build(context, _expandedNotifier)).toList(),
NavigationSection(expandedNotifier: _expandedNotifier),
ThumbnailsSection(expandedNotifier: _expandedNotifier),
ViewerSection(expandedNotifier: _expandedNotifier),
VideoSection(expandedNotifier: _expandedNotifier),
PrivacySection(expandedNotifier: _expandedNotifier),
AccessibilitySection(expandedNotifier: _expandedNotifier),
DisplaySection(expandedNotifier: _expandedNotifier),
LanguageSection(expandedNotifier: _expandedNotifier),
],
), ),
); );
}), }),
@ -206,4 +220,14 @@ class _SettingsPageState extends State<SettingsPage> with FeedbackMixin {
break; 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:flutter/material.dart';
import 'package:tuple/tuple.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 { class CollectionActionEditorPage extends StatelessWidget {
static const routeName = '/settings/collection_actions'; 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/colors.dart';
import 'package:aves/theme/icons.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/extensions/build_context.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/tile_leading.dart';
import 'package:aves/widgets/settings/common/tiles.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/collection_actions_editor.dart';
import 'package:aves/widgets/settings/thumbnails/overlay.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class ThumbnailsSection extends StatelessWidget { class ThumbnailsSection extends SettingsSection {
final ValueNotifier<String?> expandedNotifier; @override
String get key => 'thumbnails';
const ThumbnailsSection({
Key? key,
required this.expandedNotifier,
}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget icon(BuildContext context) => SettingsTileLeading(
final iconSize = IconTheme.of(context).size! * MediaQuery.textScaleFactorOf(context);
final iconColor = context.select<AvesColorsData, Color>((v) => v.neutral);
return AvesExpansionTile(
leading: SettingsTileLeading(
icon: AIcons.grid, icon: AIcons.grid,
color: context.select<AvesColorsData, Color>((v) => v.thumbnails), color: context.select<AvesColorsData, Color>((v) => v.thumbnails),
), );
title: context.l10n.settingsSectionThumbnails,
expandedNotifier: expandedNotifier, @override
showHighlight: false, String title(BuildContext context) => context.l10n.settingsSectionThumbnails;
children: [
const CollectionActionsTile(), @override
SettingsSwitchListTile( List<SettingsTile> tiles(BuildContext context) => [
selector: (context, s) => s.showThumbnailFavourite, SettingsTileCollectionQuickActions(),
onChanged: (v) => settings.showThumbnailFavourite = v, SettingsTileThumbnailOverlay(),
title: context.l10n.settingsThumbnailShowFavouriteIcon, ];
trailing: Padding( }
padding: EdgeInsets.symmetric(horizontal: iconSize * (1 - FavouriteIcon.scale) / 2),
child: Icon( class SettingsTileCollectionQuickActions extends SettingsTile {
AIcons.favourite, @override
size: iconSize * FavouriteIcon.scale, String title(BuildContext context) => context.l10n.settingsCollectionQuickActionsTile;
color: iconColor,
), @override
), Widget build(BuildContext context) => SettingsSubPageTile(
), title: title(context),
SettingsSwitchListTile( routeName: CollectionActionEditorPage.routeName,
selector: (context, s) => s.showThumbnailLocation, builder: (context) => const CollectionActionEditorPage(),
onChanged: (v) => settings.showThumbnailLocation = v, );
title: context.l10n.settingsThumbnailShowLocationIcon, }
trailing: Icon(
AIcons.location, class SettingsTileThumbnailOverlay extends SettingsTile {
size: iconSize, @override
color: iconColor, String title(BuildContext context) => context.l10n.settingsThumbnailOverlayTile;
),
), @override
SettingsSwitchListTile( Widget build(BuildContext context) => SettingsSubPageTile(
selector: (context, s) => s.showThumbnailMotionPhoto, title: title(context),
onChanged: (v) => settings.showThumbnailMotionPhoto = v, routeName: ThumbnailOverlayPage.routeName,
title: context.l10n.settingsThumbnailShowMotionPhotoIcon, builder: (context) => const ThumbnailOverlayPage(),
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

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

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:provider/provider.dart';
import 'package:tuple/tuple.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 { class ViewerOverlayPage extends StatelessWidget {
static const routeName = '/settings/viewer_overlay'; 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/enums/enums.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/services/common/services.dart'; import 'package:aves/services/common/services.dart';
import 'package:aves/theme/colors.dart'; import 'package:aves/theme/colors.dart';
import 'package:aves/theme/icons.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/extensions/build_context.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/tile_leading.dart';
import 'package:aves/widgets/settings/common/tiles.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/entry_background.dart';
import 'package:aves/widgets/settings/viewer/overlay.dart'; import 'package:aves/widgets/settings/viewer/overlay.dart';
import 'package:aves/widgets/settings/viewer/viewer_actions_editor.dart'; import 'package:aves/widgets/settings/viewer/viewer_actions_editor.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class ViewerSection extends StatelessWidget { class ViewerSection extends SettingsSection {
final ValueNotifier<String?> expandedNotifier; @override
String get key => 'viewer';
const ViewerSection({
Key? key,
required this.expandedNotifier,
}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget icon(BuildContext context) => SettingsTileLeading(
return AvesExpansionTile(
leading: SettingsTileLeading(
icon: AIcons.image, icon: AIcons.image,
color: context.select<AvesColorsData, Color>((v) => v.image), color: context.select<AvesColorsData, Color>((v) => v.image),
), );
title: context.l10n.settingsSectionViewer,
expandedNotifier: expandedNotifier, @override
showHighlight: false, String title(BuildContext context) => context.l10n.settingsSectionViewer;
children: [
const ViewerActionsTile(), @override
const ViewerOverlayTile(), FutureOr<List<SettingsTile>> tiles(BuildContext context) async {
const _CutoutModeSwitch(), final canSetCutoutMode = await windowService.canSetCutoutMode();
SettingsSwitchListTile( return [
selector: (context, s) => s.viewerMaxBrightness, SettingsTileViewerQuickActions(),
onChanged: (v) => settings.viewerMaxBrightness = v, SettingsTileViewerOverlay(),
title: context.l10n.settingsViewerMaximumBrightness, if (canSetCutoutMode) SettingsTileViewerCutoutMode(),
), SettingsTileViewerMaxBrightness(),
SettingsSwitchListTile( SettingsTileViewerMotionPhotoAutoPlay(),
selector: (context, s) => s.enableMotionPhotoAutoPlay, SettingsTileViewerImageBackground(),
onChanged: (v) => settings.enableMotionPhotoAutoPlay = v, ];
title: context.l10n.settingsMotionPhotoAutoPlay, }
), }
Selector<Settings, EntryBackground>(
selector: (context, s) => s.imageBackground, class SettingsTileViewerQuickActions extends SettingsTile {
builder: (context, current, child) => ListTile( @override
title: Text(context.l10n.settingsImageBackground), String title(BuildContext context) => context.l10n.settingsViewerQuickActionsTile;
trailing: EntryBackgroundSelector(
getter: () => current, @override
setter: (value) => settings.imageBackground = value, 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:aves/widgets/settings/common/quick_actions/editor_page.dart';
import 'package:flutter/material.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 { class ViewerActionEditorPage extends StatelessWidget {
static const routeName = '/settings/viewer_actions'; 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/aves_dialog.dart';
import 'package:aves/widgets/dialogs/video_speed_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/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/overlay/notifications.dart';
import 'package:aves/widgets/viewer/video/controller.dart'; import 'package:aves/widgets/viewer/video/controller.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';

View file

@ -1,4 +1,11 @@
{ {
"de": [
"settingsSearchFieldLabel",
"settingsSearchEmpty",
"settingsThumbnailOverlayTile",
"settingsThumbnailOverlayTitle"
],
"es": [ "es": [
"entryActionShowGeoTiffOnMap", "entryActionShowGeoTiffOnMap",
"entryActionConvertMotionPhotoToStillImage", "entryActionConvertMotionPhotoToStillImage",
@ -9,7 +16,32 @@
"coverDialogTabApp", "coverDialogTabApp",
"coverDialogTabColor", "coverDialogTabColor",
"appPickDialogTitle", "appPickDialogTitle",
"appPickDialogNone" "appPickDialogNone",
"settingsSearchFieldLabel",
"settingsSearchEmpty",
"settingsThumbnailOverlayTile",
"settingsThumbnailOverlayTitle"
],
"fr": [
"settingsSearchFieldLabel",
"settingsSearchEmpty",
"settingsThumbnailOverlayTile",
"settingsThumbnailOverlayTitle"
],
"id": [
"settingsSearchFieldLabel",
"settingsSearchEmpty",
"settingsThumbnailOverlayTile",
"settingsThumbnailOverlayTitle"
],
"it": [
"settingsSearchFieldLabel",
"settingsSearchEmpty",
"settingsThumbnailOverlayTile",
"settingsThumbnailOverlayTitle"
], ],
"ja": [ "ja": [
@ -22,6 +54,38 @@
"coverDialogTabApp", "coverDialogTabApp",
"coverDialogTabColor", "coverDialogTabColor",
"appPickDialogTitle", "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"
] ]
} }