#8 #93 theme brightness, color toggle

This commit is contained in:
Thibault Deckers 2022-03-16 12:44:32 +09:00
parent 44da3cb222
commit 5913358817
106 changed files with 1451 additions and 872 deletions

View file

@ -177,6 +177,10 @@
"accessibilityAnimationsRemove": "Prevent screen effects",
"accessibilityAnimationsKeep": "Keep screen effects",
"themeBrightnessLight": "Light",
"themeBrightnessDark": "Dark",
"themeBrightnessBlack": "Black",
"albumTierNew": "New",
"albumTierPinned": "Pinned",
"albumTierSpecial": "Common",
@ -671,6 +675,10 @@
"settingsTimeToTakeActionTile": "Time to take action",
"settingsTimeToTakeActionTitle": "Time to Take Action",
"settingsSectionDisplay": "Display",
"settingsThemeBrightness": "Theme",
"settingsThemeColorful": "Colorful",
"settingsSectionLanguage": "Language & Formats",
"settingsLanguage": "Language",
"settingsCoordinateFormatTile": "Coordinate format",

View file

@ -177,7 +177,8 @@ extension ExtraEntryAction on EntryAction {
switch (this) {
case EntryAction.debug:
return ShaderMask(
shaderCallback: AColors.debugGradient.createShader,
shaderCallback: AvesColorsData.debugGradient.createShader,
blendMode: BlendMode.srcIn,
child: child,
);
default:

View file

@ -55,7 +55,8 @@ extension ExtraEntryInfoAction on EntryInfoAction {
switch (this) {
case EntryInfoAction.debug:
return ShaderMask(
shaderCallback: AColors.debugGradient.createShader,
shaderCallback: AvesColorsData.debugGradient.createShader,
blendMode: BlendMode.srcIn,
child: child,
);
default:

View file

@ -1,4 +1,3 @@
import 'package:aves/image_providers/app_icon_image_provider.dart';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/theme/colors.dart';
@ -7,13 +6,11 @@ import 'package:aves/utils/android_file_utils.dart';
import 'package:aves/widgets/common/identity/aves_icons.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:palette_generator/palette_generator.dart';
import 'package:provider/provider.dart';
class AlbumFilter extends CollectionFilter {
static const type = 'album';
static final Map<String, Color> _appColors = {};
final String album;
final String? displayName;
@ -56,6 +53,7 @@ class AlbumFilter extends CollectionFilter {
@override
Future<Color> color(BuildContext context) {
final colors = context.watch<AvesColorsData>();
// do not use async/await and rely on `SynchronousFuture`
// to prevent rebuilding of the `FutureBuilder` listening on this future
final albumType = androidFileUtils.getAlbumType(album);
@ -63,31 +61,19 @@ class AlbumFilter extends CollectionFilter {
case AlbumType.regular:
break;
case AlbumType.app:
if (_appColors.containsKey(album)) return SynchronousFuture(_appColors[album]!);
final packageName = androidFileUtils.getAlbumAppPackageName(album);
if (packageName != null) {
return PaletteGenerator.fromImageProvider(
AppIconImage(packageName: packageName, size: 24),
).then((palette) async {
// `dominantColor` is most representative but can have low contrast with a dark background
// `vibrantColor` is usually representative and has good contrast with a dark background
final color = palette.vibrantColor?.color ?? (await super.color(context));
_appColors[album] = color;
return color;
});
}
final appColor = colors.appColor(album);
if (appColor != null) return appColor;
break;
case AlbumType.camera:
return SynchronousFuture(AColors.albumCamera);
return SynchronousFuture(colors.albumCamera);
case AlbumType.download:
return SynchronousFuture(AColors.albumDownload);
return SynchronousFuture(colors.albumDownload);
case AlbumType.screenRecordings:
return SynchronousFuture(AColors.albumScreenRecordings);
return SynchronousFuture(colors.albumScreenRecordings);
case AlbumType.screenshots:
return SynchronousFuture(AColors.albumScreenshots);
return SynchronousFuture(colors.albumScreenshots);
case AlbumType.videoCaptures:
return SynchronousFuture(AColors.albumVideoCaptures);
return SynchronousFuture(colors.albumVideoCaptures);
}
return super.color(context);
}

View file

@ -4,6 +4,7 @@ import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class FavouriteFilter extends CollectionFilter {
static const type = 'favourite';
@ -33,7 +34,10 @@ class FavouriteFilter extends CollectionFilter {
Widget iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(AIcons.favourite, size: size);
@override
Future<Color> color(BuildContext context) => SynchronousFuture(AColors.favourite);
Future<Color> color(BuildContext context) {
final colors = context.watch<AvesColorsData>();
return SynchronousFuture(colors.favourite);
}
@override
String get category => type;

View file

@ -12,11 +12,12 @@ import 'package:aves/model/filters/rating.dart';
import 'package:aves/model/filters/tag.dart';
import 'package:aves/model/filters/trash.dart';
import 'package:aves/model/filters/type.dart';
import 'package:aves/utils/color_utils.dart';
import 'package:aves/theme/colors.dart';
import 'package:collection/collection.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';
@immutable
abstract class CollectionFilter extends Equatable implements Comparable<CollectionFilter> {
@ -93,7 +94,10 @@ abstract class CollectionFilter extends Equatable implements Comparable<Collecti
Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => null;
Future<Color> color(BuildContext context) => SynchronousFuture(stringToColor(getLabel(context)));
Future<Color> color(BuildContext context) {
final colors = context.watch<AvesColorsData>();
return SynchronousFuture(colors.fromString(getLabel(context)));
}
String get category;

View file

@ -1,12 +1,14 @@
import 'dart:async';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/ref/mime_types.dart';
import 'package:aves/theme/colors.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/utils/color_utils.dart';
import 'package:aves/utils/mime_utils.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';
class MimeFilter extends CollectionFilter {
static const type = 'mime';
@ -15,7 +17,6 @@ class MimeFilter extends CollectionFilter {
late final EntryFilter _test;
late final String _label;
late final IconData _icon;
late final Color _color;
static final image = MimeFilter(MimeTypes.anyImage);
static final video = MimeFilter(MimeTypes.anyVideo);
@ -25,7 +26,6 @@ class MimeFilter extends CollectionFilter {
MimeFilter(this.mime) {
IconData? icon;
Color? color;
var lowMime = mime.toLowerCase();
if (lowMime.endsWith('/*')) {
lowMime = lowMime.substring(0, lowMime.length - 2);
@ -33,17 +33,14 @@ class MimeFilter extends CollectionFilter {
_label = lowMime.toUpperCase();
if (mime == MimeTypes.anyImage) {
icon = AIcons.image;
color = AColors.image;
} else if (mime == MimeTypes.anyVideo) {
icon = AIcons.video;
color = AColors.video;
}
} else {
_test = (entry) => entry.mimeType == lowMime;
_label = MimeUtils.displayType(lowMime);
}
_icon = icon ?? AIcons.vector;
_color = color ?? stringToColor(_label);
}
MimeFilter.fromMap(Map<String, dynamic> json)
@ -79,7 +76,17 @@ class MimeFilter extends CollectionFilter {
Widget iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(_icon, size: size);
@override
Future<Color> color(BuildContext context) => SynchronousFuture(_color);
Future<Color> color(BuildContext context) {
final colors = context.watch<AvesColorsData>();
switch (mime) {
case MimeTypes.anyImage:
return SynchronousFuture(colors.image);
case MimeTypes.anyVideo:
return SynchronousFuture(colors.video);
default:
return SynchronousFuture(colors.fromString(_label));
}
}
@override
String get category => type;

View file

@ -1,10 +1,11 @@
import 'package:aves/model/entry.dart';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/theme/colors.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';
class QueryFilter extends CollectionFilter {
static const type = 'query';
@ -67,7 +68,14 @@ class QueryFilter extends CollectionFilter {
Widget iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(AIcons.text, size: size);
@override
Future<Color> color(BuildContext context) => colorful ? super.color(context) : SynchronousFuture(AvesFilterChip.defaultOutlineColor);
Future<Color> color(BuildContext context) {
if (colorful) {
return super.color(context);
}
final colors = context.watch<AvesColorsData>();
return SynchronousFuture(colors.neutral);
}
@override
String get category => type;

View file

@ -4,6 +4,7 @@ import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';
class TypeFilter extends CollectionFilter {
static const type = 'type';
@ -18,7 +19,6 @@ class TypeFilter extends CollectionFilter {
final String itemType;
late final EntryFilter _test;
late final IconData _icon;
late final Color _color;
static final animated = TypeFilter._private(_animated);
static final geotiff = TypeFilter._private(_geotiff);
@ -35,32 +35,26 @@ class TypeFilter extends CollectionFilter {
case _animated:
_test = (entry) => entry.isAnimated;
_icon = AIcons.animated;
_color = AColors.animated;
break;
case _geotiff:
_test = (entry) => entry.isGeotiff;
_icon = AIcons.geo;
_color = AColors.geotiff;
break;
case _motionPhoto:
_test = (entry) => entry.isMotionPhoto;
_icon = AIcons.motionPhoto;
_color = AColors.motionPhoto;
break;
case _panorama:
_test = (entry) => entry.isImage && entry.is360;
_icon = AIcons.threeSixty;
_color = AColors.panorama;
break;
case _raw:
_test = (entry) => entry.isRaw;
_icon = AIcons.raw;
_color = AColors.raw;
break;
case _sphericalVideo:
_test = (entry) => entry.isVideo && entry.is360;
_icon = AIcons.threeSixty;
_color = AColors.sphericalVideo;
break;
}
}
@ -106,7 +100,24 @@ class TypeFilter extends CollectionFilter {
Widget iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(_icon, size: size);
@override
Future<Color> color(BuildContext context) => SynchronousFuture(_color);
Future<Color> color(BuildContext context) {
final colors = context.watch<AvesColorsData>();
switch (itemType) {
case _animated:
return SynchronousFuture(colors.animated);
case _geotiff:
return SynchronousFuture(colors.geotiff);
case _motionPhoto:
return SynchronousFuture(colors.motionPhoto);
case _panorama:
return SynchronousFuture(colors.panorama);
case _raw:
return SynchronousFuture(colors.raw);
case _sphericalVideo:
return SynchronousFuture(colors.sphericalVideo);
}
return super.color(context);
}
@override
String get category => type;

View file

@ -16,6 +16,8 @@ class SettingsDefaults {
static const isInstalledAppAccessAllowed = false;
static const isErrorReportingAllowed = false;
static const tileLayout = TileLayout.grid;
static const themeBrightness = AvesThemeBrightness.system;
static const themeColorMode = AvesThemeColorMode.polychrome;
// navigation
static const mustBackTwiceToExit = true;

View file

@ -2,6 +2,10 @@ enum AccessibilityAnimations { system, disabled, enabled }
enum AccessibilityTimeout { system, appDefault, s10, s30, s60, s120 }
enum AvesThemeColorMode { monochrome, polychrome }
enum AvesThemeBrightness { system, light, dark, black }
enum ConfirmationDialog { delete, moveToBin }
enum CoordinateFormat { dms, decimal }

View file

@ -0,0 +1,30 @@
import 'package:flutter/material.dart';
import 'enums.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
extension ExtraAvesThemeBrightness on AvesThemeBrightness {
String getName(BuildContext context) {
switch (this) {
case AvesThemeBrightness.system:
return context.l10n.settingsSystemDefault;
case AvesThemeBrightness.light:
return context.l10n.themeBrightnessLight;
case AvesThemeBrightness.dark:
return context.l10n.themeBrightnessDark;
case AvesThemeBrightness.black:
return context.l10n.themeBrightnessBlack;
}
}
ThemeMode get appThemeMode {
switch (this) {
case AvesThemeBrightness.system:
return ThemeMode.system;
case AvesThemeBrightness.light:
return ThemeMode.light;
case AvesThemeBrightness.dark:
case AvesThemeBrightness.black:
return ThemeMode.dark;
}
}
}

View file

@ -41,6 +41,8 @@ class Settings extends ChangeNotifier {
static const isInstalledAppAccessAllowedKey = 'is_installed_app_access_allowed';
static const isErrorReportingAllowedKey = 'is_crashlytics_enabled';
static const localeKey = 'locale';
static const themeBrightnessKey = 'theme_brightness';
static const themeColorModeKey = 'theme_color_mode';
static const catalogTimeZoneKey = 'catalog_time_zone';
static const tileExtentPrefixKey = 'tile_extent_';
static const tileLayoutPrefixKey = 'tile_layout_';
@ -239,6 +241,14 @@ class Settings extends ChangeNotifier {
return _appliedLocale!;
}
AvesThemeBrightness get themeBrightness => getEnumOrDefault(themeBrightnessKey, SettingsDefaults.themeBrightness, AvesThemeBrightness.values);
set themeBrightness(AvesThemeBrightness newValue) => setAndNotify(themeBrightnessKey, newValue.toString());
AvesThemeColorMode get themeColorMode => getEnumOrDefault(themeColorModeKey, SettingsDefaults.themeColorMode, AvesThemeColorMode.values);
set themeColorMode(AvesThemeColorMode newValue) => setAndNotify(themeColorModeKey, newValue.toString());
String get catalogTimeZone => getString(catalogTimeZoneKey) ?? '';
set catalogTimeZone(String newValue) => setAndNotify(catalogTimeZoneKey, newValue);
@ -675,6 +685,8 @@ class Settings extends ChangeNotifier {
}
break;
case localeKey:
case themeBrightnessKey:
case themeColorModeKey:
case keepScreenOnKey:
case homePageKey:
case collectionGroupFactorKey:

View file

@ -325,4 +325,7 @@ class CollectionLens with ChangeNotifier {
sections = Map.unmodifiable(Map.fromEntries(sections.entries.where((kv) => kv.value.isNotEmpty)));
notifyListeners();
}
@override
String toString() => '$runtimeType#${shortHash(this)}{id=$id, source=$source, filters=$filters, entryCount=$entryCount}';
}

View file

@ -1,37 +1,125 @@
import 'package:aves/utils/color_utils.dart';
import 'package:aves/image_providers/app_icon_image_provider.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/utils/android_file_utils.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:palette_generator/palette_generator.dart';
import 'package:provider/provider.dart';
class AvesColorsProvider extends StatelessWidget {
final Widget child;
const AvesColorsProvider({
Key? key,
required this.child,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return ProxyProvider<Settings, AvesColorsData>(
update: (context, settings, __) {
final isDark = Theme.of(context).brightness == Brightness.dark;
switch (settings.themeColorMode) {
case AvesThemeColorMode.monochrome:
return isDark ? _MonochromeOnDark() : _MonochromeOnLight();
case AvesThemeColorMode.polychrome:
return isDark ? NeonOnDark() : PastelOnLight();
}
},
child: child,
);
}
}
abstract class AvesColorsData {
Color get neutral;
Color fromHue(double hue);
Color? fromBrandColor(Color? color);
final Map<String, Color> _stringColors = {}, _appColors = {};
Color fromString(String string) {
var color = _stringColors[string];
if (color == null) {
final hash = string.codeUnits.fold<int>(0, (prev, el) => prev = el + ((prev << 5) - prev));
final hue = (hash % 360).toDouble();
color = fromHue(hue);
_stringColors[string] = color;
}
return color;
}
Future<Color>? appColor(String album) {
if (_appColors.containsKey(album)) return SynchronousFuture(_appColors[album]!);
final packageName = androidFileUtils.getAlbumAppPackageName(album);
if (packageName == null) return null;
return PaletteGenerator.fromImageProvider(
AppIconImage(packageName: packageName, size: 24),
).then((palette) async {
// `dominantColor` is most representative but can have low contrast with a dark background
// `vibrantColor` is usually representative and has good contrast with a dark background
final color = palette.vibrantColor?.color ?? fromString(album);
_appColors[album] = color;
return color;
});
}
static const Color _neutralOnDark = Colors.white;
static const Color _neutralOnLight = Color(0xAA000000);
class AColors {
// mime
static final image = stringToColor('Image');
static final video = stringToColor('Video');
Color get image => fromHue(243);
Color get video => fromHue(323);
// type
static const favourite = Colors.red;
static final animated = stringToColor('Animated');
static final geotiff = stringToColor('GeoTIFF');
static final motionPhoto = stringToColor('Motion Photo');
static final panorama = stringToColor('Panorama');
static final raw = stringToColor('Raw');
static final sphericalVideo = stringToColor('360° Video');
Color get favourite => fromHue(0);
Color get animated => fromHue(83);
Color get geotiff => fromHue(70);
Color get motionPhoto => fromHue(104);
Color get panorama => fromHue(5);
Color get raw => fromHue(208);
Color get sphericalVideo => fromHue(174);
// albums
static final albumCamera = stringToColor('Camera');
static final albumDownload = stringToColor('Download');
static final albumScreenshots = stringToColor('Screenshots');
static final albumScreenRecordings = stringToColor('Screen recordings');
static final albumVideoCaptures = stringToColor('Video Captures');
Color get albumCamera => fromHue(165);
Color get albumDownload => fromHue(104);
Color get albumScreenshots => fromHue(149);
Color get albumScreenRecordings => fromHue(222);
Color get albumVideoCaptures => fromHue(266);
// info
static final xmp = stringToColor('XMP');
Color get xmp => fromHue(275);
// settings
static final accessibility = stringToColor('Accessibility');
static final language = stringToColor('Language');
static final navigation = stringToColor('Navigation');
static final privacy = stringToColor('Privacy');
static final thumbnails = stringToColor('Thumbnails');
Color get accessibility => fromHue(134);
Color get display => fromHue(50);
Color get language => fromHue(264);
Color get navigation => fromHue(140);
Color get privacy => fromHue(344);
Color get thumbnails => fromHue(87);
// debug
static const debugGradient = LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
@ -41,3 +129,51 @@ class AColors {
],
);
}
abstract class _Monochrome extends AvesColorsData {
@override
Color fromHue(double hue) => neutral;
@override
Color? fromBrandColor(Color? color) => neutral;
@override
Color fromString(String string) => neutral;
@override
Future<Color>? appColor(String album) => SynchronousFuture(neutral);
}
class _MonochromeOnDark extends _Monochrome {
@override
Color get neutral => AvesColorsData._neutralOnDark;
}
class _MonochromeOnLight extends _Monochrome {
@override
Color get neutral => AvesColorsData._neutralOnLight;
}
class NeonOnDark extends AvesColorsData {
@override
Color get neutral => AvesColorsData._neutralOnDark;
@override
Color fromHue(double hue) => HSLColor.fromAHSL(1.0, hue, .8, .6).toColor();
@override
Color? fromBrandColor(Color? color) => color;
}
class PastelOnLight extends AvesColorsData {
@override
Color get neutral => AvesColorsData._neutralOnLight;
@override
Color fromHue(double hue) => _pastellize(HSLColor.fromAHSL(1.0, hue, .8, .6).toColor());
@override
Color? fromBrandColor(Color? color) => color != null ? _pastellize(color) : null;
Color _pastellize(Color color) => Color.lerp(color, Colors.white, .5)!;
}

View file

@ -47,6 +47,7 @@ class Durations {
static const tagEditorTransition = Duration(milliseconds: 200);
// settings animations
static const themeColorModeAnimation = Duration(milliseconds: 400);
static const quickActionListAnimation = Duration(milliseconds: 200);
static const quickActionHighlightAnimation = Duration(milliseconds: 200);

View file

@ -14,6 +14,7 @@ class AIcons {
static const IconData checked = Icons.done_outlined;
static const IconData date = Icons.calendar_today_outlined;
static const IconData disc = Icons.fiber_manual_record;
static const IconData display = Icons.light_mode_outlined;
static const IconData error = Icons.error_outline;
static const IconData folder = Icons.folder_outlined;
static const IconData grid = Icons.grid_on_outlined;

View file

@ -1,50 +1,181 @@
import 'dart:ui';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
class Themes {
static const _accentColor = Colors.indigoAccent;
static final darkTheme = ThemeData(
brightness: Brightness.dark,
// canvas color is used as background for the drawer and popups
// when using a popup menu on a dialog, lighten the background via `PopupMenuTheme`
canvasColor: Colors.grey[850],
scaffoldBackgroundColor: Colors.grey.shade900,
dialogBackgroundColor: Colors.grey[850],
indicatorColor: _accentColor,
toggleableActiveColor: _accentColor,
tooltipTheme: const TooltipThemeData(
static const _tooltipTheme = TooltipThemeData(
verticalOffset: 32,
),
appBarTheme: AppBarTheme(
backgroundColor: Colors.grey.shade900,
titleTextStyle: const TextStyle(
);
static const _appBarTitleTextStyle = TextStyle(
fontSize: 20,
fontWeight: FontWeight.normal,
fontFeatures: [FontFeature.enable('smcp')],
),
),
colorScheme: ColorScheme.dark(
);
static const _snackBarTheme = SnackBarThemeData(
actionTextColor: _accentColor,
behavior: SnackBarBehavior.floating,
);
static final _typography = Typography.material2018(platform: TargetPlatform.android);
static final _lightThemeTypo = _typography.black;
static final _lightTitleColor = _lightThemeTypo.titleMedium!.color!;
static final _lightBodyColor = _lightThemeTypo.bodyMedium!.color!;
static final _lightLabelColor = _lightThemeTypo.labelMedium!.color!;
static const _lightActionIconColor = Color(0xAA000000);
static const _lightFirstLayer = Color(0xFFFAFAFA); // aka `Colors.grey[50]`
static const _lightSecondLayer = Color(0xFFF5F5F5); // aka `Colors.grey[100]`
static const _lightThirdLayer = Color(0xFFEEEEEE); // aka `Colors.grey[200]`
static final lightTheme = ThemeData(
colorScheme: ColorScheme.light(
primary: _accentColor,
secondary: _accentColor,
// surface color is used as background for the date picker header
surface: Colors.grey.shade800,
onPrimary: Colors.white,
onSecondary: Colors.white,
onPrimary: _lightBodyColor,
onSecondary: _lightBodyColor,
),
snackBarTheme: SnackBarThemeData(
backgroundColor: Colors.grey.shade800,
actionTextColor: _accentColor,
contentTextStyle: const TextStyle(
color: Colors.white,
brightness: Brightness.light,
// `canvasColor` is used by `Drawer`, `DropdownButton` and `ExpansionTileCard`
canvasColor: _lightSecondLayer,
scaffoldBackgroundColor: _lightFirstLayer,
// `cardColor` is used by `ExpansionPanel`
cardColor: _lightSecondLayer,
dialogBackgroundColor: _lightSecondLayer,
indicatorColor: _accentColor,
toggleableActiveColor: _accentColor,
typography: _typography,
appBarTheme: AppBarTheme(
backgroundColor: _lightFirstLayer,
// `foregroundColor` is used by icons
foregroundColor: _lightActionIconColor,
// `titleTextStyle.color` is used by text
titleTextStyle: _appBarTitleTextStyle.copyWith(color: _lightTitleColor),
systemOverlayStyle: SystemUiOverlayStyle.dark,
),
behavior: SnackBarBehavior.floating,
listTileTheme: const ListTileThemeData(
iconColor: _lightActionIconColor,
),
popupMenuTheme: const PopupMenuThemeData(
color: _lightSecondLayer,
),
snackBarTheme: _snackBarTheme,
tabBarTheme: TabBarTheme(
labelColor: _lightTitleColor,
unselectedLabelColor: Colors.black54,
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
primary: Colors.white,
primary: _lightLabelColor,
),
),
tooltipTheme: _tooltipTheme,
);
static final _darkThemeTypo = _typography.white;
static final _darkTitleColor = _darkThemeTypo.titleMedium!.color!;
static final _darkBodyColor = _darkThemeTypo.bodyMedium!.color!;
static final _darkLabelColor = _darkThemeTypo.labelMedium!.color!;
static const _darkFirstLayer = Color(0xFF212121); // aka `Colors.grey[900]`
static const _darkSecondLayer = Color(0xFF363636);
static const _darkThirdLayer = Color(0xFF424242); // aka `Colors.grey[800]`
static final darkTheme = ThemeData(
colorScheme: ColorScheme.dark(
primary: _accentColor,
secondary: _accentColor,
// surface color is used by the date/time pickers
surface: Colors.grey.shade800,
onPrimary: _darkBodyColor,
onSecondary: _darkBodyColor,
),
brightness: Brightness.dark,
// `canvasColor` is used by `Drawer`, `DropdownButton` and `ExpansionTileCard`
canvasColor: _darkSecondLayer,
scaffoldBackgroundColor: _darkFirstLayer,
// `cardColor` is used by `ExpansionPanel`
cardColor: _darkSecondLayer,
dialogBackgroundColor: _darkSecondLayer,
indicatorColor: _accentColor,
toggleableActiveColor: _accentColor,
typography: _typography,
appBarTheme: AppBarTheme(
backgroundColor: _darkFirstLayer,
// `foregroundColor` is used by icons
foregroundColor: _darkTitleColor,
// `titleTextStyle.color` is used by text
titleTextStyle: _appBarTitleTextStyle.copyWith(color: _darkTitleColor),
systemOverlayStyle: SystemUiOverlayStyle.light,
),
popupMenuTheme: const PopupMenuThemeData(
color: _darkSecondLayer,
),
snackBarTheme: _snackBarTheme.copyWith(
backgroundColor: Colors.grey.shade800,
contentTextStyle: TextStyle(
color: _darkBodyColor,
),
),
tabBarTheme: TabBarTheme(
labelColor: _darkTitleColor,
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
primary: _darkLabelColor,
),
),
tooltipTheme: _tooltipTheme,
);
static const _blackFirstLayer = Colors.black;
static const _blackSecondLayer = Color(0xFF212121); // aka `Colors.grey[900]`
static const _blackThirdLayer = Color(0xFF303030); // aka `Colors.grey[850]`
static final blackTheme = darkTheme.copyWith(
// `canvasColor` is used by `Drawer`, `DropdownButton` and `ExpansionTileCard`
canvasColor: _blackSecondLayer,
scaffoldBackgroundColor: _blackFirstLayer,
// `cardColor` is used by `ExpansionPanel`
cardColor: _blackSecondLayer,
dialogBackgroundColor: _blackSecondLayer,
appBarTheme: darkTheme.appBarTheme.copyWith(
backgroundColor: _blackFirstLayer,
),
popupMenuTheme: darkTheme.popupMenuTheme.copyWith(
color: _blackSecondLayer,
),
);
static Color overlayBackgroundColor({
required Brightness brightness,
required bool blurred,
}) {
switch (brightness) {
case Brightness.dark:
return blurred ? Colors.black26 : Colors.black45;
case Brightness.light:
return blurred ? Colors.white54 : const Color(0xCCFFFFFF);
}
}
static Color thirdLayerColor(BuildContext context) {
final isBlack = context.select<Settings, bool>((v) => v.themeBrightness == AvesThemeBrightness.black);
if (isBlack) {
return _blackThirdLayer;
} else {
switch (Theme.of(context).brightness) {
case Brightness.dark:
return _darkThirdLayer;
case Brightness.light:
return _lightThirdLayer;
}
}
}
}

View file

@ -1,14 +0,0 @@
import 'package:flutter/material.dart';
final Map<String, Color> _stringColors = {};
Color stringToColor(String string, {double saturation = .8, double lightness = .6}) {
var color = _stringColors[string];
if (color == null) {
final hash = string.codeUnits.fold<int>(0, (prev, el) => prev = el + ((prev << 5) - prev));
final hue = (hash % 360).toDouble();
color = HSLColor.fromAHSL(1.0, hue, saturation, lightness).toColor();
_stringColors[string] = color;
}
return color;
}

View file

@ -53,7 +53,7 @@ class _AppReferenceState extends State<AppReference> {
mainAxisSize: MainAxisSize.min,
children: [
AvesLogo(
size: style.fontSize! * MediaQuery.textScaleFactorOf(context) * 1.25,
size: style.fontSize! * MediaQuery.textScaleFactorOf(context) * 1.3,
),
const SizedBox(width: 8),
Text(

View file

@ -4,9 +4,11 @@ import 'dart:typed_data';
import 'package:aves/app_flavor.dart';
import 'package:aves/flutter_version.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/ref/mime_types.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/theme/colors.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/common/action_mixins/feedback.dart';
@ -72,16 +74,19 @@ class _BugReportState extends State<BugReport> with FeedbackMixin {
builder: (context, snapshot) {
final info = snapshot.data;
if (info == null) return const SizedBox();
final theme = Theme.of(context);
return Container(
decoration: BoxDecoration(
color: Colors.grey.shade800,
color: theme.cardColor,
border: Border.all(
color: Colors.white,
color: theme.colorScheme.onPrimary,
),
borderRadius: const BorderRadius.all(Radius.circular(8)),
),
constraints: const BoxConstraints(maxHeight: 100),
margin: const EdgeInsets.symmetric(vertical: 8),
clipBehavior: Clip.antiAlias,
child: Theme(
data: Theme.of(context).copyWith(
scrollbarTheme: const ScrollbarThemeData(
@ -115,13 +120,14 @@ class _BugReportState extends State<BugReport> with FeedbackMixin {
),
isExpanded: _showInstructions,
canTapOnHeader: true,
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
backgroundColor: Colors.transparent,
),
],
);
}
Widget _buildStep(int step, String text, String buttonText, VoidCallback onPressed) {
final isMonochrome = settings.themeColorMode == AvesThemeColorMode.monochrome;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
@ -130,7 +136,7 @@ class _BugReportState extends State<BugReport> with FeedbackMixin {
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
border: Border.fromBorderSide(BorderSide(
color: Theme.of(context).colorScheme.secondary,
color: isMonochrome ? context.select<AvesColorsData, Color>((v) => v.neutral) : Theme.of(context).colorScheme.secondary,
width: AvesFilterChip.outlineWidth,
)),
shape: BoxShape.circle,
@ -162,7 +168,7 @@ class _BugReportState extends State<BugReport> with FeedbackMixin {
'Device: ${androidInfo.manufacturer} ${androidInfo.model}',
'Google Play services: ${hasPlayServices ? 'ready' : 'not available'}',
'System locales: ${WidgetsBinding.instance!.window.locales.join(', ')}',
'Aves locale: ${settings.locale} -> ${settings.appliedLocale}',
'Aves locale: ${settings.locale ?? 'system'} -> ${settings.appliedLocale}',
].join('\n');
}

View file

@ -1,5 +1,6 @@
import 'package:aves/app_flavor.dart';
import 'package:aves/ref/brand_colors.dart';
import 'package:aves/theme/colors.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/common/basic/link_chip.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
@ -40,6 +41,7 @@ class _LicensesState extends State<Licenses> {
@override
Widget build(BuildContext context) {
final colors = context.watch<AvesColorsData>();
return SliverPadding(
padding: const EdgeInsets.symmetric(horizontal: 8),
sliver: SliverList(
@ -49,25 +51,25 @@ class _LicensesState extends State<Licenses> {
const SizedBox(height: 16),
AvesExpansionTile(
title: context.l10n.aboutLicensesAndroidLibraries,
color: BrandColors.android,
highlightColor: colors.fromBrandColor(BrandColors.android),
expandedNotifier: _expandedNotifier,
children: _platform.map((package) => LicenseRow(package: package)).toList(),
),
AvesExpansionTile(
title: context.l10n.aboutLicensesFlutterPlugins,
color: BrandColors.flutter,
highlightColor: colors.fromBrandColor(BrandColors.flutter),
expandedNotifier: _expandedNotifier,
children: _flutterPlugins.map((package) => LicenseRow(package: package)).toList(),
),
AvesExpansionTile(
title: context.l10n.aboutLicensesFlutterPackages,
color: BrandColors.flutter,
highlightColor: colors.fromBrandColor(BrandColors.flutter),
expandedNotifier: _expandedNotifier,
children: _flutterPackages.map((package) => LicenseRow(package: package)).toList(),
),
AvesExpansionTile(
title: context.l10n.aboutLicensesDartPackages,
color: BrandColors.flutter,
highlightColor: colors.fromBrandColor(BrandColors.flutter),
expandedNotifier: _expandedNotifier,
children: _dartPackages.map((package) => LicenseRow(package: package)).toList(),
),

View file

@ -6,13 +6,16 @@ import 'package:aves/app_mode.dart';
import 'package:aves/l10n/l10n.dart';
import 'package:aves/model/device.dart';
import 'package:aves/model/settings/enums/accessibility_animations.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/enums/screen_on.dart';
import 'package:aves/model/settings/enums/theme_brightness.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/model/source/collection_source.dart';
import 'package:aves/model/source/media_store_source.dart';
import 'package:aves/services/accessibility_service.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/theme/colors.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/theme/themes.dart';
@ -101,11 +104,16 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
: Scaffold(
body: snapshot.hasError ? _buildError(snapshot.error!) : const SizedBox(),
);
return Selector<Settings, Tuple2<Locale?, bool>>(
selector: (context, s) => Tuple2(s.locale, s.initialized ? s.accessibilityAnimations.animate : true),
return Selector<Settings, Tuple3<Locale?, bool, AvesThemeBrightness>>(
selector: (context, s) => Tuple3(
s.locale,
s.initialized ? s.accessibilityAnimations.animate : true,
s.initialized ? s.themeBrightness : AvesThemeBrightness.system,
),
builder: (context, s, child) {
final settingsLocale = s.item1;
final areAnimationsEnabled = s.item2;
final themeBrightness = s.item3;
return MaterialApp(
navigatorKey: _navigatorKey,
home: home,
@ -126,11 +134,15 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
child: child!,
);
}
return child!;
return AvesColorsProvider(
child: child!,
);
// return child!;
},
onGenerateTitle: (context) => context.l10n.appName,
darkTheme: Themes.darkTheme,
themeMode: ThemeMode.dark,
theme: Themes.lightTheme,
darkTheme: themeBrightness == AvesThemeBrightness.black ? Themes.blackTheme : Themes.darkTheme,
themeMode: themeBrightness.appThemeMode,
locale: settingsLocale,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,

View file

@ -1,11 +1,15 @@
import 'dart:async';
import 'package:aves/model/entry.dart';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/filters/query.dart';
import 'package:aves/model/filters/trash.dart';
import 'package:aves/model/highlight.dart';
import 'package:aves/model/selection.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/model/source/collection_source.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/widgets/collection/collection_grid.dart';
import 'package:aves/widgets/common/basic/insets.dart';
import 'package:aves/widgets/common/behaviour/double_back_pop.dart';
@ -21,11 +25,15 @@ import 'package:provider/provider.dart';
class CollectionPage extends StatefulWidget {
static const routeName = '/collection';
final CollectionLens collection;
final CollectionSource source;
final Set<CollectionFilter?>? filters;
final bool Function(AvesEntry element)? highlightTest;
const CollectionPage({
Key? key,
required this.collection,
required this.source,
required this.filters,
this.highlightTest,
}) : super(key: key);
@override
@ -34,17 +42,23 @@ class CollectionPage extends StatefulWidget {
class _CollectionPageState extends State<CollectionPage> {
final List<StreamSubscription> _subscriptions = [];
CollectionLens get collection => widget.collection;
late CollectionLens _collection;
@override
void initState() {
// do not seed this widget with the collection, but control its lifecycle here instead,
// as the collection properties may change and they should not be reset by a widget update (e.g. with theme change)
_collection = CollectionLens(
source: widget.source,
filters: widget.filters,
);
super.initState();
_subscriptions.add(settings.updateStream.where((event) => event.key == Settings.enableBinKey).listen((_) {
if (!settings.enableBin) {
collection.removeFilter(TrashFilter.instance);
_collection.removeFilter(TrashFilter.instance);
}
}));
WidgetsBinding.instance!.addPostFrameCallback((_) => _checkInitHighlight());
}
@override
@ -52,13 +66,13 @@ class _CollectionPageState extends State<CollectionPage> {
_subscriptions
..forEach((sub) => sub.cancel())
..clear();
collection.dispose();
_collection.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final liveFilter = collection.filters.firstWhereOrNull((v) => v is QueryFilter && v.live) as QueryFilter?;
final liveFilter = _collection.filters.firstWhereOrNull((v) => v is QueryFilter && v.live) as QueryFilter?;
return MediaQueryDataProvider(
child: Scaffold(
body: SelectionProvider<AvesEntry>(
@ -79,7 +93,7 @@ class _CollectionPageState extends State<CollectionPage> {
child: SafeArea(
bottom: false,
child: ChangeNotifierProvider<CollectionLens>.value(
value: collection,
value: _collection,
child: const CollectionGrid(
// key is expected by test driver
key: Key('collection-grid'),
@ -93,9 +107,21 @@ class _CollectionPageState extends State<CollectionPage> {
),
),
),
drawer: AppDrawer(currentCollection: collection),
drawer: AppDrawer(currentCollection: _collection),
resizeToAvoidBottomInset: false,
),
);
}
Future<void> _checkInitHighlight() async {
final highlightTest = widget.highlightTest;
if (highlightTest == null) return;
final delayDuration = context.read<DurationsData>().staggeredAnimationPageTarget;
await Future.delayed(delayDuration + Durations.highlightScrollInitDelay);
final targetEntry = _collection.sortedEntries.firstWhereOrNull(highlightTest);
if (targetEntry != null) {
context.read<HighlightInfo>().trackItem(targetEntry, highlightItem: targetEntry);
}
}
}

View file

@ -20,25 +20,20 @@ class AlbumSectionHeader extends StatelessWidget {
@override
Widget build(BuildContext context) {
Widget? albumIcon;
if (directory != null) {
albumIcon = IconUtils.getAlbumIcon(context: context, albumPath: directory!);
final _directory = directory;
if (_directory != null) {
albumIcon = IconUtils.getAlbumIcon(context: context, albumPath: _directory);
if (albumIcon != null) {
albumIcon = RepaintBoundary(
child: Material(
type: MaterialType.circle,
elevation: 3,
color: Colors.transparent,
shadowColor: Colors.black,
child: albumIcon,
),
);
}
}
return SectionHeader<AvesEntry>(
sectionKey: EntryAlbumSectionKey(directory),
sectionKey: EntryAlbumSectionKey(_directory),
leading: albumIcon,
title: albumName ?? context.l10n.sectionUnknown,
trailing: directory != null && androidFileUtils.isOnRemovableStorage(directory!)
trailing: _directory != null && androidFileUtils.isOnRemovableStorage(_directory)
? const Icon(
AIcons.removableStorage,
size: 16,

View file

@ -25,7 +25,7 @@ class EntryListDetails extends StatelessWidget {
return Container(
padding: EntryListDetailsTheme.contentPadding,
foregroundDecoration: BoxDecoration(
border: Border(top: AvesBorder.straightSide),
border: Border(top: AvesBorder.straightSide(context)),
),
margin: EntryListDetailsTheme.contentMargin,
child: IconTheme.merge(

View file

@ -150,43 +150,38 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
action = SnackBarAction(
label: l10n.showButtonLabel,
onPressed: () async {
late CollectionLens targetCollection;
final newUris = movedOps.map((v) => v.newFields['uri'] as String?).toSet();
bool highlightTest(AvesEntry entry) => newUris.contains(entry.uri);
final highlightInfo = context.read<HighlightInfo>();
final collection = context.read<CollectionLens?>();
if (collection != null) {
targetCollection = collection;
}
if (collection == null || collection.filters.any((f) => f is AlbumFilter || f is TrashFilter)) {
targetCollection = CollectionLens(
source: source,
filters: collection?.filters.where((f) => f != TrashFilter.instance).toSet(),
);
final targetFilters = collection?.filters.where((f) => f != TrashFilter.instance).toSet() ?? {};
// we could simply add the filter to the current collection
// but navigating makes the change less jarring
if (destinationAlbums.length == 1) {
final destinationAlbum = destinationAlbums.single;
final filter = AlbumFilter(destinationAlbum, source.getAlbumDisplayName(context, destinationAlbum));
targetCollection.addFilter(filter);
targetFilters.removeWhere((f) => f is AlbumFilter);
targetFilters.add(AlbumFilter(destinationAlbum, source.getAlbumDisplayName(context, destinationAlbum)));
}
unawaited(Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
settings: const RouteSettings(name: CollectionPage.routeName),
builder: (context) => CollectionPage(
collection: targetCollection,
source: source,
filters: targetFilters,
highlightTest: highlightTest,
),
),
(route) => false,
));
final delayDuration = context.read<DurationsData>().staggeredAnimationPageTarget;
await Future.delayed(delayDuration);
}
} else {
// track in current page, without navigation
await Future.delayed(Durations.highlightScrollInitDelay);
final newUris = movedOps.map((v) => v.newFields['uri'] as String?).toSet();
final targetEntry = targetCollection.sortedEntries.firstWhereOrNull((entry) => newUris.contains(entry.uri));
final targetEntry = collection.sortedEntries.firstWhereOrNull(highlightTest);
if (targetEntry != null) {
highlightInfo.trackItem(targetEntry, highlightItem: targetEntry);
context.read<HighlightInfo>().trackItem(targetEntry, highlightItem: targetEntry);
}
}
},
);

View file

@ -171,8 +171,8 @@ class _ReportOverlayState<T> extends State<ReportOverlay<T>> with SingleTickerPr
Container(
width: diameter + 2,
height: diameter + 2,
decoration: const BoxDecoration(
color: Color(0xBB000000),
decoration: BoxDecoration(
color: Theme.of(context).brightness == Brightness.dark ? const Color(0xBB000000) : const Color(0xEEFFFFFF),
shape: BoxShape.circle,
),
),
@ -190,7 +190,7 @@ class _ReportOverlayState<T> extends State<ReportOverlay<T>> with SingleTickerPr
percent: percent,
lineWidth: strokeWidth,
radius: diameter / 2,
backgroundColor: Colors.white24,
backgroundColor: Theme.of(context).colorScheme.onSurface.withOpacity(.2),
progressColor: progressColor,
animation: animate,
center: Text(
@ -270,6 +270,8 @@ class _FeedbackMessageState extends State<_FeedbackMessage> {
Widget build(BuildContext context) {
final text = Text(widget.message);
final duration = widget.duration;
final theme = Theme.of(context);
final contentTextStyle = theme.snackBarTheme.contentTextStyle ?? ThemeData(brightness: theme.brightness).textTheme.subtitle1;
return duration == null
? text
: Row(
@ -286,7 +288,10 @@ class _FeedbackMessageState extends State<_FeedbackMessage> {
progressColor: Colors.grey,
animation: true,
animationDuration: duration.inMilliseconds,
center: Text('$_remainingSecs'),
center: Text(
'$_remainingSecs',
style: contentTextStyle,
),
animateFromLastPercent: true,
reverse: true,
),

View file

@ -1,3 +1,5 @@
import 'dart:ui';
import 'package:aves/model/source/collection_source.dart';
import 'package:aves/model/source/enums.dart';
import 'package:aves/model/source/events.dart';
@ -62,25 +64,28 @@ class SourceStateSubtitle extends StatelessWidget {
final subtitle = sourceState.getName(context.l10n);
if (subtitle == null) return const SizedBox();
final subtitleStyle = Theme.of(context).textTheme.caption!;
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(subtitle, style: subtitleStyle),
ValueListenableBuilder<ProgressEvent>(
final theme = Theme.of(context);
return DefaultTextStyle.merge(
style: theme.textTheme.caption!.copyWith(fontFeatures: const [FontFeature.disable('smcp')]),
child: ValueListenableBuilder<ProgressEvent>(
valueListenable: source.progressNotifier,
builder: (context, progress, snapshot) {
if (progress.total == 0 || sourceState == SourceState.locatingCountries) return const SizedBox();
return Padding(
padding: const EdgeInsetsDirectional.only(start: 8),
child: Text(
'${progress.done}/${progress.total}',
style: subtitleStyle.copyWith(color: Colors.white30),
return Text.rich(
TextSpan(
children: [
TextSpan(text: subtitle),
if (progress.total != 0 && sourceState != SourceState.locatingCountries) ...[
const WidgetSpan(child: SizedBox(width: 8)),
TextSpan(
text: '${progress.done}/${progress.total}',
style: TextStyle(color: theme.brightness == Brightness.dark ? Colors.white30 : Colors.black26),
),
]
],
),
);
},
),
],
);
}
}

View file

@ -27,7 +27,7 @@ class ColorListTile extends StatelessWidget {
width: radius * 2,
decoration: BoxDecoration(
color: value,
border: AvesBorder.border,
border: AvesBorder.border(context),
shape: BoxShape.circle,
),
),

View file

@ -1,3 +1,4 @@
import 'package:aves/widgets/common/fx/borders.dart';
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:url_launcher/url_launcher.dart';
@ -18,9 +19,10 @@ class MarkdownContainer extends StatelessWidget {
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 8),
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(16)),
color: Colors.white10,
decoration: BoxDecoration(
color: Theme.of(context).canvasColor,
border: Border.all(color: Theme.of(context).dividerColor, width: AvesBorder.curvedBorderWidth),
borderRadius: const BorderRadius.all(Radius.circular(16)),
),
constraints: const BoxConstraints(maxWidth: maxWidth),
child: ClipRRect(

View file

@ -18,8 +18,13 @@ class MenuRow extends StatelessWidget {
children: [
if (icon != null)
Padding(
padding: const EdgeInsetsDirectional.only(end: 8),
child: icon,
padding: const EdgeInsetsDirectional.only(end: 12),
child: IconTheme.merge(
data: IconThemeData(
color: ListTileTheme.of(context).iconColor,
),
child: icon!,
),
),
Expanded(child: Text(text)),
],
@ -110,6 +115,7 @@ class _PopupMenuItemExpansionPanelState<T> extends State<PopupMenuItemExpansionP
),
isExpanded: _isExpanded,
canTapOnHeader: true,
backgroundColor: Colors.transparent,
),
],
);

View file

@ -6,6 +6,7 @@ import 'package:aves/widgets/common/basic/menu.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/fx/sweeper.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class FavouriteToggler extends StatefulWidget {
final Set<AvesEntry> entries;
@ -73,7 +74,10 @@ class _FavouriteTogglerState extends State<FavouriteToggler> {
),
Sweeper(
key: ValueKey(entries.length == 1 ? entries.first : entries.length),
builder: (context) => const Icon(AIcons.favourite, color: AColors.favourite),
builder: (context) => Icon(
AIcons.favourite,
color: context.select<AvesColorsData, Color>((v) => v.favourite),
),
toggledNotifier: isFavouriteNotifier,
),
],

View file

@ -3,7 +3,7 @@ import 'dart:ui';
import 'package:flutter/material.dart';
class AvesBorder {
static const borderColor = Colors.white30;
static Color _borderColor(BuildContext context) => Theme.of(context).brightness == Brightness.dark ? Colors.white30 : Colors.black26;
// directly uses `devicePixelRatio` as it never changes, to avoid visiting ancestors via `MediaQuery`
@ -13,15 +13,15 @@ class AvesBorder {
// 1 device pixel for curves is too thin
static double get curvedBorderWidth => window.devicePixelRatio > 2 ? 0.5 : 1.0;
static BorderSide get straightSide => BorderSide(
color: borderColor,
static BorderSide straightSide(BuildContext context) => BorderSide(
color: _borderColor(context),
width: straightBorderWidth,
);
static BorderSide get curvedSide => BorderSide(
color: borderColor,
static BorderSide curvedSide(BuildContext context) => BorderSide(
color: _borderColor(context),
width: curvedBorderWidth,
);
static Border get border => Border.fromBorderSide(curvedSide);
static Border border(BuildContext context) => Border.fromBorderSide(curvedSide(context));
}

View file

@ -0,0 +1,26 @@
import 'package:flutter/painting.dart';
class MatrixColorFilters {
static const ColorFilter greyscale = ColorFilter.matrix(<double>[
0.2126,
0.7152,
0.0722,
0,
0,
0.2126,
0.7152,
0.0722,
0,
0,
0.2126,
0.7152,
0.0722,
0,
0,
0,
0,
0,
1,
0,
]);
}

View file

@ -44,13 +44,13 @@ class GridItemSelectionOverlay<T> extends StatelessWidget {
child: child,
);
child = AnimatedContainer(
duration: duration,
alignment: AlignmentDirectional.topEnd,
padding: padding,
decoration: BoxDecoration(
color: isSelected ? Colors.black54 : Colors.transparent,
borderRadius: borderRadius,
),
duration: duration,
child: child,
);
return child;

View file

@ -245,6 +245,10 @@ class _ScaleOverlayState extends State<_ScaleOverlay> {
double get gridWidth => widget.viewportWidth;
// `Color(0x00FFFFFF)` is different from `Color(0x00000000)` (or `Colors.transparent`)
// when used in gradients or lerping to it
static const transparentWhite = Color(0x00FFFFFF);
@override
void initState() {
super.initState();
@ -309,23 +313,34 @@ class _ScaleOverlayState extends State<_ScaleOverlay> {
break;
}
final isDark = Theme.of(context).brightness == Brightness.dark;
return _init
? BoxDecoration(
gradient: RadialGradient(
center: FractionalOffset.fromOffsetAndSize(gradientCenter, context.select<MediaQueryData, Size>((mq) => mq.size)),
radius: 1,
colors: const [
colors: isDark
? const [
Colors.black,
Colors.black54,
]
: const [
Colors.white,
Colors.white38,
],
),
)
: const BoxDecoration(
: BoxDecoration(
// provide dummy gradient to lerp to the other one during animation
gradient: RadialGradient(
colors: [
colors: isDark
? const [
Colors.transparent,
Colors.transparent,
]
: const [
transparentWhite,
transparentWhite,
],
),
);

View file

@ -8,7 +8,7 @@ class AvesExpansionTile extends StatelessWidget {
final String value;
final Widget? leading;
final String title;
final Color? color;
final Color? highlightColor;
final ValueNotifier<String?>? expandedNotifier;
final bool initiallyExpanded, showHighlight;
final List<Widget> children;
@ -18,7 +18,7 @@ class AvesExpansionTile extends StatelessWidget {
String? value,
this.leading,
required this.title,
this.color,
this.highlightColor,
this.expandedNotifier,
this.initiallyExpanded = false,
this.showHighlight = true,
@ -31,7 +31,7 @@ class AvesExpansionTile extends StatelessWidget {
final enabled = children.isNotEmpty == true;
Widget titleChild = HighlightTitle(
title: title,
color: color,
color: highlightColor,
enabled: enabled,
showHighlight: showHighlight,
);
@ -63,8 +63,8 @@ class AvesExpansionTile extends StatelessWidget {
expandable: enabled,
initiallyExpanded: initiallyExpanded,
finalPadding: const EdgeInsets.symmetric(vertical: 6.0),
baseColor: Colors.grey.shade900,
expandedColor: Colors.grey[850],
baseColor: theme.scaffoldBackgroundColor,
expandedColor: theme.canvasColor,
duration: animationDuration,
shadowColor: theme.shadowColor,
child: Column(

View file

@ -6,6 +6,7 @@ import 'package:aves/model/filters/location.dart';
import 'package:aves/model/filters/tag.dart';
import 'package:aves/model/settings/enums/accessibility_animations.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/colors.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/basic/menu.dart';
@ -46,7 +47,6 @@ class AvesFilterChip extends StatefulWidget {
final FilterCallback? onTap;
final OffsetFilterCallback? onLongPress;
static const Color defaultOutlineColor = Colors.white;
static const double defaultRadius = 32;
static const double outlineWidth = 2;
static const double minChipHeight = kMinInteractiveDimension;
@ -156,7 +156,7 @@ class _AvesFilterChipState extends State<AvesFilterChip> {
// the existing widget FutureBuilder cycles again from the start, with a frame in `waiting` state and no data.
// So we save the result of the Future to a local variable because of this specific case.
_colorFuture = filter.color(context);
_outlineColor = AvesFilterChip.defaultOutlineColor;
_outlineColor = context.read<AvesColorsData>().neutral;
}
@override
@ -270,7 +270,7 @@ class _AvesFilterChipState extends State<AvesFilterChip> {
return DecoratedBox(
decoration: BoxDecoration(
border: Border.fromBorderSide(BorderSide(
color: widget.useFilterColor ? _outlineColor : AvesFilterChip.defaultOutlineColor,
color: widget.useFilterColor ? _outlineColor : context.select<AvesColorsData, Color>((v) => v.neutral),
width: AvesFilterChip.outlineWidth,
)),
borderRadius: borderRadius,

View file

@ -27,7 +27,6 @@ class VideoIcon extends StatelessWidget {
if (showDuration) {
child = DefaultTextStyle(
style: TextStyle(
color: Colors.grey.shade200,
fontSize: gridTheme.fontSize,
),
child: child,
@ -146,7 +145,6 @@ class MultiPageIcon extends StatelessWidget {
);
return DefaultTextStyle(
style: TextStyle(
color: Colors.grey.shade200,
fontSize: context.select<GridThemeData, double>((t) => t.fontSize),
),
child: child,
@ -167,7 +165,6 @@ class RatingIcon extends StatelessWidget {
final gridTheme = context.watch<GridThemeData>();
return DefaultTextStyle(
style: TextStyle(
color: Colors.grey.shade200,
fontSize: gridTheme.fontSize,
),
child: OverlayIcon(
@ -195,7 +192,6 @@ class TrashIcon extends StatelessWidget {
return DefaultTextStyle(
style: TextStyle(
color: Colors.grey.shade200,
fontSize: context.select<GridThemeData, double>((t) => t.fontSize),
),
child: child,
@ -224,8 +220,6 @@ class OverlayIcon extends StatelessWidget {
final iconChild = Icon(
icon,
size: size,
// consistent with the color used for the text next to it
color: DefaultTextStyle.of(context).style.color,
);
final iconBox = SizedBox(
width: size,
@ -243,7 +237,7 @@ class OverlayIcon extends StatelessWidget {
margin: margin,
padding: text != null ? EdgeInsetsDirectional.only(end: size / 4) : null,
decoration: BoxDecoration(
color: const Color(0xBB000000),
color: Theme.of(context).brightness == Brightness.dark ? const Color(0xAA000000) : const Color(0xCCFFFFFF),
borderRadius: BorderRadius.all(Radius.circular(size)),
),
child: text == null
@ -254,7 +248,11 @@ class OverlayIcon extends StatelessWidget {
children: [
iconBox,
const SizedBox(width: 2),
Text(text!),
Text(
text!,
// consistent with the color used for the icon next to it
style: TextStyle(color: IconTheme.of(context).color),
),
],
),
);

View file

@ -1,4 +1,9 @@
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/widgets/common/fx/borders.dart';
import 'package:aves/widgets/common/fx/colors.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class AvesLogo extends StatelessWidget {
final double size;
@ -10,14 +15,32 @@ class AvesLogo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CircleAvatar(
backgroundColor: Colors.white,
radius: size / 2,
child: Padding(
padding: EdgeInsets.only(top: size / 15),
child: CustomPaint(
final theme = Theme.of(context);
Widget child = CustomPaint(
size: Size(size / 1.4, size / 1.4),
painter: AvesLogoPainter(),
);
if (context.select<Settings, bool>((v) => v.themeColorMode == AvesThemeColorMode.monochrome)) {
final tint = Color.lerp(theme.colorScheme.secondary, Colors.white, .5)!;
child = ColorFiltered(
colorFilter: ColorFilter.mode(tint, BlendMode.modulate),
child: ColorFiltered(
colorFilter: MatrixColorFilters.greyscale,
child: child,
),
);
}
return CircleAvatar(
backgroundColor: theme.dividerColor,
radius: size / 2,
child: CircleAvatar(
backgroundColor: Colors.white,
radius: size / 2 - AvesBorder.curvedBorderWidth,
child: Padding(
padding: EdgeInsets.only(top: size / 15),
child: child,
),
),
);

View file

@ -22,7 +22,7 @@ class AvesOutlinedButton extends StatelessWidget {
);
}),
foregroundColor: MaterialStateProperty.resolveWith<Color>((states) {
return states.contains(MaterialState.disabled) ? theme.disabledColor : Colors.white;
return states.contains(MaterialState.disabled) ? theme.disabledColor : theme.colorScheme.onSecondary;
}),
);
return icon != null

View file

@ -1,8 +1,11 @@
import 'dart:ui';
import 'package:aves/utils/color_utils.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/widgets/common/fx/highlight_decoration.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class HighlightTitle extends StatelessWidget {
final String title;
@ -34,18 +37,19 @@ class HighlightTitle extends StatelessWidget {
@override
Widget build(BuildContext context) {
final style = TextStyle(
shadows: shadows,
shadows: Theme.of(context).brightness == Brightness.dark ? shadows : null,
fontSize: fontSize,
letterSpacing: 1.0,
fontFeatures: const [FontFeature.enable('smcp')],
);
final colors = context.watch<AvesColorsData>();
return Align(
alignment: AlignmentDirectional.centerStart,
child: Container(
decoration: showHighlight
decoration: showHighlight && context.select<Settings, bool>((v) => v.themeColorMode == AvesThemeColorMode.polychrome)
? HighlightDecoration(
color: enabled ? color ?? stringToColor(title) : disabledColor,
color: enabled ? color ?? colors.fromString(title) : disabledColor,
)
: null,
margin: const EdgeInsets.symmetric(vertical: 4.0),

View file

@ -22,19 +22,20 @@ class Attribution extends StatelessWidget {
case EntryMapStyle.stamenWatercolor:
return _buildAttributionMarkdown(context, context.l10n.mapAttributionStamen);
default:
return const SizedBox.shrink();
return const SizedBox();
}
}
Widget _buildAttributionMarkdown(BuildContext context, String data) {
final theme = Theme.of(context);
return Padding(
padding: const EdgeInsets.only(top: 4),
child: MarkdownBody(
data: data,
selectable: true,
styleSheet: MarkdownStyleSheet(
a: TextStyle(color: Theme.of(context).colorScheme.secondary),
p: const TextStyle(color: Colors.white70, fontSize: InfoRowGroup.fontSize),
a: TextStyle(color: theme.colorScheme.secondary),
p: theme.textTheme.caption!.merge(const TextStyle(fontSize: InfoRowGroup.fontSize)),
),
onTapLink: (text, href, title) async {
if (href != null && await canLaunch(href)) {

View file

@ -5,6 +5,7 @@ import 'package:aves/model/settings/settings.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/theme/themes.dart';
import 'package:aves/utils/debouncer.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/fx/blurred.dart';
@ -15,7 +16,6 @@ import 'package:aves/widgets/common/map/theme.dart';
import 'package:aves/widgets/common/map/zoomed_bounds.dart';
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
import 'package:aves/widgets/viewer/info/notifications.dart';
import 'package:aves/widgets/viewer/overlay/common.dart';
import 'package:flutter/material.dart';
import 'package:latlong2/latlong.dart';
import 'package:provider/provider.dart';
@ -207,10 +207,10 @@ class MapOverlayButton extends StatelessWidget {
enabled: blurred,
child: Material(
type: MaterialType.circle,
color: overlayBackgroundColor(blurred: blurred),
color: Themes.overlayBackgroundColor(brightness: Theme.of(context).brightness, blurred: blurred),
child: Ink(
decoration: BoxDecoration(
border: AvesBorder.border,
border: AvesBorder.border(context),
shape: BoxShape.circle,
),
child: Selector<MapThemeData, VisualDensity?>(
@ -278,9 +278,10 @@ class _OverlayCoordinateFilterChipState extends State<_OverlayCoordinateFilterCh
@override
Widget build(BuildContext context) {
final blurred = settings.enableOverlayBlurEffect;
final theme = Theme.of(context);
return Theme(
data: Theme.of(context).copyWith(
scaffoldBackgroundColor: overlayBackgroundColor(blurred: blurred),
data: theme.copyWith(
scaffoldBackgroundColor: Themes.overlayBackgroundColor(brightness: theme.brightness, blurred: blurred),
),
child: Align(
alignment: Alignment.topLeft,

View file

@ -1,3 +1,4 @@
import 'package:aves/widgets/common/fx/borders.dart';
import 'package:aves/widgets/common/map/theme.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
@ -28,6 +29,10 @@ class MapDecorator extends StatelessWidget {
borderRadius: mapBorderRadius,
child: Container(
color: mapBackground,
foregroundDecoration: BoxDecoration(
border: AvesBorder.border(context),
borderRadius: mapBorderRadius,
),
child: Stack(
children: [
const GridPaper(

View file

@ -14,12 +14,14 @@ class ImageMarker extends StatelessWidget {
static const double outerBorderRadiusDim = 8;
static const double outerBorderWidth = 1.5;
static const double innerBorderWidth = 2;
static const outerBorderColor = Colors.white30;
static const innerBorderColor = Color(0xFF212121);
static const outerBorderRadius = BorderRadius.all(Radius.circular(outerBorderRadiusDim));
static const innerRadius = Radius.circular(outerBorderRadiusDim - outerBorderWidth);
static const innerBorderRadius = BorderRadius.all(innerRadius);
static Color themedOuterBorderColor(bool isDark) => isDark ? Colors.white30 : Colors.black26;
static Color themedInnerBorderColor(bool isDark) => isDark ? const Color(0xFF212121) : Colors.white;
const ImageMarker({
Key? key,
required this.entry,
@ -46,7 +48,12 @@ class ImageMarker extends StatelessWidget {
child: child,
);
const outerDecoration = BoxDecoration(
final theme = Theme.of(context);
final isDark = theme.brightness == Brightness.dark;
final outerBorderColor = themedOuterBorderColor(isDark);
final innerBorderColor = themedInnerBorderColor(isDark);
final outerDecoration = BoxDecoration(
border: Border.fromBorderSide(BorderSide(
color: outerBorderColor,
width: outerBorderWidth,
@ -54,7 +61,7 @@ class ImageMarker extends StatelessWidget {
borderRadius: outerBorderRadius,
);
const innerDecoration = BoxDecoration(
final innerDecoration = BoxDecoration(
border: Border.fromBorderSide(BorderSide(
color: innerBorderColor,
width: innerBorderWidth,
@ -72,7 +79,7 @@ class ImageMarker extends StatelessWidget {
);
if (count != null) {
const borderSide = BorderSide(
final borderSide = BorderSide(
color: innerBorderColor,
width: innerBorderWidth,
);
@ -82,28 +89,28 @@ class ImageMarker extends StatelessWidget {
Container(
padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 2),
decoration: ShapeDecoration(
color: Theme.of(context).colorScheme.secondary,
color: theme.colorScheme.secondary,
shape: context.isRtl
? const CustomRoundedRectangleBorder(
? CustomRoundedRectangleBorder(
leftSide: borderSide,
rightSide: borderSide,
topSide: borderSide,
bottomSide: borderSide,
topRightCornerSide: borderSide,
bottomLeftCornerSide: borderSide,
borderRadius: BorderRadius.only(
borderRadius: const BorderRadius.only(
topRight: innerRadius,
bottomLeft: innerRadius,
),
)
: const CustomRoundedRectangleBorder(
: CustomRoundedRectangleBorder(
leftSide: borderSide,
rightSide: borderSide,
topSide: borderSide,
bottomSide: borderSide,
topLeftCornerSide: borderSide,
bottomRightCornerSide: borderSide,
borderRadius: BorderRadius.only(
borderRadius: const BorderRadius.only(
topLeft: innerRadius,
bottomRight: innerRadius,
),
@ -111,7 +118,10 @@ class ImageMarker extends StatelessWidget {
),
child: Text(
'$count',
style: const TextStyle(fontSize: 12),
style: const TextStyle(
fontSize: 12,
color: Colors.white,
),
),
),
],
@ -190,17 +200,22 @@ class DotMarker extends StatelessWidget {
@override
Widget build(BuildContext context) {
const outerDecoration = BoxDecoration(
final theme = Theme.of(context);
final isDark = theme.brightness == Brightness.dark;
final outerBorderColor = ImageMarker.themedOuterBorderColor(isDark);
final innerBorderColor = ImageMarker.themedInnerBorderColor(isDark);
final outerDecoration = BoxDecoration(
border: Border.fromBorderSide(BorderSide(
color: ImageMarker.outerBorderColor,
color: outerBorderColor,
width: ImageMarker.outerBorderWidth,
)),
borderRadius: outerBorderRadius,
);
const innerDecoration = BoxDecoration(
final innerDecoration = BoxDecoration(
border: Border.fromBorderSide(BorderSide(
color: ImageMarker.innerBorderColor,
color: innerBorderColor,
width: ImageMarker.innerBorderWidth,
)),
borderRadius: innerBorderRadius,
@ -216,7 +231,7 @@ class DotMarker extends StatelessWidget {
child: Container(
width: diameter,
height: diameter,
color: Theme.of(context).colorScheme.secondary,
color: theme.colorScheme.secondary,
),
),
),

View file

@ -14,7 +14,7 @@ class SliverAppBarTitleWrapper extends StatelessWidget {
@override
Widget build(BuildContext context) {
final toolbarOpacity = context.dependOnInheritedWidgetOfExactType<FlexibleSpaceBarSettings>()!.toolbarOpacity;
final baseColor = (DefaultTextStyle.of(context).style.color ?? Theme.of(context).primaryTextTheme.headline6!.color!);
final baseColor = (DefaultTextStyle.of(context).style.color ?? Theme.of(context).textTheme.headline6!.color!);
return DefaultTextStyle.merge(
style: TextStyle(color: baseColor.withOpacity(toolbarOpacity)),
child: child,

View file

@ -179,9 +179,12 @@ class _ThumbnailImageState extends State<ThumbnailImage> {
Color? _loadingBackgroundColor;
Color get loadingBackgroundColor {
Color loadingBackgroundColor(BuildContext context) {
if (_loadingBackgroundColor == null) {
final rgb = 0x30 + entry.uri.hashCode % 0x20;
var rgb = 0x30 + entry.uri.hashCode % 0x20;
if (Theme.of(context).brightness == Brightness.light) {
rgb = 0xFF - rgb;
}
_loadingBackgroundColor = Color.fromARGB(0xFF, rgb, rgb, rgb);
}
return _loadingBackgroundColor!;
@ -201,7 +204,7 @@ class _ThumbnailImageState extends State<ThumbnailImage> {
final imageInfo = _lastImageInfo;
Widget image = imageInfo == null
? Container(
color: widget.showLoadingBackground ? loadingBackgroundColor : Colors.transparent,
color: widget.showLoadingBackground ? loadingBackgroundColor(context) : Colors.transparent,
width: extent,
height: extent,
)

View file

@ -70,7 +70,7 @@ class _DebugAndroidAppSectionState extends State<DebugAndroidAppSection> with Au
),
TextSpan(
text: ' ${package.packageName}\n',
style: InfoRowGroup.keyStyle,
style: InfoRowGroup.keyStyle(context),
),
WidgetSpan(
alignment: PlaceholderAlignment.middle,
@ -94,7 +94,7 @@ class _DebugAndroidAppSectionState extends State<DebugAndroidAppSection> with Au
),
TextSpan(
text: ' ${package.potentialDirs.join(', ')}\n',
style: InfoRowGroup.baseStyle,
style: InfoRowGroup.valueStyle,
),
],
),

View file

@ -2,6 +2,7 @@ import 'package:aves/model/covers.dart';
import 'package:aves/model/entry.dart';
import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/fx/borders.dart';
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
import 'package:aves/widgets/common/thumbnail/image.dart';
import 'package:aves/widgets/dialogs/item_pick_dialog.dart';
@ -105,6 +106,11 @@ class _AddShortcutDialogState extends State<AddShortcutDialog> {
Widget _buildCover(AvesEntry entry, double extent) {
return GestureDetector(
onTap: _pickEntry,
child: Container(
decoration: BoxDecoration(
border: AvesBorder.border(context),
borderRadius: const BorderRadius.all(Radius.circular(32)),
),
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(32)),
child: SizedBox(
@ -116,6 +122,7 @@ class _AddShortcutDialogState extends State<AddShortcutDialog> {
),
),
),
),
);
}

View file

@ -5,6 +5,7 @@ import 'package:aves/model/metadata/fields.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/theme/format.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/theme/themes.dart';
import 'package:aves/widgets/common/basic/wheel.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
@ -33,11 +34,6 @@ class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
bool _showOptions = false;
final Set<MetadataField> _fields = {...DateModifier.writableDateFields};
// use a different shade to avoid having the same background
// on the dialog (using the theme `dialogBackgroundColor`)
// and on the dropdown (using the theme `canvasColor`)
static final dropdownColor = Colors.grey.shade800;
@override
void initState() {
super.initState();
@ -81,7 +77,7 @@ class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
value: _action,
onChanged: (v) => setState(() => _action = v!),
isExpanded: true,
dropdownColor: dropdownColor,
dropdownColor: Themes.thirdLayerColor(context),
),
),
AnimatedSwitcher(
@ -169,7 +165,7 @@ class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
value: _copyFieldSource,
onChanged: (v) => setState(() => _copyFieldSource = v!),
isExpanded: true,
dropdownColor: dropdownColor,
dropdownColor: Themes.thirdLayerColor(context),
),
);
}

View file

@ -1,7 +1,9 @@
import 'package:aves/model/metadata/enums.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/ref/brand_colors.dart';
import 'package:aves/theme/colors.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/utils/color_utils.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/fx/highlight_decoration.dart';
import 'package:aves/widgets/common/identity/highlight_title.dart';
@ -97,6 +99,22 @@ class _RemoveEntryMetadataDialogState extends State<RemoveEntryMetadataDialog> {
Widget _toTile(MetadataType type) {
final text = type.getText();
Widget child = Text(
text,
style: TextStyle(
shadows: Theme.of(context).brightness == Brightness.dark ? HighlightTitle.shadows : null,
),
);
if (context.select<Settings, bool>((v) => v.themeColorMode == AvesThemeColorMode.polychrome)) {
final colors = context.watch<AvesColorsData>();
child = DecoratedBox(
decoration: HighlightDecoration(
color: colors.fromBrandColor(BrandColors.get(text)) ?? colors.fromString(text),
),
child: child,
);
}
return SwitchListTile(
value: _types.contains(type),
onChanged: (selected) {
@ -106,17 +124,7 @@ class _RemoveEntryMetadataDialogState extends State<RemoveEntryMetadataDialog> {
},
title: Align(
alignment: Alignment.centerLeft,
child: DecoratedBox(
decoration: HighlightDecoration(
color: BrandColors.get(text) ?? stringToColor(text),
),
child: Text(
text,
style: const TextStyle(
shadows: HighlightTitle.shadows,
),
),
),
child: child,
),
);
}

View file

@ -78,7 +78,12 @@ class _TileViewDialogState<S, G, L> extends State<TileViewDialog<S, G, L>> with
final tabs = <Tuple2<Tab, Widget>>[
if (sortOptions.isNotEmpty)
Tuple2(
_buildTab(context, const Key('tab-sort'), AIcons.sort, l10n.viewDialogTabSort),
_buildTab(
context,
const Key('tab-sort'),
AIcons.sort,
l10n.viewDialogTabSort,
),
Column(
children: sortOptions.entries
.map((kv) => _buildRadioListTile<S>(
@ -92,7 +97,13 @@ class _TileViewDialogState<S, G, L> extends State<TileViewDialog<S, G, L>> with
),
if (groupOptions.isNotEmpty)
Tuple2(
_buildTab(context, const Key('tab-group'), AIcons.group, l10n.viewDialogTabGroup, color: canGroup ? null : Theme.of(context).disabledColor),
_buildTab(
context,
const Key('tab-group'),
AIcons.group,
l10n.viewDialogTabGroup,
color: canGroup ? null : Theme.of(context).disabledColor,
),
Column(
children: groupOptions.entries
.map((kv) => _buildRadioListTile<G>(
@ -106,7 +117,12 @@ class _TileViewDialogState<S, G, L> extends State<TileViewDialog<S, G, L>> with
),
if (layoutOptions.isNotEmpty)
Tuple2(
_buildTab(context, const Key('tab-layout'), AIcons.layout, l10n.viewDialogTabLayout),
_buildTab(
context,
const Key('tab-layout'),
AIcons.layout,
l10n.viewDialogTabLayout,
),
Column(
children: layoutOptions.entries
.map((kv) => _buildRadioListTile<L>(
@ -209,7 +225,13 @@ class _TileViewDialogState<S, G, L> extends State<TileViewDialog<S, G, L>> with
);
}
Tab _buildTab(BuildContext context, Key key, IconData icon, String text, {Color? color}) {
Tab _buildTab(
BuildContext context,
Key key,
IconData icon,
String text, {
Color? color,
}) {
// cannot use `IconTheme` over `TabBar` to change size,
// because `TabBar` does so internally
final textScaleFactor = MediaQuery.textScaleFactorOf(context);

View file

@ -1,6 +1,7 @@
import 'package:aves/model/entry.dart';
import 'package:aves/ref/languages.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/theme/themes.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/viewer/video/controller.dart';
import 'package:collection/collection.dart';
@ -167,10 +168,7 @@ class _VideoStreamSelectionDialogState extends State<VideoStreamSelectionDialog>
value: current,
onChanged: streams.length > 1 ? (newValue) => setState(() => setter(newValue)) : null,
isExpanded: true,
// use a different shade to avoid having the same background
// on the dialog (using the theme `dialogBackgroundColor`)
// and on the dropdown (using the theme `canvasColor`)
dropdownColor: Colors.grey.shade800,
dropdownColor: Themes.thirdLayerColor(context),
),
),
];

View file

@ -121,6 +121,7 @@ class AppDrawer extends StatelessWidget {
Text(
context.l10n.appName,
style: const TextStyle(
color: Colors.white,
fontSize: 44,
fontWeight: FontWeight.w300,
letterSpacing: 1.0,
@ -136,6 +137,7 @@ class AppDrawer extends StatelessWidget {
style: ButtonStyle(
foregroundColor: MaterialStateProperty.all<Color>(Colors.white),
overlayColor: MaterialStateProperty.all<Color>(Colors.white24),
side: MaterialStateProperty.all<BorderSide>(BorderSide(width: 1, color: Colors.white.withOpacity(0.12))),
),
),
child: Wrap(

View file

@ -65,12 +65,10 @@ class CollectionNavTile extends StatelessWidget {
MaterialPageRoute(
settings: const RouteSettings(name: CollectionPage.routeName),
builder: (context) => CollectionPage(
collection: CollectionLens(
source: context.read<CollectionSource>(),
filters: {filter},
),
),
),
(route) => false,
);
}

View file

@ -77,7 +77,8 @@ class DrawerPageIcon extends StatelessWidget {
return const Icon(AIcons.tag);
case AppDebugPage.routeName:
return ShaderMask(
shaderCallback: AColors.debugGradient.createShader,
shaderCallback: AvesColorsData.debugGradient.createShader,
blendMode: BlendMode.srcIn,
child: const Icon(AIcons.debug),
);
default:

View file

@ -14,7 +14,6 @@ import 'package:aves/theme/icons.dart';
import 'package:aves/utils/android_file_utils.dart';
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
import 'package:aves/widgets/common/thumbnail/image.dart';
import 'package:aves/widgets/filter_grids/common/filter_grid_page.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
@ -151,7 +150,7 @@ class CoveredFilterChip<T extends CollectionFilter> extends StatelessWidget {
radius: radius(extent),
),
banner: banner,
details: showText ? _buildDetails(source, filter) : null,
details: showText ? _buildDetails(context, source, filter) : null,
padding: titlePadding,
heroType: heroType,
onTap: onTap,
@ -159,7 +158,9 @@ class CoveredFilterChip<T extends CollectionFilter> extends StatelessWidget {
);
}
Widget _buildDetails(CollectionSource source, T filter) {
Color _detailColor(BuildContext context) => Theme.of(context).textTheme.caption!.color!;
Widget _buildDetails(BuildContext context, CollectionSource source, T filter) {
final padding = min<double>(8.0, extent / 16);
final iconSize = detailIconSize(extent);
final fontSize = detailFontSize(extent);
@ -172,7 +173,7 @@ class CoveredFilterChip<T extends CollectionFilter> extends StatelessWidget {
duration: Durations.chipDecorationAnimation,
child: Icon(
AIcons.pin,
color: FilterGridPage.detailColor,
color: _detailColor(context),
size: iconSize,
),
),
@ -182,14 +183,14 @@ class CoveredFilterChip<T extends CollectionFilter> extends StatelessWidget {
duration: Durations.chipDecorationAnimation,
child: Icon(
AIcons.removableStorage,
color: FilterGridPage.detailColor,
color: _detailColor(context),
size: iconSize,
),
),
Text(
'${source.count(filter)}',
style: TextStyle(
color: FilterGridPage.detailColor,
color: _detailColor(context),
fontSize: fontSize,
),
),

View file

@ -67,8 +67,6 @@ class FilterGridPage<T extends CollectionFilter> extends StatelessWidget {
required this.heroType,
}) : super(key: key);
static const Color detailColor = Color(0xFFE0E0E0);
@override
Widget build(BuildContext context) {
return MediaQueryDataProvider(

View file

@ -52,7 +52,7 @@ class FilterNavigationPage<T extends CollectionFilter> extends StatelessWidget {
emptyBuilder: () => ValueListenableBuilder<SourceState>(
valueListenable: source.stateNotifier,
builder: (context, sourceState, child) {
return sourceState != SourceState.loading ? emptyBuilder() : const SizedBox.shrink();
return sourceState != SourceState.loading ? emptyBuilder() : const SizedBox();
},
),
// do not always enable hero, otherwise unwanted hero gets triggered

View file

@ -2,7 +2,6 @@ import 'package:aves/app_mode.dart';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/selection.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/model/source/collection_source.dart';
import 'package:aves/model/source/enums.dart';
import 'package:aves/widgets/collection/collection_page.dart';
@ -95,12 +94,10 @@ class _InteractiveFilterTileState<T extends CollectionFilter> extends State<Inte
MaterialPageRoute(
settings: const RouteSettings(name: CollectionPage.routeName),
builder: (context) => CollectionPage(
collection: CollectionLens(
source: context.read<CollectionSource>(),
filters: {filter},
),
),
),
);
});
}

View file

@ -37,7 +37,7 @@ class FilterListDetails<T extends CollectionFilter> extends StatelessWidget {
return Container(
padding: FilterListDetailsTheme.contentPadding,
foregroundDecoration: BoxDecoration(
border: Border(top: AvesBorder.straightSide),
border: Border(top: AvesBorder.straightSide(context)),
),
margin: FilterListDetailsTheme.contentMargin,
child: Column(

View file

@ -252,11 +252,9 @@ class _HomePageState extends State<HomePage> {
return DirectMaterialPageRoute(
settings: const RouteSettings(name: CollectionPage.routeName),
builder: (_) => CollectionPage(
collection: CollectionLens(
source: source,
filters: filters,
),
),
);
}
}

View file

@ -382,10 +382,8 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
settings: const RouteSettings(name: CollectionPage.routeName),
builder: (context) {
return CollectionPage(
collection: CollectionLens(
source: openingCollection.source,
filters: openingCollection.filters,
)..addFilter(filter),
filters: {...openingCollection.filters, filter},
);
},
),

View file

@ -266,12 +266,10 @@ class CollectionSearchDelegate {
MaterialPageRoute(
settings: const RouteSettings(name: CollectionPage.routeName),
builder: (context) => CollectionPage(
collection: CollectionLens(
source: source,
filters: {filter},
),
),
),
(route) => false,
);
}

View file

@ -1,3 +1,5 @@
import 'dart:ui';
import 'package:aves/theme/durations.dart';
import 'package:aves/utils/debouncer.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
@ -109,7 +111,9 @@ class _SearchPageState extends State<SearchPage> {
return Scaffold(
appBar: AppBar(
leading: widget.delegate.buildLeading(context),
title: TextField(
title: DefaultTextStyle.merge(
style: const TextStyle(fontFeatures: [FontFeature.disable('smcp')]),
child: TextField(
controller: widget.delegate.queryTextController,
focusNode: _focusNode,
style: theme.textTheme.headline6,
@ -121,6 +125,7 @@ class _SearchPageState extends State<SearchPage> {
hintStyle: theme.inputDecorationTheme.hintStyle,
),
),
),
actions: widget.delegate.buildActions(context),
),
body: AnimatedSwitcher(

View file

@ -1,11 +1,15 @@
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/remove_animations.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:flutter/material.dart';
import 'package:provider/provider.dart';
class AccessibilitySection extends StatelessWidget {
final ValueNotifier<String?> expandedNotifier;
@ -20,14 +24,21 @@ class AccessibilitySection extends StatelessWidget {
return AvesExpansionTile(
leading: SettingsTileLeading(
icon: AIcons.accessibility,
color: AColors.accessibility,
color: context.select<AvesColorsData, Color>((v) => v.accessibility),
),
title: context.l10n.settingsSectionAccessibility,
expandedNotifier: expandedNotifier,
showHighlight: false,
children: const [
RemoveAnimationsTile(),
TimeToTakeActionTile(),
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(),
],
);
}

View file

@ -1,30 +0,0 @@
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/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class RemoveAnimationsTile extends StatelessWidget {
const RemoveAnimationsTile({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final currentAnimations = context.select<Settings, AccessibilityAnimations>((s) => s.accessibilityAnimations);
return ListTile(
title: Text(context.l10n.settingsRemoveAnimationsTile),
subtitle: Text(currentAnimations.getName(context)),
onTap: () => showSelectionDialog<AccessibilityAnimations>(
context: context,
builder: (context) => AvesSelectionDialog<AccessibilityAnimations>(
initialValue: currentAnimations,
options: Map.fromEntries(AccessibilityAnimations.values.map((v) => MapEntry(v, v.getName(context)))),
title: context.l10n.settingsRemoveAnimationsTitle,
),
onSelection: (v) => settings.accessibilityAnimations = v,
),
);
}
}

View file

@ -3,9 +3,8 @@ import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/services/accessibility_service.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
import 'package:aves/widgets/settings/common/tiles.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class TimeToTakeActionTile extends StatefulWidget {
const TimeToTakeActionTile({Key? key}) : super(key: key);
@ -25,26 +24,20 @@ class _TimeToTakeActionTileState extends State<TimeToTakeActionTile> {
@override
Widget build(BuildContext context) {
final currentTimeToTakeAction = context.select<Settings, AccessibilityTimeout>((s) => s.timeToTakeAction);
return FutureBuilder<bool>(
future: _hasSystemOptionLoader,
builder: (context, snapshot) {
if (snapshot.hasError || !snapshot.hasData) return const SizedBox.shrink();
if (snapshot.hasError || !snapshot.hasData) return const SizedBox();
final hasSystemOption = snapshot.data!;
final optionValues = hasSystemOption ? AccessibilityTimeout.values : AccessibilityTimeout.values.where((v) => v != AccessibilityTimeout.system).toList();
return ListTile(
title: Text(context.l10n.settingsTimeToTakeActionTile),
subtitle: Text(currentTimeToTakeAction.getName(context)),
onTap: () => showSelectionDialog<AccessibilityTimeout>(
context: context,
builder: (context) => AvesSelectionDialog<AccessibilityTimeout>(
initialValue: currentTimeToTakeAction,
options: Map.fromEntries(optionValues.map((v) => MapEntry(v, v.getName(context)))),
title: context.l10n.settingsTimeToTakeActionTitle,
),
return SettingsSelectionListTile<AccessibilityTimeout>(
values: optionValues,
getName: (context, v) => v.getName(context),
selector: (context, s) => s.timeToTakeAction,
onSelection: (v) => settings.timeToTakeAction = v,
),
tileTitle: context.l10n.settingsTimeToTakeActionTile,
dialogTitle: context.l10n.settingsTimeToTakeActionTitle,
);
},
);

View file

@ -1,3 +1,4 @@
import 'package:aves/theme/durations.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
import 'package:decorated_icon/decorated_icon.dart';
@ -15,7 +16,7 @@ class SettingsTileLeading extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
return AnimatedContainer(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
border: Border.fromBorderSide(BorderSide(
@ -24,9 +25,10 @@ class SettingsTileLeading extends StatelessWidget {
)),
shape: BoxShape.circle,
),
duration: Durations.themeColorModeAnimation,
child: DecoratedIcon(
icon,
shadows: Constants.embossShadows,
shadows: Theme.of(context).brightness == Brightness.dark ? Constants.embossShadows : null,
size: 18,
),
);

View file

@ -0,0 +1,91 @@
import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class SettingsSwitchListTile extends StatelessWidget {
final bool Function(BuildContext, Settings) selector;
final ValueChanged<bool> onChanged;
final String title;
final String? subtitle;
final Widget? trailing;
const SettingsSwitchListTile({
Key? key,
required this.selector,
required this.onChanged,
required this.title,
this.subtitle,
this.trailing,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Selector<Settings, bool>(
selector: selector,
builder: (context, current, child) {
Widget titleWidget = Text(title);
if (trailing != null) {
titleWidget = Row(
children: [
Expanded(child: titleWidget),
AnimatedOpacity(
opacity: current ? 1 : .2,
duration: Durations.toggleableTransitionAnimation,
child: trailing,
),
],
);
}
return SwitchListTile(
value: current,
onChanged: onChanged,
title: titleWidget,
subtitle: subtitle != null ? Text(subtitle!) : null,
);
},
);
}
}
class SettingsSelectionListTile<T extends Enum> extends StatelessWidget {
final List<T> values;
final String Function(BuildContext, T) getName;
final T Function(BuildContext, Settings) selector;
final ValueChanged<T> onSelection;
final String tileTitle, dialogTitle;
final TextBuilder<T>? optionSubtitleBuilder;
const SettingsSelectionListTile({
Key? key,
required this.values,
required this.getName,
required this.selector,
required this.onSelection,
required this.tileTitle,
required this.dialogTitle,
this.optionSubtitleBuilder,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Selector<Settings, T>(
selector: selector,
builder: (context, current, child) => ListTile(
title: Text(tileTitle),
subtitle: Text(getName(context, current)),
onTap: () => showSelectionDialog<T>(
context: context,
builder: (context) => AvesSelectionDialog<T>(
initialValue: current,
options: Map.fromEntries(values.map((v) => MapEntry(v, getName(context, v)))),
optionSubtitleBuilder: optionSubtitleBuilder,
title: dialogTitle,
),
onSelection: onSelection,
),
),
);
}
}

View file

@ -0,0 +1,48 @@
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/enums/theme_brightness.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: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);
@override
Widget build(BuildContext context) {
return AvesExpansionTile(
leading: 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.settingsThemeColorful,
),
],
);
}
}

View file

@ -7,8 +7,8 @@ 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/dialogs/aves_selection_dialog.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:flutter/material.dart';
import 'package:provider/provider.dart';
@ -24,9 +24,6 @@ class LanguageSection extends StatelessWidget {
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
final currentCoordinateFormat = context.select<Settings, CoordinateFormat>((s) => s.coordinateFormat);
final currentUnitSystem = context.select<Settings, UnitSystem>((s) => s.unitSystem);
return AvesExpansionTile(
// key is expected by test driver
key: const Key('section-language'),
@ -35,39 +32,29 @@ class LanguageSection extends StatelessWidget {
value: 'language',
leading: SettingsTileLeading(
icon: AIcons.language,
color: AColors.language,
color: context.select<AvesColorsData, Color>((v) => v.language),
),
title: l10n.settingsSectionLanguage,
expandedNotifier: expandedNotifier,
showHighlight: false,
children: [
const LocaleTile(),
ListTile(
title: Text(l10n.settingsCoordinateFormatTile),
subtitle: Text(currentCoordinateFormat.getName(context)),
onTap: () => showSelectionDialog<CoordinateFormat>(
context: context,
builder: (context) => AvesSelectionDialog<CoordinateFormat>(
initialValue: currentCoordinateFormat,
options: Map.fromEntries(CoordinateFormat.values.map((v) => MapEntry(v, v.getName(context)))),
optionSubtitleBuilder: (value) => value.format(l10n, Constants.pointNemo),
title: l10n.settingsCoordinateFormatTitle,
),
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),
),
),
ListTile(
title: Text(l10n.settingsUnitSystemTile),
subtitle: Text(currentUnitSystem.getName(context)),
onTap: () => showSelectionDialog<UnitSystem>(
context: context,
builder: (context) => AvesSelectionDialog<UnitSystem>(
initialValue: currentUnitSystem,
options: Map.fromEntries(UnitSystem.values.map((v) => MapEntry(v, v.getName(context)))),
title: l10n.settingsUnitSystemTitle,
),
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,
),
],
);

View file

@ -6,8 +6,8 @@ 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/dialogs/aves_selection_dialog.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:flutter/material.dart';
@ -23,51 +23,37 @@ class NavigationSection extends StatelessWidget {
@override
Widget build(BuildContext context) {
final currentHomePage = context.select<Settings, HomePageSetting>((s) => s.homePage);
final currentKeepScreenOn = context.select<Settings, KeepScreenOn>((s) => s.keepScreenOn);
final currentMustBackTwiceToExit = context.select<Settings, bool>((s) => s.mustBackTwiceToExit);
return AvesExpansionTile(
leading: SettingsTileLeading(
icon: AIcons.home,
color: AColors.navigation,
color: context.select<AvesColorsData, Color>((v) => v.navigation),
),
title: context.l10n.settingsSectionNavigation,
expandedNotifier: expandedNotifier,
showHighlight: false,
children: [
ListTile(
title: Text(context.l10n.settingsHome),
subtitle: Text(currentHomePage.getName(context)),
onTap: () => showSelectionDialog<HomePageSetting>(
context: context,
builder: (context) => AvesSelectionDialog<HomePageSetting>(
initialValue: currentHomePage,
options: Map.fromEntries(HomePageSetting.values.map((v) => MapEntry(v, v.getName(context)))),
title: context.l10n.settingsHome,
),
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(),
ListTile(
title: Text(context.l10n.settingsKeepScreenOnTile),
subtitle: Text(currentKeepScreenOn.getName(context)),
onTap: () => showSelectionDialog<KeepScreenOn>(
context: context,
builder: (context) => AvesSelectionDialog<KeepScreenOn>(
initialValue: currentKeepScreenOn,
options: Map.fromEntries(KeepScreenOn.values.map((v) => MapEntry(v, v.getName(context)))),
title: context.l10n.settingsKeepScreenOnTitle,
),
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,
),
),
SwitchListTile(
value: currentMustBackTwiceToExit,
SettingsSwitchListTile(
selector: (context, s) => s.mustBackTwiceToExit,
onChanged: (v) => settings.mustBackTwiceToExit = v,
title: Text(context.l10n.settingsDoubleBackExit),
title: context.l10n.settingsDoubleBackExit,
),
],
);

View file

@ -6,6 +6,7 @@ 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:flutter/material.dart';
@ -26,56 +27,44 @@ class PrivacySection extends StatelessWidget {
return AvesExpansionTile(
leading: SettingsTileLeading(
icon: AIcons.privacy,
color: AColors.privacy,
color: context.select<AvesColorsData, Color>((v) => v.privacy),
),
title: context.l10n.settingsSectionPrivacy,
expandedNotifier: expandedNotifier,
showHighlight: false,
children: [
Selector<Settings, bool>(
SettingsSwitchListTile(
selector: (context, s) => s.isInstalledAppAccessAllowed,
builder: (context, current, child) => SwitchListTile(
value: current,
onChanged: (v) => settings.isInstalledAppAccessAllowed = v,
title: Text(context.l10n.settingsAllowInstalledAppAccess),
subtitle: Text(context.l10n.settingsAllowInstalledAppAccessSubtitle),
),
title: context.l10n.settingsAllowInstalledAppAccess,
subtitle: context.l10n.settingsAllowInstalledAppAccessSubtitle,
),
if (canEnableErrorReporting)
Selector<Settings, bool>(
SettingsSwitchListTile(
selector: (context, s) => s.isErrorReportingAllowed,
builder: (context, current, child) => SwitchListTile(
value: current,
onChanged: (v) => settings.isErrorReportingAllowed = v,
title: Text(context.l10n.settingsAllowErrorReporting),
title: context.l10n.settingsAllowErrorReporting,
),
),
Selector<Settings, bool>(
SettingsSwitchListTile(
selector: (context, s) => s.saveSearchHistory,
builder: (context, current, child) => SwitchListTile(
value: current,
onChanged: (v) {
settings.saveSearchHistory = v;
if (!v) {
settings.searchHistory = [];
}
},
title: Text(context.l10n.settingsSaveSearchHistory),
title: context.l10n.settingsSaveSearchHistory,
),
),
Selector<Settings, bool>(
SettingsSwitchListTile(
selector: (context, s) => s.enableBin,
builder: (context, current, child) => SwitchListTile(
value: current,
onChanged: (v) {
settings.enableBin = v;
if (!v) {
settings.searchHistory = [];
}
},
title: Text(context.l10n.settingsEnableBin),
subtitle: Text(context.l10n.settingsEnableBinSubtitle),
),
title: context.l10n.settingsEnableBin,
subtitle: context.l10n.settingsEnableBinSubtitle,
),
const HiddenItemsTile(),
if (device.canGrantDirectoryAccess) const StorageAccessTile(),

View file

@ -14,6 +14,7 @@ import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
import 'package:aves/widgets/settings/accessibility/accessibility.dart';
import 'package:aves/widgets/settings/app_export/items.dart';
import 'package:aves/widgets/settings/app_export/selection_dialog.dart';
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';
@ -98,6 +99,7 @@ class _SettingsPageState extends State<SettingsPage> with FeedbackMixin {
VideoSection(expandedNotifier: _expandedNotifier),
PrivacySection(expandedNotifier: _expandedNotifier),
AccessibilitySection(expandedNotifier: _expandedNotifier),
DisplaySection(expandedNotifier: _expandedNotifier),
LanguageSection(expandedNotifier: _expandedNotifier),
],
),

View file

@ -1,11 +1,11 @@
import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/colors.dart';
import 'package:aves/theme/durations.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/thumbnails/collection_actions_editor.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
@ -21,131 +21,77 @@ class ThumbnailsSection extends StatelessWidget {
@override
Widget build(BuildContext context) {
final iconSize = IconTheme.of(context).size! * MediaQuery.textScaleFactorOf(context);
double opacityFor(bool enabled) => enabled ? 1 : .2;
final iconColor = context.select<AvesColorsData, Color>((v) => v.neutral);
return AvesExpansionTile(
leading: SettingsTileLeading(
icon: AIcons.grid,
color: AColors.thumbnails,
color: context.select<AvesColorsData, Color>((v) => v.thumbnails),
),
title: context.l10n.settingsSectionThumbnails,
expandedNotifier: expandedNotifier,
showHighlight: false,
children: [
const CollectionActionsTile(),
Selector<Settings, bool>(
SettingsSwitchListTile(
selector: (context, s) => s.showThumbnailFavourite,
builder: (context, current, child) => SwitchListTile(
value: current,
onChanged: (v) => settings.showThumbnailFavourite = v,
title: Row(
children: [
Expanded(child: Text(context.l10n.settingsThumbnailShowFavouriteIcon)),
AnimatedOpacity(
opacity: opacityFor(current),
duration: Durations.toggleableTransitionAnimation,
child: Padding(
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,
),
),
),
],
),
),
),
Selector<Settings, bool>(
SettingsSwitchListTile(
selector: (context, s) => s.showThumbnailLocation,
builder: (context, current, child) => SwitchListTile(
value: current,
onChanged: (v) => settings.showThumbnailLocation = v,
title: Row(
children: [
Expanded(child: Text(context.l10n.settingsThumbnailShowLocationIcon)),
AnimatedOpacity(
opacity: opacityFor(current),
duration: Durations.toggleableTransitionAnimation,
child: Icon(
title: context.l10n.settingsThumbnailShowLocationIcon,
trailing: Icon(
AIcons.location,
size: iconSize,
color: iconColor,
),
),
],
),
),
),
Selector<Settings, bool>(
SettingsSwitchListTile(
selector: (context, s) => s.showThumbnailMotionPhoto,
builder: (context, current, child) => SwitchListTile(
value: current,
onChanged: (v) => settings.showThumbnailMotionPhoto = v,
title: Row(
children: [
Expanded(child: Text(context.l10n.settingsThumbnailShowMotionPhotoIcon)),
AnimatedOpacity(
opacity: opacityFor(current),
duration: Durations.toggleableTransitionAnimation,
child: Padding(
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,
),
),
),
],
),
),
),
Selector<Settings, bool>(
SettingsSwitchListTile(
selector: (context, s) => s.showThumbnailRating,
builder: (context, current, child) => SwitchListTile(
value: current,
onChanged: (v) => settings.showThumbnailRating = v,
title: Row(
children: [
Expanded(child: Text(context.l10n.settingsThumbnailShowRating)),
AnimatedOpacity(
opacity: opacityFor(current),
duration: Durations.toggleableTransitionAnimation,
child: Icon(
title: context.l10n.settingsThumbnailShowRating,
trailing: Icon(
AIcons.rating,
size: iconSize,
color: iconColor,
),
),
],
),
),
),
Selector<Settings, bool>(
SettingsSwitchListTile(
selector: (context, s) => s.showThumbnailRaw,
builder: (context, current, child) => SwitchListTile(
value: current,
onChanged: (v) => settings.showThumbnailRaw = v,
title: Row(
children: [
Expanded(child: Text(context.l10n.settingsThumbnailShowRawIcon)),
AnimatedOpacity(
opacity: opacityFor(current),
duration: Durations.toggleableTransitionAnimation,
child: Icon(
title: context.l10n.settingsThumbnailShowRawIcon,
trailing: Icon(
AIcons.raw,
size: iconSize,
color: iconColor,
),
),
],
),
),
),
Selector<Settings, bool>(
SettingsSwitchListTile(
selector: (context, s) => s.showThumbnailVideoDuration,
builder: (context, current, child) => SwitchListTile(
value: current,
onChanged: (v) => settings.showThumbnailVideoDuration = v,
title: Text(context.l10n.settingsThumbnailShowVideoDuration),
),
title: context.l10n.settingsThumbnailShowVideoDuration,
),
],
);

View file

@ -2,9 +2,8 @@ import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/enums/video_controls.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
import 'package:aves/widgets/settings/common/tiles.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class VideoControlsTile extends StatelessWidget {
const VideoControlsTile({Key? key}) : super(key: key);
@ -40,37 +39,23 @@ class VideoControlsPage extends StatelessWidget {
body: SafeArea(
child: ListView(
children: [
Selector<Settings, VideoControls>(
SettingsSelectionListTile<VideoControls>(
values: VideoControls.values,
getName: (context, v) => v.getName(context),
selector: (context, s) => s.videoControls,
builder: (context, current, child) => ListTile(
title: Text(context.l10n.settingsVideoButtonsTile),
subtitle: Text(current.getName(context)),
onTap: () => showSelectionDialog<VideoControls>(
context: context,
builder: (context) => AvesSelectionDialog<VideoControls>(
initialValue: current,
options: Map.fromEntries(VideoControls.values.map((v) => MapEntry(v, v.getName(context)))),
title: context.l10n.settingsVideoButtonsTitle,
),
onSelection: (v) => settings.videoControls = v,
tileTitle: context.l10n.settingsVideoButtonsTile,
dialogTitle: context.l10n.settingsVideoButtonsTitle,
),
),
),
Selector<Settings, bool>(
SettingsSwitchListTile(
selector: (context, s) => s.videoGestureDoubleTapTogglePlay,
builder: (context, current, child) => SwitchListTile(
value: current,
onChanged: (v) => settings.videoGestureDoubleTapTogglePlay = v,
title: Text(context.l10n.settingsVideoGestureDoubleTapTogglePlay),
title: context.l10n.settingsVideoGestureDoubleTapTogglePlay,
),
),
Selector<Settings, bool>(
SettingsSwitchListTile(
selector: (context, s) => s.videoGestureSideDoubleTapSeek,
builder: (context, current, child) => SwitchListTile(
value: current,
onChanged: (v) => settings.videoGestureSideDoubleTapSeek = v,
title: Text(context.l10n.settingsVideoGestureSideDoubleTapSeek),
),
title: context.l10n.settingsVideoGestureSideDoubleTapSeek,
),
],
),

View file

@ -32,7 +32,7 @@ class SubtitleSample extends StatelessWidget {
Color(0xffeaecc6),
],
),
border: AvesBorder.border,
border: AvesBorder.border(context),
borderRadius: const BorderRadius.all(Radius.circular(24)),
),
height: 128,

View file

@ -2,7 +2,7 @@ import 'package:aves/model/settings/settings.dart';
import 'package:aves/widgets/common/basic/color_list_tile.dart';
import 'package:aves/widgets/common/basic/slider_list_tile.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
import 'package:aves/widgets/settings/common/tiles.dart';
import 'package:aves/widgets/settings/video/subtitle_sample.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
@ -30,8 +30,6 @@ class SubtitleThemeTile extends StatelessWidget {
class SubtitleThemePage extends StatelessWidget {
static const routeName = '/settings/video/subtitle_theme';
static const textAlignOptions = [TextAlign.left, TextAlign.center, TextAlign.right];
const SubtitleThemePage({Key? key}) : super(key: key);
@override
@ -54,18 +52,13 @@ class SubtitleThemePage extends StatelessWidget {
Expanded(
child: ListView(
children: [
ListTile(
title: Text(context.l10n.settingsSubtitleThemeTextAlignmentTile),
subtitle: Text(_getTextAlignName(context, settings.subtitleTextAlignment)),
onTap: () => showSelectionDialog<TextAlign>(
context: context,
builder: (context) => AvesSelectionDialog<TextAlign>(
initialValue: settings.subtitleTextAlignment,
options: Map.fromEntries(textAlignOptions.map((v) => MapEntry(v, _getTextAlignName(context, v)))),
title: context.l10n.settingsSubtitleThemeTextAlignmentTitle,
),
SettingsSelectionListTile<TextAlign>(
values: const [TextAlign.left, TextAlign.center, TextAlign.right],
getName: _getTextAlignName,
selector: (context, s) => s.subtitleTextAlignment,
onSelection: (v) => settings.subtitleTextAlignment = v,
),
tileTitle: context.l10n.settingsSubtitleThemeTextAlignmentTile,
dialogTitle: context.l10n.settingsSubtitleThemeTextAlignmentTitle,
),
SliderListTile(
title: context.l10n.settingsSubtitleThemeTextSize,
@ -95,10 +88,10 @@ class SubtitleThemePage extends StatelessWidget {
value: settings.subtitleBackgroundColor.opacity,
onChanged: (v) => settings.subtitleBackgroundColor = settings.subtitleBackgroundColor.withOpacity(v),
),
SwitchListTile(
value: settings.subtitleShowOutline,
SettingsSwitchListTile(
selector: (context, s) => s.subtitleShowOutline,
onChanged: (v) => settings.subtitleShowOutline = v,
title: Text(context.l10n.settingsSubtitleThemeShowOutline),
title: context.l10n.settingsSubtitleThemeShowOutline,
),
],
),

View file

@ -7,8 +7,8 @@ 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/dialogs/aves_selection_dialog.dart';
import 'package:aves/widgets/settings/common/tile_leading.dart';
import 'package:aves/widgets/settings/common/tiles.dart';
import 'package:aves/widgets/settings/video/controls.dart';
import 'package:aves/widgets/settings/video/subtitle_theme.dart';
import 'package:flutter/material.dart';
@ -28,45 +28,28 @@ class VideoSection extends StatelessWidget {
Widget build(BuildContext context) {
final children = [
if (!standalonePage)
Selector<Settings, bool>(
SettingsSwitchListTile(
selector: (context, s) => !s.hiddenFilters.contains(MimeFilter.video),
builder: (context, current, child) => SwitchListTile(
value: current,
onChanged: (v) => settings.changeFilterVisibility({MimeFilter.video}, v),
title: Text(context.l10n.settingsVideoShowVideos),
title: context.l10n.settingsVideoShowVideos,
),
),
Selector<Settings, bool>(
SettingsSwitchListTile(
selector: (context, s) => s.enableVideoHardwareAcceleration,
builder: (context, current, child) => SwitchListTile(
value: current,
onChanged: (v) => settings.enableVideoHardwareAcceleration = v,
title: Text(context.l10n.settingsVideoEnableHardwareAcceleration),
title: context.l10n.settingsVideoEnableHardwareAcceleration,
),
),
Selector<Settings, bool>(
SettingsSwitchListTile(
selector: (context, s) => s.enableVideoAutoPlay,
builder: (context, current, child) => SwitchListTile(
value: current,
onChanged: (v) => settings.enableVideoAutoPlay = v,
title: Text(context.l10n.settingsVideoEnableAutoPlay),
title: context.l10n.settingsVideoEnableAutoPlay,
),
),
Selector<Settings, VideoLoopMode>(
SettingsSelectionListTile<VideoLoopMode>(
values: VideoLoopMode.values,
getName: (context, v) => v.getName(context),
selector: (context, s) => s.videoLoopMode,
builder: (context, current, child) => ListTile(
title: Text(context.l10n.settingsVideoLoopModeTile),
subtitle: Text(current.getName(context)),
onTap: () => showSelectionDialog<VideoLoopMode>(
context: context,
builder: (context) => AvesSelectionDialog<VideoLoopMode>(
initialValue: current,
options: Map.fromEntries(VideoLoopMode.values.map((v) => MapEntry(v, v.getName(context)))),
title: context.l10n.settingsVideoLoopModeTitle,
),
onSelection: (v) => settings.videoLoopMode = v,
),
),
tileTitle: context.l10n.settingsVideoLoopModeTile,
dialogTitle: context.l10n.settingsVideoLoopModeTitle,
),
const VideoControlsTile(),
const SubtitleThemeTile(),
@ -79,7 +62,7 @@ class VideoSection extends StatelessWidget {
: AvesExpansionTile(
leading: SettingsTileLeading(
icon: AIcons.video,
color: AColors.video,
color: context.select<AvesColorsData, Color>((v) => v.video),
),
title: context.l10n.settingsSectionVideo,
expandedNotifier: expandedNotifier,

View file

@ -49,7 +49,7 @@ class _EntryBackgroundSelectorState extends State<EntryBackgroundSelector> {
width: radius * 2,
decoration: BoxDecoration(
color: selected.isColor ? selected.color : null,
border: AvesBorder.border,
border: AvesBorder.border(context),
shape: BoxShape.circle,
),
child: selected == EntryBackground.checkered

View file

@ -1,5 +1,6 @@
import 'package:aves/model/settings/settings.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/settings/common/tiles.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:tuple/tuple.dart';
@ -38,22 +39,16 @@ class ViewerOverlayPage extends StatelessWidget {
body: SafeArea(
child: ListView(
children: [
Selector<Settings, bool>(
SettingsSwitchListTile(
selector: (context, s) => s.showOverlayOnOpening,
builder: (context, current, child) => SwitchListTile(
value: current,
onChanged: (v) => settings.showOverlayOnOpening = v,
title: Text(context.l10n.settingsViewerShowOverlayOnOpening),
title: context.l10n.settingsViewerShowOverlayOnOpening,
),
),
Selector<Settings, bool>(
SettingsSwitchListTile(
selector: (context, s) => s.showOverlayInfo,
builder: (context, current, child) => SwitchListTile(
value: current,
onChanged: (v) => settings.showOverlayInfo = v,
title: Text(context.l10n.settingsViewerShowInformation),
subtitle: Text(context.l10n.settingsViewerShowInformationSubtitle),
),
title: context.l10n.settingsViewerShowInformation,
subtitle: context.l10n.settingsViewerShowInformationSubtitle,
),
Selector<Settings, Tuple2<bool, bool>>(
selector: (context, s) => Tuple2(s.showOverlayInfo, s.showOverlayShootingDetails),
@ -67,29 +62,20 @@ class ViewerOverlayPage extends StatelessWidget {
);
},
),
Selector<Settings, bool>(
SettingsSwitchListTile(
selector: (context, s) => s.showOverlayMinimap,
builder: (context, current, child) => SwitchListTile(
value: current,
onChanged: (v) => settings.showOverlayMinimap = v,
title: Text(context.l10n.settingsViewerShowMinimap),
title: context.l10n.settingsViewerShowMinimap,
),
),
Selector<Settings, bool>(
SettingsSwitchListTile(
selector: (context, s) => s.showOverlayThumbnailPreview,
builder: (context, current, child) => SwitchListTile(
value: current,
onChanged: (v) => settings.showOverlayThumbnailPreview = v,
title: Text(context.l10n.settingsViewerShowOverlayThumbnails),
title: context.l10n.settingsViewerShowOverlayThumbnails,
),
),
Selector<Settings, bool>(
SettingsSwitchListTile(
selector: (context, s) => s.enableOverlayBlurEffect,
builder: (context, current, child) => SwitchListTile(
value: current,
onChanged: (v) => settings.enableOverlayBlurEffect = v,
title: Text(context.l10n.settingsViewerEnableOverlayBlurEffect),
),
title: context.l10n.settingsViewerEnableOverlayBlurEffect,
),
],
),

View file

@ -6,6 +6,7 @@ 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/viewer/entry_background.dart';
import 'package:aves/widgets/settings/viewer/overlay.dart';
import 'package:aves/widgets/settings/viewer/viewer_actions_editor.dart';
@ -25,7 +26,7 @@ class ViewerSection extends StatelessWidget {
return AvesExpansionTile(
leading: SettingsTileLeading(
icon: AIcons.image,
color: AColors.image,
color: context.select<AvesColorsData, Color>((v) => v.image),
),
title: context.l10n.settingsSectionViewer,
expandedNotifier: expandedNotifier,
@ -34,21 +35,15 @@ class ViewerSection extends StatelessWidget {
const ViewerActionsTile(),
const ViewerOverlayTile(),
const _CutoutModeSwitch(),
Selector<Settings, bool>(
SettingsSwitchListTile(
selector: (context, s) => s.viewerMaxBrightness,
builder: (context, current, child) => SwitchListTile(
value: current,
onChanged: (v) => settings.viewerMaxBrightness = v,
title: Text(context.l10n.settingsViewerMaximumBrightness),
title: context.l10n.settingsViewerMaximumBrightness,
),
),
Selector<Settings, bool>(
SettingsSwitchListTile(
selector: (context, s) => s.enableMotionPhotoAutoPlay,
builder: (context, current, child) => SwitchListTile(
value: current,
onChanged: (v) => settings.enableMotionPhotoAutoPlay = v,
title: Text(context.l10n.settingsMotionPhotoAutoPlay),
),
title: context.l10n.settingsMotionPhotoAutoPlay,
),
Selector<Settings, EntryBackground>(
selector: (context, s) => s.imageBackground,
@ -87,13 +82,10 @@ class _CutoutModeSwitchState extends State<_CutoutModeSwitch> {
future: _canSet,
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data!) {
return Selector<Settings, bool>(
return SettingsSwitchListTile(
selector: (context, s) => s.viewerUseCutout,
builder: (context, current, child) => SwitchListTile(
value: current,
onChanged: (v) => settings.viewerUseCutout = v,
title: Text(context.l10n.settingsViewerUseCutout),
),
title: context.l10n.settingsViewerUseCutout,
);
}
return const SizedBox.shrink();

View file

@ -1,4 +1,6 @@
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
@ -49,6 +51,8 @@ class FilterTable<T extends Comparable> extends StatelessWidget {
builder: (context, constraints) {
final showPercentIndicator = constraints.maxWidth - (chipWidth + countWidth) > percentIndicatorMinWidth;
final displayedEntries = maxRowCount != null ? sortedEntries.take(maxRowCount!) : sortedEntries;
final theme = Theme.of(context);
final isMonochrome = settings.themeColorMode == AvesThemeColorMode.monochrome;
return Table(
children: displayedEntries.map((kv) {
final filter = filterBuilder(kv.key);
@ -81,14 +85,16 @@ class FilterTable<T extends Comparable> extends StatelessWidget {
return LinearPercentIndicator(
percent: percent,
lineHeight: lineHeight,
backgroundColor: Colors.white24,
progressColor: color,
backgroundColor: theme.colorScheme.onPrimary.withOpacity(.1),
progressColor: isMonochrome ? theme.colorScheme.secondary : color,
animation: true,
isRTL: isRtl,
barRadius: barRadius,
center: Text(
intl.NumberFormat.percentPattern().format(percent),
style: const TextStyle(shadows: Constants.embossShadows),
style: TextStyle(
shadows: theme.brightness == Brightness.dark ? Constants.embossShadows : null,
),
),
padding: EdgeInsets.zero,
);
@ -98,7 +104,9 @@ class FilterTable<T extends Comparable> extends StatelessWidget {
),
Text(
'$count',
style: const TextStyle(color: Colors.white70),
style: TextStyle(
color: theme.textTheme.caption!.color,
),
textAlign: TextAlign.end,
),
],

View file

@ -10,8 +10,8 @@ import 'package:aves/model/settings/enums/accessibility_animations.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/model/source/collection_source.dart';
import 'package:aves/theme/colors.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/utils/color_utils.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/utils/mime_utils.dart';
import 'package:aves/widgets/collection/collection_page.dart';
@ -22,7 +22,7 @@ import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
import 'package:aves/widgets/stats/filter_table.dart';
import 'package:charts_flutter/flutter.dart' as charts;
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart' as intl;
import 'package:percent_indicator/linear_percent_indicator.dart';
@ -77,11 +77,17 @@ class StatsPage extends StatelessWidget {
text: context.l10n.collectionEmptyImages,
);
} else {
final theme = Theme.of(context);
final isDark = theme.brightness == Brightness.dark;
final animate = context.select<Settings, bool>((v) => v.accessibilityAnimations.animate);
final byMimeTypes = groupBy<AvesEntry, String>(entries, (entry) => entry.mimeType).map<String, int>((k, v) => MapEntry(k, v.length));
final imagesByMimeTypes = Map.fromEntries(byMimeTypes.entries.where((kv) => kv.key.startsWith('image')));
final videoByMimeTypes = Map.fromEntries(byMimeTypes.entries.where((kv) => kv.key.startsWith('video')));
final mimeDonuts = Wrap(
final mimeDonuts = Provider.value(
value: isDark ? NeonOnDark() : PastelOnLight(),
child: Builder(
builder: (context) {
return Wrap(
alignment: WrapAlignment.center,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
@ -89,6 +95,9 @@ class StatsPage extends StatelessWidget {
_buildMimeDonut(context, AIcons.video, videoByMimeTypes, animate),
],
);
},
),
);
final catalogued = entries.where((entry) => entry.isCatalogued);
final withGps = catalogued.where((entry) => entry.hasGps);
@ -114,14 +123,16 @@ class StatsPage extends StatelessWidget {
child: LinearPercentIndicator(
percent: withGpsPercent,
lineHeight: lineHeight,
backgroundColor: Colors.white24,
progressColor: Theme.of(context).colorScheme.secondary,
backgroundColor: theme.colorScheme.onPrimary.withOpacity(.1),
progressColor: theme.colorScheme.secondary,
animation: animate,
isRTL: context.isRtl,
barRadius: barRadius,
center: Text(
intl.NumberFormat.percentPattern().format(withGpsPercent),
style: const TextStyle(shadows: Constants.embossShadows),
style: TextStyle(
shadows: isDark ? Constants.embossShadows : null,
),
),
padding: EdgeInsets.zero,
),
@ -176,7 +187,17 @@ class StatsPage extends StatelessWidget {
final sum = byMimeTypes.values.fold<int>(0, (prev, v) => prev + v);
final seriesData = byMimeTypes.entries.map((kv) => EntryByMimeDatum(mimeType: kv.key, entryCount: kv.value)).toList();
final colors = context.watch<AvesColorsData>();
final seriesData = byMimeTypes.entries.map((kv) {
final mimeType = kv.key;
final displayText = MimeUtils.displayType(mimeType);
return EntryByMimeDatum(
mimeType: mimeType,
displayText: displayText,
color: colors.fromString(displayText),
entryCount: kv.value,
);
}).toList();
seriesData.sort((d1, d2) {
final c = d2.entryCount.compareTo(d1.entryCount);
return c != 0 ? c : compareAsciiUpperCase(d1.displayText, d2.displayText);
@ -250,7 +271,9 @@ class StatsPage extends StatelessWidget {
const SizedBox(width: 8),
Text(
'${d.entryCount}',
style: const TextStyle(color: Colors.white70),
style: TextStyle(
color: Theme.of(context).textTheme.caption!.color,
),
),
],
),
@ -327,29 +350,28 @@ class StatsPage extends StatelessWidget {
MaterialPageRoute(
settings: const RouteSettings(name: CollectionPage.routeName),
builder: (context) => CollectionPage(
collection: CollectionLens(
source: source,
filters: {filter},
),
),
),
(route) => false,
);
}
}
class EntryByMimeDatum {
final String mimeType;
final String displayText;
@immutable
class EntryByMimeDatum extends Equatable {
final String mimeType, displayText;
final Color color;
final int entryCount;
EntryByMimeDatum({
required this.mimeType,
required this.entryCount,
}) : displayText = MimeUtils.displayType(mimeType);
Color get color => stringToColor(displayText);
@override
String toString() => '$runtimeType#${shortHash(this)}{mimeType=$mimeType, displayText=$displayText, entryCount=$entryCount}';
List<Object?> get props => [mimeType, displayText, color, entryCount];
const EntryByMimeDatum({
required this.mimeType,
required this.displayText,
required this.color,
required this.entryCount,
});
}

View file

@ -8,16 +8,13 @@ import 'package:aves/model/device.dart';
import 'package:aves/model/entry.dart';
import 'package:aves/model/entry_metadata_edition.dart';
import 'package:aves/model/filters/album.dart';
import 'package:aves/model/highlight.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/model/source/collection_source.dart';
import 'package:aves/services/common/image_op_events.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/services/media/enums.dart';
import 'package:aves/services/media/media_file_service.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/widgets/collection/collection_page.dart';
import 'package:aves/widgets/common/action_mixins/entry_storage.dart';
import 'package:aves/widgets/common/action_mixins/feedback.dart';
@ -262,28 +259,19 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
final showAction = isMainMode && newUris.isNotEmpty
? SnackBarAction(
label: context.l10n.showButtonLabel,
onPressed: () async {
final highlightInfo = context.read<HighlightInfo>();
final targetCollection = CollectionLens(
source: source,
filters: {AlbumFilter(destinationAlbum, source.getAlbumDisplayName(context, destinationAlbum))},
);
unawaited(Navigator.pushAndRemoveUntil(
onPressed: () {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
settings: const RouteSettings(name: CollectionPage.routeName),
builder: (context) => CollectionPage(
collection: targetCollection,
source: source,
filters: {AlbumFilter(destinationAlbum, source.getAlbumDisplayName(context, destinationAlbum))},
highlightTest: (entry) => newUris.contains(entry.uri),
),
),
(route) => false,
));
final delayDuration = context.read<DurationsData>().staggeredAnimationPageTarget;
await Future.delayed(delayDuration + Durations.highlightScrollInitDelay);
final targetEntry = targetCollection.sortedEntries.firstWhereOrNull((entry) => newUris.contains(entry.uri));
if (targetEntry != null) {
highlightInfo.trackItem(targetEntry, highlightItem: targetEntry);
}
);
},
)
: null;

View file

@ -39,7 +39,7 @@ class ViewerVerticalPageView extends StatefulWidget {
}
class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> {
final ValueNotifier<Color> _backgroundColorNotifier = ValueNotifier(Colors.black);
final ValueNotifier<double> _backgroundOpacityNotifier = ValueNotifier(1);
final ValueNotifier<bool> _isVerticallyScrollingNotifier = ValueNotifier(false);
Timer? _verticalScrollMonitoringTimer;
AvesEntry? _oldEntry;
@ -122,12 +122,15 @@ class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> {
imagePage,
infoPage,
];
return ValueListenableBuilder<Color>(
valueListenable: _backgroundColorNotifier,
builder: (context, backgroundColor, child) => Container(
color: backgroundColor,
return ValueListenableBuilder<double>(
valueListenable: _backgroundOpacityNotifier,
builder: (context, backgroundOpacity, child) {
final background = Theme.of(context).brightness == Brightness.dark ? Colors.black : Colors.white;
return Container(
color: background.withOpacity(backgroundOpacity),
child: child,
),
);
},
child: PageView(
// key is expected by test driver
key: const Key('vertical-pageview'),
@ -196,7 +199,7 @@ class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> {
final page = widget.verticalPager.page!;
final opacity = min(1.0, page);
_backgroundColorNotifier.value = _backgroundColorNotifier.value.withOpacity(opacity * opacity);
_backgroundOpacityNotifier.value = opacity * opacity;
if (page <= 1 && settings.viewerMaxBrightness) {
_systemBrightness?.then((system) {

View file

@ -35,7 +35,11 @@ class EntryViewerPage extends StatelessWidget {
),
),
),
backgroundColor: Navigator.canPop(context) ? Colors.transparent : Colors.black,
backgroundColor: Navigator.canPop(context)
? Colors.transparent
: Theme.of(context).brightness == Brightness.dark
? Colors.black
: Colors.white,
resizeToAvoidBottomInset: false,
),
);

View file

@ -414,10 +414,8 @@ class _EntryViewerStackState extends State<EntryViewerStack> with FeedbackMixin,
settings: const RouteSettings(name: CollectionPage.routeName),
builder: (context) {
return CollectionPage(
collection: CollectionLens(
source: baseCollection.source,
filters: baseCollection.filters,
)..addFilter(filter),
filters: {...baseCollection.filters, filter},
);
},
),

View file

@ -8,6 +8,7 @@ import 'package:aves/model/filters/rating.dart';
import 'package:aves/model/filters/tag.dart';
import 'package:aves/model/filters/type.dart';
import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/theme/colors.dart';
import 'package:aves/theme/format.dart';
import 'package:aves/utils/file_utils.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
@ -156,7 +157,7 @@ class BasicSection extends StatelessWidget {
DecoratedBox(
decoration: BoxDecoration(
border: Border.fromBorderSide(BorderSide(
color: isEditing ? Theme.of(context).disabledColor : AvesFilterChip.defaultOutlineColor,
color: isEditing ? Theme.of(context).disabledColor : context.select<AvesColorsData, Color>((v) => v.neutral),
width: AvesFilterChip.outlineWidth,
)),
borderRadius: const BorderRadius.all(Radius.circular(AvesFilterChip.defaultRadius)),

View file

@ -21,7 +21,6 @@ class SectionRow extends StatelessWidget {
width: dim,
child: Divider(
thickness: AvesFilterChip.outlineWidth,
color: Colors.white70,
),
);
return Row(
@ -47,11 +46,11 @@ class InfoRowGroup extends StatefulWidget {
final Map<String, InfoLinkHandler>? linkHandlers;
static const keyValuePadding = 16;
static const linkColor = Colors.blue;
static const fontSize = 13.0;
static const baseStyle = TextStyle(fontSize: fontSize);
static final keyStyle = baseStyle.copyWith(color: Colors.white70, height: 2.0);
static final linkStyle = baseStyle.copyWith(color: linkColor, decoration: TextDecoration.underline);
static const valueStyle = TextStyle(fontSize: fontSize);
static final _keyStyle = valueStyle.copyWith(height: 2.0);
static TextStyle keyStyle(BuildContext context) => Theme.of(context).textTheme.caption!.merge(_keyStyle);
const InfoRowGroup({
Key? key,
@ -77,9 +76,11 @@ class _InfoRowGroupState extends State<InfoRowGroup> {
Widget build(BuildContext context) {
if (keyValues.isEmpty) return const SizedBox.shrink();
final _keyStyle = InfoRowGroup.keyStyle(context);
// compute the size of keys and space in order to align values
final textScaleFactor = MediaQuery.textScaleFactorOf(context);
final keySizes = Map.fromEntries(keyValues.keys.map((key) => MapEntry(key, _getSpanWidth(TextSpan(text: key, style: InfoRowGroup.keyStyle), textScaleFactor))));
final keySizes = Map.fromEntries(keyValues.keys.map((key) => MapEntry(key, _getSpanWidth(TextSpan(text: key, style: _keyStyle), textScaleFactor))));
final lastKey = keyValues.keys.last;
return LayoutBuilder(
@ -102,7 +103,10 @@ class _InfoRowGroupState extends State<InfoRowGroup> {
value = handler.linkText(context);
// open link on tap
recognizer = TapGestureRecognizer()..onTap = () => handler.onTap(context);
style = InfoRowGroup.linkStyle;
// `colorScheme.secondary` is overridden upstream as an `ExpansionTileCard` theming workaround,
// so we use `colorScheme.primary` instead
final linkColor = Theme.of(context).colorScheme.primary;
style = InfoRowGroup.valueStyle.copyWith(color: linkColor, decoration: TextDecoration.underline);
} else {
value = kv.value;
// long values are clipped, and made expandable by tapping them
@ -125,14 +129,14 @@ class _InfoRowGroupState extends State<InfoRowGroup> {
// (e.g. keys on the right for RTL locale, whatever the key intrinsic directionality)
// and each span respects the directionality of its inner text only
return [
TextSpan(text: '${Constants.fsi}$key${Constants.pdi}', style: InfoRowGroup.keyStyle),
TextSpan(text: '${Constants.fsi}$key${Constants.pdi}', style: _keyStyle),
WidgetSpan(child: SizedBox(width: thisSpaceSize)),
TextSpan(text: '${Constants.fsi}$value${Constants.pdi}', style: style, recognizer: recognizer),
];
},
).toList(),
),
style: InfoRowGroup.baseStyle,
style: InfoRowGroup.valueStyle,
);
},
);

View file

@ -3,7 +3,7 @@ import 'dart:collection';
import 'package:aves/model/entry.dart';
import 'package:aves/ref/brand_colors.dart';
import 'package:aves/services/metadata/svg_metadata_service.dart';
import 'package:aves/utils/color_utils.dart';
import 'package:aves/theme/colors.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';
@ -15,6 +15,7 @@ import 'package:aves/widgets/viewer/info/metadata/xmp_tile.dart';
import 'package:aves/widgets/viewer/source_viewer_page.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class MetadataDirTile extends StatelessWidget {
final AvesEntry entry;
@ -58,9 +59,10 @@ class MetadataDirTile extends StatelessWidget {
break;
}
final colors = context.watch<AvesColorsData>();
return AvesExpansionTile(
title: title,
color: dir.color ?? BrandColors.get(dirName) ?? stringToColor(dirName),
highlightColor: dir.color ?? colors.fromBrandColor(BrandColors.get(dirName)) ?? colors.fromString(dirName),
expandedNotifier: expandedDirectoryNotifier,
initiallyExpanded: initiallyExpanded,
children: [

View file

@ -7,9 +7,9 @@ import 'package:aves/model/video/metadata.dart';
import 'package:aves/ref/mime_types.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/services/metadata/svg_metadata_service.dart';
import 'package:aves/theme/colors.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/utils/color_utils.dart';
import 'package:aves/widgets/viewer/info/common.dart';
import 'package:aves/widgets/viewer/info/metadata/metadata_dir_tile.dart';
import 'package:collection/collection.dart';
@ -218,13 +218,14 @@ class _MetadataSectionSliverState extends State<MetadataSectionSliver> {
if (knownStreams.isNotEmpty) {
final indexDigits = knownStreams.length.toString().length;
final colors = context.read<AvesColorsData>();
for (final stream in knownStreams) {
final index = (stream[Keys.index] ?? 0) + 1;
final typeText = getTypeText(stream);
final dirName = 'Stream ${index.toString().padLeft(indexDigits, '0')}$typeText';
final formattedStreamTags = VideoMetadataFormatter.formatInfo(stream);
if (formattedStreamTags.isNotEmpty) {
final color = stringToColor(typeText);
final color = colors.fromString(typeText);
directories.add(MetadataDirectory(dirName, _toSortedTags(formattedStreamTags), color: color));
}
}

View file

@ -1,4 +1,5 @@
import 'package:aves/ref/brand_colors.dart';
import 'package:aves/theme/colors.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/utils/string_utils.dart';
import 'package:aves/utils/xmp_utils.dart';
@ -19,6 +20,7 @@ import 'package:collection/collection.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
@immutable
class XmpNamespace extends Equatable {
@ -98,7 +100,10 @@ class XmpNamespace extends Equatable {
'Iptc4xmpCore': 'IPTC Core',
'Iptc4xmpExt': 'IPTC Extension',
'lr': 'Lightroom',
'MicrosoftPhoto': 'Microsoft Photo',
'mediapro': 'MediaPro',
'MicrosoftPhoto': 'Microsoft Photo 1.0',
'MP1': 'Microsoft Photo 1.1',
'MP': 'Microsoft Photo 1.2',
'mwg-rs': 'Regions',
'nga': 'National Gallery of Art',
'panorama': 'Panorama',
@ -122,7 +127,7 @@ class XmpNamespace extends Equatable {
Map<String, String> get buildProps => rawProps;
List<Widget> buildNamespaceSection() {
List<Widget> buildNamespaceSection(BuildContext context) {
final props = buildProps.entries
.map((kv) {
final prop = XmpProp(kv.key, kv.value);
@ -149,7 +154,7 @@ class XmpNamespace extends Equatable {
padding: const EdgeInsets.only(top: 8),
child: HighlightTitle(
title: displayTitle,
color: BrandColors.get(displayTitle),
color: context.select<AvesColorsData, Color?>((v) => v.fromBrandColor(BrandColors.get(displayTitle))),
selectable: true,
),
),

View file

@ -7,6 +7,7 @@ import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class XmpDirTile extends StatefulWidget {
final AvesEntry entry;
@ -43,7 +44,7 @@ class _XmpDirTileState extends State<XmpDirTile> {
return AvesExpansionTile(
// title may contain parent to distinguish multiple XMP directories
title: widget.title,
color: AColors.xmp,
highlightColor: context.select<AvesColorsData, Color>((v) => v.xmp),
expandedNotifier: widget.expandedNotifier,
initiallyExpanded: widget.initiallyExpanded,
children: [
@ -51,7 +52,7 @@ class _XmpDirTileState extends State<XmpDirTile> {
padding: const EdgeInsets.only(left: 8, right: 8, bottom: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: sections.expand((section) => section.buildNamespaceSection()).toList(),
children: sections.expand((section) => section.buildNamespaceSection(context)).toList(),
),
),
],

View file

@ -63,7 +63,7 @@ class _OwnerPropState extends State<OwnerProp> {
children: [
TextSpan(
text: context.l10n.viewerInfoLabelOwner,
style: InfoRowGroup.keyStyle,
style: InfoRowGroup.keyStyle(context),
),
WidgetSpan(
alignment: PlaceholderAlignment.middle,
@ -81,7 +81,7 @@ class _OwnerPropState extends State<OwnerProp> {
),
TextSpan(
text: appName,
style: InfoRowGroup.baseStyle,
style: InfoRowGroup.valueStyle,
),
],
),

View file

@ -1,10 +1,9 @@
import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/themes.dart';
import 'package:aves/widgets/common/fx/blurred.dart';
import 'package:aves/widgets/common/fx/borders.dart';
import 'package:flutter/material.dart';
Color overlayBackgroundColor({required bool blurred}) => blurred ? Colors.black26 : Colors.black38;
class OverlayButton extends StatelessWidget {
final Animation<double> scale;
final BorderRadius? borderRadius;
@ -19,6 +18,7 @@ class OverlayButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final brightness = Theme.of(context).brightness;
final blurred = settings.enableOverlayBlurEffect;
return ScaleTransition(
scale: scale,
@ -29,10 +29,10 @@ class OverlayButton extends StatelessWidget {
child: Material(
type: MaterialType.button,
borderRadius: borderRadius,
color: overlayBackgroundColor(blurred: blurred),
color: Themes.overlayBackgroundColor(brightness: brightness, blurred: blurred),
child: Ink(
decoration: BoxDecoration(
border: AvesBorder.border,
border: AvesBorder.border(context),
borderRadius: borderRadius,
),
child: child,
@ -43,10 +43,10 @@ class OverlayButton extends StatelessWidget {
enabled: blurred,
child: Material(
type: MaterialType.circle,
color: overlayBackgroundColor(blurred: blurred),
color: Themes.overlayBackgroundColor(brightness: brightness, blurred: blurred),
child: Ink(
decoration: BoxDecoration(
border: AvesBorder.border,
border: AvesBorder.border(context),
shape: BoxShape.circle,
),
child: child,
@ -78,6 +78,7 @@ class OverlayTextButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final blurred = settings.enableOverlayBlurEffect;
final theme = Theme.of(context);
return SizeTransition(
sizeFactor: scale,
child: BlurredRRect.all(
@ -86,11 +87,11 @@ class OverlayTextButton extends StatelessWidget {
child: OutlinedButton(
onPressed: onPressed,
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all<Color>(overlayBackgroundColor(blurred: blurred)),
foregroundColor: MaterialStateProperty.all<Color>(Colors.white),
overlayColor: MaterialStateProperty.all<Color>(Colors.white.withOpacity(0.12)),
backgroundColor: MaterialStateProperty.all<Color>(Themes.overlayBackgroundColor(brightness: theme.brightness, blurred: blurred)),
foregroundColor: MaterialStateProperty.all<Color>(theme.colorScheme.onSurface),
overlayColor: theme.brightness == Brightness.dark ? MaterialStateProperty.all<Color>(Colors.white.withOpacity(0.12)) : null,
minimumSize: _minSize,
side: MaterialStateProperty.all<BorderSide>(AvesBorder.curvedSide),
side: MaterialStateProperty.all<BorderSide>(AvesBorder.curvedSide(context)),
shape: MaterialStateProperty.all<OutlinedBorder>(const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(_borderRadius)),
)),

View file

@ -24,6 +24,8 @@ const double _iconSize = 16.0;
const double _interRowPadding = 2.0;
const double _subRowMinWidth = 300.0;
List<Shadow>? _shadows(BuildContext context) => Theme.of(context).brightness == Brightness.dark ? Constants.embossShadows : null;
class ViewerDetailOverlay extends StatefulWidget {
final List<AvesEntry> entries;
final int index;
@ -142,7 +144,7 @@ class ViewerDetailOverlayContent extends StatelessWidget {
return DefaultTextStyle(
style: Theme.of(context).textTheme.bodyText2!.copyWith(
shadows: Constants.embossShadows,
shadows: _shadows(context),
),
softWrap: false,
overflow: TextOverflow.fade,
@ -271,7 +273,7 @@ class _LocationRow extends AnimatedWidget {
}
return Row(
children: [
const DecoratedIcon(AIcons.location, shadows: Constants.embossShadows, size: _iconSize),
DecoratedIcon(AIcons.location, shadows: _shadows(context), size: _iconSize),
const SizedBox(width: _iconPadding),
Expanded(child: Text(location, strutStyle: Constants.overflowStrutStyle)),
],
@ -352,7 +354,7 @@ class _DateRow extends StatelessWidget {
return Row(
children: [
const DecoratedIcon(AIcons.date, shadows: Constants.embossShadows, size: _iconSize),
DecoratedIcon(AIcons.date, shadows: _shadows(context), size: _iconSize),
const SizedBox(width: _iconPadding),
Expanded(flex: 3, child: Text(dateText, strutStyle: Constants.overflowStrutStyle)),
Expanded(flex: 2, child: Text(resolutionText, strutStyle: Constants.overflowStrutStyle)),
@ -381,7 +383,7 @@ class _ShootingRow extends StatelessWidget {
return Row(
children: [
const DecoratedIcon(AIcons.shooting, shadows: Constants.embossShadows, size: _iconSize),
DecoratedIcon(AIcons.shooting, shadows: _shadows(context), size: _iconSize),
const SizedBox(width: _iconPadding),
Expanded(child: Text(apertureText, strutStyle: Constants.overflowStrutStyle)),
Expanded(child: Text(details.exposureTime ?? Constants.overlayUnknown, strutStyle: Constants.overflowStrutStyle)),

Some files were not shown because too many files have changed in this diff Show more