parent
44da3cb222
commit
5913358817
106 changed files with 1451 additions and 872 deletions
|
@ -177,6 +177,10 @@
|
||||||
"accessibilityAnimationsRemove": "Prevent screen effects",
|
"accessibilityAnimationsRemove": "Prevent screen effects",
|
||||||
"accessibilityAnimationsKeep": "Keep screen effects",
|
"accessibilityAnimationsKeep": "Keep screen effects",
|
||||||
|
|
||||||
|
"themeBrightnessLight": "Light",
|
||||||
|
"themeBrightnessDark": "Dark",
|
||||||
|
"themeBrightnessBlack": "Black",
|
||||||
|
|
||||||
"albumTierNew": "New",
|
"albumTierNew": "New",
|
||||||
"albumTierPinned": "Pinned",
|
"albumTierPinned": "Pinned",
|
||||||
"albumTierSpecial": "Common",
|
"albumTierSpecial": "Common",
|
||||||
|
@ -671,6 +675,10 @@
|
||||||
"settingsTimeToTakeActionTile": "Time to take action",
|
"settingsTimeToTakeActionTile": "Time to take action",
|
||||||
"settingsTimeToTakeActionTitle": "Time to Take Action",
|
"settingsTimeToTakeActionTitle": "Time to Take Action",
|
||||||
|
|
||||||
|
"settingsSectionDisplay": "Display",
|
||||||
|
"settingsThemeBrightness": "Theme",
|
||||||
|
"settingsThemeColorful": "Colorful",
|
||||||
|
|
||||||
"settingsSectionLanguage": "Language & Formats",
|
"settingsSectionLanguage": "Language & Formats",
|
||||||
"settingsLanguage": "Language",
|
"settingsLanguage": "Language",
|
||||||
"settingsCoordinateFormatTile": "Coordinate format",
|
"settingsCoordinateFormatTile": "Coordinate format",
|
||||||
|
|
|
@ -177,7 +177,8 @@ extension ExtraEntryAction on EntryAction {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case EntryAction.debug:
|
case EntryAction.debug:
|
||||||
return ShaderMask(
|
return ShaderMask(
|
||||||
shaderCallback: AColors.debugGradient.createShader,
|
shaderCallback: AvesColorsData.debugGradient.createShader,
|
||||||
|
blendMode: BlendMode.srcIn,
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -55,7 +55,8 @@ extension ExtraEntryInfoAction on EntryInfoAction {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case EntryInfoAction.debug:
|
case EntryInfoAction.debug:
|
||||||
return ShaderMask(
|
return ShaderMask(
|
||||||
shaderCallback: AColors.debugGradient.createShader,
|
shaderCallback: AvesColorsData.debugGradient.createShader,
|
||||||
|
blendMode: BlendMode.srcIn,
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
default:
|
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/model/filters/filters.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/theme/colors.dart';
|
import 'package:aves/theme/colors.dart';
|
||||||
|
@ -7,13 +6,11 @@ import 'package:aves/utils/android_file_utils.dart';
|
||||||
import 'package:aves/widgets/common/identity/aves_icons.dart';
|
import 'package:aves/widgets/common/identity/aves_icons.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:palette_generator/palette_generator.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class AlbumFilter extends CollectionFilter {
|
class AlbumFilter extends CollectionFilter {
|
||||||
static const type = 'album';
|
static const type = 'album';
|
||||||
|
|
||||||
static final Map<String, Color> _appColors = {};
|
|
||||||
|
|
||||||
final String album;
|
final String album;
|
||||||
final String? displayName;
|
final String? displayName;
|
||||||
|
|
||||||
|
@ -56,6 +53,7 @@ class AlbumFilter extends CollectionFilter {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Color> color(BuildContext context) {
|
Future<Color> color(BuildContext context) {
|
||||||
|
final colors = context.watch<AvesColorsData>();
|
||||||
// do not use async/await and rely on `SynchronousFuture`
|
// do not use async/await and rely on `SynchronousFuture`
|
||||||
// to prevent rebuilding of the `FutureBuilder` listening on this future
|
// to prevent rebuilding of the `FutureBuilder` listening on this future
|
||||||
final albumType = androidFileUtils.getAlbumType(album);
|
final albumType = androidFileUtils.getAlbumType(album);
|
||||||
|
@ -63,31 +61,19 @@ class AlbumFilter extends CollectionFilter {
|
||||||
case AlbumType.regular:
|
case AlbumType.regular:
|
||||||
break;
|
break;
|
||||||
case AlbumType.app:
|
case AlbumType.app:
|
||||||
if (_appColors.containsKey(album)) return SynchronousFuture(_appColors[album]!);
|
final appColor = colors.appColor(album);
|
||||||
|
if (appColor != null) return appColor;
|
||||||
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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case AlbumType.camera:
|
case AlbumType.camera:
|
||||||
return SynchronousFuture(AColors.albumCamera);
|
return SynchronousFuture(colors.albumCamera);
|
||||||
case AlbumType.download:
|
case AlbumType.download:
|
||||||
return SynchronousFuture(AColors.albumDownload);
|
return SynchronousFuture(colors.albumDownload);
|
||||||
case AlbumType.screenRecordings:
|
case AlbumType.screenRecordings:
|
||||||
return SynchronousFuture(AColors.albumScreenRecordings);
|
return SynchronousFuture(colors.albumScreenRecordings);
|
||||||
case AlbumType.screenshots:
|
case AlbumType.screenshots:
|
||||||
return SynchronousFuture(AColors.albumScreenshots);
|
return SynchronousFuture(colors.albumScreenshots);
|
||||||
case AlbumType.videoCaptures:
|
case AlbumType.videoCaptures:
|
||||||
return SynchronousFuture(AColors.albumVideoCaptures);
|
return SynchronousFuture(colors.albumVideoCaptures);
|
||||||
}
|
}
|
||||||
return super.color(context);
|
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:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class FavouriteFilter extends CollectionFilter {
|
class FavouriteFilter extends CollectionFilter {
|
||||||
static const type = 'favourite';
|
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);
|
Widget iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(AIcons.favourite, size: size);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Color> color(BuildContext context) => SynchronousFuture(AColors.favourite);
|
Future<Color> color(BuildContext context) {
|
||||||
|
final colors = context.watch<AvesColorsData>();
|
||||||
|
return SynchronousFuture(colors.favourite);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get category => type;
|
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/tag.dart';
|
||||||
import 'package:aves/model/filters/trash.dart';
|
import 'package:aves/model/filters/trash.dart';
|
||||||
import 'package:aves/model/filters/type.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:collection/collection.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
@immutable
|
@immutable
|
||||||
abstract class CollectionFilter extends Equatable implements Comparable<CollectionFilter> {
|
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;
|
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;
|
String get category;
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:aves/model/filters/filters.dart';
|
import 'package:aves/model/filters/filters.dart';
|
||||||
import 'package:aves/ref/mime_types.dart';
|
import 'package:aves/ref/mime_types.dart';
|
||||||
import 'package:aves/theme/colors.dart';
|
import 'package:aves/theme/colors.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/utils/color_utils.dart';
|
|
||||||
import 'package:aves/utils/mime_utils.dart';
|
import 'package:aves/utils/mime_utils.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class MimeFilter extends CollectionFilter {
|
class MimeFilter extends CollectionFilter {
|
||||||
static const type = 'mime';
|
static const type = 'mime';
|
||||||
|
@ -15,7 +17,6 @@ class MimeFilter extends CollectionFilter {
|
||||||
late final EntryFilter _test;
|
late final EntryFilter _test;
|
||||||
late final String _label;
|
late final String _label;
|
||||||
late final IconData _icon;
|
late final IconData _icon;
|
||||||
late final Color _color;
|
|
||||||
|
|
||||||
static final image = MimeFilter(MimeTypes.anyImage);
|
static final image = MimeFilter(MimeTypes.anyImage);
|
||||||
static final video = MimeFilter(MimeTypes.anyVideo);
|
static final video = MimeFilter(MimeTypes.anyVideo);
|
||||||
|
@ -25,7 +26,6 @@ class MimeFilter extends CollectionFilter {
|
||||||
|
|
||||||
MimeFilter(this.mime) {
|
MimeFilter(this.mime) {
|
||||||
IconData? icon;
|
IconData? icon;
|
||||||
Color? color;
|
|
||||||
var lowMime = mime.toLowerCase();
|
var lowMime = mime.toLowerCase();
|
||||||
if (lowMime.endsWith('/*')) {
|
if (lowMime.endsWith('/*')) {
|
||||||
lowMime = lowMime.substring(0, lowMime.length - 2);
|
lowMime = lowMime.substring(0, lowMime.length - 2);
|
||||||
|
@ -33,17 +33,14 @@ class MimeFilter extends CollectionFilter {
|
||||||
_label = lowMime.toUpperCase();
|
_label = lowMime.toUpperCase();
|
||||||
if (mime == MimeTypes.anyImage) {
|
if (mime == MimeTypes.anyImage) {
|
||||||
icon = AIcons.image;
|
icon = AIcons.image;
|
||||||
color = AColors.image;
|
|
||||||
} else if (mime == MimeTypes.anyVideo) {
|
} else if (mime == MimeTypes.anyVideo) {
|
||||||
icon = AIcons.video;
|
icon = AIcons.video;
|
||||||
color = AColors.video;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_test = (entry) => entry.mimeType == lowMime;
|
_test = (entry) => entry.mimeType == lowMime;
|
||||||
_label = MimeUtils.displayType(lowMime);
|
_label = MimeUtils.displayType(lowMime);
|
||||||
}
|
}
|
||||||
_icon = icon ?? AIcons.vector;
|
_icon = icon ?? AIcons.vector;
|
||||||
_color = color ?? stringToColor(_label);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MimeFilter.fromMap(Map<String, dynamic> json)
|
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);
|
Widget iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(_icon, size: size);
|
||||||
|
|
||||||
@override
|
@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
|
@override
|
||||||
String get category => type;
|
String get category => type;
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import 'package:aves/model/entry.dart';
|
import 'package:aves/model/entry.dart';
|
||||||
import 'package:aves/model/filters/filters.dart';
|
import 'package:aves/model/filters/filters.dart';
|
||||||
|
import 'package:aves/theme/colors.dart';
|
||||||
import 'package:aves/theme/icons.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/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class QueryFilter extends CollectionFilter {
|
class QueryFilter extends CollectionFilter {
|
||||||
static const type = 'query';
|
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);
|
Widget iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(AIcons.text, size: size);
|
||||||
|
|
||||||
@override
|
@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
|
@override
|
||||||
String get category => type;
|
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:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class TypeFilter extends CollectionFilter {
|
class TypeFilter extends CollectionFilter {
|
||||||
static const type = 'type';
|
static const type = 'type';
|
||||||
|
@ -18,7 +19,6 @@ class TypeFilter extends CollectionFilter {
|
||||||
final String itemType;
|
final String itemType;
|
||||||
late final EntryFilter _test;
|
late final EntryFilter _test;
|
||||||
late final IconData _icon;
|
late final IconData _icon;
|
||||||
late final Color _color;
|
|
||||||
|
|
||||||
static final animated = TypeFilter._private(_animated);
|
static final animated = TypeFilter._private(_animated);
|
||||||
static final geotiff = TypeFilter._private(_geotiff);
|
static final geotiff = TypeFilter._private(_geotiff);
|
||||||
|
@ -35,32 +35,26 @@ class TypeFilter extends CollectionFilter {
|
||||||
case _animated:
|
case _animated:
|
||||||
_test = (entry) => entry.isAnimated;
|
_test = (entry) => entry.isAnimated;
|
||||||
_icon = AIcons.animated;
|
_icon = AIcons.animated;
|
||||||
_color = AColors.animated;
|
|
||||||
break;
|
break;
|
||||||
case _geotiff:
|
case _geotiff:
|
||||||
_test = (entry) => entry.isGeotiff;
|
_test = (entry) => entry.isGeotiff;
|
||||||
_icon = AIcons.geo;
|
_icon = AIcons.geo;
|
||||||
_color = AColors.geotiff;
|
|
||||||
break;
|
break;
|
||||||
case _motionPhoto:
|
case _motionPhoto:
|
||||||
_test = (entry) => entry.isMotionPhoto;
|
_test = (entry) => entry.isMotionPhoto;
|
||||||
_icon = AIcons.motionPhoto;
|
_icon = AIcons.motionPhoto;
|
||||||
_color = AColors.motionPhoto;
|
|
||||||
break;
|
break;
|
||||||
case _panorama:
|
case _panorama:
|
||||||
_test = (entry) => entry.isImage && entry.is360;
|
_test = (entry) => entry.isImage && entry.is360;
|
||||||
_icon = AIcons.threeSixty;
|
_icon = AIcons.threeSixty;
|
||||||
_color = AColors.panorama;
|
|
||||||
break;
|
break;
|
||||||
case _raw:
|
case _raw:
|
||||||
_test = (entry) => entry.isRaw;
|
_test = (entry) => entry.isRaw;
|
||||||
_icon = AIcons.raw;
|
_icon = AIcons.raw;
|
||||||
_color = AColors.raw;
|
|
||||||
break;
|
break;
|
||||||
case _sphericalVideo:
|
case _sphericalVideo:
|
||||||
_test = (entry) => entry.isVideo && entry.is360;
|
_test = (entry) => entry.isVideo && entry.is360;
|
||||||
_icon = AIcons.threeSixty;
|
_icon = AIcons.threeSixty;
|
||||||
_color = AColors.sphericalVideo;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,7 +100,24 @@ class TypeFilter extends CollectionFilter {
|
||||||
Widget iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(_icon, size: size);
|
Widget iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(_icon, size: size);
|
||||||
|
|
||||||
@override
|
@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
|
@override
|
||||||
String get category => type;
|
String get category => type;
|
||||||
|
|
|
@ -16,6 +16,8 @@ class SettingsDefaults {
|
||||||
static const isInstalledAppAccessAllowed = false;
|
static const isInstalledAppAccessAllowed = false;
|
||||||
static const isErrorReportingAllowed = false;
|
static const isErrorReportingAllowed = false;
|
||||||
static const tileLayout = TileLayout.grid;
|
static const tileLayout = TileLayout.grid;
|
||||||
|
static const themeBrightness = AvesThemeBrightness.system;
|
||||||
|
static const themeColorMode = AvesThemeColorMode.polychrome;
|
||||||
|
|
||||||
// navigation
|
// navigation
|
||||||
static const mustBackTwiceToExit = true;
|
static const mustBackTwiceToExit = true;
|
||||||
|
|
|
@ -2,6 +2,10 @@ enum AccessibilityAnimations { system, disabled, enabled }
|
||||||
|
|
||||||
enum AccessibilityTimeout { system, appDefault, s10, s30, s60, s120 }
|
enum AccessibilityTimeout { system, appDefault, s10, s30, s60, s120 }
|
||||||
|
|
||||||
|
enum AvesThemeColorMode { monochrome, polychrome }
|
||||||
|
|
||||||
|
enum AvesThemeBrightness { system, light, dark, black }
|
||||||
|
|
||||||
enum ConfirmationDialog { delete, moveToBin }
|
enum ConfirmationDialog { delete, moveToBin }
|
||||||
|
|
||||||
enum CoordinateFormat { dms, decimal }
|
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 isInstalledAppAccessAllowedKey = 'is_installed_app_access_allowed';
|
||||||
static const isErrorReportingAllowedKey = 'is_crashlytics_enabled';
|
static const isErrorReportingAllowedKey = 'is_crashlytics_enabled';
|
||||||
static const localeKey = 'locale';
|
static const localeKey = 'locale';
|
||||||
|
static const themeBrightnessKey = 'theme_brightness';
|
||||||
|
static const themeColorModeKey = 'theme_color_mode';
|
||||||
static const catalogTimeZoneKey = 'catalog_time_zone';
|
static const catalogTimeZoneKey = 'catalog_time_zone';
|
||||||
static const tileExtentPrefixKey = 'tile_extent_';
|
static const tileExtentPrefixKey = 'tile_extent_';
|
||||||
static const tileLayoutPrefixKey = 'tile_layout_';
|
static const tileLayoutPrefixKey = 'tile_layout_';
|
||||||
|
@ -239,6 +241,14 @@ class Settings extends ChangeNotifier {
|
||||||
return _appliedLocale!;
|
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) ?? '';
|
String get catalogTimeZone => getString(catalogTimeZoneKey) ?? '';
|
||||||
|
|
||||||
set catalogTimeZone(String newValue) => setAndNotify(catalogTimeZoneKey, newValue);
|
set catalogTimeZone(String newValue) => setAndNotify(catalogTimeZoneKey, newValue);
|
||||||
|
@ -675,6 +685,8 @@ class Settings extends ChangeNotifier {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case localeKey:
|
case localeKey:
|
||||||
|
case themeBrightnessKey:
|
||||||
|
case themeColorModeKey:
|
||||||
case keepScreenOnKey:
|
case keepScreenOnKey:
|
||||||
case homePageKey:
|
case homePageKey:
|
||||||
case collectionGroupFactorKey:
|
case collectionGroupFactorKey:
|
||||||
|
|
|
@ -325,4 +325,7 @@ class CollectionLens with ChangeNotifier {
|
||||||
sections = Map.unmodifiable(Map.fromEntries(sections.entries.where((kv) => kv.value.isNotEmpty)));
|
sections = Map.unmodifiable(Map.fromEntries(sections.entries.where((kv) => kv.value.isNotEmpty)));
|
||||||
notifyListeners();
|
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: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
|
// mime
|
||||||
static final image = stringToColor('Image');
|
Color get image => fromHue(243);
|
||||||
static final video = stringToColor('Video');
|
|
||||||
|
Color get video => fromHue(323);
|
||||||
|
|
||||||
// type
|
// type
|
||||||
static const favourite = Colors.red;
|
Color get favourite => fromHue(0);
|
||||||
static final animated = stringToColor('Animated');
|
|
||||||
static final geotiff = stringToColor('GeoTIFF');
|
Color get animated => fromHue(83);
|
||||||
static final motionPhoto = stringToColor('Motion Photo');
|
|
||||||
static final panorama = stringToColor('Panorama');
|
Color get geotiff => fromHue(70);
|
||||||
static final raw = stringToColor('Raw');
|
|
||||||
static final sphericalVideo = stringToColor('360° Video');
|
Color get motionPhoto => fromHue(104);
|
||||||
|
|
||||||
|
Color get panorama => fromHue(5);
|
||||||
|
|
||||||
|
Color get raw => fromHue(208);
|
||||||
|
|
||||||
|
Color get sphericalVideo => fromHue(174);
|
||||||
|
|
||||||
// albums
|
// albums
|
||||||
static final albumCamera = stringToColor('Camera');
|
Color get albumCamera => fromHue(165);
|
||||||
static final albumDownload = stringToColor('Download');
|
|
||||||
static final albumScreenshots = stringToColor('Screenshots');
|
Color get albumDownload => fromHue(104);
|
||||||
static final albumScreenRecordings = stringToColor('Screen recordings');
|
|
||||||
static final albumVideoCaptures = stringToColor('Video Captures');
|
Color get albumScreenshots => fromHue(149);
|
||||||
|
|
||||||
|
Color get albumScreenRecordings => fromHue(222);
|
||||||
|
|
||||||
|
Color get albumVideoCaptures => fromHue(266);
|
||||||
|
|
||||||
// info
|
// info
|
||||||
static final xmp = stringToColor('XMP');
|
Color get xmp => fromHue(275);
|
||||||
|
|
||||||
// settings
|
// settings
|
||||||
static final accessibility = stringToColor('Accessibility');
|
Color get accessibility => fromHue(134);
|
||||||
static final language = stringToColor('Language');
|
|
||||||
static final navigation = stringToColor('Navigation');
|
|
||||||
static final privacy = stringToColor('Privacy');
|
|
||||||
static final thumbnails = stringToColor('Thumbnails');
|
|
||||||
|
|
||||||
|
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(
|
static const debugGradient = LinearGradient(
|
||||||
begin: Alignment.bottomCenter,
|
begin: Alignment.bottomCenter,
|
||||||
end: Alignment.topCenter,
|
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);
|
static const tagEditorTransition = Duration(milliseconds: 200);
|
||||||
|
|
||||||
// settings animations
|
// settings animations
|
||||||
|
static const themeColorModeAnimation = Duration(milliseconds: 400);
|
||||||
static const quickActionListAnimation = Duration(milliseconds: 200);
|
static const quickActionListAnimation = Duration(milliseconds: 200);
|
||||||
static const quickActionHighlightAnimation = 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 checked = Icons.done_outlined;
|
||||||
static const IconData date = Icons.calendar_today_outlined;
|
static const IconData date = Icons.calendar_today_outlined;
|
||||||
static const IconData disc = Icons.fiber_manual_record;
|
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 error = Icons.error_outline;
|
||||||
static const IconData folder = Icons.folder_outlined;
|
static const IconData folder = Icons.folder_outlined;
|
||||||
static const IconData grid = Icons.grid_on_outlined;
|
static const IconData grid = Icons.grid_on_outlined;
|
||||||
|
|
|
@ -1,50 +1,181 @@
|
||||||
import 'dart:ui';
|
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/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class Themes {
|
class Themes {
|
||||||
static const _accentColor = Colors.indigoAccent;
|
static const _accentColor = Colors.indigoAccent;
|
||||||
|
|
||||||
static final darkTheme = ThemeData(
|
static const _tooltipTheme = TooltipThemeData(
|
||||||
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(
|
|
||||||
verticalOffset: 32,
|
verticalOffset: 32,
|
||||||
),
|
);
|
||||||
appBarTheme: AppBarTheme(
|
|
||||||
backgroundColor: Colors.grey.shade900,
|
static const _appBarTitleTextStyle = TextStyle(
|
||||||
titleTextStyle: const TextStyle(
|
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
fontWeight: FontWeight.normal,
|
fontWeight: FontWeight.normal,
|
||||||
fontFeatures: [FontFeature.enable('smcp')],
|
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,
|
primary: _accentColor,
|
||||||
secondary: _accentColor,
|
secondary: _accentColor,
|
||||||
// surface color is used as background for the date picker header
|
onPrimary: _lightBodyColor,
|
||||||
surface: Colors.grey.shade800,
|
onSecondary: _lightBodyColor,
|
||||||
onPrimary: Colors.white,
|
|
||||||
onSecondary: Colors.white,
|
|
||||||
),
|
),
|
||||||
snackBarTheme: SnackBarThemeData(
|
brightness: Brightness.light,
|
||||||
backgroundColor: Colors.grey.shade800,
|
// `canvasColor` is used by `Drawer`, `DropdownButton` and `ExpansionTileCard`
|
||||||
actionTextColor: _accentColor,
|
canvasColor: _lightSecondLayer,
|
||||||
contentTextStyle: const TextStyle(
|
scaffoldBackgroundColor: _lightFirstLayer,
|
||||||
color: Colors.white,
|
// `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(
|
textButtonTheme: TextButtonThemeData(
|
||||||
style: TextButton.styleFrom(
|
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,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
AvesLogo(
|
AvesLogo(
|
||||||
size: style.fontSize! * MediaQuery.textScaleFactorOf(context) * 1.25,
|
size: style.fontSize! * MediaQuery.textScaleFactorOf(context) * 1.3,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
|
|
|
@ -4,9 +4,11 @@ import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:aves/app_flavor.dart';
|
import 'package:aves/app_flavor.dart';
|
||||||
import 'package:aves/flutter_version.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/model/settings/settings.dart';
|
||||||
import 'package:aves/ref/mime_types.dart';
|
import 'package:aves/ref/mime_types.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
|
import 'package:aves/theme/colors.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/utils/constants.dart';
|
import 'package:aves/utils/constants.dart';
|
||||||
import 'package:aves/widgets/common/action_mixins/feedback.dart';
|
import 'package:aves/widgets/common/action_mixins/feedback.dart';
|
||||||
|
@ -72,16 +74,19 @@ class _BugReportState extends State<BugReport> with FeedbackMixin {
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
final info = snapshot.data;
|
final info = snapshot.data;
|
||||||
if (info == null) return const SizedBox();
|
if (info == null) return const SizedBox();
|
||||||
|
|
||||||
|
final theme = Theme.of(context);
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.grey.shade800,
|
color: theme.cardColor,
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: Colors.white,
|
color: theme.colorScheme.onPrimary,
|
||||||
),
|
),
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
),
|
),
|
||||||
constraints: const BoxConstraints(maxHeight: 100),
|
constraints: const BoxConstraints(maxHeight: 100),
|
||||||
margin: const EdgeInsets.symmetric(vertical: 8),
|
margin: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
child: Theme(
|
child: Theme(
|
||||||
data: Theme.of(context).copyWith(
|
data: Theme.of(context).copyWith(
|
||||||
scrollbarTheme: const ScrollbarThemeData(
|
scrollbarTheme: const ScrollbarThemeData(
|
||||||
|
@ -115,13 +120,14 @@ class _BugReportState extends State<BugReport> with FeedbackMixin {
|
||||||
),
|
),
|
||||||
isExpanded: _showInstructions,
|
isExpanded: _showInstructions,
|
||||||
canTapOnHeader: true,
|
canTapOnHeader: true,
|
||||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
backgroundColor: Colors.transparent,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildStep(int step, String text, String buttonText, VoidCallback onPressed) {
|
Widget _buildStep(int step, String text, String buttonText, VoidCallback onPressed) {
|
||||||
|
final isMonochrome = settings.themeColorMode == AvesThemeColorMode.monochrome;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
@ -130,7 +136,7 @@ class _BugReportState extends State<BugReport> with FeedbackMixin {
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border.fromBorderSide(BorderSide(
|
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,
|
width: AvesFilterChip.outlineWidth,
|
||||||
)),
|
)),
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
|
@ -162,7 +168,7 @@ class _BugReportState extends State<BugReport> with FeedbackMixin {
|
||||||
'Device: ${androidInfo.manufacturer} ${androidInfo.model}',
|
'Device: ${androidInfo.manufacturer} ${androidInfo.model}',
|
||||||
'Google Play services: ${hasPlayServices ? 'ready' : 'not available'}',
|
'Google Play services: ${hasPlayServices ? 'ready' : 'not available'}',
|
||||||
'System locales: ${WidgetsBinding.instance!.window.locales.join(', ')}',
|
'System locales: ${WidgetsBinding.instance!.window.locales.join(', ')}',
|
||||||
'Aves locale: ${settings.locale} -> ${settings.appliedLocale}',
|
'Aves locale: ${settings.locale ?? 'system'} -> ${settings.appliedLocale}',
|
||||||
].join('\n');
|
].join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:aves/app_flavor.dart';
|
import 'package:aves/app_flavor.dart';
|
||||||
import 'package:aves/ref/brand_colors.dart';
|
import 'package:aves/ref/brand_colors.dart';
|
||||||
|
import 'package:aves/theme/colors.dart';
|
||||||
import 'package:aves/utils/constants.dart';
|
import 'package:aves/utils/constants.dart';
|
||||||
import 'package:aves/widgets/common/basic/link_chip.dart';
|
import 'package:aves/widgets/common/basic/link_chip.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
|
@ -40,6 +41,7 @@ class _LicensesState extends State<Licenses> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final colors = context.watch<AvesColorsData>();
|
||||||
return SliverPadding(
|
return SliverPadding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
sliver: SliverList(
|
sliver: SliverList(
|
||||||
|
@ -49,25 +51,25 @@ class _LicensesState extends State<Licenses> {
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
AvesExpansionTile(
|
AvesExpansionTile(
|
||||||
title: context.l10n.aboutLicensesAndroidLibraries,
|
title: context.l10n.aboutLicensesAndroidLibraries,
|
||||||
color: BrandColors.android,
|
highlightColor: colors.fromBrandColor(BrandColors.android),
|
||||||
expandedNotifier: _expandedNotifier,
|
expandedNotifier: _expandedNotifier,
|
||||||
children: _platform.map((package) => LicenseRow(package: package)).toList(),
|
children: _platform.map((package) => LicenseRow(package: package)).toList(),
|
||||||
),
|
),
|
||||||
AvesExpansionTile(
|
AvesExpansionTile(
|
||||||
title: context.l10n.aboutLicensesFlutterPlugins,
|
title: context.l10n.aboutLicensesFlutterPlugins,
|
||||||
color: BrandColors.flutter,
|
highlightColor: colors.fromBrandColor(BrandColors.flutter),
|
||||||
expandedNotifier: _expandedNotifier,
|
expandedNotifier: _expandedNotifier,
|
||||||
children: _flutterPlugins.map((package) => LicenseRow(package: package)).toList(),
|
children: _flutterPlugins.map((package) => LicenseRow(package: package)).toList(),
|
||||||
),
|
),
|
||||||
AvesExpansionTile(
|
AvesExpansionTile(
|
||||||
title: context.l10n.aboutLicensesFlutterPackages,
|
title: context.l10n.aboutLicensesFlutterPackages,
|
||||||
color: BrandColors.flutter,
|
highlightColor: colors.fromBrandColor(BrandColors.flutter),
|
||||||
expandedNotifier: _expandedNotifier,
|
expandedNotifier: _expandedNotifier,
|
||||||
children: _flutterPackages.map((package) => LicenseRow(package: package)).toList(),
|
children: _flutterPackages.map((package) => LicenseRow(package: package)).toList(),
|
||||||
),
|
),
|
||||||
AvesExpansionTile(
|
AvesExpansionTile(
|
||||||
title: context.l10n.aboutLicensesDartPackages,
|
title: context.l10n.aboutLicensesDartPackages,
|
||||||
color: BrandColors.flutter,
|
highlightColor: colors.fromBrandColor(BrandColors.flutter),
|
||||||
expandedNotifier: _expandedNotifier,
|
expandedNotifier: _expandedNotifier,
|
||||||
children: _dartPackages.map((package) => LicenseRow(package: package)).toList(),
|
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/l10n/l10n.dart';
|
||||||
import 'package:aves/model/device.dart';
|
import 'package:aves/model/device.dart';
|
||||||
import 'package:aves/model/settings/enums/accessibility_animations.dart';
|
import 'package:aves/model/settings/enums/accessibility_animations.dart';
|
||||||
|
import 'package:aves/model/settings/enums/enums.dart';
|
||||||
import 'package:aves/model/settings/enums/screen_on.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/settings/settings.dart';
|
||||||
import 'package:aves/model/source/collection_lens.dart';
|
import 'package:aves/model/source/collection_lens.dart';
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/model/source/media_store_source.dart';
|
import 'package:aves/model/source/media_store_source.dart';
|
||||||
import 'package:aves/services/accessibility_service.dart';
|
import 'package:aves/services/accessibility_service.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
|
import 'package:aves/theme/colors.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/theme/themes.dart';
|
import 'package:aves/theme/themes.dart';
|
||||||
|
@ -101,11 +104,16 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
|
||||||
: Scaffold(
|
: Scaffold(
|
||||||
body: snapshot.hasError ? _buildError(snapshot.error!) : const SizedBox(),
|
body: snapshot.hasError ? _buildError(snapshot.error!) : const SizedBox(),
|
||||||
);
|
);
|
||||||
return Selector<Settings, Tuple2<Locale?, bool>>(
|
return Selector<Settings, Tuple3<Locale?, bool, AvesThemeBrightness>>(
|
||||||
selector: (context, s) => Tuple2(s.locale, s.initialized ? s.accessibilityAnimations.animate : true),
|
selector: (context, s) => Tuple3(
|
||||||
|
s.locale,
|
||||||
|
s.initialized ? s.accessibilityAnimations.animate : true,
|
||||||
|
s.initialized ? s.themeBrightness : AvesThemeBrightness.system,
|
||||||
|
),
|
||||||
builder: (context, s, child) {
|
builder: (context, s, child) {
|
||||||
final settingsLocale = s.item1;
|
final settingsLocale = s.item1;
|
||||||
final areAnimationsEnabled = s.item2;
|
final areAnimationsEnabled = s.item2;
|
||||||
|
final themeBrightness = s.item3;
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
navigatorKey: _navigatorKey,
|
navigatorKey: _navigatorKey,
|
||||||
home: home,
|
home: home,
|
||||||
|
@ -126,11 +134,15 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
|
||||||
child: child!,
|
child: child!,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return child!;
|
return AvesColorsProvider(
|
||||||
|
child: child!,
|
||||||
|
);
|
||||||
|
// return child!;
|
||||||
},
|
},
|
||||||
onGenerateTitle: (context) => context.l10n.appName,
|
onGenerateTitle: (context) => context.l10n.appName,
|
||||||
darkTheme: Themes.darkTheme,
|
theme: Themes.lightTheme,
|
||||||
themeMode: ThemeMode.dark,
|
darkTheme: themeBrightness == AvesThemeBrightness.black ? Themes.blackTheme : Themes.darkTheme,
|
||||||
|
themeMode: themeBrightness.appThemeMode,
|
||||||
locale: settingsLocale,
|
locale: settingsLocale,
|
||||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||||
supportedLocales: AppLocalizations.supportedLocales,
|
supportedLocales: AppLocalizations.supportedLocales,
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:aves/model/entry.dart';
|
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/query.dart';
|
||||||
import 'package:aves/model/filters/trash.dart';
|
import 'package:aves/model/filters/trash.dart';
|
||||||
|
import 'package:aves/model/highlight.dart';
|
||||||
import 'package:aves/model/selection.dart';
|
import 'package:aves/model/selection.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/collection_lens.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/collection/collection_grid.dart';
|
||||||
import 'package:aves/widgets/common/basic/insets.dart';
|
import 'package:aves/widgets/common/basic/insets.dart';
|
||||||
import 'package:aves/widgets/common/behaviour/double_back_pop.dart';
|
import 'package:aves/widgets/common/behaviour/double_back_pop.dart';
|
||||||
|
@ -21,11 +25,15 @@ import 'package:provider/provider.dart';
|
||||||
class CollectionPage extends StatefulWidget {
|
class CollectionPage extends StatefulWidget {
|
||||||
static const routeName = '/collection';
|
static const routeName = '/collection';
|
||||||
|
|
||||||
final CollectionLens collection;
|
final CollectionSource source;
|
||||||
|
final Set<CollectionFilter?>? filters;
|
||||||
|
final bool Function(AvesEntry element)? highlightTest;
|
||||||
|
|
||||||
const CollectionPage({
|
const CollectionPage({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.collection,
|
required this.source,
|
||||||
|
required this.filters,
|
||||||
|
this.highlightTest,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -34,17 +42,23 @@ class CollectionPage extends StatefulWidget {
|
||||||
|
|
||||||
class _CollectionPageState extends State<CollectionPage> {
|
class _CollectionPageState extends State<CollectionPage> {
|
||||||
final List<StreamSubscription> _subscriptions = [];
|
final List<StreamSubscription> _subscriptions = [];
|
||||||
|
late CollectionLens _collection;
|
||||||
CollectionLens get collection => widget.collection;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
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();
|
super.initState();
|
||||||
_subscriptions.add(settings.updateStream.where((event) => event.key == Settings.enableBinKey).listen((_) {
|
_subscriptions.add(settings.updateStream.where((event) => event.key == Settings.enableBinKey).listen((_) {
|
||||||
if (!settings.enableBin) {
|
if (!settings.enableBin) {
|
||||||
collection.removeFilter(TrashFilter.instance);
|
_collection.removeFilter(TrashFilter.instance);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
WidgetsBinding.instance!.addPostFrameCallback((_) => _checkInitHighlight());
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -52,13 +66,13 @@ class _CollectionPageState extends State<CollectionPage> {
|
||||||
_subscriptions
|
_subscriptions
|
||||||
..forEach((sub) => sub.cancel())
|
..forEach((sub) => sub.cancel())
|
||||||
..clear();
|
..clear();
|
||||||
collection.dispose();
|
_collection.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
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(
|
return MediaQueryDataProvider(
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
body: SelectionProvider<AvesEntry>(
|
body: SelectionProvider<AvesEntry>(
|
||||||
|
@ -79,7 +93,7 @@ class _CollectionPageState extends State<CollectionPage> {
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
bottom: false,
|
bottom: false,
|
||||||
child: ChangeNotifierProvider<CollectionLens>.value(
|
child: ChangeNotifierProvider<CollectionLens>.value(
|
||||||
value: collection,
|
value: _collection,
|
||||||
child: const CollectionGrid(
|
child: const CollectionGrid(
|
||||||
// key is expected by test driver
|
// key is expected by test driver
|
||||||
key: Key('collection-grid'),
|
key: Key('collection-grid'),
|
||||||
|
@ -93,9 +107,21 @@ class _CollectionPageState extends State<CollectionPage> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
drawer: AppDrawer(currentCollection: collection),
|
drawer: AppDrawer(currentCollection: _collection),
|
||||||
resizeToAvoidBottomInset: false,
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Widget? albumIcon;
|
Widget? albumIcon;
|
||||||
if (directory != null) {
|
final _directory = directory;
|
||||||
albumIcon = IconUtils.getAlbumIcon(context: context, albumPath: directory!);
|
if (_directory != null) {
|
||||||
|
albumIcon = IconUtils.getAlbumIcon(context: context, albumPath: _directory);
|
||||||
if (albumIcon != null) {
|
if (albumIcon != null) {
|
||||||
albumIcon = RepaintBoundary(
|
albumIcon = RepaintBoundary(
|
||||||
child: Material(
|
|
||||||
type: MaterialType.circle,
|
|
||||||
elevation: 3,
|
|
||||||
color: Colors.transparent,
|
|
||||||
shadowColor: Colors.black,
|
|
||||||
child: albumIcon,
|
child: albumIcon,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return SectionHeader<AvesEntry>(
|
return SectionHeader<AvesEntry>(
|
||||||
sectionKey: EntryAlbumSectionKey(directory),
|
sectionKey: EntryAlbumSectionKey(_directory),
|
||||||
leading: albumIcon,
|
leading: albumIcon,
|
||||||
title: albumName ?? context.l10n.sectionUnknown,
|
title: albumName ?? context.l10n.sectionUnknown,
|
||||||
trailing: directory != null && androidFileUtils.isOnRemovableStorage(directory!)
|
trailing: _directory != null && androidFileUtils.isOnRemovableStorage(_directory)
|
||||||
? const Icon(
|
? const Icon(
|
||||||
AIcons.removableStorage,
|
AIcons.removableStorage,
|
||||||
size: 16,
|
size: 16,
|
||||||
|
|
|
@ -25,7 +25,7 @@ class EntryListDetails extends StatelessWidget {
|
||||||
return Container(
|
return Container(
|
||||||
padding: EntryListDetailsTheme.contentPadding,
|
padding: EntryListDetailsTheme.contentPadding,
|
||||||
foregroundDecoration: BoxDecoration(
|
foregroundDecoration: BoxDecoration(
|
||||||
border: Border(top: AvesBorder.straightSide),
|
border: Border(top: AvesBorder.straightSide(context)),
|
||||||
),
|
),
|
||||||
margin: EntryListDetailsTheme.contentMargin,
|
margin: EntryListDetailsTheme.contentMargin,
|
||||||
child: IconTheme.merge(
|
child: IconTheme.merge(
|
||||||
|
|
|
@ -150,43 +150,38 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
||||||
action = SnackBarAction(
|
action = SnackBarAction(
|
||||||
label: l10n.showButtonLabel,
|
label: l10n.showButtonLabel,
|
||||||
onPressed: () async {
|
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?>();
|
final collection = context.read<CollectionLens?>();
|
||||||
if (collection != null) {
|
|
||||||
targetCollection = collection;
|
|
||||||
}
|
|
||||||
if (collection == null || collection.filters.any((f) => f is AlbumFilter || f is TrashFilter)) {
|
if (collection == null || collection.filters.any((f) => f is AlbumFilter || f is TrashFilter)) {
|
||||||
targetCollection = CollectionLens(
|
final targetFilters = collection?.filters.where((f) => f != TrashFilter.instance).toSet() ?? {};
|
||||||
source: source,
|
|
||||||
filters: collection?.filters.where((f) => f != TrashFilter.instance).toSet(),
|
|
||||||
);
|
|
||||||
// we could simply add the filter to the current collection
|
// we could simply add the filter to the current collection
|
||||||
// but navigating makes the change less jarring
|
// but navigating makes the change less jarring
|
||||||
if (destinationAlbums.length == 1) {
|
if (destinationAlbums.length == 1) {
|
||||||
final destinationAlbum = destinationAlbums.single;
|
final destinationAlbum = destinationAlbums.single;
|
||||||
final filter = AlbumFilter(destinationAlbum, source.getAlbumDisplayName(context, destinationAlbum));
|
targetFilters.removeWhere((f) => f is AlbumFilter);
|
||||||
targetCollection.addFilter(filter);
|
targetFilters.add(AlbumFilter(destinationAlbum, source.getAlbumDisplayName(context, destinationAlbum)));
|
||||||
}
|
}
|
||||||
unawaited(Navigator.pushAndRemoveUntil(
|
unawaited(Navigator.pushAndRemoveUntil(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
settings: const RouteSettings(name: CollectionPage.routeName),
|
settings: const RouteSettings(name: CollectionPage.routeName),
|
||||||
builder: (context) => CollectionPage(
|
builder: (context) => CollectionPage(
|
||||||
collection: targetCollection,
|
source: source,
|
||||||
|
filters: targetFilters,
|
||||||
|
highlightTest: highlightTest,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(route) => false,
|
(route) => false,
|
||||||
));
|
));
|
||||||
final delayDuration = context.read<DurationsData>().staggeredAnimationPageTarget;
|
} else {
|
||||||
await Future.delayed(delayDuration);
|
// track in current page, without navigation
|
||||||
}
|
|
||||||
await Future.delayed(Durations.highlightScrollInitDelay);
|
await Future.delayed(Durations.highlightScrollInitDelay);
|
||||||
final newUris = movedOps.map((v) => v.newFields['uri'] as String?).toSet();
|
final targetEntry = collection.sortedEntries.firstWhereOrNull(highlightTest);
|
||||||
final targetEntry = targetCollection.sortedEntries.firstWhereOrNull((entry) => newUris.contains(entry.uri));
|
|
||||||
if (targetEntry != null) {
|
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(
|
Container(
|
||||||
width: diameter + 2,
|
width: diameter + 2,
|
||||||
height: diameter + 2,
|
height: diameter + 2,
|
||||||
decoration: const BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Color(0xBB000000),
|
color: Theme.of(context).brightness == Brightness.dark ? const Color(0xBB000000) : const Color(0xEEFFFFFF),
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -190,7 +190,7 @@ class _ReportOverlayState<T> extends State<ReportOverlay<T>> with SingleTickerPr
|
||||||
percent: percent,
|
percent: percent,
|
||||||
lineWidth: strokeWidth,
|
lineWidth: strokeWidth,
|
||||||
radius: diameter / 2,
|
radius: diameter / 2,
|
||||||
backgroundColor: Colors.white24,
|
backgroundColor: Theme.of(context).colorScheme.onSurface.withOpacity(.2),
|
||||||
progressColor: progressColor,
|
progressColor: progressColor,
|
||||||
animation: animate,
|
animation: animate,
|
||||||
center: Text(
|
center: Text(
|
||||||
|
@ -270,6 +270,8 @@ class _FeedbackMessageState extends State<_FeedbackMessage> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final text = Text(widget.message);
|
final text = Text(widget.message);
|
||||||
final duration = widget.duration;
|
final duration = widget.duration;
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final contentTextStyle = theme.snackBarTheme.contentTextStyle ?? ThemeData(brightness: theme.brightness).textTheme.subtitle1;
|
||||||
return duration == null
|
return duration == null
|
||||||
? text
|
? text
|
||||||
: Row(
|
: Row(
|
||||||
|
@ -286,7 +288,10 @@ class _FeedbackMessageState extends State<_FeedbackMessage> {
|
||||||
progressColor: Colors.grey,
|
progressColor: Colors.grey,
|
||||||
animation: true,
|
animation: true,
|
||||||
animationDuration: duration.inMilliseconds,
|
animationDuration: duration.inMilliseconds,
|
||||||
center: Text('$_remainingSecs'),
|
center: Text(
|
||||||
|
'$_remainingSecs',
|
||||||
|
style: contentTextStyle,
|
||||||
|
),
|
||||||
animateFromLastPercent: true,
|
animateFromLastPercent: true,
|
||||||
reverse: true,
|
reverse: true,
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums.dart';
|
||||||
import 'package:aves/model/source/events.dart';
|
import 'package:aves/model/source/events.dart';
|
||||||
|
@ -62,25 +64,28 @@ class SourceStateSubtitle extends StatelessWidget {
|
||||||
final subtitle = sourceState.getName(context.l10n);
|
final subtitle = sourceState.getName(context.l10n);
|
||||||
if (subtitle == null) return const SizedBox();
|
if (subtitle == null) return const SizedBox();
|
||||||
|
|
||||||
final subtitleStyle = Theme.of(context).textTheme.caption!;
|
final theme = Theme.of(context);
|
||||||
return Row(
|
return DefaultTextStyle.merge(
|
||||||
mainAxisSize: MainAxisSize.min,
|
style: theme.textTheme.caption!.copyWith(fontFeatures: const [FontFeature.disable('smcp')]),
|
||||||
children: [
|
child: ValueListenableBuilder<ProgressEvent>(
|
||||||
Text(subtitle, style: subtitleStyle),
|
|
||||||
ValueListenableBuilder<ProgressEvent>(
|
|
||||||
valueListenable: source.progressNotifier,
|
valueListenable: source.progressNotifier,
|
||||||
builder: (context, progress, snapshot) {
|
builder: (context, progress, snapshot) {
|
||||||
if (progress.total == 0 || sourceState == SourceState.locatingCountries) return const SizedBox();
|
return Text.rich(
|
||||||
return Padding(
|
TextSpan(
|
||||||
padding: const EdgeInsetsDirectional.only(start: 8),
|
children: [
|
||||||
child: Text(
|
TextSpan(text: subtitle),
|
||||||
'${progress.done}/${progress.total}',
|
if (progress.total != 0 && sourceState != SourceState.locatingCountries) ...[
|
||||||
style: subtitleStyle.copyWith(color: Colors.white30),
|
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,
|
width: radius * 2,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: value,
|
color: value,
|
||||||
border: AvesBorder.border,
|
border: AvesBorder.border(context),
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:aves/widgets/common/fx/borders.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
@ -18,9 +19,10 @@ class MarkdownContainer extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 8),
|
margin: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
decoration: const BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
color: Theme.of(context).canvasColor,
|
||||||
color: Colors.white10,
|
border: Border.all(color: Theme.of(context).dividerColor, width: AvesBorder.curvedBorderWidth),
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
||||||
),
|
),
|
||||||
constraints: const BoxConstraints(maxWidth: maxWidth),
|
constraints: const BoxConstraints(maxWidth: maxWidth),
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
|
|
|
@ -18,8 +18,13 @@ class MenuRow extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
if (icon != null)
|
if (icon != null)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsetsDirectional.only(end: 8),
|
padding: const EdgeInsetsDirectional.only(end: 12),
|
||||||
child: icon,
|
child: IconTheme.merge(
|
||||||
|
data: IconThemeData(
|
||||||
|
color: ListTileTheme.of(context).iconColor,
|
||||||
|
),
|
||||||
|
child: icon!,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Expanded(child: Text(text)),
|
Expanded(child: Text(text)),
|
||||||
],
|
],
|
||||||
|
@ -110,6 +115,7 @@ class _PopupMenuItemExpansionPanelState<T> extends State<PopupMenuItemExpansionP
|
||||||
),
|
),
|
||||||
isExpanded: _isExpanded,
|
isExpanded: _isExpanded,
|
||||||
canTapOnHeader: true,
|
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/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/fx/sweeper.dart';
|
import 'package:aves/widgets/common/fx/sweeper.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class FavouriteToggler extends StatefulWidget {
|
class FavouriteToggler extends StatefulWidget {
|
||||||
final Set<AvesEntry> entries;
|
final Set<AvesEntry> entries;
|
||||||
|
@ -73,7 +74,10 @@ class _FavouriteTogglerState extends State<FavouriteToggler> {
|
||||||
),
|
),
|
||||||
Sweeper(
|
Sweeper(
|
||||||
key: ValueKey(entries.length == 1 ? entries.first : entries.length),
|
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,
|
toggledNotifier: isFavouriteNotifier,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -3,7 +3,7 @@ import 'dart:ui';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class AvesBorder {
|
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`
|
// 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
|
// 1 device pixel for curves is too thin
|
||||||
static double get curvedBorderWidth => window.devicePixelRatio > 2 ? 0.5 : 1.0;
|
static double get curvedBorderWidth => window.devicePixelRatio > 2 ? 0.5 : 1.0;
|
||||||
|
|
||||||
static BorderSide get straightSide => BorderSide(
|
static BorderSide straightSide(BuildContext context) => BorderSide(
|
||||||
color: borderColor,
|
color: _borderColor(context),
|
||||||
width: straightBorderWidth,
|
width: straightBorderWidth,
|
||||||
);
|
);
|
||||||
|
|
||||||
static BorderSide get curvedSide => BorderSide(
|
static BorderSide curvedSide(BuildContext context) => BorderSide(
|
||||||
color: borderColor,
|
color: _borderColor(context),
|
||||||
width: curvedBorderWidth,
|
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: child,
|
||||||
);
|
);
|
||||||
child = AnimatedContainer(
|
child = AnimatedContainer(
|
||||||
duration: duration,
|
|
||||||
alignment: AlignmentDirectional.topEnd,
|
alignment: AlignmentDirectional.topEnd,
|
||||||
padding: padding,
|
padding: padding,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isSelected ? Colors.black54 : Colors.transparent,
|
color: isSelected ? Colors.black54 : Colors.transparent,
|
||||||
borderRadius: borderRadius,
|
borderRadius: borderRadius,
|
||||||
),
|
),
|
||||||
|
duration: duration,
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
return child;
|
return child;
|
||||||
|
|
|
@ -245,6 +245,10 @@ class _ScaleOverlayState extends State<_ScaleOverlay> {
|
||||||
|
|
||||||
double get gridWidth => widget.viewportWidth;
|
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
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
@ -309,23 +313,34 @@ class _ScaleOverlayState extends State<_ScaleOverlay> {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
return _init
|
return _init
|
||||||
? BoxDecoration(
|
? BoxDecoration(
|
||||||
gradient: RadialGradient(
|
gradient: RadialGradient(
|
||||||
center: FractionalOffset.fromOffsetAndSize(gradientCenter, context.select<MediaQueryData, Size>((mq) => mq.size)),
|
center: FractionalOffset.fromOffsetAndSize(gradientCenter, context.select<MediaQueryData, Size>((mq) => mq.size)),
|
||||||
radius: 1,
|
radius: 1,
|
||||||
colors: const [
|
colors: isDark
|
||||||
|
? const [
|
||||||
Colors.black,
|
Colors.black,
|
||||||
Colors.black54,
|
Colors.black54,
|
||||||
|
]
|
||||||
|
: const [
|
||||||
|
Colors.white,
|
||||||
|
Colors.white38,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: const BoxDecoration(
|
: BoxDecoration(
|
||||||
// provide dummy gradient to lerp to the other one during animation
|
// provide dummy gradient to lerp to the other one during animation
|
||||||
gradient: RadialGradient(
|
gradient: RadialGradient(
|
||||||
colors: [
|
colors: isDark
|
||||||
|
? const [
|
||||||
Colors.transparent,
|
Colors.transparent,
|
||||||
Colors.transparent,
|
Colors.transparent,
|
||||||
|
]
|
||||||
|
: const [
|
||||||
|
transparentWhite,
|
||||||
|
transparentWhite,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -8,7 +8,7 @@ class AvesExpansionTile extends StatelessWidget {
|
||||||
final String value;
|
final String value;
|
||||||
final Widget? leading;
|
final Widget? leading;
|
||||||
final String title;
|
final String title;
|
||||||
final Color? color;
|
final Color? highlightColor;
|
||||||
final ValueNotifier<String?>? expandedNotifier;
|
final ValueNotifier<String?>? expandedNotifier;
|
||||||
final bool initiallyExpanded, showHighlight;
|
final bool initiallyExpanded, showHighlight;
|
||||||
final List<Widget> children;
|
final List<Widget> children;
|
||||||
|
@ -18,7 +18,7 @@ class AvesExpansionTile extends StatelessWidget {
|
||||||
String? value,
|
String? value,
|
||||||
this.leading,
|
this.leading,
|
||||||
required this.title,
|
required this.title,
|
||||||
this.color,
|
this.highlightColor,
|
||||||
this.expandedNotifier,
|
this.expandedNotifier,
|
||||||
this.initiallyExpanded = false,
|
this.initiallyExpanded = false,
|
||||||
this.showHighlight = true,
|
this.showHighlight = true,
|
||||||
|
@ -31,7 +31,7 @@ class AvesExpansionTile extends StatelessWidget {
|
||||||
final enabled = children.isNotEmpty == true;
|
final enabled = children.isNotEmpty == true;
|
||||||
Widget titleChild = HighlightTitle(
|
Widget titleChild = HighlightTitle(
|
||||||
title: title,
|
title: title,
|
||||||
color: color,
|
color: highlightColor,
|
||||||
enabled: enabled,
|
enabled: enabled,
|
||||||
showHighlight: showHighlight,
|
showHighlight: showHighlight,
|
||||||
);
|
);
|
||||||
|
@ -63,8 +63,8 @@ class AvesExpansionTile extends StatelessWidget {
|
||||||
expandable: enabled,
|
expandable: enabled,
|
||||||
initiallyExpanded: initiallyExpanded,
|
initiallyExpanded: initiallyExpanded,
|
||||||
finalPadding: const EdgeInsets.symmetric(vertical: 6.0),
|
finalPadding: const EdgeInsets.symmetric(vertical: 6.0),
|
||||||
baseColor: Colors.grey.shade900,
|
baseColor: theme.scaffoldBackgroundColor,
|
||||||
expandedColor: Colors.grey[850],
|
expandedColor: theme.canvasColor,
|
||||||
duration: animationDuration,
|
duration: animationDuration,
|
||||||
shadowColor: theme.shadowColor,
|
shadowColor: theme.shadowColor,
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
|
@ -6,6 +6,7 @@ import 'package:aves/model/filters/location.dart';
|
||||||
import 'package:aves/model/filters/tag.dart';
|
import 'package:aves/model/filters/tag.dart';
|
||||||
import 'package:aves/model/settings/enums/accessibility_animations.dart';
|
import 'package:aves/model/settings/enums/accessibility_animations.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
|
import 'package:aves/theme/colors.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/widgets/common/basic/menu.dart';
|
import 'package:aves/widgets/common/basic/menu.dart';
|
||||||
|
@ -46,7 +47,6 @@ class AvesFilterChip extends StatefulWidget {
|
||||||
final FilterCallback? onTap;
|
final FilterCallback? onTap;
|
||||||
final OffsetFilterCallback? onLongPress;
|
final OffsetFilterCallback? onLongPress;
|
||||||
|
|
||||||
static const Color defaultOutlineColor = Colors.white;
|
|
||||||
static const double defaultRadius = 32;
|
static const double defaultRadius = 32;
|
||||||
static const double outlineWidth = 2;
|
static const double outlineWidth = 2;
|
||||||
static const double minChipHeight = kMinInteractiveDimension;
|
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.
|
// 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.
|
// So we save the result of the Future to a local variable because of this specific case.
|
||||||
_colorFuture = filter.color(context);
|
_colorFuture = filter.color(context);
|
||||||
_outlineColor = AvesFilterChip.defaultOutlineColor;
|
_outlineColor = context.read<AvesColorsData>().neutral;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -270,7 +270,7 @@ class _AvesFilterChipState extends State<AvesFilterChip> {
|
||||||
return DecoratedBox(
|
return DecoratedBox(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border.fromBorderSide(BorderSide(
|
border: Border.fromBorderSide(BorderSide(
|
||||||
color: widget.useFilterColor ? _outlineColor : AvesFilterChip.defaultOutlineColor,
|
color: widget.useFilterColor ? _outlineColor : context.select<AvesColorsData, Color>((v) => v.neutral),
|
||||||
width: AvesFilterChip.outlineWidth,
|
width: AvesFilterChip.outlineWidth,
|
||||||
)),
|
)),
|
||||||
borderRadius: borderRadius,
|
borderRadius: borderRadius,
|
||||||
|
|
|
@ -27,7 +27,6 @@ class VideoIcon extends StatelessWidget {
|
||||||
if (showDuration) {
|
if (showDuration) {
|
||||||
child = DefaultTextStyle(
|
child = DefaultTextStyle(
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.grey.shade200,
|
|
||||||
fontSize: gridTheme.fontSize,
|
fontSize: gridTheme.fontSize,
|
||||||
),
|
),
|
||||||
child: child,
|
child: child,
|
||||||
|
@ -146,7 +145,6 @@ class MultiPageIcon extends StatelessWidget {
|
||||||
);
|
);
|
||||||
return DefaultTextStyle(
|
return DefaultTextStyle(
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.grey.shade200,
|
|
||||||
fontSize: context.select<GridThemeData, double>((t) => t.fontSize),
|
fontSize: context.select<GridThemeData, double>((t) => t.fontSize),
|
||||||
),
|
),
|
||||||
child: child,
|
child: child,
|
||||||
|
@ -167,7 +165,6 @@ class RatingIcon extends StatelessWidget {
|
||||||
final gridTheme = context.watch<GridThemeData>();
|
final gridTheme = context.watch<GridThemeData>();
|
||||||
return DefaultTextStyle(
|
return DefaultTextStyle(
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.grey.shade200,
|
|
||||||
fontSize: gridTheme.fontSize,
|
fontSize: gridTheme.fontSize,
|
||||||
),
|
),
|
||||||
child: OverlayIcon(
|
child: OverlayIcon(
|
||||||
|
@ -195,7 +192,6 @@ class TrashIcon extends StatelessWidget {
|
||||||
|
|
||||||
return DefaultTextStyle(
|
return DefaultTextStyle(
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.grey.shade200,
|
|
||||||
fontSize: context.select<GridThemeData, double>((t) => t.fontSize),
|
fontSize: context.select<GridThemeData, double>((t) => t.fontSize),
|
||||||
),
|
),
|
||||||
child: child,
|
child: child,
|
||||||
|
@ -224,8 +220,6 @@ class OverlayIcon extends StatelessWidget {
|
||||||
final iconChild = Icon(
|
final iconChild = Icon(
|
||||||
icon,
|
icon,
|
||||||
size: size,
|
size: size,
|
||||||
// consistent with the color used for the text next to it
|
|
||||||
color: DefaultTextStyle.of(context).style.color,
|
|
||||||
);
|
);
|
||||||
final iconBox = SizedBox(
|
final iconBox = SizedBox(
|
||||||
width: size,
|
width: size,
|
||||||
|
@ -243,7 +237,7 @@ class OverlayIcon extends StatelessWidget {
|
||||||
margin: margin,
|
margin: margin,
|
||||||
padding: text != null ? EdgeInsetsDirectional.only(end: size / 4) : null,
|
padding: text != null ? EdgeInsetsDirectional.only(end: size / 4) : null,
|
||||||
decoration: BoxDecoration(
|
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)),
|
borderRadius: BorderRadius.all(Radius.circular(size)),
|
||||||
),
|
),
|
||||||
child: text == null
|
child: text == null
|
||||||
|
@ -254,7 +248,11 @@ class OverlayIcon extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
iconBox,
|
iconBox,
|
||||||
const SizedBox(width: 2),
|
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:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class AvesLogo extends StatelessWidget {
|
class AvesLogo extends StatelessWidget {
|
||||||
final double size;
|
final double size;
|
||||||
|
@ -10,14 +15,32 @@ class AvesLogo extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return CircleAvatar(
|
final theme = Theme.of(context);
|
||||||
backgroundColor: Colors.white,
|
|
||||||
radius: size / 2,
|
Widget child = CustomPaint(
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.only(top: size / 15),
|
|
||||||
child: CustomPaint(
|
|
||||||
size: Size(size / 1.4, size / 1.4),
|
size: Size(size / 1.4, size / 1.4),
|
||||||
painter: AvesLogoPainter(),
|
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) {
|
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
|
return icon != null
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
import 'dart:ui';
|
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:aves/widgets/common/fx/highlight_decoration.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class HighlightTitle extends StatelessWidget {
|
class HighlightTitle extends StatelessWidget {
|
||||||
final String title;
|
final String title;
|
||||||
|
@ -34,18 +37,19 @@ class HighlightTitle extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final style = TextStyle(
|
final style = TextStyle(
|
||||||
shadows: shadows,
|
shadows: Theme.of(context).brightness == Brightness.dark ? shadows : null,
|
||||||
fontSize: fontSize,
|
fontSize: fontSize,
|
||||||
letterSpacing: 1.0,
|
letterSpacing: 1.0,
|
||||||
fontFeatures: const [FontFeature.enable('smcp')],
|
fontFeatures: const [FontFeature.enable('smcp')],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final colors = context.watch<AvesColorsData>();
|
||||||
return Align(
|
return Align(
|
||||||
alignment: AlignmentDirectional.centerStart,
|
alignment: AlignmentDirectional.centerStart,
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: showHighlight
|
decoration: showHighlight && context.select<Settings, bool>((v) => v.themeColorMode == AvesThemeColorMode.polychrome)
|
||||||
? HighlightDecoration(
|
? HighlightDecoration(
|
||||||
color: enabled ? color ?? stringToColor(title) : disabledColor,
|
color: enabled ? color ?? colors.fromString(title) : disabledColor,
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
margin: const EdgeInsets.symmetric(vertical: 4.0),
|
margin: const EdgeInsets.symmetric(vertical: 4.0),
|
||||||
|
|
|
@ -22,19 +22,20 @@ class Attribution extends StatelessWidget {
|
||||||
case EntryMapStyle.stamenWatercolor:
|
case EntryMapStyle.stamenWatercolor:
|
||||||
return _buildAttributionMarkdown(context, context.l10n.mapAttributionStamen);
|
return _buildAttributionMarkdown(context, context.l10n.mapAttributionStamen);
|
||||||
default:
|
default:
|
||||||
return const SizedBox.shrink();
|
return const SizedBox();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildAttributionMarkdown(BuildContext context, String data) {
|
Widget _buildAttributionMarkdown(BuildContext context, String data) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(top: 4),
|
padding: const EdgeInsets.only(top: 4),
|
||||||
child: MarkdownBody(
|
child: MarkdownBody(
|
||||||
data: data,
|
data: data,
|
||||||
selectable: true,
|
selectable: true,
|
||||||
styleSheet: MarkdownStyleSheet(
|
styleSheet: MarkdownStyleSheet(
|
||||||
a: TextStyle(color: Theme.of(context).colorScheme.secondary),
|
a: TextStyle(color: theme.colorScheme.secondary),
|
||||||
p: const TextStyle(color: Colors.white70, fontSize: InfoRowGroup.fontSize),
|
p: theme.textTheme.caption!.merge(const TextStyle(fontSize: InfoRowGroup.fontSize)),
|
||||||
),
|
),
|
||||||
onTapLink: (text, href, title) async {
|
onTapLink: (text, href, title) async {
|
||||||
if (href != null && await canLaunch(href)) {
|
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/services/common/services.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
|
import 'package:aves/theme/themes.dart';
|
||||||
import 'package:aves/utils/debouncer.dart';
|
import 'package:aves/utils/debouncer.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/fx/blurred.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/common/map/zoomed_bounds.dart';
|
||||||
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
|
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
|
||||||
import 'package:aves/widgets/viewer/info/notifications.dart';
|
import 'package:aves/widgets/viewer/info/notifications.dart';
|
||||||
import 'package:aves/widgets/viewer/overlay/common.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -207,10 +207,10 @@ class MapOverlayButton extends StatelessWidget {
|
||||||
enabled: blurred,
|
enabled: blurred,
|
||||||
child: Material(
|
child: Material(
|
||||||
type: MaterialType.circle,
|
type: MaterialType.circle,
|
||||||
color: overlayBackgroundColor(blurred: blurred),
|
color: Themes.overlayBackgroundColor(brightness: Theme.of(context).brightness, blurred: blurred),
|
||||||
child: Ink(
|
child: Ink(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: AvesBorder.border,
|
border: AvesBorder.border(context),
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
child: Selector<MapThemeData, VisualDensity?>(
|
child: Selector<MapThemeData, VisualDensity?>(
|
||||||
|
@ -278,9 +278,10 @@ class _OverlayCoordinateFilterChipState extends State<_OverlayCoordinateFilterCh
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final blurred = settings.enableOverlayBlurEffect;
|
final blurred = settings.enableOverlayBlurEffect;
|
||||||
|
final theme = Theme.of(context);
|
||||||
return Theme(
|
return Theme(
|
||||||
data: Theme.of(context).copyWith(
|
data: theme.copyWith(
|
||||||
scaffoldBackgroundColor: overlayBackgroundColor(blurred: blurred),
|
scaffoldBackgroundColor: Themes.overlayBackgroundColor(brightness: theme.brightness, blurred: blurred),
|
||||||
),
|
),
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.topLeft,
|
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:aves/widgets/common/map/theme.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -28,6 +29,10 @@ class MapDecorator extends StatelessWidget {
|
||||||
borderRadius: mapBorderRadius,
|
borderRadius: mapBorderRadius,
|
||||||
child: Container(
|
child: Container(
|
||||||
color: mapBackground,
|
color: mapBackground,
|
||||||
|
foregroundDecoration: BoxDecoration(
|
||||||
|
border: AvesBorder.border(context),
|
||||||
|
borderRadius: mapBorderRadius,
|
||||||
|
),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
const GridPaper(
|
const GridPaper(
|
||||||
|
|
|
@ -14,12 +14,14 @@ class ImageMarker extends StatelessWidget {
|
||||||
static const double outerBorderRadiusDim = 8;
|
static const double outerBorderRadiusDim = 8;
|
||||||
static const double outerBorderWidth = 1.5;
|
static const double outerBorderWidth = 1.5;
|
||||||
static const double innerBorderWidth = 2;
|
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 outerBorderRadius = BorderRadius.all(Radius.circular(outerBorderRadiusDim));
|
||||||
static const innerRadius = Radius.circular(outerBorderRadiusDim - outerBorderWidth);
|
static const innerRadius = Radius.circular(outerBorderRadiusDim - outerBorderWidth);
|
||||||
static const innerBorderRadius = BorderRadius.all(innerRadius);
|
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({
|
const ImageMarker({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.entry,
|
required this.entry,
|
||||||
|
@ -46,7 +48,12 @@ class ImageMarker extends StatelessWidget {
|
||||||
child: child,
|
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(
|
border: Border.fromBorderSide(BorderSide(
|
||||||
color: outerBorderColor,
|
color: outerBorderColor,
|
||||||
width: outerBorderWidth,
|
width: outerBorderWidth,
|
||||||
|
@ -54,7 +61,7 @@ class ImageMarker extends StatelessWidget {
|
||||||
borderRadius: outerBorderRadius,
|
borderRadius: outerBorderRadius,
|
||||||
);
|
);
|
||||||
|
|
||||||
const innerDecoration = BoxDecoration(
|
final innerDecoration = BoxDecoration(
|
||||||
border: Border.fromBorderSide(BorderSide(
|
border: Border.fromBorderSide(BorderSide(
|
||||||
color: innerBorderColor,
|
color: innerBorderColor,
|
||||||
width: innerBorderWidth,
|
width: innerBorderWidth,
|
||||||
|
@ -72,7 +79,7 @@ class ImageMarker extends StatelessWidget {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (count != null) {
|
if (count != null) {
|
||||||
const borderSide = BorderSide(
|
final borderSide = BorderSide(
|
||||||
color: innerBorderColor,
|
color: innerBorderColor,
|
||||||
width: innerBorderWidth,
|
width: innerBorderWidth,
|
||||||
);
|
);
|
||||||
|
@ -82,28 +89,28 @@ class ImageMarker extends StatelessWidget {
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 2),
|
padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 2),
|
||||||
decoration: ShapeDecoration(
|
decoration: ShapeDecoration(
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
color: theme.colorScheme.secondary,
|
||||||
shape: context.isRtl
|
shape: context.isRtl
|
||||||
? const CustomRoundedRectangleBorder(
|
? CustomRoundedRectangleBorder(
|
||||||
leftSide: borderSide,
|
leftSide: borderSide,
|
||||||
rightSide: borderSide,
|
rightSide: borderSide,
|
||||||
topSide: borderSide,
|
topSide: borderSide,
|
||||||
bottomSide: borderSide,
|
bottomSide: borderSide,
|
||||||
topRightCornerSide: borderSide,
|
topRightCornerSide: borderSide,
|
||||||
bottomLeftCornerSide: borderSide,
|
bottomLeftCornerSide: borderSide,
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius: const BorderRadius.only(
|
||||||
topRight: innerRadius,
|
topRight: innerRadius,
|
||||||
bottomLeft: innerRadius,
|
bottomLeft: innerRadius,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: const CustomRoundedRectangleBorder(
|
: CustomRoundedRectangleBorder(
|
||||||
leftSide: borderSide,
|
leftSide: borderSide,
|
||||||
rightSide: borderSide,
|
rightSide: borderSide,
|
||||||
topSide: borderSide,
|
topSide: borderSide,
|
||||||
bottomSide: borderSide,
|
bottomSide: borderSide,
|
||||||
topLeftCornerSide: borderSide,
|
topLeftCornerSide: borderSide,
|
||||||
bottomRightCornerSide: borderSide,
|
bottomRightCornerSide: borderSide,
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius: const BorderRadius.only(
|
||||||
topLeft: innerRadius,
|
topLeft: innerRadius,
|
||||||
bottomRight: innerRadius,
|
bottomRight: innerRadius,
|
||||||
),
|
),
|
||||||
|
@ -111,7 +118,10 @@ class ImageMarker extends StatelessWidget {
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'$count',
|
'$count',
|
||||||
style: const TextStyle(fontSize: 12),
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -190,17 +200,22 @@ class DotMarker extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
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(
|
border: Border.fromBorderSide(BorderSide(
|
||||||
color: ImageMarker.outerBorderColor,
|
color: outerBorderColor,
|
||||||
width: ImageMarker.outerBorderWidth,
|
width: ImageMarker.outerBorderWidth,
|
||||||
)),
|
)),
|
||||||
borderRadius: outerBorderRadius,
|
borderRadius: outerBorderRadius,
|
||||||
);
|
);
|
||||||
|
|
||||||
const innerDecoration = BoxDecoration(
|
final innerDecoration = BoxDecoration(
|
||||||
border: Border.fromBorderSide(BorderSide(
|
border: Border.fromBorderSide(BorderSide(
|
||||||
color: ImageMarker.innerBorderColor,
|
color: innerBorderColor,
|
||||||
width: ImageMarker.innerBorderWidth,
|
width: ImageMarker.innerBorderWidth,
|
||||||
)),
|
)),
|
||||||
borderRadius: innerBorderRadius,
|
borderRadius: innerBorderRadius,
|
||||||
|
@ -216,7 +231,7 @@ class DotMarker extends StatelessWidget {
|
||||||
child: Container(
|
child: Container(
|
||||||
width: diameter,
|
width: diameter,
|
||||||
height: diameter,
|
height: diameter,
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
color: theme.colorScheme.secondary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -14,7 +14,7 @@ class SliverAppBarTitleWrapper extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final toolbarOpacity = context.dependOnInheritedWidgetOfExactType<FlexibleSpaceBarSettings>()!.toolbarOpacity;
|
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(
|
return DefaultTextStyle.merge(
|
||||||
style: TextStyle(color: baseColor.withOpacity(toolbarOpacity)),
|
style: TextStyle(color: baseColor.withOpacity(toolbarOpacity)),
|
||||||
child: child,
|
child: child,
|
||||||
|
|
|
@ -179,9 +179,12 @@ class _ThumbnailImageState extends State<ThumbnailImage> {
|
||||||
|
|
||||||
Color? _loadingBackgroundColor;
|
Color? _loadingBackgroundColor;
|
||||||
|
|
||||||
Color get loadingBackgroundColor {
|
Color loadingBackgroundColor(BuildContext context) {
|
||||||
if (_loadingBackgroundColor == null) {
|
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);
|
_loadingBackgroundColor = Color.fromARGB(0xFF, rgb, rgb, rgb);
|
||||||
}
|
}
|
||||||
return _loadingBackgroundColor!;
|
return _loadingBackgroundColor!;
|
||||||
|
@ -201,7 +204,7 @@ class _ThumbnailImageState extends State<ThumbnailImage> {
|
||||||
final imageInfo = _lastImageInfo;
|
final imageInfo = _lastImageInfo;
|
||||||
Widget image = imageInfo == null
|
Widget image = imageInfo == null
|
||||||
? Container(
|
? Container(
|
||||||
color: widget.showLoadingBackground ? loadingBackgroundColor : Colors.transparent,
|
color: widget.showLoadingBackground ? loadingBackgroundColor(context) : Colors.transparent,
|
||||||
width: extent,
|
width: extent,
|
||||||
height: extent,
|
height: extent,
|
||||||
)
|
)
|
||||||
|
|
|
@ -70,7 +70,7 @@ class _DebugAndroidAppSectionState extends State<DebugAndroidAppSection> with Au
|
||||||
),
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: ' ${package.packageName}\n',
|
text: ' ${package.packageName}\n',
|
||||||
style: InfoRowGroup.keyStyle,
|
style: InfoRowGroup.keyStyle(context),
|
||||||
),
|
),
|
||||||
WidgetSpan(
|
WidgetSpan(
|
||||||
alignment: PlaceholderAlignment.middle,
|
alignment: PlaceholderAlignment.middle,
|
||||||
|
@ -94,7 +94,7 @@ class _DebugAndroidAppSectionState extends State<DebugAndroidAppSection> with Au
|
||||||
),
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: ' ${package.potentialDirs.join(', ')}\n',
|
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/entry.dart';
|
||||||
import 'package:aves/model/source/collection_lens.dart';
|
import 'package:aves/model/source/collection_lens.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.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/providers/media_query_data_provider.dart';
|
||||||
import 'package:aves/widgets/common/thumbnail/image.dart';
|
import 'package:aves/widgets/common/thumbnail/image.dart';
|
||||||
import 'package:aves/widgets/dialogs/item_pick_dialog.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) {
|
Widget _buildCover(AvesEntry entry, double extent) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: _pickEntry,
|
onTap: _pickEntry,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: AvesBorder.border(context),
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(32)),
|
||||||
|
),
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(32)),
|
borderRadius: const BorderRadius.all(Radius.circular(32)),
|
||||||
child: SizedBox(
|
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/durations.dart';
|
||||||
import 'package:aves/theme/format.dart';
|
import 'package:aves/theme/format.dart';
|
||||||
import 'package:aves/theme/icons.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/basic/wheel.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||||
|
@ -33,11 +34,6 @@ class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
|
||||||
bool _showOptions = false;
|
bool _showOptions = false;
|
||||||
final Set<MetadataField> _fields = {...DateModifier.writableDateFields};
|
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
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
@ -81,7 +77,7 @@ class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
|
||||||
value: _action,
|
value: _action,
|
||||||
onChanged: (v) => setState(() => _action = v!),
|
onChanged: (v) => setState(() => _action = v!),
|
||||||
isExpanded: true,
|
isExpanded: true,
|
||||||
dropdownColor: dropdownColor,
|
dropdownColor: Themes.thirdLayerColor(context),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
AnimatedSwitcher(
|
AnimatedSwitcher(
|
||||||
|
@ -169,7 +165,7 @@ class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
|
||||||
value: _copyFieldSource,
|
value: _copyFieldSource,
|
||||||
onChanged: (v) => setState(() => _copyFieldSource = v!),
|
onChanged: (v) => setState(() => _copyFieldSource = v!),
|
||||||
isExpanded: true,
|
isExpanded: true,
|
||||||
dropdownColor: dropdownColor,
|
dropdownColor: Themes.thirdLayerColor(context),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import 'package:aves/model/metadata/enums.dart';
|
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/ref/brand_colors.dart';
|
||||||
|
import 'package:aves/theme/colors.dart';
|
||||||
import 'package:aves/theme/durations.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/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/fx/highlight_decoration.dart';
|
import 'package:aves/widgets/common/fx/highlight_decoration.dart';
|
||||||
import 'package:aves/widgets/common/identity/highlight_title.dart';
|
import 'package:aves/widgets/common/identity/highlight_title.dart';
|
||||||
|
@ -97,6 +99,22 @@ class _RemoveEntryMetadataDialogState extends State<RemoveEntryMetadataDialog> {
|
||||||
|
|
||||||
Widget _toTile(MetadataType type) {
|
Widget _toTile(MetadataType type) {
|
||||||
final text = type.getText();
|
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(
|
return SwitchListTile(
|
||||||
value: _types.contains(type),
|
value: _types.contains(type),
|
||||||
onChanged: (selected) {
|
onChanged: (selected) {
|
||||||
|
@ -106,17 +124,7 @@ class _RemoveEntryMetadataDialogState extends State<RemoveEntryMetadataDialog> {
|
||||||
},
|
},
|
||||||
title: Align(
|
title: Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: DecoratedBox(
|
child: child,
|
||||||
decoration: HighlightDecoration(
|
|
||||||
color: BrandColors.get(text) ?? stringToColor(text),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
text,
|
|
||||||
style: const TextStyle(
|
|
||||||
shadows: HighlightTitle.shadows,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,7 +78,12 @@ class _TileViewDialogState<S, G, L> extends State<TileViewDialog<S, G, L>> with
|
||||||
final tabs = <Tuple2<Tab, Widget>>[
|
final tabs = <Tuple2<Tab, Widget>>[
|
||||||
if (sortOptions.isNotEmpty)
|
if (sortOptions.isNotEmpty)
|
||||||
Tuple2(
|
Tuple2(
|
||||||
_buildTab(context, const Key('tab-sort'), AIcons.sort, l10n.viewDialogTabSort),
|
_buildTab(
|
||||||
|
context,
|
||||||
|
const Key('tab-sort'),
|
||||||
|
AIcons.sort,
|
||||||
|
l10n.viewDialogTabSort,
|
||||||
|
),
|
||||||
Column(
|
Column(
|
||||||
children: sortOptions.entries
|
children: sortOptions.entries
|
||||||
.map((kv) => _buildRadioListTile<S>(
|
.map((kv) => _buildRadioListTile<S>(
|
||||||
|
@ -92,7 +97,13 @@ class _TileViewDialogState<S, G, L> extends State<TileViewDialog<S, G, L>> with
|
||||||
),
|
),
|
||||||
if (groupOptions.isNotEmpty)
|
if (groupOptions.isNotEmpty)
|
||||||
Tuple2(
|
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(
|
Column(
|
||||||
children: groupOptions.entries
|
children: groupOptions.entries
|
||||||
.map((kv) => _buildRadioListTile<G>(
|
.map((kv) => _buildRadioListTile<G>(
|
||||||
|
@ -106,7 +117,12 @@ class _TileViewDialogState<S, G, L> extends State<TileViewDialog<S, G, L>> with
|
||||||
),
|
),
|
||||||
if (layoutOptions.isNotEmpty)
|
if (layoutOptions.isNotEmpty)
|
||||||
Tuple2(
|
Tuple2(
|
||||||
_buildTab(context, const Key('tab-layout'), AIcons.layout, l10n.viewDialogTabLayout),
|
_buildTab(
|
||||||
|
context,
|
||||||
|
const Key('tab-layout'),
|
||||||
|
AIcons.layout,
|
||||||
|
l10n.viewDialogTabLayout,
|
||||||
|
),
|
||||||
Column(
|
Column(
|
||||||
children: layoutOptions.entries
|
children: layoutOptions.entries
|
||||||
.map((kv) => _buildRadioListTile<L>(
|
.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,
|
// cannot use `IconTheme` over `TabBar` to change size,
|
||||||
// because `TabBar` does so internally
|
// because `TabBar` does so internally
|
||||||
final textScaleFactor = MediaQuery.textScaleFactorOf(context);
|
final textScaleFactor = MediaQuery.textScaleFactorOf(context);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:aves/model/entry.dart';
|
import 'package:aves/model/entry.dart';
|
||||||
import 'package:aves/ref/languages.dart';
|
import 'package:aves/ref/languages.dart';
|
||||||
import 'package:aves/theme/icons.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/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/viewer/video/controller.dart';
|
import 'package:aves/widgets/viewer/video/controller.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
@ -167,10 +168,7 @@ class _VideoStreamSelectionDialogState extends State<VideoStreamSelectionDialog>
|
||||||
value: current,
|
value: current,
|
||||||
onChanged: streams.length > 1 ? (newValue) => setState(() => setter(newValue)) : null,
|
onChanged: streams.length > 1 ? (newValue) => setState(() => setter(newValue)) : null,
|
||||||
isExpanded: true,
|
isExpanded: true,
|
||||||
// use a different shade to avoid having the same background
|
dropdownColor: Themes.thirdLayerColor(context),
|
||||||
// on the dialog (using the theme `dialogBackgroundColor`)
|
|
||||||
// and on the dropdown (using the theme `canvasColor`)
|
|
||||||
dropdownColor: Colors.grey.shade800,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
|
@ -121,6 +121,7 @@ class AppDrawer extends StatelessWidget {
|
||||||
Text(
|
Text(
|
||||||
context.l10n.appName,
|
context.l10n.appName,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
fontSize: 44,
|
fontSize: 44,
|
||||||
fontWeight: FontWeight.w300,
|
fontWeight: FontWeight.w300,
|
||||||
letterSpacing: 1.0,
|
letterSpacing: 1.0,
|
||||||
|
@ -136,6 +137,7 @@ class AppDrawer extends StatelessWidget {
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
foregroundColor: MaterialStateProperty.all<Color>(Colors.white),
|
foregroundColor: MaterialStateProperty.all<Color>(Colors.white),
|
||||||
overlayColor: MaterialStateProperty.all<Color>(Colors.white24),
|
overlayColor: MaterialStateProperty.all<Color>(Colors.white24),
|
||||||
|
side: MaterialStateProperty.all<BorderSide>(BorderSide(width: 1, color: Colors.white.withOpacity(0.12))),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Wrap(
|
child: Wrap(
|
||||||
|
|
|
@ -65,12 +65,10 @@ class CollectionNavTile extends StatelessWidget {
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
settings: const RouteSettings(name: CollectionPage.routeName),
|
settings: const RouteSettings(name: CollectionPage.routeName),
|
||||||
builder: (context) => CollectionPage(
|
builder: (context) => CollectionPage(
|
||||||
collection: CollectionLens(
|
|
||||||
source: context.read<CollectionSource>(),
|
source: context.read<CollectionSource>(),
|
||||||
filters: {filter},
|
filters: {filter},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
(route) => false,
|
(route) => false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,7 +77,8 @@ class DrawerPageIcon extends StatelessWidget {
|
||||||
return const Icon(AIcons.tag);
|
return const Icon(AIcons.tag);
|
||||||
case AppDebugPage.routeName:
|
case AppDebugPage.routeName:
|
||||||
return ShaderMask(
|
return ShaderMask(
|
||||||
shaderCallback: AColors.debugGradient.createShader,
|
shaderCallback: AvesColorsData.debugGradient.createShader,
|
||||||
|
blendMode: BlendMode.srcIn,
|
||||||
child: const Icon(AIcons.debug),
|
child: const Icon(AIcons.debug),
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -14,7 +14,6 @@ import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/utils/android_file_utils.dart';
|
import 'package:aves/utils/android_file_utils.dart';
|
||||||
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
|
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
|
||||||
import 'package:aves/widgets/common/thumbnail/image.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:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
@ -151,7 +150,7 @@ class CoveredFilterChip<T extends CollectionFilter> extends StatelessWidget {
|
||||||
radius: radius(extent),
|
radius: radius(extent),
|
||||||
),
|
),
|
||||||
banner: banner,
|
banner: banner,
|
||||||
details: showText ? _buildDetails(source, filter) : null,
|
details: showText ? _buildDetails(context, source, filter) : null,
|
||||||
padding: titlePadding,
|
padding: titlePadding,
|
||||||
heroType: heroType,
|
heroType: heroType,
|
||||||
onTap: onTap,
|
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 padding = min<double>(8.0, extent / 16);
|
||||||
final iconSize = detailIconSize(extent);
|
final iconSize = detailIconSize(extent);
|
||||||
final fontSize = detailFontSize(extent);
|
final fontSize = detailFontSize(extent);
|
||||||
|
@ -172,7 +173,7 @@ class CoveredFilterChip<T extends CollectionFilter> extends StatelessWidget {
|
||||||
duration: Durations.chipDecorationAnimation,
|
duration: Durations.chipDecorationAnimation,
|
||||||
child: Icon(
|
child: Icon(
|
||||||
AIcons.pin,
|
AIcons.pin,
|
||||||
color: FilterGridPage.detailColor,
|
color: _detailColor(context),
|
||||||
size: iconSize,
|
size: iconSize,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -182,14 +183,14 @@ class CoveredFilterChip<T extends CollectionFilter> extends StatelessWidget {
|
||||||
duration: Durations.chipDecorationAnimation,
|
duration: Durations.chipDecorationAnimation,
|
||||||
child: Icon(
|
child: Icon(
|
||||||
AIcons.removableStorage,
|
AIcons.removableStorage,
|
||||||
color: FilterGridPage.detailColor,
|
color: _detailColor(context),
|
||||||
size: iconSize,
|
size: iconSize,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'${source.count(filter)}',
|
'${source.count(filter)}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: FilterGridPage.detailColor,
|
color: _detailColor(context),
|
||||||
fontSize: fontSize,
|
fontSize: fontSize,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -67,8 +67,6 @@ class FilterGridPage<T extends CollectionFilter> extends StatelessWidget {
|
||||||
required this.heroType,
|
required this.heroType,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
static const Color detailColor = Color(0xFFE0E0E0);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MediaQueryDataProvider(
|
return MediaQueryDataProvider(
|
||||||
|
|
|
@ -52,7 +52,7 @@ class FilterNavigationPage<T extends CollectionFilter> extends StatelessWidget {
|
||||||
emptyBuilder: () => ValueListenableBuilder<SourceState>(
|
emptyBuilder: () => ValueListenableBuilder<SourceState>(
|
||||||
valueListenable: source.stateNotifier,
|
valueListenable: source.stateNotifier,
|
||||||
builder: (context, sourceState, child) {
|
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
|
// 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/filters/filters.dart';
|
||||||
import 'package:aves/model/selection.dart';
|
import 'package:aves/model/selection.dart';
|
||||||
import 'package:aves/model/settings/settings.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/collection_source.dart';
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums.dart';
|
||||||
import 'package:aves/widgets/collection/collection_page.dart';
|
import 'package:aves/widgets/collection/collection_page.dart';
|
||||||
|
@ -95,12 +94,10 @@ class _InteractiveFilterTileState<T extends CollectionFilter> extends State<Inte
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
settings: const RouteSettings(name: CollectionPage.routeName),
|
settings: const RouteSettings(name: CollectionPage.routeName),
|
||||||
builder: (context) => CollectionPage(
|
builder: (context) => CollectionPage(
|
||||||
collection: CollectionLens(
|
|
||||||
source: context.read<CollectionSource>(),
|
source: context.read<CollectionSource>(),
|
||||||
filters: {filter},
|
filters: {filter},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ class FilterListDetails<T extends CollectionFilter> extends StatelessWidget {
|
||||||
return Container(
|
return Container(
|
||||||
padding: FilterListDetailsTheme.contentPadding,
|
padding: FilterListDetailsTheme.contentPadding,
|
||||||
foregroundDecoration: BoxDecoration(
|
foregroundDecoration: BoxDecoration(
|
||||||
border: Border(top: AvesBorder.straightSide),
|
border: Border(top: AvesBorder.straightSide(context)),
|
||||||
),
|
),
|
||||||
margin: FilterListDetailsTheme.contentMargin,
|
margin: FilterListDetailsTheme.contentMargin,
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
|
@ -252,11 +252,9 @@ class _HomePageState extends State<HomePage> {
|
||||||
return DirectMaterialPageRoute(
|
return DirectMaterialPageRoute(
|
||||||
settings: const RouteSettings(name: CollectionPage.routeName),
|
settings: const RouteSettings(name: CollectionPage.routeName),
|
||||||
builder: (_) => CollectionPage(
|
builder: (_) => CollectionPage(
|
||||||
collection: CollectionLens(
|
|
||||||
source: source,
|
source: source,
|
||||||
filters: filters,
|
filters: filters,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -382,10 +382,8 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
|
||||||
settings: const RouteSettings(name: CollectionPage.routeName),
|
settings: const RouteSettings(name: CollectionPage.routeName),
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return CollectionPage(
|
return CollectionPage(
|
||||||
collection: CollectionLens(
|
|
||||||
source: openingCollection.source,
|
source: openingCollection.source,
|
||||||
filters: openingCollection.filters,
|
filters: {...openingCollection.filters, filter},
|
||||||
)..addFilter(filter),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -266,12 +266,10 @@ class CollectionSearchDelegate {
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
settings: const RouteSettings(name: CollectionPage.routeName),
|
settings: const RouteSettings(name: CollectionPage.routeName),
|
||||||
builder: (context) => CollectionPage(
|
builder: (context) => CollectionPage(
|
||||||
collection: CollectionLens(
|
|
||||||
source: source,
|
source: source,
|
||||||
filters: {filter},
|
filters: {filter},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
(route) => false,
|
(route) => false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/utils/debouncer.dart';
|
import 'package:aves/utils/debouncer.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
|
@ -109,7 +111,9 @@ class _SearchPageState extends State<SearchPage> {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: widget.delegate.buildLeading(context),
|
leading: widget.delegate.buildLeading(context),
|
||||||
title: TextField(
|
title: DefaultTextStyle.merge(
|
||||||
|
style: const TextStyle(fontFeatures: [FontFeature.disable('smcp')]),
|
||||||
|
child: TextField(
|
||||||
controller: widget.delegate.queryTextController,
|
controller: widget.delegate.queryTextController,
|
||||||
focusNode: _focusNode,
|
focusNode: _focusNode,
|
||||||
style: theme.textTheme.headline6,
|
style: theme.textTheme.headline6,
|
||||||
|
@ -121,6 +125,7 @@ class _SearchPageState extends State<SearchPage> {
|
||||||
hintStyle: theme.inputDecorationTheme.hintStyle,
|
hintStyle: theme.inputDecorationTheme.hintStyle,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
actions: widget.delegate.buildActions(context),
|
actions: widget.delegate.buildActions(context),
|
||||||
),
|
),
|
||||||
body: AnimatedSwitcher(
|
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/colors.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
|
import 'package:aves/widgets/common/identity/aves_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/accessibility/time_to_take_action.dart';
|
||||||
import 'package:aves/widgets/settings/common/tile_leading.dart';
|
import 'package:aves/widgets/settings/common/tile_leading.dart';
|
||||||
|
import 'package:aves/widgets/settings/common/tiles.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class AccessibilitySection extends StatelessWidget {
|
class AccessibilitySection extends StatelessWidget {
|
||||||
final ValueNotifier<String?> expandedNotifier;
|
final ValueNotifier<String?> expandedNotifier;
|
||||||
|
@ -20,14 +24,21 @@ class AccessibilitySection extends StatelessWidget {
|
||||||
return AvesExpansionTile(
|
return AvesExpansionTile(
|
||||||
leading: SettingsTileLeading(
|
leading: SettingsTileLeading(
|
||||||
icon: AIcons.accessibility,
|
icon: AIcons.accessibility,
|
||||||
color: AColors.accessibility,
|
color: context.select<AvesColorsData, Color>((v) => v.accessibility),
|
||||||
),
|
),
|
||||||
title: context.l10n.settingsSectionAccessibility,
|
title: context.l10n.settingsSectionAccessibility,
|
||||||
expandedNotifier: expandedNotifier,
|
expandedNotifier: expandedNotifier,
|
||||||
showHighlight: false,
|
showHighlight: false,
|
||||||
children: const [
|
children: [
|
||||||
RemoveAnimationsTile(),
|
SettingsSelectionListTile<AccessibilityAnimations>(
|
||||||
TimeToTakeActionTile(),
|
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/model/settings/settings.dart';
|
||||||
import 'package:aves/services/accessibility_service.dart';
|
import 'package:aves/services/accessibility_service.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.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:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
class TimeToTakeActionTile extends StatefulWidget {
|
class TimeToTakeActionTile extends StatefulWidget {
|
||||||
const TimeToTakeActionTile({Key? key}) : super(key: key);
|
const TimeToTakeActionTile({Key? key}) : super(key: key);
|
||||||
|
@ -25,26 +24,20 @@ class _TimeToTakeActionTileState extends State<TimeToTakeActionTile> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final currentTimeToTakeAction = context.select<Settings, AccessibilityTimeout>((s) => s.timeToTakeAction);
|
|
||||||
|
|
||||||
return FutureBuilder<bool>(
|
return FutureBuilder<bool>(
|
||||||
future: _hasSystemOptionLoader,
|
future: _hasSystemOptionLoader,
|
||||||
builder: (context, snapshot) {
|
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 hasSystemOption = snapshot.data!;
|
||||||
final optionValues = hasSystemOption ? AccessibilityTimeout.values : AccessibilityTimeout.values.where((v) => v != AccessibilityTimeout.system).toList();
|
final optionValues = hasSystemOption ? AccessibilityTimeout.values : AccessibilityTimeout.values.where((v) => v != AccessibilityTimeout.system).toList();
|
||||||
return ListTile(
|
|
||||||
title: Text(context.l10n.settingsTimeToTakeActionTile),
|
return SettingsSelectionListTile<AccessibilityTimeout>(
|
||||||
subtitle: Text(currentTimeToTakeAction.getName(context)),
|
values: optionValues,
|
||||||
onTap: () => showSelectionDialog<AccessibilityTimeout>(
|
getName: (context, v) => v.getName(context),
|
||||||
context: context,
|
selector: (context, s) => s.timeToTakeAction,
|
||||||
builder: (context) => AvesSelectionDialog<AccessibilityTimeout>(
|
|
||||||
initialValue: currentTimeToTakeAction,
|
|
||||||
options: Map.fromEntries(optionValues.map((v) => MapEntry(v, v.getName(context)))),
|
|
||||||
title: context.l10n.settingsTimeToTakeActionTitle,
|
|
||||||
),
|
|
||||||
onSelection: (v) => settings.timeToTakeAction = v,
|
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/utils/constants.dart';
|
||||||
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
|
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
|
||||||
import 'package:decorated_icon/decorated_icon.dart';
|
import 'package:decorated_icon/decorated_icon.dart';
|
||||||
|
@ -15,7 +16,7 @@ class SettingsTileLeading extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return AnimatedContainer(
|
||||||
padding: const EdgeInsets.all(6),
|
padding: const EdgeInsets.all(6),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border.fromBorderSide(BorderSide(
|
border: Border.fromBorderSide(BorderSide(
|
||||||
|
@ -24,9 +25,10 @@ class SettingsTileLeading extends StatelessWidget {
|
||||||
)),
|
)),
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
|
duration: Durations.themeColorModeAnimation,
|
||||||
child: DecoratedIcon(
|
child: DecoratedIcon(
|
||||||
icon,
|
icon,
|
||||||
shadows: Constants.embossShadows,
|
shadows: Theme.of(context).brightness == Brightness.dark ? Constants.embossShadows : null,
|
||||||
size: 18,
|
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/utils/constants.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
|
import 'package:aves/widgets/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/tile_leading.dart';
|
||||||
|
import 'package:aves/widgets/settings/common/tiles.dart';
|
||||||
import 'package:aves/widgets/settings/language/locale.dart';
|
import 'package:aves/widgets/settings/language/locale.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -24,9 +24,6 @@ class LanguageSection extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final l10n = context.l10n;
|
final l10n = context.l10n;
|
||||||
final currentCoordinateFormat = context.select<Settings, CoordinateFormat>((s) => s.coordinateFormat);
|
|
||||||
final currentUnitSystem = context.select<Settings, UnitSystem>((s) => s.unitSystem);
|
|
||||||
|
|
||||||
return AvesExpansionTile(
|
return AvesExpansionTile(
|
||||||
// key is expected by test driver
|
// key is expected by test driver
|
||||||
key: const Key('section-language'),
|
key: const Key('section-language'),
|
||||||
|
@ -35,39 +32,29 @@ class LanguageSection extends StatelessWidget {
|
||||||
value: 'language',
|
value: 'language',
|
||||||
leading: SettingsTileLeading(
|
leading: SettingsTileLeading(
|
||||||
icon: AIcons.language,
|
icon: AIcons.language,
|
||||||
color: AColors.language,
|
color: context.select<AvesColorsData, Color>((v) => v.language),
|
||||||
),
|
),
|
||||||
title: l10n.settingsSectionLanguage,
|
title: l10n.settingsSectionLanguage,
|
||||||
expandedNotifier: expandedNotifier,
|
expandedNotifier: expandedNotifier,
|
||||||
showHighlight: false,
|
showHighlight: false,
|
||||||
children: [
|
children: [
|
||||||
const LocaleTile(),
|
const LocaleTile(),
|
||||||
ListTile(
|
SettingsSelectionListTile<CoordinateFormat>(
|
||||||
title: Text(l10n.settingsCoordinateFormatTile),
|
values: CoordinateFormat.values,
|
||||||
subtitle: Text(currentCoordinateFormat.getName(context)),
|
getName: (context, v) => v.getName(context),
|
||||||
onTap: () => showSelectionDialog<CoordinateFormat>(
|
selector: (context, s) => s.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,
|
|
||||||
),
|
|
||||||
onSelection: (v) => settings.coordinateFormat = v,
|
onSelection: (v) => settings.coordinateFormat = v,
|
||||||
|
tileTitle: l10n.settingsCoordinateFormatTile,
|
||||||
|
dialogTitle: l10n.settingsCoordinateFormatTitle,
|
||||||
|
optionSubtitleBuilder: (value) => value.format(l10n, Constants.pointNemo),
|
||||||
),
|
),
|
||||||
),
|
SettingsSelectionListTile<UnitSystem>(
|
||||||
ListTile(
|
values: UnitSystem.values,
|
||||||
title: Text(l10n.settingsUnitSystemTile),
|
getName: (context, v) => v.getName(context),
|
||||||
subtitle: Text(currentUnitSystem.getName(context)),
|
selector: (context, s) => s.unitSystem,
|
||||||
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,
|
|
||||||
),
|
|
||||||
onSelection: (v) => settings.unitSystem = v,
|
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/theme/icons.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
|
import 'package:aves/widgets/common/identity/aves_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/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/confirmation_dialogs.dart';
|
||||||
import 'package:aves/widgets/settings/navigation/drawer.dart';
|
import 'package:aves/widgets/settings/navigation/drawer.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -23,51 +23,37 @@ class NavigationSection extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
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(
|
return AvesExpansionTile(
|
||||||
leading: SettingsTileLeading(
|
leading: SettingsTileLeading(
|
||||||
icon: AIcons.home,
|
icon: AIcons.home,
|
||||||
color: AColors.navigation,
|
color: context.select<AvesColorsData, Color>((v) => v.navigation),
|
||||||
),
|
),
|
||||||
title: context.l10n.settingsSectionNavigation,
|
title: context.l10n.settingsSectionNavigation,
|
||||||
expandedNotifier: expandedNotifier,
|
expandedNotifier: expandedNotifier,
|
||||||
showHighlight: false,
|
showHighlight: false,
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
SettingsSelectionListTile<HomePageSetting>(
|
||||||
title: Text(context.l10n.settingsHome),
|
values: HomePageSetting.values,
|
||||||
subtitle: Text(currentHomePage.getName(context)),
|
getName: (context, v) => v.getName(context),
|
||||||
onTap: () => showSelectionDialog<HomePageSetting>(
|
selector: (context, s) => s.homePage,
|
||||||
context: context,
|
|
||||||
builder: (context) => AvesSelectionDialog<HomePageSetting>(
|
|
||||||
initialValue: currentHomePage,
|
|
||||||
options: Map.fromEntries(HomePageSetting.values.map((v) => MapEntry(v, v.getName(context)))),
|
|
||||||
title: context.l10n.settingsHome,
|
|
||||||
),
|
|
||||||
onSelection: (v) => settings.homePage = v,
|
onSelection: (v) => settings.homePage = v,
|
||||||
),
|
tileTitle: context.l10n.settingsHome,
|
||||||
|
dialogTitle: context.l10n.settingsHome,
|
||||||
),
|
),
|
||||||
const NavigationDrawerTile(),
|
const NavigationDrawerTile(),
|
||||||
const ConfirmationDialogTile(),
|
const ConfirmationDialogTile(),
|
||||||
ListTile(
|
SettingsSelectionListTile<KeepScreenOn>(
|
||||||
title: Text(context.l10n.settingsKeepScreenOnTile),
|
values: KeepScreenOn.values,
|
||||||
subtitle: Text(currentKeepScreenOn.getName(context)),
|
getName: (context, v) => v.getName(context),
|
||||||
onTap: () => showSelectionDialog<KeepScreenOn>(
|
selector: (context, s) => s.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,
|
|
||||||
),
|
|
||||||
onSelection: (v) => settings.keepScreenOn = v,
|
onSelection: (v) => settings.keepScreenOn = v,
|
||||||
|
tileTitle: context.l10n.settingsKeepScreenOnTile,
|
||||||
|
dialogTitle: context.l10n.settingsKeepScreenOnTitle,
|
||||||
),
|
),
|
||||||
),
|
SettingsSwitchListTile(
|
||||||
SwitchListTile(
|
selector: (context, s) => s.mustBackTwiceToExit,
|
||||||
value: currentMustBackTwiceToExit,
|
|
||||||
onChanged: (v) => settings.mustBackTwiceToExit = v,
|
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/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
|
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
|
||||||
import 'package:aves/widgets/settings/common/tile_leading.dart';
|
import 'package:aves/widgets/settings/common/tile_leading.dart';
|
||||||
|
import 'package:aves/widgets/settings/common/tiles.dart';
|
||||||
import 'package:aves/widgets/settings/privacy/access_grants.dart';
|
import 'package:aves/widgets/settings/privacy/access_grants.dart';
|
||||||
import 'package:aves/widgets/settings/privacy/hidden_items.dart';
|
import 'package:aves/widgets/settings/privacy/hidden_items.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -26,56 +27,44 @@ class PrivacySection extends StatelessWidget {
|
||||||
return AvesExpansionTile(
|
return AvesExpansionTile(
|
||||||
leading: SettingsTileLeading(
|
leading: SettingsTileLeading(
|
||||||
icon: AIcons.privacy,
|
icon: AIcons.privacy,
|
||||||
color: AColors.privacy,
|
color: context.select<AvesColorsData, Color>((v) => v.privacy),
|
||||||
),
|
),
|
||||||
title: context.l10n.settingsSectionPrivacy,
|
title: context.l10n.settingsSectionPrivacy,
|
||||||
expandedNotifier: expandedNotifier,
|
expandedNotifier: expandedNotifier,
|
||||||
showHighlight: false,
|
showHighlight: false,
|
||||||
children: [
|
children: [
|
||||||
Selector<Settings, bool>(
|
SettingsSwitchListTile(
|
||||||
selector: (context, s) => s.isInstalledAppAccessAllowed,
|
selector: (context, s) => s.isInstalledAppAccessAllowed,
|
||||||
builder: (context, current, child) => SwitchListTile(
|
|
||||||
value: current,
|
|
||||||
onChanged: (v) => settings.isInstalledAppAccessAllowed = v,
|
onChanged: (v) => settings.isInstalledAppAccessAllowed = v,
|
||||||
title: Text(context.l10n.settingsAllowInstalledAppAccess),
|
title: context.l10n.settingsAllowInstalledAppAccess,
|
||||||
subtitle: Text(context.l10n.settingsAllowInstalledAppAccessSubtitle),
|
subtitle: context.l10n.settingsAllowInstalledAppAccessSubtitle,
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if (canEnableErrorReporting)
|
if (canEnableErrorReporting)
|
||||||
Selector<Settings, bool>(
|
SettingsSwitchListTile(
|
||||||
selector: (context, s) => s.isErrorReportingAllowed,
|
selector: (context, s) => s.isErrorReportingAllowed,
|
||||||
builder: (context, current, child) => SwitchListTile(
|
|
||||||
value: current,
|
|
||||||
onChanged: (v) => settings.isErrorReportingAllowed = v,
|
onChanged: (v) => settings.isErrorReportingAllowed = v,
|
||||||
title: Text(context.l10n.settingsAllowErrorReporting),
|
title: context.l10n.settingsAllowErrorReporting,
|
||||||
),
|
),
|
||||||
),
|
SettingsSwitchListTile(
|
||||||
Selector<Settings, bool>(
|
|
||||||
selector: (context, s) => s.saveSearchHistory,
|
selector: (context, s) => s.saveSearchHistory,
|
||||||
builder: (context, current, child) => SwitchListTile(
|
|
||||||
value: current,
|
|
||||||
onChanged: (v) {
|
onChanged: (v) {
|
||||||
settings.saveSearchHistory = v;
|
settings.saveSearchHistory = v;
|
||||||
if (!v) {
|
if (!v) {
|
||||||
settings.searchHistory = [];
|
settings.searchHistory = [];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
title: Text(context.l10n.settingsSaveSearchHistory),
|
title: context.l10n.settingsSaveSearchHistory,
|
||||||
),
|
),
|
||||||
),
|
SettingsSwitchListTile(
|
||||||
Selector<Settings, bool>(
|
|
||||||
selector: (context, s) => s.enableBin,
|
selector: (context, s) => s.enableBin,
|
||||||
builder: (context, current, child) => SwitchListTile(
|
|
||||||
value: current,
|
|
||||||
onChanged: (v) {
|
onChanged: (v) {
|
||||||
settings.enableBin = v;
|
settings.enableBin = v;
|
||||||
if (!v) {
|
if (!v) {
|
||||||
settings.searchHistory = [];
|
settings.searchHistory = [];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
title: Text(context.l10n.settingsEnableBin),
|
title: context.l10n.settingsEnableBin,
|
||||||
subtitle: Text(context.l10n.settingsEnableBinSubtitle),
|
subtitle: context.l10n.settingsEnableBinSubtitle,
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const HiddenItemsTile(),
|
const HiddenItemsTile(),
|
||||||
if (device.canGrantDirectoryAccess) const StorageAccessTile(),
|
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/accessibility/accessibility.dart';
|
||||||
import 'package:aves/widgets/settings/app_export/items.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/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/language/language.dart';
|
||||||
import 'package:aves/widgets/settings/navigation/navigation.dart';
|
import 'package:aves/widgets/settings/navigation/navigation.dart';
|
||||||
import 'package:aves/widgets/settings/privacy/privacy.dart';
|
import 'package:aves/widgets/settings/privacy/privacy.dart';
|
||||||
|
@ -98,6 +99,7 @@ class _SettingsPageState extends State<SettingsPage> with FeedbackMixin {
|
||||||
VideoSection(expandedNotifier: _expandedNotifier),
|
VideoSection(expandedNotifier: _expandedNotifier),
|
||||||
PrivacySection(expandedNotifier: _expandedNotifier),
|
PrivacySection(expandedNotifier: _expandedNotifier),
|
||||||
AccessibilitySection(expandedNotifier: _expandedNotifier),
|
AccessibilitySection(expandedNotifier: _expandedNotifier),
|
||||||
|
DisplaySection(expandedNotifier: _expandedNotifier),
|
||||||
LanguageSection(expandedNotifier: _expandedNotifier),
|
LanguageSection(expandedNotifier: _expandedNotifier),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/theme/colors.dart';
|
import 'package:aves/theme/colors.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
|
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
|
||||||
import 'package:aves/widgets/common/identity/aves_icons.dart';
|
import 'package:aves/widgets/common/identity/aves_icons.dart';
|
||||||
import 'package:aves/widgets/settings/common/tile_leading.dart';
|
import 'package:aves/widgets/settings/common/tile_leading.dart';
|
||||||
|
import 'package:aves/widgets/settings/common/tiles.dart';
|
||||||
import 'package:aves/widgets/settings/thumbnails/collection_actions_editor.dart';
|
import 'package:aves/widgets/settings/thumbnails/collection_actions_editor.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -21,131 +21,77 @@ class ThumbnailsSection extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final iconSize = IconTheme.of(context).size! * MediaQuery.textScaleFactorOf(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(
|
return AvesExpansionTile(
|
||||||
leading: SettingsTileLeading(
|
leading: SettingsTileLeading(
|
||||||
icon: AIcons.grid,
|
icon: AIcons.grid,
|
||||||
color: AColors.thumbnails,
|
color: context.select<AvesColorsData, Color>((v) => v.thumbnails),
|
||||||
),
|
),
|
||||||
title: context.l10n.settingsSectionThumbnails,
|
title: context.l10n.settingsSectionThumbnails,
|
||||||
expandedNotifier: expandedNotifier,
|
expandedNotifier: expandedNotifier,
|
||||||
showHighlight: false,
|
showHighlight: false,
|
||||||
children: [
|
children: [
|
||||||
const CollectionActionsTile(),
|
const CollectionActionsTile(),
|
||||||
Selector<Settings, bool>(
|
SettingsSwitchListTile(
|
||||||
selector: (context, s) => s.showThumbnailFavourite,
|
selector: (context, s) => s.showThumbnailFavourite,
|
||||||
builder: (context, current, child) => SwitchListTile(
|
|
||||||
value: current,
|
|
||||||
onChanged: (v) => settings.showThumbnailFavourite = v,
|
onChanged: (v) => settings.showThumbnailFavourite = v,
|
||||||
title: Row(
|
title: context.l10n.settingsThumbnailShowFavouriteIcon,
|
||||||
children: [
|
trailing: Padding(
|
||||||
Expanded(child: Text(context.l10n.settingsThumbnailShowFavouriteIcon)),
|
|
||||||
AnimatedOpacity(
|
|
||||||
opacity: opacityFor(current),
|
|
||||||
duration: Durations.toggleableTransitionAnimation,
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: iconSize * (1 - FavouriteIcon.scale) / 2),
|
padding: EdgeInsets.symmetric(horizontal: iconSize * (1 - FavouriteIcon.scale) / 2),
|
||||||
child: Icon(
|
child: Icon(
|
||||||
AIcons.favourite,
|
AIcons.favourite,
|
||||||
size: iconSize * FavouriteIcon.scale,
|
size: iconSize * FavouriteIcon.scale,
|
||||||
|
color: iconColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
SettingsSwitchListTile(
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Selector<Settings, bool>(
|
|
||||||
selector: (context, s) => s.showThumbnailLocation,
|
selector: (context, s) => s.showThumbnailLocation,
|
||||||
builder: (context, current, child) => SwitchListTile(
|
|
||||||
value: current,
|
|
||||||
onChanged: (v) => settings.showThumbnailLocation = v,
|
onChanged: (v) => settings.showThumbnailLocation = v,
|
||||||
title: Row(
|
title: context.l10n.settingsThumbnailShowLocationIcon,
|
||||||
children: [
|
trailing: Icon(
|
||||||
Expanded(child: Text(context.l10n.settingsThumbnailShowLocationIcon)),
|
|
||||||
AnimatedOpacity(
|
|
||||||
opacity: opacityFor(current),
|
|
||||||
duration: Durations.toggleableTransitionAnimation,
|
|
||||||
child: Icon(
|
|
||||||
AIcons.location,
|
AIcons.location,
|
||||||
size: iconSize,
|
size: iconSize,
|
||||||
|
color: iconColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
SettingsSwitchListTile(
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Selector<Settings, bool>(
|
|
||||||
selector: (context, s) => s.showThumbnailMotionPhoto,
|
selector: (context, s) => s.showThumbnailMotionPhoto,
|
||||||
builder: (context, current, child) => SwitchListTile(
|
|
||||||
value: current,
|
|
||||||
onChanged: (v) => settings.showThumbnailMotionPhoto = v,
|
onChanged: (v) => settings.showThumbnailMotionPhoto = v,
|
||||||
title: Row(
|
title: context.l10n.settingsThumbnailShowMotionPhotoIcon,
|
||||||
children: [
|
trailing: Padding(
|
||||||
Expanded(child: Text(context.l10n.settingsThumbnailShowMotionPhotoIcon)),
|
|
||||||
AnimatedOpacity(
|
|
||||||
opacity: opacityFor(current),
|
|
||||||
duration: Durations.toggleableTransitionAnimation,
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: iconSize * (1 - MotionPhotoIcon.scale) / 2),
|
padding: EdgeInsets.symmetric(horizontal: iconSize * (1 - MotionPhotoIcon.scale) / 2),
|
||||||
child: Icon(
|
child: Icon(
|
||||||
AIcons.motionPhoto,
|
AIcons.motionPhoto,
|
||||||
size: iconSize * MotionPhotoIcon.scale,
|
size: iconSize * MotionPhotoIcon.scale,
|
||||||
|
color: iconColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
SettingsSwitchListTile(
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Selector<Settings, bool>(
|
|
||||||
selector: (context, s) => s.showThumbnailRating,
|
selector: (context, s) => s.showThumbnailRating,
|
||||||
builder: (context, current, child) => SwitchListTile(
|
|
||||||
value: current,
|
|
||||||
onChanged: (v) => settings.showThumbnailRating = v,
|
onChanged: (v) => settings.showThumbnailRating = v,
|
||||||
title: Row(
|
title: context.l10n.settingsThumbnailShowRating,
|
||||||
children: [
|
trailing: Icon(
|
||||||
Expanded(child: Text(context.l10n.settingsThumbnailShowRating)),
|
|
||||||
AnimatedOpacity(
|
|
||||||
opacity: opacityFor(current),
|
|
||||||
duration: Durations.toggleableTransitionAnimation,
|
|
||||||
child: Icon(
|
|
||||||
AIcons.rating,
|
AIcons.rating,
|
||||||
size: iconSize,
|
size: iconSize,
|
||||||
|
color: iconColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
SettingsSwitchListTile(
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Selector<Settings, bool>(
|
|
||||||
selector: (context, s) => s.showThumbnailRaw,
|
selector: (context, s) => s.showThumbnailRaw,
|
||||||
builder: (context, current, child) => SwitchListTile(
|
|
||||||
value: current,
|
|
||||||
onChanged: (v) => settings.showThumbnailRaw = v,
|
onChanged: (v) => settings.showThumbnailRaw = v,
|
||||||
title: Row(
|
title: context.l10n.settingsThumbnailShowRawIcon,
|
||||||
children: [
|
trailing: Icon(
|
||||||
Expanded(child: Text(context.l10n.settingsThumbnailShowRawIcon)),
|
|
||||||
AnimatedOpacity(
|
|
||||||
opacity: opacityFor(current),
|
|
||||||
duration: Durations.toggleableTransitionAnimation,
|
|
||||||
child: Icon(
|
|
||||||
AIcons.raw,
|
AIcons.raw,
|
||||||
size: iconSize,
|
size: iconSize,
|
||||||
|
color: iconColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
SettingsSwitchListTile(
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Selector<Settings, bool>(
|
|
||||||
selector: (context, s) => s.showThumbnailVideoDuration,
|
selector: (context, s) => s.showThumbnailVideoDuration,
|
||||||
builder: (context, current, child) => SwitchListTile(
|
|
||||||
value: current,
|
|
||||||
onChanged: (v) => settings.showThumbnailVideoDuration = v,
|
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/enums/video_controls.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.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:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
class VideoControlsTile extends StatelessWidget {
|
class VideoControlsTile extends StatelessWidget {
|
||||||
const VideoControlsTile({Key? key}) : super(key: key);
|
const VideoControlsTile({Key? key}) : super(key: key);
|
||||||
|
@ -40,37 +39,23 @@ class VideoControlsPage extends StatelessWidget {
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: ListView(
|
child: ListView(
|
||||||
children: [
|
children: [
|
||||||
Selector<Settings, VideoControls>(
|
SettingsSelectionListTile<VideoControls>(
|
||||||
|
values: VideoControls.values,
|
||||||
|
getName: (context, v) => v.getName(context),
|
||||||
selector: (context, s) => s.videoControls,
|
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,
|
onSelection: (v) => settings.videoControls = v,
|
||||||
|
tileTitle: context.l10n.settingsVideoButtonsTile,
|
||||||
|
dialogTitle: context.l10n.settingsVideoButtonsTitle,
|
||||||
),
|
),
|
||||||
),
|
SettingsSwitchListTile(
|
||||||
),
|
|
||||||
Selector<Settings, bool>(
|
|
||||||
selector: (context, s) => s.videoGestureDoubleTapTogglePlay,
|
selector: (context, s) => s.videoGestureDoubleTapTogglePlay,
|
||||||
builder: (context, current, child) => SwitchListTile(
|
|
||||||
value: current,
|
|
||||||
onChanged: (v) => settings.videoGestureDoubleTapTogglePlay = v,
|
onChanged: (v) => settings.videoGestureDoubleTapTogglePlay = v,
|
||||||
title: Text(context.l10n.settingsVideoGestureDoubleTapTogglePlay),
|
title: context.l10n.settingsVideoGestureDoubleTapTogglePlay,
|
||||||
),
|
),
|
||||||
),
|
SettingsSwitchListTile(
|
||||||
Selector<Settings, bool>(
|
|
||||||
selector: (context, s) => s.videoGestureSideDoubleTapSeek,
|
selector: (context, s) => s.videoGestureSideDoubleTapSeek,
|
||||||
builder: (context, current, child) => SwitchListTile(
|
|
||||||
value: current,
|
|
||||||
onChanged: (v) => settings.videoGestureSideDoubleTapSeek = v,
|
onChanged: (v) => settings.videoGestureSideDoubleTapSeek = v,
|
||||||
title: Text(context.l10n.settingsVideoGestureSideDoubleTapSeek),
|
title: context.l10n.settingsVideoGestureSideDoubleTapSeek,
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -32,7 +32,7 @@ class SubtitleSample extends StatelessWidget {
|
||||||
Color(0xffeaecc6),
|
Color(0xffeaecc6),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
border: AvesBorder.border,
|
border: AvesBorder.border(context),
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
||||||
),
|
),
|
||||||
height: 128,
|
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/color_list_tile.dart';
|
||||||
import 'package:aves/widgets/common/basic/slider_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/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:aves/widgets/settings/video/subtitle_sample.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -30,8 +30,6 @@ class SubtitleThemeTile extends StatelessWidget {
|
||||||
class SubtitleThemePage extends StatelessWidget {
|
class SubtitleThemePage extends StatelessWidget {
|
||||||
static const routeName = '/settings/video/subtitle_theme';
|
static const routeName = '/settings/video/subtitle_theme';
|
||||||
|
|
||||||
static const textAlignOptions = [TextAlign.left, TextAlign.center, TextAlign.right];
|
|
||||||
|
|
||||||
const SubtitleThemePage({Key? key}) : super(key: key);
|
const SubtitleThemePage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -54,18 +52,13 @@ class SubtitleThemePage extends StatelessWidget {
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView(
|
child: ListView(
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
SettingsSelectionListTile<TextAlign>(
|
||||||
title: Text(context.l10n.settingsSubtitleThemeTextAlignmentTile),
|
values: const [TextAlign.left, TextAlign.center, TextAlign.right],
|
||||||
subtitle: Text(_getTextAlignName(context, settings.subtitleTextAlignment)),
|
getName: _getTextAlignName,
|
||||||
onTap: () => showSelectionDialog<TextAlign>(
|
selector: (context, s) => s.subtitleTextAlignment,
|
||||||
context: context,
|
|
||||||
builder: (context) => AvesSelectionDialog<TextAlign>(
|
|
||||||
initialValue: settings.subtitleTextAlignment,
|
|
||||||
options: Map.fromEntries(textAlignOptions.map((v) => MapEntry(v, _getTextAlignName(context, v)))),
|
|
||||||
title: context.l10n.settingsSubtitleThemeTextAlignmentTitle,
|
|
||||||
),
|
|
||||||
onSelection: (v) => settings.subtitleTextAlignment = v,
|
onSelection: (v) => settings.subtitleTextAlignment = v,
|
||||||
),
|
tileTitle: context.l10n.settingsSubtitleThemeTextAlignmentTile,
|
||||||
|
dialogTitle: context.l10n.settingsSubtitleThemeTextAlignmentTitle,
|
||||||
),
|
),
|
||||||
SliderListTile(
|
SliderListTile(
|
||||||
title: context.l10n.settingsSubtitleThemeTextSize,
|
title: context.l10n.settingsSubtitleThemeTextSize,
|
||||||
|
@ -95,10 +88,10 @@ class SubtitleThemePage extends StatelessWidget {
|
||||||
value: settings.subtitleBackgroundColor.opacity,
|
value: settings.subtitleBackgroundColor.opacity,
|
||||||
onChanged: (v) => settings.subtitleBackgroundColor = settings.subtitleBackgroundColor.withOpacity(v),
|
onChanged: (v) => settings.subtitleBackgroundColor = settings.subtitleBackgroundColor.withOpacity(v),
|
||||||
),
|
),
|
||||||
SwitchListTile(
|
SettingsSwitchListTile(
|
||||||
value: settings.subtitleShowOutline,
|
selector: (context, s) => s.subtitleShowOutline,
|
||||||
onChanged: (v) => settings.subtitleShowOutline = v,
|
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/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/identity/aves_expansion_tile.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/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/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/controls.dart';
|
||||||
import 'package:aves/widgets/settings/video/subtitle_theme.dart';
|
import 'package:aves/widgets/settings/video/subtitle_theme.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -28,45 +28,28 @@ class VideoSection extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final children = [
|
final children = [
|
||||||
if (!standalonePage)
|
if (!standalonePage)
|
||||||
Selector<Settings, bool>(
|
SettingsSwitchListTile(
|
||||||
selector: (context, s) => !s.hiddenFilters.contains(MimeFilter.video),
|
selector: (context, s) => !s.hiddenFilters.contains(MimeFilter.video),
|
||||||
builder: (context, current, child) => SwitchListTile(
|
|
||||||
value: current,
|
|
||||||
onChanged: (v) => settings.changeFilterVisibility({MimeFilter.video}, v),
|
onChanged: (v) => settings.changeFilterVisibility({MimeFilter.video}, v),
|
||||||
title: Text(context.l10n.settingsVideoShowVideos),
|
title: context.l10n.settingsVideoShowVideos,
|
||||||
),
|
),
|
||||||
),
|
SettingsSwitchListTile(
|
||||||
Selector<Settings, bool>(
|
|
||||||
selector: (context, s) => s.enableVideoHardwareAcceleration,
|
selector: (context, s) => s.enableVideoHardwareAcceleration,
|
||||||
builder: (context, current, child) => SwitchListTile(
|
|
||||||
value: current,
|
|
||||||
onChanged: (v) => settings.enableVideoHardwareAcceleration = v,
|
onChanged: (v) => settings.enableVideoHardwareAcceleration = v,
|
||||||
title: Text(context.l10n.settingsVideoEnableHardwareAcceleration),
|
title: context.l10n.settingsVideoEnableHardwareAcceleration,
|
||||||
),
|
),
|
||||||
),
|
SettingsSwitchListTile(
|
||||||
Selector<Settings, bool>(
|
|
||||||
selector: (context, s) => s.enableVideoAutoPlay,
|
selector: (context, s) => s.enableVideoAutoPlay,
|
||||||
builder: (context, current, child) => SwitchListTile(
|
|
||||||
value: current,
|
|
||||||
onChanged: (v) => settings.enableVideoAutoPlay = v,
|
onChanged: (v) => settings.enableVideoAutoPlay = v,
|
||||||
title: Text(context.l10n.settingsVideoEnableAutoPlay),
|
title: context.l10n.settingsVideoEnableAutoPlay,
|
||||||
),
|
),
|
||||||
),
|
SettingsSelectionListTile<VideoLoopMode>(
|
||||||
Selector<Settings, VideoLoopMode>(
|
values: VideoLoopMode.values,
|
||||||
|
getName: (context, v) => v.getName(context),
|
||||||
selector: (context, s) => s.videoLoopMode,
|
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,
|
onSelection: (v) => settings.videoLoopMode = v,
|
||||||
),
|
tileTitle: context.l10n.settingsVideoLoopModeTile,
|
||||||
),
|
dialogTitle: context.l10n.settingsVideoLoopModeTitle,
|
||||||
),
|
),
|
||||||
const VideoControlsTile(),
|
const VideoControlsTile(),
|
||||||
const SubtitleThemeTile(),
|
const SubtitleThemeTile(),
|
||||||
|
@ -79,7 +62,7 @@ class VideoSection extends StatelessWidget {
|
||||||
: AvesExpansionTile(
|
: AvesExpansionTile(
|
||||||
leading: SettingsTileLeading(
|
leading: SettingsTileLeading(
|
||||||
icon: AIcons.video,
|
icon: AIcons.video,
|
||||||
color: AColors.video,
|
color: context.select<AvesColorsData, Color>((v) => v.video),
|
||||||
),
|
),
|
||||||
title: context.l10n.settingsSectionVideo,
|
title: context.l10n.settingsSectionVideo,
|
||||||
expandedNotifier: expandedNotifier,
|
expandedNotifier: expandedNotifier,
|
||||||
|
|
|
@ -49,7 +49,7 @@ class _EntryBackgroundSelectorState extends State<EntryBackgroundSelector> {
|
||||||
width: radius * 2,
|
width: radius * 2,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: selected.isColor ? selected.color : null,
|
color: selected.isColor ? selected.color : null,
|
||||||
border: AvesBorder.border,
|
border: AvesBorder.border(context),
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
child: selected == EntryBackground.checkered
|
child: selected == EntryBackground.checkered
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.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:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
@ -38,22 +39,16 @@ class ViewerOverlayPage extends StatelessWidget {
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: ListView(
|
child: ListView(
|
||||||
children: [
|
children: [
|
||||||
Selector<Settings, bool>(
|
SettingsSwitchListTile(
|
||||||
selector: (context, s) => s.showOverlayOnOpening,
|
selector: (context, s) => s.showOverlayOnOpening,
|
||||||
builder: (context, current, child) => SwitchListTile(
|
|
||||||
value: current,
|
|
||||||
onChanged: (v) => settings.showOverlayOnOpening = v,
|
onChanged: (v) => settings.showOverlayOnOpening = v,
|
||||||
title: Text(context.l10n.settingsViewerShowOverlayOnOpening),
|
title: context.l10n.settingsViewerShowOverlayOnOpening,
|
||||||
),
|
),
|
||||||
),
|
SettingsSwitchListTile(
|
||||||
Selector<Settings, bool>(
|
|
||||||
selector: (context, s) => s.showOverlayInfo,
|
selector: (context, s) => s.showOverlayInfo,
|
||||||
builder: (context, current, child) => SwitchListTile(
|
|
||||||
value: current,
|
|
||||||
onChanged: (v) => settings.showOverlayInfo = v,
|
onChanged: (v) => settings.showOverlayInfo = v,
|
||||||
title: Text(context.l10n.settingsViewerShowInformation),
|
title: context.l10n.settingsViewerShowInformation,
|
||||||
subtitle: Text(context.l10n.settingsViewerShowInformationSubtitle),
|
subtitle: context.l10n.settingsViewerShowInformationSubtitle,
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Selector<Settings, Tuple2<bool, bool>>(
|
Selector<Settings, Tuple2<bool, bool>>(
|
||||||
selector: (context, s) => Tuple2(s.showOverlayInfo, s.showOverlayShootingDetails),
|
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,
|
selector: (context, s) => s.showOverlayMinimap,
|
||||||
builder: (context, current, child) => SwitchListTile(
|
|
||||||
value: current,
|
|
||||||
onChanged: (v) => settings.showOverlayMinimap = v,
|
onChanged: (v) => settings.showOverlayMinimap = v,
|
||||||
title: Text(context.l10n.settingsViewerShowMinimap),
|
title: context.l10n.settingsViewerShowMinimap,
|
||||||
),
|
),
|
||||||
),
|
SettingsSwitchListTile(
|
||||||
Selector<Settings, bool>(
|
|
||||||
selector: (context, s) => s.showOverlayThumbnailPreview,
|
selector: (context, s) => s.showOverlayThumbnailPreview,
|
||||||
builder: (context, current, child) => SwitchListTile(
|
|
||||||
value: current,
|
|
||||||
onChanged: (v) => settings.showOverlayThumbnailPreview = v,
|
onChanged: (v) => settings.showOverlayThumbnailPreview = v,
|
||||||
title: Text(context.l10n.settingsViewerShowOverlayThumbnails),
|
title: context.l10n.settingsViewerShowOverlayThumbnails,
|
||||||
),
|
),
|
||||||
),
|
SettingsSwitchListTile(
|
||||||
Selector<Settings, bool>(
|
|
||||||
selector: (context, s) => s.enableOverlayBlurEffect,
|
selector: (context, s) => s.enableOverlayBlurEffect,
|
||||||
builder: (context, current, child) => SwitchListTile(
|
|
||||||
value: current,
|
|
||||||
onChanged: (v) => settings.enableOverlayBlurEffect = v,
|
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/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
|
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
|
||||||
import 'package:aves/widgets/settings/common/tile_leading.dart';
|
import 'package:aves/widgets/settings/common/tile_leading.dart';
|
||||||
|
import 'package:aves/widgets/settings/common/tiles.dart';
|
||||||
import 'package:aves/widgets/settings/viewer/entry_background.dart';
|
import 'package:aves/widgets/settings/viewer/entry_background.dart';
|
||||||
import 'package:aves/widgets/settings/viewer/overlay.dart';
|
import 'package:aves/widgets/settings/viewer/overlay.dart';
|
||||||
import 'package:aves/widgets/settings/viewer/viewer_actions_editor.dart';
|
import 'package:aves/widgets/settings/viewer/viewer_actions_editor.dart';
|
||||||
|
@ -25,7 +26,7 @@ class ViewerSection extends StatelessWidget {
|
||||||
return AvesExpansionTile(
|
return AvesExpansionTile(
|
||||||
leading: SettingsTileLeading(
|
leading: SettingsTileLeading(
|
||||||
icon: AIcons.image,
|
icon: AIcons.image,
|
||||||
color: AColors.image,
|
color: context.select<AvesColorsData, Color>((v) => v.image),
|
||||||
),
|
),
|
||||||
title: context.l10n.settingsSectionViewer,
|
title: context.l10n.settingsSectionViewer,
|
||||||
expandedNotifier: expandedNotifier,
|
expandedNotifier: expandedNotifier,
|
||||||
|
@ -34,21 +35,15 @@ class ViewerSection extends StatelessWidget {
|
||||||
const ViewerActionsTile(),
|
const ViewerActionsTile(),
|
||||||
const ViewerOverlayTile(),
|
const ViewerOverlayTile(),
|
||||||
const _CutoutModeSwitch(),
|
const _CutoutModeSwitch(),
|
||||||
Selector<Settings, bool>(
|
SettingsSwitchListTile(
|
||||||
selector: (context, s) => s.viewerMaxBrightness,
|
selector: (context, s) => s.viewerMaxBrightness,
|
||||||
builder: (context, current, child) => SwitchListTile(
|
|
||||||
value: current,
|
|
||||||
onChanged: (v) => settings.viewerMaxBrightness = v,
|
onChanged: (v) => settings.viewerMaxBrightness = v,
|
||||||
title: Text(context.l10n.settingsViewerMaximumBrightness),
|
title: context.l10n.settingsViewerMaximumBrightness,
|
||||||
),
|
),
|
||||||
),
|
SettingsSwitchListTile(
|
||||||
Selector<Settings, bool>(
|
|
||||||
selector: (context, s) => s.enableMotionPhotoAutoPlay,
|
selector: (context, s) => s.enableMotionPhotoAutoPlay,
|
||||||
builder: (context, current, child) => SwitchListTile(
|
|
||||||
value: current,
|
|
||||||
onChanged: (v) => settings.enableMotionPhotoAutoPlay = v,
|
onChanged: (v) => settings.enableMotionPhotoAutoPlay = v,
|
||||||
title: Text(context.l10n.settingsMotionPhotoAutoPlay),
|
title: context.l10n.settingsMotionPhotoAutoPlay,
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Selector<Settings, EntryBackground>(
|
Selector<Settings, EntryBackground>(
|
||||||
selector: (context, s) => s.imageBackground,
|
selector: (context, s) => s.imageBackground,
|
||||||
|
@ -87,13 +82,10 @@ class _CutoutModeSwitchState extends State<_CutoutModeSwitch> {
|
||||||
future: _canSet,
|
future: _canSet,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.hasData && snapshot.data!) {
|
if (snapshot.hasData && snapshot.data!) {
|
||||||
return Selector<Settings, bool>(
|
return SettingsSwitchListTile(
|
||||||
selector: (context, s) => s.viewerUseCutout,
|
selector: (context, s) => s.viewerUseCutout,
|
||||||
builder: (context, current, child) => SwitchListTile(
|
|
||||||
value: current,
|
|
||||||
onChanged: (v) => settings.viewerUseCutout = v,
|
onChanged: (v) => settings.viewerUseCutout = v,
|
||||||
title: Text(context.l10n.settingsViewerUseCutout),
|
title: context.l10n.settingsViewerUseCutout,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import 'package:aves/model/filters/filters.dart';
|
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/utils/constants.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/identity/aves_filter_chip.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) {
|
builder: (context, constraints) {
|
||||||
final showPercentIndicator = constraints.maxWidth - (chipWidth + countWidth) > percentIndicatorMinWidth;
|
final showPercentIndicator = constraints.maxWidth - (chipWidth + countWidth) > percentIndicatorMinWidth;
|
||||||
final displayedEntries = maxRowCount != null ? sortedEntries.take(maxRowCount!) : sortedEntries;
|
final displayedEntries = maxRowCount != null ? sortedEntries.take(maxRowCount!) : sortedEntries;
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final isMonochrome = settings.themeColorMode == AvesThemeColorMode.monochrome;
|
||||||
return Table(
|
return Table(
|
||||||
children: displayedEntries.map((kv) {
|
children: displayedEntries.map((kv) {
|
||||||
final filter = filterBuilder(kv.key);
|
final filter = filterBuilder(kv.key);
|
||||||
|
@ -81,14 +85,16 @@ class FilterTable<T extends Comparable> extends StatelessWidget {
|
||||||
return LinearPercentIndicator(
|
return LinearPercentIndicator(
|
||||||
percent: percent,
|
percent: percent,
|
||||||
lineHeight: lineHeight,
|
lineHeight: lineHeight,
|
||||||
backgroundColor: Colors.white24,
|
backgroundColor: theme.colorScheme.onPrimary.withOpacity(.1),
|
||||||
progressColor: color,
|
progressColor: isMonochrome ? theme.colorScheme.secondary : color,
|
||||||
animation: true,
|
animation: true,
|
||||||
isRTL: isRtl,
|
isRTL: isRtl,
|
||||||
barRadius: barRadius,
|
barRadius: barRadius,
|
||||||
center: Text(
|
center: Text(
|
||||||
intl.NumberFormat.percentPattern().format(percent),
|
intl.NumberFormat.percentPattern().format(percent),
|
||||||
style: const TextStyle(shadows: Constants.embossShadows),
|
style: TextStyle(
|
||||||
|
shadows: theme.brightness == Brightness.dark ? Constants.embossShadows : null,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
);
|
);
|
||||||
|
@ -98,7 +104,9 @@ class FilterTable<T extends Comparable> extends StatelessWidget {
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'$count',
|
'$count',
|
||||||
style: const TextStyle(color: Colors.white70),
|
style: TextStyle(
|
||||||
|
color: theme.textTheme.caption!.color,
|
||||||
|
),
|
||||||
textAlign: TextAlign.end,
|
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/settings/settings.dart';
|
||||||
import 'package:aves/model/source/collection_lens.dart';
|
import 'package:aves/model/source/collection_lens.dart';
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
|
import 'package:aves/theme/colors.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/utils/color_utils.dart';
|
|
||||||
import 'package:aves/utils/constants.dart';
|
import 'package:aves/utils/constants.dart';
|
||||||
import 'package:aves/utils/mime_utils.dart';
|
import 'package:aves/utils/mime_utils.dart';
|
||||||
import 'package:aves/widgets/collection/collection_page.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:aves/widgets/stats/filter_table.dart';
|
||||||
import 'package:charts_flutter/flutter.dart' as charts;
|
import 'package:charts_flutter/flutter.dart' as charts;
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart' as intl;
|
import 'package:intl/intl.dart' as intl;
|
||||||
import 'package:percent_indicator/linear_percent_indicator.dart';
|
import 'package:percent_indicator/linear_percent_indicator.dart';
|
||||||
|
@ -77,11 +77,17 @@ class StatsPage extends StatelessWidget {
|
||||||
text: context.l10n.collectionEmptyImages,
|
text: context.l10n.collectionEmptyImages,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final isDark = theme.brightness == Brightness.dark;
|
||||||
final animate = context.select<Settings, bool>((v) => v.accessibilityAnimations.animate);
|
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 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 imagesByMimeTypes = Map.fromEntries(byMimeTypes.entries.where((kv) => kv.key.startsWith('image')));
|
||||||
final videoByMimeTypes = Map.fromEntries(byMimeTypes.entries.where((kv) => kv.key.startsWith('video')));
|
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,
|
alignment: WrapAlignment.center,
|
||||||
crossAxisAlignment: WrapCrossAlignment.center,
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
|
@ -89,6 +95,9 @@ class StatsPage extends StatelessWidget {
|
||||||
_buildMimeDonut(context, AIcons.video, videoByMimeTypes, animate),
|
_buildMimeDonut(context, AIcons.video, videoByMimeTypes, animate),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
final catalogued = entries.where((entry) => entry.isCatalogued);
|
final catalogued = entries.where((entry) => entry.isCatalogued);
|
||||||
final withGps = catalogued.where((entry) => entry.hasGps);
|
final withGps = catalogued.where((entry) => entry.hasGps);
|
||||||
|
@ -114,14 +123,16 @@ class StatsPage extends StatelessWidget {
|
||||||
child: LinearPercentIndicator(
|
child: LinearPercentIndicator(
|
||||||
percent: withGpsPercent,
|
percent: withGpsPercent,
|
||||||
lineHeight: lineHeight,
|
lineHeight: lineHeight,
|
||||||
backgroundColor: Colors.white24,
|
backgroundColor: theme.colorScheme.onPrimary.withOpacity(.1),
|
||||||
progressColor: Theme.of(context).colorScheme.secondary,
|
progressColor: theme.colorScheme.secondary,
|
||||||
animation: animate,
|
animation: animate,
|
||||||
isRTL: context.isRtl,
|
isRTL: context.isRtl,
|
||||||
barRadius: barRadius,
|
barRadius: barRadius,
|
||||||
center: Text(
|
center: Text(
|
||||||
intl.NumberFormat.percentPattern().format(withGpsPercent),
|
intl.NumberFormat.percentPattern().format(withGpsPercent),
|
||||||
style: const TextStyle(shadows: Constants.embossShadows),
|
style: TextStyle(
|
||||||
|
shadows: isDark ? Constants.embossShadows : null,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
),
|
),
|
||||||
|
@ -176,7 +187,17 @@ class StatsPage extends StatelessWidget {
|
||||||
|
|
||||||
final sum = byMimeTypes.values.fold<int>(0, (prev, v) => prev + v);
|
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) {
|
seriesData.sort((d1, d2) {
|
||||||
final c = d2.entryCount.compareTo(d1.entryCount);
|
final c = d2.entryCount.compareTo(d1.entryCount);
|
||||||
return c != 0 ? c : compareAsciiUpperCase(d1.displayText, d2.displayText);
|
return c != 0 ? c : compareAsciiUpperCase(d1.displayText, d2.displayText);
|
||||||
|
@ -250,7 +271,9 @@ class StatsPage extends StatelessWidget {
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
'${d.entryCount}',
|
'${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(
|
MaterialPageRoute(
|
||||||
settings: const RouteSettings(name: CollectionPage.routeName),
|
settings: const RouteSettings(name: CollectionPage.routeName),
|
||||||
builder: (context) => CollectionPage(
|
builder: (context) => CollectionPage(
|
||||||
collection: CollectionLens(
|
|
||||||
source: source,
|
source: source,
|
||||||
filters: {filter},
|
filters: {filter},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
(route) => false,
|
(route) => false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class EntryByMimeDatum {
|
@immutable
|
||||||
final String mimeType;
|
class EntryByMimeDatum extends Equatable {
|
||||||
final String displayText;
|
final String mimeType, displayText;
|
||||||
|
final Color color;
|
||||||
final int entryCount;
|
final int entryCount;
|
||||||
|
|
||||||
EntryByMimeDatum({
|
|
||||||
required this.mimeType,
|
|
||||||
required this.entryCount,
|
|
||||||
}) : displayText = MimeUtils.displayType(mimeType);
|
|
||||||
|
|
||||||
Color get color => stringToColor(displayText);
|
|
||||||
|
|
||||||
@override
|
@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.dart';
|
||||||
import 'package:aves/model/entry_metadata_edition.dart';
|
import 'package:aves/model/entry_metadata_edition.dart';
|
||||||
import 'package:aves/model/filters/album.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/enums/enums.dart';
|
||||||
import 'package:aves/model/settings/settings.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/collection_source.dart';
|
||||||
import 'package:aves/services/common/image_op_events.dart';
|
import 'package:aves/services/common/image_op_events.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/services/media/enums.dart';
|
import 'package:aves/services/media/enums.dart';
|
||||||
import 'package:aves/services/media/media_file_service.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/collection/collection_page.dart';
|
||||||
import 'package:aves/widgets/common/action_mixins/entry_storage.dart';
|
import 'package:aves/widgets/common/action_mixins/entry_storage.dart';
|
||||||
import 'package:aves/widgets/common/action_mixins/feedback.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
|
final showAction = isMainMode && newUris.isNotEmpty
|
||||||
? SnackBarAction(
|
? SnackBarAction(
|
||||||
label: context.l10n.showButtonLabel,
|
label: context.l10n.showButtonLabel,
|
||||||
onPressed: () async {
|
onPressed: () {
|
||||||
final highlightInfo = context.read<HighlightInfo>();
|
Navigator.pushAndRemoveUntil(
|
||||||
final targetCollection = CollectionLens(
|
|
||||||
source: source,
|
|
||||||
filters: {AlbumFilter(destinationAlbum, source.getAlbumDisplayName(context, destinationAlbum))},
|
|
||||||
);
|
|
||||||
unawaited(Navigator.pushAndRemoveUntil(
|
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
settings: const RouteSettings(name: CollectionPage.routeName),
|
settings: const RouteSettings(name: CollectionPage.routeName),
|
||||||
builder: (context) => CollectionPage(
|
builder: (context) => CollectionPage(
|
||||||
collection: targetCollection,
|
source: source,
|
||||||
|
filters: {AlbumFilter(destinationAlbum, source.getAlbumDisplayName(context, destinationAlbum))},
|
||||||
|
highlightTest: (entry) => newUris.contains(entry.uri),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(route) => false,
|
(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;
|
: null;
|
||||||
|
|
|
@ -39,7 +39,7 @@ class ViewerVerticalPageView extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> {
|
class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> {
|
||||||
final ValueNotifier<Color> _backgroundColorNotifier = ValueNotifier(Colors.black);
|
final ValueNotifier<double> _backgroundOpacityNotifier = ValueNotifier(1);
|
||||||
final ValueNotifier<bool> _isVerticallyScrollingNotifier = ValueNotifier(false);
|
final ValueNotifier<bool> _isVerticallyScrollingNotifier = ValueNotifier(false);
|
||||||
Timer? _verticalScrollMonitoringTimer;
|
Timer? _verticalScrollMonitoringTimer;
|
||||||
AvesEntry? _oldEntry;
|
AvesEntry? _oldEntry;
|
||||||
|
@ -122,12 +122,15 @@ class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> {
|
||||||
imagePage,
|
imagePage,
|
||||||
infoPage,
|
infoPage,
|
||||||
];
|
];
|
||||||
return ValueListenableBuilder<Color>(
|
return ValueListenableBuilder<double>(
|
||||||
valueListenable: _backgroundColorNotifier,
|
valueListenable: _backgroundOpacityNotifier,
|
||||||
builder: (context, backgroundColor, child) => Container(
|
builder: (context, backgroundOpacity, child) {
|
||||||
color: backgroundColor,
|
final background = Theme.of(context).brightness == Brightness.dark ? Colors.black : Colors.white;
|
||||||
|
return Container(
|
||||||
|
color: background.withOpacity(backgroundOpacity),
|
||||||
child: child,
|
child: child,
|
||||||
),
|
);
|
||||||
|
},
|
||||||
child: PageView(
|
child: PageView(
|
||||||
// key is expected by test driver
|
// key is expected by test driver
|
||||||
key: const Key('vertical-pageview'),
|
key: const Key('vertical-pageview'),
|
||||||
|
@ -196,7 +199,7 @@ class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> {
|
||||||
final page = widget.verticalPager.page!;
|
final page = widget.verticalPager.page!;
|
||||||
|
|
||||||
final opacity = min(1.0, page);
|
final opacity = min(1.0, page);
|
||||||
_backgroundColorNotifier.value = _backgroundColorNotifier.value.withOpacity(opacity * opacity);
|
_backgroundOpacityNotifier.value = opacity * opacity;
|
||||||
|
|
||||||
if (page <= 1 && settings.viewerMaxBrightness) {
|
if (page <= 1 && settings.viewerMaxBrightness) {
|
||||||
_systemBrightness?.then((system) {
|
_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,
|
resizeToAvoidBottomInset: false,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -414,10 +414,8 @@ class _EntryViewerStackState extends State<EntryViewerStack> with FeedbackMixin,
|
||||||
settings: const RouteSettings(name: CollectionPage.routeName),
|
settings: const RouteSettings(name: CollectionPage.routeName),
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return CollectionPage(
|
return CollectionPage(
|
||||||
collection: CollectionLens(
|
|
||||||
source: baseCollection.source,
|
source: baseCollection.source,
|
||||||
filters: baseCollection.filters,
|
filters: {...baseCollection.filters, filter},
|
||||||
)..addFilter(filter),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -8,6 +8,7 @@ import 'package:aves/model/filters/rating.dart';
|
||||||
import 'package:aves/model/filters/tag.dart';
|
import 'package:aves/model/filters/tag.dart';
|
||||||
import 'package:aves/model/filters/type.dart';
|
import 'package:aves/model/filters/type.dart';
|
||||||
import 'package:aves/model/source/collection_lens.dart';
|
import 'package:aves/model/source/collection_lens.dart';
|
||||||
|
import 'package:aves/theme/colors.dart';
|
||||||
import 'package:aves/theme/format.dart';
|
import 'package:aves/theme/format.dart';
|
||||||
import 'package:aves/utils/file_utils.dart';
|
import 'package:aves/utils/file_utils.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
|
@ -156,7 +157,7 @@ class BasicSection extends StatelessWidget {
|
||||||
DecoratedBox(
|
DecoratedBox(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border.fromBorderSide(BorderSide(
|
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,
|
width: AvesFilterChip.outlineWidth,
|
||||||
)),
|
)),
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(AvesFilterChip.defaultRadius)),
|
borderRadius: const BorderRadius.all(Radius.circular(AvesFilterChip.defaultRadius)),
|
||||||
|
|
|
@ -21,7 +21,6 @@ class SectionRow extends StatelessWidget {
|
||||||
width: dim,
|
width: dim,
|
||||||
child: Divider(
|
child: Divider(
|
||||||
thickness: AvesFilterChip.outlineWidth,
|
thickness: AvesFilterChip.outlineWidth,
|
||||||
color: Colors.white70,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return Row(
|
return Row(
|
||||||
|
@ -47,11 +46,11 @@ class InfoRowGroup extends StatefulWidget {
|
||||||
final Map<String, InfoLinkHandler>? linkHandlers;
|
final Map<String, InfoLinkHandler>? linkHandlers;
|
||||||
|
|
||||||
static const keyValuePadding = 16;
|
static const keyValuePadding = 16;
|
||||||
static const linkColor = Colors.blue;
|
|
||||||
static const fontSize = 13.0;
|
static const fontSize = 13.0;
|
||||||
static const baseStyle = TextStyle(fontSize: fontSize);
|
static const valueStyle = TextStyle(fontSize: fontSize);
|
||||||
static final keyStyle = baseStyle.copyWith(color: Colors.white70, height: 2.0);
|
static final _keyStyle = valueStyle.copyWith(height: 2.0);
|
||||||
static final linkStyle = baseStyle.copyWith(color: linkColor, decoration: TextDecoration.underline);
|
|
||||||
|
static TextStyle keyStyle(BuildContext context) => Theme.of(context).textTheme.caption!.merge(_keyStyle);
|
||||||
|
|
||||||
const InfoRowGroup({
|
const InfoRowGroup({
|
||||||
Key? key,
|
Key? key,
|
||||||
|
@ -77,9 +76,11 @@ class _InfoRowGroupState extends State<InfoRowGroup> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (keyValues.isEmpty) return const SizedBox.shrink();
|
if (keyValues.isEmpty) return const SizedBox.shrink();
|
||||||
|
|
||||||
|
final _keyStyle = InfoRowGroup.keyStyle(context);
|
||||||
|
|
||||||
// compute the size of keys and space in order to align values
|
// compute the size of keys and space in order to align values
|
||||||
final textScaleFactor = MediaQuery.textScaleFactorOf(context);
|
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;
|
final lastKey = keyValues.keys.last;
|
||||||
return LayoutBuilder(
|
return LayoutBuilder(
|
||||||
|
@ -102,7 +103,10 @@ class _InfoRowGroupState extends State<InfoRowGroup> {
|
||||||
value = handler.linkText(context);
|
value = handler.linkText(context);
|
||||||
// open link on tap
|
// open link on tap
|
||||||
recognizer = TapGestureRecognizer()..onTap = () => handler.onTap(context);
|
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 {
|
} else {
|
||||||
value = kv.value;
|
value = kv.value;
|
||||||
// long values are clipped, and made expandable by tapping them
|
// 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)
|
// (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
|
// and each span respects the directionality of its inner text only
|
||||||
return [
|
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)),
|
WidgetSpan(child: SizedBox(width: thisSpaceSize)),
|
||||||
TextSpan(text: '${Constants.fsi}$value${Constants.pdi}', style: style, recognizer: recognizer),
|
TextSpan(text: '${Constants.fsi}$value${Constants.pdi}', style: style, recognizer: recognizer),
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
).toList(),
|
).toList(),
|
||||||
),
|
),
|
||||||
style: InfoRowGroup.baseStyle,
|
style: InfoRowGroup.valueStyle,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,7 +3,7 @@ import 'dart:collection';
|
||||||
import 'package:aves/model/entry.dart';
|
import 'package:aves/model/entry.dart';
|
||||||
import 'package:aves/ref/brand_colors.dart';
|
import 'package:aves/ref/brand_colors.dart';
|
||||||
import 'package:aves/services/metadata/svg_metadata_service.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/utils/constants.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
|
import 'package:aves/widgets/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:aves/widgets/viewer/source_viewer_page.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class MetadataDirTile extends StatelessWidget {
|
class MetadataDirTile extends StatelessWidget {
|
||||||
final AvesEntry entry;
|
final AvesEntry entry;
|
||||||
|
@ -58,9 +59,10 @@ class MetadataDirTile extends StatelessWidget {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final colors = context.watch<AvesColorsData>();
|
||||||
return AvesExpansionTile(
|
return AvesExpansionTile(
|
||||||
title: title,
|
title: title,
|
||||||
color: dir.color ?? BrandColors.get(dirName) ?? stringToColor(dirName),
|
highlightColor: dir.color ?? colors.fromBrandColor(BrandColors.get(dirName)) ?? colors.fromString(dirName),
|
||||||
expandedNotifier: expandedDirectoryNotifier,
|
expandedNotifier: expandedDirectoryNotifier,
|
||||||
initiallyExpanded: initiallyExpanded,
|
initiallyExpanded: initiallyExpanded,
|
||||||
children: [
|
children: [
|
||||||
|
|
|
@ -7,9 +7,9 @@ import 'package:aves/model/video/metadata.dart';
|
||||||
import 'package:aves/ref/mime_types.dart';
|
import 'package:aves/ref/mime_types.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/services/metadata/svg_metadata_service.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/durations.dart';
|
||||||
import 'package:aves/theme/icons.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/common.dart';
|
||||||
import 'package:aves/widgets/viewer/info/metadata/metadata_dir_tile.dart';
|
import 'package:aves/widgets/viewer/info/metadata/metadata_dir_tile.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
@ -218,13 +218,14 @@ class _MetadataSectionSliverState extends State<MetadataSectionSliver> {
|
||||||
if (knownStreams.isNotEmpty) {
|
if (knownStreams.isNotEmpty) {
|
||||||
final indexDigits = knownStreams.length.toString().length;
|
final indexDigits = knownStreams.length.toString().length;
|
||||||
|
|
||||||
|
final colors = context.read<AvesColorsData>();
|
||||||
for (final stream in knownStreams) {
|
for (final stream in knownStreams) {
|
||||||
final index = (stream[Keys.index] ?? 0) + 1;
|
final index = (stream[Keys.index] ?? 0) + 1;
|
||||||
final typeText = getTypeText(stream);
|
final typeText = getTypeText(stream);
|
||||||
final dirName = 'Stream ${index.toString().padLeft(indexDigits, '0')} • $typeText';
|
final dirName = 'Stream ${index.toString().padLeft(indexDigits, '0')} • $typeText';
|
||||||
final formattedStreamTags = VideoMetadataFormatter.formatInfo(stream);
|
final formattedStreamTags = VideoMetadataFormatter.formatInfo(stream);
|
||||||
if (formattedStreamTags.isNotEmpty) {
|
if (formattedStreamTags.isNotEmpty) {
|
||||||
final color = stringToColor(typeText);
|
final color = colors.fromString(typeText);
|
||||||
directories.add(MetadataDirectory(dirName, _toSortedTags(formattedStreamTags), color: color));
|
directories.add(MetadataDirectory(dirName, _toSortedTags(formattedStreamTags), color: color));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:aves/ref/brand_colors.dart';
|
import 'package:aves/ref/brand_colors.dart';
|
||||||
|
import 'package:aves/theme/colors.dart';
|
||||||
import 'package:aves/utils/constants.dart';
|
import 'package:aves/utils/constants.dart';
|
||||||
import 'package:aves/utils/string_utils.dart';
|
import 'package:aves/utils/string_utils.dart';
|
||||||
import 'package:aves/utils/xmp_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:equatable/equatable.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
@immutable
|
@immutable
|
||||||
class XmpNamespace extends Equatable {
|
class XmpNamespace extends Equatable {
|
||||||
|
@ -98,7 +100,10 @@ class XmpNamespace extends Equatable {
|
||||||
'Iptc4xmpCore': 'IPTC Core',
|
'Iptc4xmpCore': 'IPTC Core',
|
||||||
'Iptc4xmpExt': 'IPTC Extension',
|
'Iptc4xmpExt': 'IPTC Extension',
|
||||||
'lr': 'Lightroom',
|
'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',
|
'mwg-rs': 'Regions',
|
||||||
'nga': 'National Gallery of Art',
|
'nga': 'National Gallery of Art',
|
||||||
'panorama': 'Panorama',
|
'panorama': 'Panorama',
|
||||||
|
@ -122,7 +127,7 @@ class XmpNamespace extends Equatable {
|
||||||
|
|
||||||
Map<String, String> get buildProps => rawProps;
|
Map<String, String> get buildProps => rawProps;
|
||||||
|
|
||||||
List<Widget> buildNamespaceSection() {
|
List<Widget> buildNamespaceSection(BuildContext context) {
|
||||||
final props = buildProps.entries
|
final props = buildProps.entries
|
||||||
.map((kv) {
|
.map((kv) {
|
||||||
final prop = XmpProp(kv.key, kv.value);
|
final prop = XmpProp(kv.key, kv.value);
|
||||||
|
@ -149,7 +154,7 @@ class XmpNamespace extends Equatable {
|
||||||
padding: const EdgeInsets.only(top: 8),
|
padding: const EdgeInsets.only(top: 8),
|
||||||
child: HighlightTitle(
|
child: HighlightTitle(
|
||||||
title: displayTitle,
|
title: displayTitle,
|
||||||
color: BrandColors.get(displayTitle),
|
color: context.select<AvesColorsData, Color?>((v) => v.fromBrandColor(BrandColors.get(displayTitle))),
|
||||||
selectable: true,
|
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:aves/widgets/viewer/info/metadata/xmp_namespaces.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class XmpDirTile extends StatefulWidget {
|
class XmpDirTile extends StatefulWidget {
|
||||||
final AvesEntry entry;
|
final AvesEntry entry;
|
||||||
|
@ -43,7 +44,7 @@ class _XmpDirTileState extends State<XmpDirTile> {
|
||||||
return AvesExpansionTile(
|
return AvesExpansionTile(
|
||||||
// title may contain parent to distinguish multiple XMP directories
|
// title may contain parent to distinguish multiple XMP directories
|
||||||
title: widget.title,
|
title: widget.title,
|
||||||
color: AColors.xmp,
|
highlightColor: context.select<AvesColorsData, Color>((v) => v.xmp),
|
||||||
expandedNotifier: widget.expandedNotifier,
|
expandedNotifier: widget.expandedNotifier,
|
||||||
initiallyExpanded: widget.initiallyExpanded,
|
initiallyExpanded: widget.initiallyExpanded,
|
||||||
children: [
|
children: [
|
||||||
|
@ -51,7 +52,7 @@ class _XmpDirTileState extends State<XmpDirTile> {
|
||||||
padding: const EdgeInsets.only(left: 8, right: 8, bottom: 8),
|
padding: const EdgeInsets.only(left: 8, right: 8, bottom: 8),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
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: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: context.l10n.viewerInfoLabelOwner,
|
text: context.l10n.viewerInfoLabelOwner,
|
||||||
style: InfoRowGroup.keyStyle,
|
style: InfoRowGroup.keyStyle(context),
|
||||||
),
|
),
|
||||||
WidgetSpan(
|
WidgetSpan(
|
||||||
alignment: PlaceholderAlignment.middle,
|
alignment: PlaceholderAlignment.middle,
|
||||||
|
@ -81,7 +81,7 @@ class _OwnerPropState extends State<OwnerProp> {
|
||||||
),
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: appName,
|
text: appName,
|
||||||
style: InfoRowGroup.baseStyle,
|
style: InfoRowGroup.valueStyle,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import 'package:aves/model/settings/settings.dart';
|
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/blurred.dart';
|
||||||
import 'package:aves/widgets/common/fx/borders.dart';
|
import 'package:aves/widgets/common/fx/borders.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
Color overlayBackgroundColor({required bool blurred}) => blurred ? Colors.black26 : Colors.black38;
|
|
||||||
|
|
||||||
class OverlayButton extends StatelessWidget {
|
class OverlayButton extends StatelessWidget {
|
||||||
final Animation<double> scale;
|
final Animation<double> scale;
|
||||||
final BorderRadius? borderRadius;
|
final BorderRadius? borderRadius;
|
||||||
|
@ -19,6 +18,7 @@ class OverlayButton extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final brightness = Theme.of(context).brightness;
|
||||||
final blurred = settings.enableOverlayBlurEffect;
|
final blurred = settings.enableOverlayBlurEffect;
|
||||||
return ScaleTransition(
|
return ScaleTransition(
|
||||||
scale: scale,
|
scale: scale,
|
||||||
|
@ -29,10 +29,10 @@ class OverlayButton extends StatelessWidget {
|
||||||
child: Material(
|
child: Material(
|
||||||
type: MaterialType.button,
|
type: MaterialType.button,
|
||||||
borderRadius: borderRadius,
|
borderRadius: borderRadius,
|
||||||
color: overlayBackgroundColor(blurred: blurred),
|
color: Themes.overlayBackgroundColor(brightness: brightness, blurred: blurred),
|
||||||
child: Ink(
|
child: Ink(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: AvesBorder.border,
|
border: AvesBorder.border(context),
|
||||||
borderRadius: borderRadius,
|
borderRadius: borderRadius,
|
||||||
),
|
),
|
||||||
child: child,
|
child: child,
|
||||||
|
@ -43,10 +43,10 @@ class OverlayButton extends StatelessWidget {
|
||||||
enabled: blurred,
|
enabled: blurred,
|
||||||
child: Material(
|
child: Material(
|
||||||
type: MaterialType.circle,
|
type: MaterialType.circle,
|
||||||
color: overlayBackgroundColor(blurred: blurred),
|
color: Themes.overlayBackgroundColor(brightness: brightness, blurred: blurred),
|
||||||
child: Ink(
|
child: Ink(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: AvesBorder.border,
|
border: AvesBorder.border(context),
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
child: child,
|
child: child,
|
||||||
|
@ -78,6 +78,7 @@ class OverlayTextButton extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final blurred = settings.enableOverlayBlurEffect;
|
final blurred = settings.enableOverlayBlurEffect;
|
||||||
|
final theme = Theme.of(context);
|
||||||
return SizeTransition(
|
return SizeTransition(
|
||||||
sizeFactor: scale,
|
sizeFactor: scale,
|
||||||
child: BlurredRRect.all(
|
child: BlurredRRect.all(
|
||||||
|
@ -86,11 +87,11 @@ class OverlayTextButton extends StatelessWidget {
|
||||||
child: OutlinedButton(
|
child: OutlinedButton(
|
||||||
onPressed: onPressed,
|
onPressed: onPressed,
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
backgroundColor: MaterialStateProperty.all<Color>(overlayBackgroundColor(blurred: blurred)),
|
backgroundColor: MaterialStateProperty.all<Color>(Themes.overlayBackgroundColor(brightness: theme.brightness, blurred: blurred)),
|
||||||
foregroundColor: MaterialStateProperty.all<Color>(Colors.white),
|
foregroundColor: MaterialStateProperty.all<Color>(theme.colorScheme.onSurface),
|
||||||
overlayColor: MaterialStateProperty.all<Color>(Colors.white.withOpacity(0.12)),
|
overlayColor: theme.brightness == Brightness.dark ? MaterialStateProperty.all<Color>(Colors.white.withOpacity(0.12)) : null,
|
||||||
minimumSize: _minSize,
|
minimumSize: _minSize,
|
||||||
side: MaterialStateProperty.all<BorderSide>(AvesBorder.curvedSide),
|
side: MaterialStateProperty.all<BorderSide>(AvesBorder.curvedSide(context)),
|
||||||
shape: MaterialStateProperty.all<OutlinedBorder>(const RoundedRectangleBorder(
|
shape: MaterialStateProperty.all<OutlinedBorder>(const RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(_borderRadius)),
|
borderRadius: BorderRadius.all(Radius.circular(_borderRadius)),
|
||||||
)),
|
)),
|
||||||
|
|
|
@ -24,6 +24,8 @@ const double _iconSize = 16.0;
|
||||||
const double _interRowPadding = 2.0;
|
const double _interRowPadding = 2.0;
|
||||||
const double _subRowMinWidth = 300.0;
|
const double _subRowMinWidth = 300.0;
|
||||||
|
|
||||||
|
List<Shadow>? _shadows(BuildContext context) => Theme.of(context).brightness == Brightness.dark ? Constants.embossShadows : null;
|
||||||
|
|
||||||
class ViewerDetailOverlay extends StatefulWidget {
|
class ViewerDetailOverlay extends StatefulWidget {
|
||||||
final List<AvesEntry> entries;
|
final List<AvesEntry> entries;
|
||||||
final int index;
|
final int index;
|
||||||
|
@ -142,7 +144,7 @@ class ViewerDetailOverlayContent extends StatelessWidget {
|
||||||
|
|
||||||
return DefaultTextStyle(
|
return DefaultTextStyle(
|
||||||
style: Theme.of(context).textTheme.bodyText2!.copyWith(
|
style: Theme.of(context).textTheme.bodyText2!.copyWith(
|
||||||
shadows: Constants.embossShadows,
|
shadows: _shadows(context),
|
||||||
),
|
),
|
||||||
softWrap: false,
|
softWrap: false,
|
||||||
overflow: TextOverflow.fade,
|
overflow: TextOverflow.fade,
|
||||||
|
@ -271,7 +273,7 @@ class _LocationRow extends AnimatedWidget {
|
||||||
}
|
}
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
const DecoratedIcon(AIcons.location, shadows: Constants.embossShadows, size: _iconSize),
|
DecoratedIcon(AIcons.location, shadows: _shadows(context), size: _iconSize),
|
||||||
const SizedBox(width: _iconPadding),
|
const SizedBox(width: _iconPadding),
|
||||||
Expanded(child: Text(location, strutStyle: Constants.overflowStrutStyle)),
|
Expanded(child: Text(location, strutStyle: Constants.overflowStrutStyle)),
|
||||||
],
|
],
|
||||||
|
@ -352,7 +354,7 @@ class _DateRow extends StatelessWidget {
|
||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
const DecoratedIcon(AIcons.date, shadows: Constants.embossShadows, size: _iconSize),
|
DecoratedIcon(AIcons.date, shadows: _shadows(context), size: _iconSize),
|
||||||
const SizedBox(width: _iconPadding),
|
const SizedBox(width: _iconPadding),
|
||||||
Expanded(flex: 3, child: Text(dateText, strutStyle: Constants.overflowStrutStyle)),
|
Expanded(flex: 3, child: Text(dateText, strutStyle: Constants.overflowStrutStyle)),
|
||||||
Expanded(flex: 2, child: Text(resolutionText, strutStyle: Constants.overflowStrutStyle)),
|
Expanded(flex: 2, child: Text(resolutionText, strutStyle: Constants.overflowStrutStyle)),
|
||||||
|
@ -381,7 +383,7 @@ class _ShootingRow extends StatelessWidget {
|
||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
const DecoratedIcon(AIcons.shooting, shadows: Constants.embossShadows, size: _iconSize),
|
DecoratedIcon(AIcons.shooting, shadows: _shadows(context), size: _iconSize),
|
||||||
const SizedBox(width: _iconPadding),
|
const SizedBox(width: _iconPadding),
|
||||||
Expanded(child: Text(apertureText, strutStyle: Constants.overflowStrutStyle)),
|
Expanded(child: Text(apertureText, strutStyle: Constants.overflowStrutStyle)),
|
||||||
Expanded(child: Text(details.exposureTime ?? Constants.overlayUnknown, 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