diff --git a/lib/model/image_entry.dart b/lib/model/image_entry.dart index 6a54de74a..4cd6f3cd3 100644 --- a/lib/model/image_entry.dart +++ b/lib/model/image_entry.dart @@ -63,6 +63,8 @@ class ImageEntry { bool get canDecode => !undecodable.contains(mimeType); + bool get canHaveAlpha => MimeTypes.alphaImages.contains(mimeType); + ImageEntry copyWith({ @required String uri, @required String path, diff --git a/lib/model/settings/settings.dart b/lib/model/settings/settings.dart index 8ee13f644..7e39a0ca8 100644 --- a/lib/model/settings/settings.dart +++ b/lib/model/settings/settings.dart @@ -55,6 +55,7 @@ class Settings extends ChangeNotifier { static const coordinateFormatKey = 'coordinates_format'; // rendering + static const rasterBackgroundKey = 'raster_background'; static const vectorBackgroundKey = 'vector_background'; // search @@ -185,6 +186,10 @@ class Settings extends ChangeNotifier { // rendering + EntryBackground get rasterBackground => getEnumOrDefault(rasterBackgroundKey, EntryBackground.transparent, EntryBackground.values); + + set rasterBackground(EntryBackground newValue) => setAndNotify(rasterBackgroundKey, newValue.toString()); + EntryBackground get vectorBackground => getEnumOrDefault(vectorBackgroundKey, EntryBackground.white, EntryBackground.values); set vectorBackground(EntryBackground newValue) => setAndNotify(vectorBackgroundKey, newValue.toString()); diff --git a/lib/ref/mime_types.dart b/lib/ref/mime_types.dart index fe95d5309..69dfe3800 100644 --- a/lib/ref/mime_types.dart +++ b/lib/ref/mime_types.dart @@ -1,15 +1,17 @@ class MimeTypes { static const anyImage = 'image/*'; + static const bmp = 'image/bmp'; static const gif = 'image/gif'; static const heic = 'image/heic'; static const heif = 'image/heif'; + static const ico = 'image/x-icon'; static const jpeg = 'image/jpeg'; static const png = 'image/png'; static const svg = 'image/svg+xml'; + static const tiff = 'image/tiff'; static const webp = 'image/webp'; - static const tiff = 'image/tiff'; static const psd = 'image/vnd.adobe.photoshop'; static const arw = 'image/x-sony-arw'; @@ -40,6 +42,10 @@ class MimeTypes { static const mp4 = 'video/mp4'; // groups + + // formats that support transparency + static const List alphaImages = [bmp, gif, ico, png, svg, tiff, webp]; + static const List rawImages = [arw, cr2, crw, dcr, dng, erf, k25, kdc, mrw, nef, nrw, orf, pef, raf, raw, rw2, sr2, srf, srw, x3f]; static bool isImage(String mimeType) => mimeType.startsWith('image'); diff --git a/lib/ref/xmp.dart b/lib/ref/xmp.dart index ebff07696..02a3bfa9e 100644 --- a/lib/ref/xmp.dart +++ b/lib/ref/xmp.dart @@ -15,6 +15,7 @@ class XMP { 'exifEX': 'Exif Ex', 'GettyImagesGIFT': 'Getty Images', 'GIMP': 'GIMP', + 'GCamera': 'Google Camera', 'GFocus': 'Google Focus', 'GPano': 'Google Panorama', 'illustrator': 'Illustrator', diff --git a/lib/widgets/fullscreen/image_view.dart b/lib/widgets/fullscreen/image_view.dart index 1c7040720..b3c5e1ada 100644 --- a/lib/widgets/fullscreen/image_view.dart +++ b/lib/widgets/fullscreen/image_view.dart @@ -1,7 +1,5 @@ import 'dart:async'; -import 'dart:math'; -import 'package:aves/image_providers/thumbnail_provider.dart'; import 'package:aves/image_providers/uri_picture_provider.dart'; import 'package:aves/model/image_entry.dart'; import 'package:aves/model/settings/entry_background.dart'; @@ -30,6 +28,8 @@ class ImageView extends StatefulWidget { final List> videoControllers; final VoidCallback onDisposed; + static const decorationCheckSize = 20.0; + const ImageView({ Key key, @required this.entry, @@ -56,8 +56,6 @@ class _ImageViewState extends State { MagnifierTapCallback get onTap => widget.onTap; - static const decorationCheckSize = 20.0; - @override void initState() { super.initState(); @@ -98,25 +96,6 @@ class _ImageViewState extends State { : child; } - ImageProvider get fastThumbnailProvider => ThumbnailProvider(ThumbnailProviderKey.fromEntry(entry)); - - // this loading builder shows a transition image until the final image is ready - // if the image is already in the cache it will show the final image, otherwise the thumbnail - // in any case, we should use `Center` + `AspectRatio` + `BoxFit.fill` so that the transition image - // is laid the same way as the final image when `contained` - Widget _loadingBuilder(BuildContext context, ImageProvider imageProvider) { - return Center( - child: AspectRatio( - // enforce original aspect ratio, as some thumbnails aspect ratios slightly differ - aspectRatio: entry.displayAspectRatio, - child: Image( - image: imageProvider, - fit: BoxFit.fill, - ), - ), - ); - } - Widget _buildRasterView() { return Magnifier( // key includes size and orientation to refresh when the image is rotated @@ -124,7 +103,6 @@ class _ImageViewState extends State { child: TiledImageView( entry: entry, viewStateNotifier: _viewStateNotifier, - baseChild: _loadingBuilder(context, fastThumbnailProvider), errorBuilder: (context, error, stackTrace) => ErrorChild(onTap: () => onTap?.call(null)), ), childSize: entry.displaySize, @@ -165,11 +143,11 @@ class _ImageViewState extends State { if (viewportSize == null) return child; final side = viewportSize.shortestSide; - final checkSize = side / ((side / decorationCheckSize).round()); + final checkSize = side / ((side / ImageView.decorationCheckSize).round()); final viewSize = entry.displaySize * viewState.scale; - final decorationSize = Size(min(viewSize.width, viewportSize.width), min(viewSize.height, viewportSize.height)); - final offset = Offset(decorationSize.width - viewportSize.width, decorationSize.height - viewportSize.height) / 2; + final decorationSize = applyBoxFit(BoxFit.none, viewSize, viewportSize).source; + final offset = ((decorationSize - viewportSize) as Offset) / 2; return Stack( alignment: Alignment.center, diff --git a/lib/widgets/fullscreen/tiled_view.dart b/lib/widgets/fullscreen/tiled_view.dart index cf829c479..c64fcea6f 100644 --- a/lib/widgets/fullscreen/tiled_view.dart +++ b/lib/widgets/fullscreen/tiled_view.dart @@ -1,23 +1,26 @@ import 'dart:math'; import 'package:aves/image_providers/region_provider.dart'; +import 'package:aves/image_providers/thumbnail_provider.dart'; import 'package:aves/image_providers/uri_image_provider.dart'; import 'package:aves/model/image_entry.dart'; +import 'package:aves/model/settings/entry_background.dart'; +import 'package:aves/model/settings/settings.dart'; import 'package:aves/utils/math_utils.dart'; +import 'package:aves/widgets/common/fx/checkered_decoration.dart'; import 'package:aves/widgets/fullscreen/image_view.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:tuple/tuple.dart'; class TiledImageView extends StatefulWidget { final ImageEntry entry; final ValueNotifier viewStateNotifier; - final Widget baseChild; final ImageErrorWidgetBuilder errorBuilder; const TiledImageView({ @required this.entry, @required this.viewStateNotifier, - @required this.baseChild, @required this.errorBuilder, }); @@ -26,159 +29,244 @@ class TiledImageView extends StatefulWidget { } class _TiledImageViewState extends State { - bool _initialized = false; - double _tileSide, _initialScale; + bool _isTilingInitialized = false; int _maxSampleSize; - Matrix4 _transform; + double _tileSide; + Matrix4 _tileTransform; + ImageStream _fullImageStream; + ImageStreamListener _fullImageListener; + final ValueNotifier _fullImageLoaded = ValueNotifier(false); ImageEntry get entry => widget.entry; ValueNotifier get viewStateNotifier => widget.viewStateNotifier; + bool get useBackground => entry.canHaveAlpha && settings.rasterBackground != EntryBackground.transparent; + bool get useTiles => entry.canTile && (entry.width > 4096 || entry.height > 4096); - ImageProvider get fullImage => UriImage( + ImageProvider get thumbnailProvider => ThumbnailProvider(ThumbnailProviderKey.fromEntry(entry)); + + ImageProvider get fullImageProvider { + if (useTiles) { + assert(_isTilingInitialized); + final displayWidth = entry.displaySize.width.round(); + final displayHeight = entry.displaySize.height.round(); + final viewState = viewStateNotifier.value; + final regionRect = _getTileRects( + x: 0, + y: 0, + layerRegionWidth: displayWidth, + layerRegionHeight: displayHeight, + displayWidth: displayWidth, + displayHeight: displayHeight, + scale: viewState.scale, + viewRect: _getViewRect(viewState, displayWidth, displayHeight), + ).item2; + return RegionProvider(RegionProviderKey.fromEntry( + entry, + sampleSize: _maxSampleSize, + rect: regionRect, + )); + } else { + return UriImage( uri: entry.uri, mimeType: entry.mimeType, rotationDegrees: entry.rotationDegrees, isFlipped: entry.isFlipped, expectedContentLength: entry.sizeBytes, ); + } + } // magic number used to derive sample size from scale static const scaleFactor = 2.0; + @override + void initState() { + super.initState(); + _fullImageListener = ImageStreamListener(_onFullImageCompleted); + if (!useTiles) _registerFullImage(); + } + @override void didUpdateWidget(TiledImageView oldWidget) { super.didUpdateWidget(oldWidget); final oldViewState = oldWidget.viewStateNotifier.value; final viewState = widget.viewStateNotifier.value; - if (oldViewState.viewportSize != viewState.viewportSize || oldWidget.entry.displaySize != widget.entry.displaySize) { - _initialized = false; + if (oldWidget.entry != widget.entry || oldViewState.viewportSize != viewState.viewportSize) { + _isTilingInitialized = false; + _fullImageLoaded.value = false; + _unregisterFullImage(); } } - void _initFromViewport(Size viewportSize) { - final displaySize = entry.displaySize; - _tileSide = viewportSize.shortestSide * scaleFactor; - _initialScale = min(viewportSize.width / displaySize.width, viewportSize.height / displaySize.height); - _maxSampleSize = _sampleSizeForScale(_initialScale); + @override + void dispose() { + _unregisterFullImage(); + super.dispose(); + } - final rotationDegrees = entry.rotationDegrees; - final isFlipped = entry.isFlipped; - _transform = null; - if (rotationDegrees != 0 || isFlipped) { - _transform = Matrix4.identity() - ..translate(entry.width / 2.0, entry.height / 2.0) - ..scale(isFlipped ? -1.0 : 1.0, 1.0, 1.0) - ..rotateZ(-toRadians(rotationDegrees.toDouble())) - ..translate(-displaySize.width / 2.0, -displaySize.height / 2.0); - } - _initialized = true; + void _registerFullImage() { + _fullImageStream = fullImageProvider.resolve(ImageConfiguration.empty); + _fullImageStream.addListener(_fullImageListener); + } + + void _unregisterFullImage() { + _fullImageStream?.removeListener(_fullImageListener); + _fullImageStream = null; + } + + void _onFullImageCompleted(ImageInfo image, bool synchronousCall) { + _unregisterFullImage(); + _fullImageLoaded.value = true; } @override Widget build(BuildContext context) { if (viewStateNotifier == null) return SizedBox.shrink(); - final displayWidth = entry.displaySize.width.round(); - final displayHeight = entry.displaySize.height.round(); - return ValueListenableBuilder( valueListenable: viewStateNotifier, builder: (context, viewState, child) { final viewportSize = viewState.viewportSize; - if (viewportSize == null) return SizedBox.shrink(); - if (!_initialized) _initFromViewport(viewportSize); + final viewportSized = viewportSize != null; + if (viewportSized && useTiles && !_isTilingInitialized) _initTiling(viewportSize); - var scale = viewState.scale; - if (scale == 0.0) { - // for initial scale as `contained` - scale = _initialScale; - } - final scaledSize = entry.displaySize * scale; - final loading = SizedBox( - width: scaledSize.width, - height: scaledSize.height, - child: widget.baseChild, - ); - - List children; - if (useTiles) { - children = [ - loading, - ..._getTiles(viewState, displayWidth, displayHeight, scale), - ]; - } else { - children = [ - if (!imageCache.statusForKey(fullImage).keepAlive) loading, - Image( - image: fullImage, - gaplessPlayback: true, - errorBuilder: widget.errorBuilder, - width: scaledSize.width, - fit: BoxFit.contain, - filterQuality: FilterQuality.medium, - ) - ]; - } - - return Stack( - alignment: Alignment.center, - children: children, + return SizedBox.fromSize( + size: entry.displaySize * viewState.scale, + child: Stack( + alignment: Alignment.center, + children: [ + if (useBackground && viewportSized) _buildBackground(viewState), + _buildLoading(viewState), + if (useTiles) ..._getTiles(viewState), + if (!useTiles) + Image( + image: fullImageProvider, + gaplessPlayback: true, + errorBuilder: widget.errorBuilder, + width: (entry.displaySize * viewState.scale).width, + fit: BoxFit.contain, + filterQuality: FilterQuality.medium, + ), + ], + ), ); }, ); } - List _getTiles(ViewState viewState, int displayWidth, int displayHeight, double scale) { - final centerOffset = viewState.position; - final viewportSize = viewState.viewportSize; - final viewOrigin = Offset( - ((displayWidth * scale - viewportSize.width) / 2 - centerOffset.dx), - ((displayHeight * scale - viewportSize.height) / 2 - centerOffset.dy), + void _initTiling(Size viewportSize) { + final displaySize = entry.displaySize; + _tileSide = viewportSize.shortestSide * scaleFactor; + // scale for initial state `contained` + final containedScale = min(viewportSize.width / displaySize.width, viewportSize.height / displaySize.height); + _maxSampleSize = _sampleSizeForScale(containedScale); + + final rotationDegrees = entry.rotationDegrees; + final isFlipped = entry.isFlipped; + _tileTransform = null; + if (rotationDegrees != 0 || isFlipped) { + _tileTransform = Matrix4.identity() + ..translate(entry.width / 2.0, entry.height / 2.0) + ..scale(isFlipped ? -1.0 : 1.0, 1.0, 1.0) + ..rotateZ(-toRadians(rotationDegrees.toDouble())) + ..translate(-displaySize.width / 2.0, -displaySize.height / 2.0); + } + _isTilingInitialized = true; + _registerFullImage(); + } + + Widget _buildLoading(ViewState viewState) { + return ValueListenableBuilder( + valueListenable: _fullImageLoaded, + builder: (context, fullImageLoaded, child) { + if (fullImageLoaded) return SizedBox.shrink(); + + return Center( + child: AspectRatio( + // enforce original aspect ratio, as some thumbnails aspect ratios slightly differ + aspectRatio: entry.displayAspectRatio, + child: Image( + image: thumbnailProvider, + fit: BoxFit.fill, + ), + ), + ); + }, ); - final viewRect = viewOrigin & viewportSize; + } + + Widget _buildBackground(ViewState viewState) { + final viewportSize = viewState.viewportSize; + assert(viewportSize != null); + + final viewSize = entry.displaySize * viewState.scale; + final decorationOffset = ((viewSize - viewportSize) as Offset) / 2 - viewState.position; + final decorationSize = applyBoxFit(BoxFit.none, viewSize, viewportSize).source; + + Decoration decoration; + final background = settings.rasterBackground; + if (background == EntryBackground.checkered) { + final side = viewportSize.shortestSide; + final checkSize = side / ((side / ImageView.decorationCheckSize).round()); + final offset = ((decorationSize - viewportSize) as Offset) / 2; + decoration = CheckeredDecoration( + checkSize: checkSize, + offset: offset, + ); + } else { + decoration = BoxDecoration( + color: background.color, + ); + } + return Positioned( + left: decorationOffset.dx >= 0 ? decorationOffset.dx : null, + top: decorationOffset.dy >= 0 ? decorationOffset.dy : null, + width: decorationSize.width, + height: decorationSize.height, + child: DecoratedBox( + decoration: decoration, + ), + ); + } + + List _getTiles(ViewState viewState) { + if (!_isTilingInitialized) return []; + + final displayWidth = entry.displaySize.width.round(); + final displayHeight = entry.displaySize.height.round(); + final viewRect = _getViewRect(viewState, displayWidth, displayHeight); + final scale = viewState.scale; final tiles = []; var minSampleSize = min(_sampleSizeForScale(scale), _maxSampleSize); for (var sampleSize = _maxSampleSize; sampleSize >= minSampleSize; sampleSize = (sampleSize / 2).floor()) { // for the largest sample size (matching the initial scale), the whole image is in view - // so we subsample the whole image instead of splitting it in tiles - final useTiles = sampleSize != _maxSampleSize; + // so we subsample the whole image without tiling + final fullImageRegion = sampleSize == _maxSampleSize; final regionSide = (_tileSide * sampleSize).round(); - final layerRegionWidth = useTiles ? regionSide : displayWidth; - final layerRegionHeight = useTiles ? regionSide : displayHeight; + final layerRegionWidth = fullImageRegion ? displayWidth : regionSide; + final layerRegionHeight = fullImageRegion ? displayHeight : regionSide; for (var x = 0; x < displayWidth; x += layerRegionWidth) { for (var y = 0; y < displayHeight; y += layerRegionHeight) { - final nextX = x + layerRegionWidth; - final nextY = y + layerRegionHeight; - final thisRegionWidth = layerRegionWidth - (nextX >= displayWidth ? nextX - displayWidth : 0); - final thisRegionHeight = layerRegionHeight - (nextY >= displayHeight ? nextY - displayHeight : 0); - final tileRect = Rect.fromLTWH(x * scale, y * scale, thisRegionWidth * scale, thisRegionHeight * scale); - - // only build visible tiles - if (viewRect.overlaps(tileRect)) { - Rectangle regionRect; - - if (_transform != null) { - // apply EXIF orientation - final regionRectDouble = Rect.fromLTWH(x.toDouble(), y.toDouble(), thisRegionWidth.toDouble(), thisRegionHeight.toDouble()); - final tl = MatrixUtils.transformPoint(_transform, regionRectDouble.topLeft); - final br = MatrixUtils.transformPoint(_transform, regionRectDouble.bottomRight); - regionRect = Rectangle.fromPoints( - Point(tl.dx.round(), tl.dy.round()), - Point(br.dx.round(), br.dy.round()), - ); - } else { - regionRect = Rectangle(x, y, thisRegionWidth, thisRegionHeight); - } - + final rects = _getTileRects( + x: x, + y: y, + layerRegionWidth: layerRegionWidth, + layerRegionHeight: layerRegionHeight, + displayWidth: displayWidth, + displayHeight: displayHeight, + scale: scale, + viewRect: viewRect, + ); + if (rects != null) { tiles.add(RegionTile( entry: entry, - tileRect: tileRect, - regionRect: regionRect, + tileRect: rects.item1, + regionRect: rects.item2, sampleSize: sampleSize, )); } @@ -188,6 +276,52 @@ class _TiledImageViewState extends State { return tiles; } + Rect _getViewRect(ViewState viewState, int displayWidth, int displayHeight) { + final scale = viewState.scale; + final centerOffset = viewState.position; + final viewportSize = viewState.viewportSize; + final viewOrigin = Offset( + ((displayWidth * scale - viewportSize.width) / 2 - centerOffset.dx), + ((displayHeight * scale - viewportSize.height) / 2 - centerOffset.dy), + ); + return viewOrigin & viewportSize; + } + + Tuple2> _getTileRects({ + @required int x, + @required int y, + @required int layerRegionWidth, + @required int layerRegionHeight, + @required int displayWidth, + @required int displayHeight, + @required double scale, + @required Rect viewRect, + }) { + final nextX = x + layerRegionWidth; + final nextY = y + layerRegionHeight; + final thisRegionWidth = layerRegionWidth - (nextX >= displayWidth ? nextX - displayWidth : 0); + final thisRegionHeight = layerRegionHeight - (nextY >= displayHeight ? nextY - displayHeight : 0); + final tileRect = Rect.fromLTWH(x * scale, y * scale, thisRegionWidth * scale, thisRegionHeight * scale); + + // only build visible tiles + if (!viewRect.overlaps(tileRect)) return null; + + Rectangle regionRect; + if (_tileTransform != null) { + // apply EXIF orientation + final regionRectDouble = Rect.fromLTWH(x.toDouble(), y.toDouble(), thisRegionWidth.toDouble(), thisRegionHeight.toDouble()); + final tl = MatrixUtils.transformPoint(_tileTransform, regionRectDouble.topLeft); + final br = MatrixUtils.transformPoint(_tileTransform, regionRectDouble.bottomRight); + regionRect = Rectangle.fromPoints( + Point(tl.dx.round(), tl.dy.round()), + Point(br.dx.round(), br.dy.round()), + ); + } else { + regionRect = Rectangle(x, y, thisRegionWidth, thisRegionHeight); + } + return Tuple2>(tileRect, regionRect); + } + int _sampleSizeForScale(double scale) { var sample = 0; if (0 < scale && scale < 1) { diff --git a/lib/widgets/settings/entry_background.dart b/lib/widgets/settings/entry_background.dart new file mode 100644 index 000000000..ade54d894 --- /dev/null +++ b/lib/widgets/settings/entry_background.dart @@ -0,0 +1,78 @@ +import 'package:aves/model/settings/entry_background.dart'; +import 'package:aves/widgets/common/fx/borders.dart'; +import 'package:aves/widgets/common/fx/checkered_decoration.dart'; +import 'package:flutter/material.dart'; + +class EntryBackgroundSelector extends StatefulWidget { + final ValueGetter getter; + final ValueSetter setter; + + const EntryBackgroundSelector({ + @required this.getter, + @required this.setter, + }); + + @override + _EntryBackgroundSelectorState createState() => _EntryBackgroundSelectorState(); +} + +class _EntryBackgroundSelectorState extends State { + @override + Widget build(BuildContext context) { + return DropdownButtonHideUnderline( + child: DropdownButton( + items: _buildItems(context), + value: widget.getter(), + onChanged: (selected) { + widget.setter(selected); + setState(() {}); + }, + ), + ); + } + + List> _buildItems(BuildContext context) { + const radius = 12.0; + return [ + EntryBackground.white, + EntryBackground.black, + EntryBackground.checkered, + EntryBackground.transparent, + ].map((selected) { + Widget child; + switch (selected) { + case EntryBackground.transparent: + child = Icon( + Icons.clear, + size: 20, + color: Colors.white30, + ); + break; + case EntryBackground.checkered: + child = ClipOval( + child: DecoratedBox( + decoration: CheckeredDecoration( + checkSize: radius, + ), + ), + ); + break; + default: + break; + } + return DropdownMenuItem( + value: selected, + child: Container( + height: radius * 2, + width: radius * 2, + decoration: BoxDecoration( + color: selected.isColor ? selected.color : null, + border: AvesCircleBorder.build(context), + shape: BoxShape.circle, + ), + child: child, + ), + ); + }).toList(); + } +} diff --git a/lib/widgets/settings/settings_page.dart b/lib/widgets/settings/settings_page.dart index a6e52de38..65c320102 100644 --- a/lib/widgets/settings/settings_page.dart +++ b/lib/widgets/settings/settings_page.dart @@ -8,7 +8,7 @@ import 'package:aves/widgets/common/identity/aves_expansion_tile.dart'; import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; import 'package:aves/widgets/dialogs/aves_selection_dialog.dart'; import 'package:aves/widgets/settings/access_grants.dart'; -import 'package:aves/widgets/settings/svg_background.dart'; +import 'package:aves/widgets/settings/entry_background.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:provider/provider.dart'; @@ -115,8 +115,18 @@ class _SettingsPageState extends State { }, ), ListTile( - title: Text('SVG background'), - trailing: SvgBackgroundSelector(), + title: Text('Raster image background'), + trailing: EntryBackgroundSelector( + getter: () => settings.rasterBackground, + setter: (value) => settings.rasterBackground = value, + ), + ), + ListTile( + title: Text('Vector image background'), + trailing: EntryBackgroundSelector( + getter: () => settings.vectorBackground, + setter: (value) => settings.vectorBackground = value, + ), ), ListTile( title: Text('Coordinate format'), diff --git a/lib/widgets/settings/svg_background.dart b/lib/widgets/settings/svg_background.dart deleted file mode 100644 index a2c68e251..000000000 --- a/lib/widgets/settings/svg_background.dart +++ /dev/null @@ -1,67 +0,0 @@ -import 'package:aves/model/settings/entry_background.dart'; -import 'package:aves/model/settings/settings.dart'; -import 'package:aves/widgets/common/fx/borders.dart'; -import 'package:aves/widgets/common/fx/checkered_decoration.dart'; -import 'package:flutter/material.dart'; - -class SvgBackgroundSelector extends StatefulWidget { - @override - _SvgBackgroundSelectorState createState() => _SvgBackgroundSelectorState(); -} - -class _SvgBackgroundSelectorState extends State { - @override - Widget build(BuildContext context) { - const radius = 12.0; - return DropdownButtonHideUnderline( - child: DropdownButton( - items: [ - EntryBackground.white, - EntryBackground.black, - EntryBackground.checkered, - EntryBackground.transparent, - ].map((selected) { - Widget child; - switch (selected) { - case EntryBackground.transparent: - child = Icon( - Icons.clear, - size: 20, - color: Colors.white30, - ); - break; - case EntryBackground.checkered: - child = ClipOval( - child: DecoratedBox( - decoration: CheckeredDecoration( - checkSize: radius, - ), - ), - ); - break; - default: - break; - } - return DropdownMenuItem( - value: selected, - child: Container( - height: radius * 2, - width: radius * 2, - decoration: BoxDecoration( - color: selected.isColor ? selected.color : null, - border: AvesCircleBorder.build(context), - shape: BoxShape.circle, - ), - child: child, - ), - ); - }).toList(), - value: settings.vectorBackground, - onChanged: (selected) { - settings.vectorBackground = selected; - setState(() {}); - }, - ), - ); - } -}