parent
44da3cb222
commit
5913358817
106 changed files with 1451 additions and 872 deletions
|
@ -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",
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 }
|
||||
|
|
30
lib/model/settings/enums/theme_brightness.dart
Normal file
30
lib/model/settings/enums/theme_brightness.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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:
|
||||
|
|
|
@ -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}';
|
||||
}
|
||||
|
|
|
@ -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)!;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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(
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
||||
|
|
|
@ -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(),
|
||||
),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
]
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
26
lib/widgets/common/fx/colors.dart
Normal file
26
lib/widgets/common/fx/colors.dart
Normal 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,
|
||||
]);
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -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> {
|
|||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
),
|
||||
];
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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},
|
||||
);
|
||||
},
|
||||
),
|
||||
|
|
|
@ -266,12 +266,10 @@ class CollectionSearchDelegate {
|
|||
MaterialPageRoute(
|
||||
settings: const RouteSettings(name: CollectionPage.routeName),
|
||||
builder: (context) => CollectionPage(
|
||||
collection: CollectionLens(
|
||||
source: source,
|
||||
filters: {filter},
|
||||
),
|
||||
),
|
||||
),
|
||||
(route) => false,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
);
|
||||
|
|
91
lib/widgets/settings/common/tiles.dart
Normal file
91
lib/widgets/settings/common/tiles.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
48
lib/widgets/settings/display/display.dart
Normal file
48
lib/widgets/settings/display/display.dart
Normal 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,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
);
|
||||
|
|
|
@ -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},
|
||||
);
|
||||
},
|
||||
),
|
||||
|
|
|
@ -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)),
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
@ -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: [
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
|
|
|
@ -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(),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -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)),
|
||||
)),
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue