filter bar: clear filter, app filter color
This commit is contained in:
parent
4c23a0f5ad
commit
cb553df009
27 changed files with 257 additions and 141 deletions
|
@ -5,8 +5,8 @@ import 'package:aves/model/settings.dart';
|
|||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:aves/utils/viewer_service.dart';
|
||||
import 'package:aves/widgets/album/collection_page.dart';
|
||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||
import 'package:aves/widgets/common/providers/media_store_collection_provider.dart';
|
||||
import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart';
|
||||
import 'package:aves/widgets/common/data_providers/media_store_collection_provider.dart';
|
||||
import 'package:aves/widgets/fullscreen/fullscreen_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
import 'package:aves/model/image_entry.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:aves/utils/color_utils.dart';
|
||||
import 'package:aves/widgets/common/icons.dart';
|
||||
import 'package:aves/widgets/common/image_providers/app_icon_image_provider.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:outline_material_icons/outline_material_icons.dart';
|
||||
import 'package:palette_generator/palette_generator.dart';
|
||||
import 'package:path/path.dart';
|
||||
|
||||
abstract class CollectionFilter {
|
||||
abstract class CollectionFilter implements Comparable<CollectionFilter> {
|
||||
static const List<String> collectionFilterOrder = [
|
||||
VideoFilter.type,
|
||||
GifFilter.type,
|
||||
|
@ -20,11 +26,19 @@ abstract class CollectionFilter {
|
|||
|
||||
String get label;
|
||||
|
||||
Widget iconBuilder(BuildContext context);
|
||||
Widget iconBuilder(BuildContext context, double size);
|
||||
|
||||
Future<Color> color(BuildContext context) => SynchronousFuture(stringToColor(label));
|
||||
|
||||
String get typeKey;
|
||||
|
||||
int get displayPriority => collectionFilterOrder.indexOf(typeKey);
|
||||
|
||||
@override
|
||||
int compareTo(CollectionFilter other) {
|
||||
final c = displayPriority.compareTo(other.displayPriority);
|
||||
return c != 0 ? c : compareAsciiUpperCase(label, other.label);
|
||||
}
|
||||
}
|
||||
|
||||
class AlbumFilter extends CollectionFilter {
|
||||
|
@ -41,7 +55,23 @@ class AlbumFilter extends CollectionFilter {
|
|||
String get label => album.split(separator).last;
|
||||
|
||||
@override
|
||||
Widget iconBuilder(context) => IconUtils.getAlbumIcon(context, album) ?? Icon(OMIcons.photoAlbum);
|
||||
Widget iconBuilder(context, size) {
|
||||
return IconUtils.getAlbumIcon(context: context, album: album, size: size) ?? Icon(OMIcons.photoAlbum, size: size);
|
||||
}
|
||||
|
||||
Future<Color> color(BuildContext context) async {
|
||||
Color color;
|
||||
if (androidFileUtils.getAlbumType(album) == AlbumType.App) {
|
||||
final palette = await PaletteGenerator.fromImageProvider(
|
||||
AppIconImage(
|
||||
packageName: androidFileUtils.getAlbumAppPackageName(album),
|
||||
size: 24,
|
||||
),
|
||||
);
|
||||
color = palette.dominantColor?.color;
|
||||
}
|
||||
return color ?? super.color(context);
|
||||
}
|
||||
|
||||
@override
|
||||
String get typeKey => type;
|
||||
|
@ -70,7 +100,7 @@ class TagFilter extends CollectionFilter {
|
|||
String get label => tag;
|
||||
|
||||
@override
|
||||
Widget iconBuilder(context) => Icon(OMIcons.localOffer);
|
||||
Widget iconBuilder(context, size) => Icon(OMIcons.localOffer, size: size);
|
||||
|
||||
@override
|
||||
String get typeKey => type;
|
||||
|
@ -99,7 +129,7 @@ class CountryFilter extends CollectionFilter {
|
|||
String get label => country;
|
||||
|
||||
@override
|
||||
Widget iconBuilder(context) => Icon(OMIcons.place);
|
||||
Widget iconBuilder(context, size) => Icon(OMIcons.place, size: size);
|
||||
|
||||
@override
|
||||
String get typeKey => type;
|
||||
|
@ -124,7 +154,7 @@ class VideoFilter extends CollectionFilter {
|
|||
String get label => 'Video';
|
||||
|
||||
@override
|
||||
Widget iconBuilder(context) => Icon(OMIcons.movie);
|
||||
Widget iconBuilder(context, size) => Icon(OMIcons.movie, size: size);
|
||||
|
||||
@override
|
||||
String get typeKey => type;
|
||||
|
@ -149,7 +179,7 @@ class GifFilter extends CollectionFilter {
|
|||
String get label => 'GIF';
|
||||
|
||||
@override
|
||||
Widget iconBuilder(context) => Icon(OMIcons.gif);
|
||||
Widget iconBuilder(context, size) => Icon(OMIcons.gif, size: size);
|
||||
|
||||
@override
|
||||
String get typeKey => type;
|
||||
|
@ -178,7 +208,7 @@ class QueryFilter extends CollectionFilter {
|
|||
String get label => '${query}';
|
||||
|
||||
@override
|
||||
Widget iconBuilder(context) => Icon(OMIcons.formatQuote);
|
||||
Widget iconBuilder(context, size) => Icon(OMIcons.formatQuote, size: size);
|
||||
|
||||
@override
|
||||
String get typeKey => type;
|
||||
|
|
|
@ -52,7 +52,7 @@ class CollectionLens with ChangeNotifier {
|
|||
source: source,
|
||||
filters: [
|
||||
...filters,
|
||||
if (filter != null) filter,
|
||||
filter,
|
||||
],
|
||||
groupFactor: groupFactor,
|
||||
sortFactor: sortFactor,
|
||||
|
@ -71,6 +71,14 @@ class CollectionLens with ChangeNotifier {
|
|||
|
||||
Object heroTag(ImageEntry entry) => '$hashCode${entry.uri}';
|
||||
|
||||
void removeFilter(CollectionFilter filter) {
|
||||
if (!filters.contains(filter)) return;
|
||||
filters.remove(filter);
|
||||
_applyFilters();
|
||||
_applySort();
|
||||
_applyGroup();
|
||||
}
|
||||
|
||||
void sort(SortFactor sortFactor) {
|
||||
this.sortFactor = sortFactor;
|
||||
_applySort();
|
||||
|
|
|
@ -82,7 +82,7 @@ class _CollectionDrawerState extends State<CollectionDrawer> {
|
|||
);
|
||||
final buildAlbumEntry = (album) => _FilteredCollectionNavTile(
|
||||
source: source,
|
||||
leading: IconUtils.getAlbumIcon(context, album),
|
||||
leading: IconUtils.getAlbumIcon(context: context, album: album),
|
||||
title: CollectionSource.getUniqueAlbumName(album, source.sortedAlbums),
|
||||
dense: true,
|
||||
filter: AlbumFilter(album),
|
||||
|
|
|
@ -3,8 +3,8 @@ import 'package:aves/widgets/album/all_collection_app_bar.dart';
|
|||
import 'package:aves/widgets/album/collection_drawer.dart';
|
||||
import 'package:aves/widgets/album/filter_bar.dart';
|
||||
import 'package:aves/widgets/album/thumbnail_collection.dart';
|
||||
import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart';
|
||||
import 'package:aves/widgets/common/menu_row.dart';
|
||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||
import 'package:aves/widgets/stats.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:outline_material_icons/outline_material_icons.dart';
|
||||
|
@ -27,7 +27,7 @@ class CollectionPage extends StatelessWidget {
|
|||
: SliverAppBar(
|
||||
title: const Text('Aves'),
|
||||
actions: _buildActions(),
|
||||
bottom: FilterBar(collection.filters),
|
||||
bottom: FilterBar(),
|
||||
floating: true,
|
||||
),
|
||||
),
|
||||
|
|
|
@ -4,7 +4,7 @@ import 'dart:ui' as ui;
|
|||
import 'package:aves/model/image_entry.dart';
|
||||
import 'package:aves/widgets/album/collection_section.dart';
|
||||
import 'package:aves/widgets/album/thumbnail.dart';
|
||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||
import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_sticky_header/flutter_sticky_header.dart';
|
||||
|
|
|
@ -162,7 +162,7 @@ class SectionHeader extends StatelessWidget {
|
|||
}
|
||||
|
||||
Widget _buildAlbumSectionHeader(BuildContext context) {
|
||||
Widget albumIcon = IconUtils.getAlbumIcon(context, sectionKey as String);
|
||||
Widget albumIcon = IconUtils.getAlbumIcon(context: context, album: sectionKey as String);
|
||||
if (albumIcon != null) {
|
||||
albumIcon = Material(
|
||||
type: MaterialType.circle,
|
||||
|
|
|
@ -1,27 +1,20 @@
|
|||
import 'package:aves/model/collection_filters.dart';
|
||||
import 'package:aves/model/collection_lens.dart';
|
||||
import 'package:aves/widgets/common/aves_filter_chip.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class FilterBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
final List<CollectionFilter> filters;
|
||||
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
|
||||
static const EdgeInsets padding = EdgeInsets.only(left: 8, right: 8, bottom: 8);
|
||||
static const EdgeInsets padding = EdgeInsets.symmetric(horizontal: 8);
|
||||
|
||||
@override
|
||||
final Size preferredSize = Size.fromHeight(kMinInteractiveDimension + padding.vertical);
|
||||
|
||||
FilterBar(Set<CollectionFilter> filters)
|
||||
: this.filters = filters.toList()
|
||||
..sort((a, b) {
|
||||
final c = a.displayPriority.compareTo(b.displayPriority);
|
||||
return c != 0 ? c : compareAsciiUpperCase(a.label, b.label);
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
debugPrint('$runtimeType build');
|
||||
final collection = Provider.of<CollectionLens>(context);
|
||||
final filters = collection.filters.toList()..sort();
|
||||
|
||||
return Container(
|
||||
// specify transparent as a workaround to prevent
|
||||
// chip border clipping when the floating app bar is fading
|
||||
|
@ -34,16 +27,18 @@ class FilterBar extends StatelessWidget implements PreferredSizeWidget {
|
|||
onNotification: (notification) => true,
|
||||
child: ListView.separated(
|
||||
scrollDirection: Axis.horizontal,
|
||||
controller: _scrollController,
|
||||
primary: false,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
padding: const EdgeInsets.all(AvesFilterChip.buttonBorderWidth / 2),
|
||||
itemBuilder: (context, index) {
|
||||
if (index >= filters.length) return null;
|
||||
final filter = filters[index];
|
||||
return AvesFilterChip(
|
||||
filter,
|
||||
onPressed: (filter) {},
|
||||
return Center(
|
||||
child: AvesFilterChip(
|
||||
filter,
|
||||
clearable: true,
|
||||
onPressed: collection.removeFilter,
|
||||
),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (context, index) => const SizedBox(width: 8),
|
||||
|
|
|
@ -2,7 +2,7 @@ import 'package:aves/model/collection_filters.dart';
|
|||
import 'package:aves/model/collection_lens.dart';
|
||||
import 'package:aves/model/image_entry.dart';
|
||||
import 'package:aves/widgets/album/thumbnail_collection.dart';
|
||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||
import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:outline_material_icons/outline_material_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
|
|
@ -4,7 +4,7 @@ import 'package:aves/model/image_entry.dart';
|
|||
import 'package:aves/utils/constants.dart';
|
||||
import 'package:aves/widgets/common/icons.dart';
|
||||
import 'package:aves/widgets/common/image_preview.dart';
|
||||
import 'package:aves/widgets/fullscreen/uri_picture_provider.dart';
|
||||
import 'package:aves/widgets/common/image_providers/uri_picture_provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:aves/utils/android_app_service.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:transparent_image/transparent_image.dart';
|
||||
|
||||
class AppIcon extends StatefulWidget {
|
||||
final String packageName;
|
||||
final double size;
|
||||
final double devicePixelRatio;
|
||||
|
||||
const AppIcon({
|
||||
Key key,
|
||||
@required this.packageName,
|
||||
@required this.size,
|
||||
@required this.devicePixelRatio,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => AppIconState();
|
||||
}
|
||||
|
||||
class AppIconState extends State<AppIcon> {
|
||||
Future<Uint8List> _byteLoader;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final dim = (widget.size * widget.devicePixelRatio).round();
|
||||
_byteLoader = AndroidAppService.getAppIcon(widget.packageName, dim);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder(
|
||||
future: _byteLoader,
|
||||
builder: (futureContext, AsyncSnapshot<Uint8List> snapshot) {
|
||||
final bytes = (snapshot.connectionState == ConnectionState.done && !snapshot.hasError) ? snapshot.data : kTransparentImage;
|
||||
return bytes.isNotEmpty
|
||||
? Image.memory(
|
||||
bytes,
|
||||
width: widget.size,
|
||||
height: widget.size,
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,56 +1,101 @@
|
|||
import 'package:aves/model/collection_filters.dart';
|
||||
import 'package:aves/utils/color_utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:outline_material_icons/outline_material_icons.dart';
|
||||
|
||||
typedef FilterCallback = void Function(CollectionFilter filter);
|
||||
|
||||
class AvesFilterChip extends StatelessWidget {
|
||||
class AvesFilterChip extends StatefulWidget {
|
||||
final CollectionFilter filter;
|
||||
final bool clearable;
|
||||
final FilterCallback onPressed;
|
||||
|
||||
String get label => filter.label;
|
||||
|
||||
static const double buttonBorderWidth = 2;
|
||||
static const double maxChipWidth = 160;
|
||||
static const double iconSize = 20;
|
||||
static const double padding = 6;
|
||||
|
||||
const AvesFilterChip(
|
||||
this.filter, {
|
||||
this.clearable = false,
|
||||
@required this.onPressed,
|
||||
});
|
||||
|
||||
@override
|
||||
_AvesFilterChipState createState() => _AvesFilterChipState();
|
||||
}
|
||||
|
||||
class _AvesFilterChipState extends State<AvesFilterChip> {
|
||||
Future<Color> _colorFuture;
|
||||
|
||||
CollectionFilter get filter => widget.filter;
|
||||
|
||||
String get label => filter.label;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initColorLoader();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(AvesFilterChip oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.filter != filter) {
|
||||
_initColorLoader();
|
||||
}
|
||||
}
|
||||
|
||||
void _initColorLoader() => _colorFuture = filter.color(context);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final icon = filter.iconBuilder(context);
|
||||
final leading = filter.iconBuilder(context, AvesFilterChip.iconSize);
|
||||
final trailing = widget.clearable ? Icon(OMIcons.clear, size: AvesFilterChip.iconSize) : null;
|
||||
|
||||
final child = Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (leading != null) ...[
|
||||
leading,
|
||||
const SizedBox(width: AvesFilterChip.padding * 1.6),
|
||||
],
|
||||
Flexible(
|
||||
child: Text(
|
||||
label,
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.fade,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
if (trailing != null) ...[
|
||||
const SizedBox(width: AvesFilterChip.padding),
|
||||
trailing,
|
||||
],
|
||||
],
|
||||
);
|
||||
|
||||
final shape = RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(42),
|
||||
);
|
||||
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: maxChipWidth),
|
||||
constraints: const BoxConstraints(maxWidth: AvesFilterChip.maxChipWidth),
|
||||
child: Tooltip(
|
||||
message: label,
|
||||
child: OutlineButton(
|
||||
onPressed: onPressed != null ? () => onPressed(filter) : null,
|
||||
borderSide: BorderSide(
|
||||
color: stringToColor(label),
|
||||
width: buttonBorderWidth,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(42),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (icon != null) ...[
|
||||
icon,
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
Flexible(
|
||||
child: Text(
|
||||
label,
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.fade,
|
||||
maxLines: 1,
|
||||
),
|
||||
child: FutureBuilder(
|
||||
future: _colorFuture,
|
||||
builder: (context, AsyncSnapshot<Color> snapshot) {
|
||||
return OutlineButton(
|
||||
onPressed: widget.onPressed != null ? () => widget.onPressed(filter) : null,
|
||||
borderSide: BorderSide(
|
||||
color: snapshot.hasData ? snapshot.data : Colors.transparent,
|
||||
width: AvesFilterChip.buttonBorderWidth,
|
||||
),
|
||||
],
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: AvesFilterChip.padding * 2),
|
||||
shape: shape,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -2,10 +2,9 @@ import 'dart:ui';
|
|||
|
||||
import 'package:aves/model/image_entry.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:aves/widgets/common/app_icon.dart';
|
||||
import 'package:aves/widgets/common/image_providers/app_icon_image_provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:outline_material_icons/outline_material_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class VideoIcon extends StatelessWidget {
|
||||
final ImageEntry entry;
|
||||
|
@ -88,23 +87,27 @@ class OverlayIcon extends StatelessWidget {
|
|||
}
|
||||
|
||||
class IconUtils {
|
||||
static Widget getAlbumIcon(BuildContext context, String albumDirectory) {
|
||||
switch (androidFileUtils.getAlbumType(albumDirectory)) {
|
||||
static Widget getAlbumIcon({
|
||||
@required BuildContext context,
|
||||
@required String album,
|
||||
double size = 24,
|
||||
}) {
|
||||
switch (androidFileUtils.getAlbumType(album)) {
|
||||
case AlbumType.Camera:
|
||||
return Icon(OMIcons.photoCamera);
|
||||
return Icon(OMIcons.photoCamera, size: size);
|
||||
case AlbumType.Screenshots:
|
||||
case AlbumType.ScreenRecordings:
|
||||
return Icon(OMIcons.smartphone);
|
||||
return Icon(OMIcons.smartphone, size: size);
|
||||
case AlbumType.Download:
|
||||
return Icon(Icons.file_download);
|
||||
return Icon(Icons.file_download, size: size);
|
||||
case AlbumType.App:
|
||||
return Selector<MediaQueryData, double>(
|
||||
selector: (c, mq) => mq.devicePixelRatio,
|
||||
builder: (c, devicePixelRatio, child) => AppIcon(
|
||||
packageName: androidFileUtils.getAlbumAppPackageName(albumDirectory),
|
||||
size: IconTheme.of(context).size,
|
||||
devicePixelRatio: devicePixelRatio,
|
||||
return Image(
|
||||
image: AppIconImage(
|
||||
packageName: androidFileUtils.getAlbumAppPackageName(album),
|
||||
size: size,
|
||||
),
|
||||
width: size,
|
||||
height: size,
|
||||
);
|
||||
case AlbumType.Default:
|
||||
default:
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
import 'dart:typed_data';
|
||||
import 'dart:ui' as ui show Codec;
|
||||
|
||||
import 'package:aves/utils/android_app_service.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AppIconImage extends ImageProvider<AppIconImageKey> {
|
||||
const AppIconImage({
|
||||
@required this.packageName,
|
||||
@required this.size,
|
||||
this.scale = 1.0,
|
||||
}) : assert(packageName != null),
|
||||
assert(scale != null);
|
||||
|
||||
final String packageName;
|
||||
final double size;
|
||||
final double scale;
|
||||
|
||||
@override
|
||||
Future<AppIconImageKey> obtainKey(ImageConfiguration configuration) {
|
||||
return SynchronousFuture<AppIconImageKey>(AppIconImageKey(
|
||||
packageName: packageName,
|
||||
sizePixels: (size * configuration.devicePixelRatio).round(),
|
||||
scale: scale,
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
ImageStreamCompleter load(AppIconImageKey key, DecoderCallback decode) {
|
||||
return MultiFrameImageStreamCompleter(
|
||||
codec: _loadAsync(key, decode),
|
||||
scale: key.scale,
|
||||
informationCollector: () sync* {
|
||||
yield ErrorDescription('uri=$packageName, size=$size');
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<ui.Codec> _loadAsync(AppIconImageKey key, DecoderCallback decode) async {
|
||||
final Uint8List bytes = await AndroidAppService.getAppIcon(key.packageName, key.sizePixels);
|
||||
if (bytes.lengthInBytes == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await decode(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
class AppIconImageKey {
|
||||
final String packageName;
|
||||
final int sizePixels;
|
||||
final double scale;
|
||||
|
||||
const AppIconImageKey({
|
||||
@required this.packageName,
|
||||
@required this.sizePixels,
|
||||
this.scale,
|
||||
});
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (other.runtimeType != runtimeType) return false;
|
||||
return other is AppIconImageKey && other.packageName == packageName && other.sizePixels == sizePixels && other.scale == scale;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => hashValues(packageName, sizePixels, scale);
|
||||
}
|
|
@ -47,7 +47,7 @@ class UriImage extends ImageProvider<UriImage> {
|
|||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (other.runtimeType != runtimeType) return false;
|
||||
return other is UriImage && other.uri == uri && other.scale == scale;
|
||||
return other is UriImage && other.uri == uri && other.mimeType == mimeType && other.scale == scale;
|
||||
}
|
||||
|
||||
@override
|
|
@ -4,9 +4,10 @@ import 'package:aves/model/image_metadata.dart';
|
|||
import 'package:aves/model/metadata_db.dart';
|
||||
import 'package:aves/model/settings.dart';
|
||||
import 'package:aves/utils/file_utils.dart';
|
||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||
import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
class DebugPage extends StatefulWidget {
|
||||
final CollectionSource source;
|
||||
|
@ -86,6 +87,9 @@ class DebugPageState extends State<DebugPage> {
|
|||
},
|
||||
),
|
||||
const Divider(),
|
||||
Text('Image cache: ${imageCache.currentSize} items, ${formatFilesize(imageCache.currentSizeBytes)}'),
|
||||
Text('SVG cache: ${PictureProvider.cacheCount} items'),
|
||||
const Divider(),
|
||||
const Text('Time dilation'),
|
||||
Slider(
|
||||
value: timeDilation,
|
||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:io';
|
|||
import 'package:aves/model/collection_lens.dart';
|
||||
import 'package:aves/model/image_entry.dart';
|
||||
import 'package:aves/utils/android_app_service.dart';
|
||||
import 'package:aves/widgets/common/image_providers/uri_image_provider.dart';
|
||||
import 'package:flushbar/flushbar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pdf/widgets.dart' as pdf;
|
||||
|
@ -77,7 +78,7 @@ class FullscreenActionDelegate {
|
|||
final doc = pdf.Document(title: entry.title);
|
||||
final image = await pdfImageFromImageProvider(
|
||||
pdf: doc.document,
|
||||
image: FileImage(File(entry.path)),
|
||||
image: UriImage(uri: entry.uri, mimeType: entry.mimeType),
|
||||
);
|
||||
doc.addPage(pdf.Page(build: (context) => pdf.Center(child: pdf.Image(image)))); // Page
|
||||
unawaited(Printing.layoutPdf(
|
||||
|
|
|
@ -3,13 +3,14 @@ import 'dart:math';
|
|||
|
||||
import 'package:aves/model/collection_lens.dart';
|
||||
import 'package:aves/model/image_entry.dart';
|
||||
import 'package:aves/widgets/common/image_providers/uri_image_provider.dart';
|
||||
import 'package:aves/widgets/fullscreen/fullscreen_action_delegate.dart';
|
||||
import 'package:aves/widgets/fullscreen/image_page.dart';
|
||||
import 'package:aves/widgets/fullscreen/info/info_page.dart';
|
||||
import 'package:aves/widgets/fullscreen/overlay/bottom.dart';
|
||||
import 'package:aves/widgets/fullscreen/overlay/top.dart';
|
||||
import 'package:aves/widgets/fullscreen/overlay/video.dart';
|
||||
import 'package:aves/widgets/fullscreen/uri_image_provider.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
@ -126,14 +127,14 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
|
|||
if (_currentVerticalPage.value == infoPage) {
|
||||
// back from info to image
|
||||
_goToVerticalPage(imagePage);
|
||||
return Future.value(false);
|
||||
return SynchronousFuture(false);
|
||||
}
|
||||
if (!ModalRoute.of(context).canPop) {
|
||||
// exit app when trying to pop a fullscreen page that is a viewer for a single entry
|
||||
exit(0);
|
||||
}
|
||||
_onLeave();
|
||||
return Future.value(true);
|
||||
return SynchronousFuture(true);
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:aves/model/collection_lens.dart';
|
||||
import 'package:aves/model/image_entry.dart';
|
||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||
import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart';
|
||||
import 'package:aves/widgets/fullscreen/fullscreen_body.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:aves/model/image_entry.dart';
|
||||
import 'package:aves/utils/constants.dart';
|
||||
import 'package:aves/widgets/fullscreen/uri_image_provider.dart';
|
||||
import 'package:aves/widgets/fullscreen/uri_picture_provider.dart';
|
||||
import 'package:aves/widgets/common/image_providers/uri_image_provider.dart';
|
||||
import 'package:aves/widgets/common/image_providers/uri_picture_provider.dart';
|
||||
import 'package:aves/widgets/fullscreen/video.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
|
|
@ -3,7 +3,7 @@ import 'package:aves/model/collection_lens.dart';
|
|||
import 'package:aves/model/image_entry.dart';
|
||||
import 'package:aves/widgets/album/collection_page.dart';
|
||||
import 'package:aves/widgets/common/aves_filter_chip.dart';
|
||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||
import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart';
|
||||
import 'package:aves/widgets/fullscreen/info/basic_section.dart';
|
||||
import 'package:aves/widgets/fullscreen/info/location_section.dart';
|
||||
import 'package:aves/widgets/fullscreen/info/metadata_section.dart';
|
||||
|
@ -155,11 +155,12 @@ class InfoPageState extends State<InfoPage> {
|
|||
|
||||
void _goToFilteredCollection(CollectionFilter filter) {
|
||||
if (collection == null) return;
|
||||
Navigator.push(
|
||||
Navigator.pushAndRemoveUntil(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => CollectionPage(collection.derive(filter)),
|
||||
),
|
||||
(route) => false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:aves/model/collection_lens.dart';
|
||||
import 'package:aves/model/image_entry.dart';
|
||||
import 'package:aves/utils/color_utils.dart';
|
||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||
import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart';
|
||||
import 'package:charts_flutter/flutter.dart' as charts;
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
|
|
@ -232,6 +232,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.1"
|
||||
palette_generator:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: palette_generator
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.2"
|
||||
path:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
|
@ -40,6 +40,7 @@ dependencies:
|
|||
outline_material_icons:
|
||||
path:
|
||||
pdf:
|
||||
palette_generator:
|
||||
pedantic:
|
||||
percent_indicator:
|
||||
permission_handler:
|
||||
|
|
Loading…
Reference in a new issue