diff --git a/lib/utils/android_file_utils.dart b/lib/utils/android_file_utils.dart index 6d7cbe8db..9b2f6a8bf 100644 --- a/lib/utils/android_file_utils.dart +++ b/lib/utils/android_file_utils.dart @@ -32,6 +32,10 @@ class AndroidFileUtils { bool isDownloadPath(String path) => path == downloadPath; + StorageVolume getStorageVolume(String path) => storageVolumes.firstWhere((v) => path.startsWith(v.path), orElse: () => null); + + bool isOnSD(String path) => getStorageVolume(path).isRemovable; + AlbumType getAlbumType(String albumDirectory) { if (albumDirectory != null) { if (androidFileUtils.isCameraPath(albumDirectory)) return AlbumType.Camera; diff --git a/lib/widgets/album/collection_drawer.dart b/lib/widgets/album/collection_drawer.dart index 241c1d73d..b60dda7af 100644 --- a/lib/widgets/album/collection_drawer.dart +++ b/lib/widgets/album/collection_drawer.dart @@ -93,13 +93,23 @@ class _CollectionDrawerState extends State { title: 'Favourites', filter: FavouriteFilter(), ); - final buildAlbumEntry = (String album) => _FilteredCollectionNavTile( - source: source, - leading: IconUtils.getAlbumIcon(context: context, album: album), - title: source.getUniqueAlbumName(album), - dense: true, - filter: AlbumFilter(album, source.getUniqueAlbumName(album)), - ); + final buildAlbumEntry = (String album) { + final uniqueName = source.getUniqueAlbumName(album); + return _FilteredCollectionNavTile( + source: source, + leading: IconUtils.getAlbumIcon(context: context, album: album), + title: uniqueName, + trailing: androidFileUtils.isOnSD(album) + ? const Icon( + OMIcons.sdStorage, + size: 16, + color: Colors.grey, + ) + : null, + dense: true, + filter: AlbumFilter(album, uniqueName), + ); + }; final buildTagEntry = (String tag) => _FilteredCollectionNavTile( source: source, leading: Icon( @@ -311,6 +321,7 @@ class _FilteredCollectionNavTile extends StatelessWidget { final CollectionSource source; final Widget leading; final String title; + final Widget trailing; final bool dense; final CollectionFilter filter; @@ -318,6 +329,7 @@ class _FilteredCollectionNavTile extends StatelessWidget { @required this.source, @required this.leading, @required this.title, + this.trailing, bool dense, @required this.filter, }) : dense = dense ?? false; @@ -330,6 +342,7 @@ class _FilteredCollectionNavTile extends StatelessWidget { child: ListTile( leading: leading, title: Text(title), + trailing: trailing, dense: dense, onTap: () => _goToCollection(context), ), diff --git a/lib/widgets/album/grid/header_album.dart b/lib/widgets/album/grid/header_album.dart index b1462fc35..30879da43 100644 --- a/lib/widgets/album/grid/header_album.dart +++ b/lib/widgets/album/grid/header_album.dart @@ -1,6 +1,8 @@ +import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/widgets/album/grid/header_generic.dart'; import 'package:aves/widgets/common/icons.dart'; import 'package:flutter/material.dart'; +import 'package:outline_material_icons/outline_material_icons.dart'; class AlbumSectionHeader extends StatelessWidget { final String folderPath, albumName; @@ -26,6 +28,13 @@ class AlbumSectionHeader extends StatelessWidget { return TitleSectionHeader( leading: albumIcon, title: albumName, + trailing: androidFileUtils.isOnSD(folderPath) + ? const Icon( + OMIcons.sdStorage, + size: 16, + color: Color(0xFF757575), + ) + : null, ); } } diff --git a/lib/widgets/album/grid/header_generic.dart b/lib/widgets/album/grid/header_generic.dart index 2600133f8..311c66732 100644 --- a/lib/widgets/album/grid/header_generic.dart +++ b/lib/widgets/album/grid/header_generic.dart @@ -70,17 +70,19 @@ class SectionHeader extends StatelessWidget { var headerExtent = 0.0; if (sectionKey is String) { // only compute height for album headers, as they're the only likely ones to split on multiple lines - final hasIcon = androidFileUtils.getAlbumType(sectionKey) != AlbumType.Default; + final hasLeading = androidFileUtils.getAlbumType(sectionKey) != AlbumType.Default; + final hasTrailing = androidFileUtils.isOnSD(sectionKey); final text = source.getUniqueAlbumName(sectionKey); final maxWidth = scrollableWidth - TitleSectionHeader.padding.horizontal; final para = RenderParagraph( TextSpan( children: [ - if (hasIcon) + if (hasLeading) // `RenderParagraph` fails to lay out `WidgetSpan` offscreen as of Flutter v1.17.0 // so we use a hair space times a magic number to match leading width - // 23 hair spaces match a width of 40.0 - TextSpan(text: '\u200A' * 23), + TextSpan(text: '\u200A' * 23), // 23 hair spaces match a width of 40.0 + if (hasTrailing) + TextSpan(text: '\u200A' * 17), TextSpan( text: text, style: Constants.titleTextStyle, @@ -97,13 +99,19 @@ class SectionHeader extends StatelessWidget { } class TitleSectionHeader extends StatelessWidget { - final Widget leading; + final Widget leading, trailing; final String title; - const TitleSectionHeader({Key key, this.leading, this.title}) : super(key: key); + const TitleSectionHeader({ + Key key, + this.leading, + this.title, + this.trailing, + }) : super(key: key); static const leadingDimension = 32.0; static const leadingPadding = EdgeInsets.only(right: 8, bottom: 4); + static const trailingPadding = EdgeInsets.only(left: 8, bottom: 4); static const padding = EdgeInsets.all(16); @override @@ -122,6 +130,12 @@ class TitleSectionHeader extends StatelessWidget { ) : null, text: title, + trailingBuilder: trailing != null + ? (context, isShadow) => Container( + padding: trailingPadding, + child: isShadow ? null : trailing, + ) + : null, style: Constants.titleTextStyle, outlineWidth: 2, ), diff --git a/lib/widgets/common/fx/outlined_text.dart b/lib/widgets/common/fx/outlined_text.dart index c03e36bd9..1c232d0eb 100644 --- a/lib/widgets/common/fx/outlined_text.dart +++ b/lib/widgets/common/fx/outlined_text.dart @@ -3,18 +3,19 @@ import 'package:flutter/material.dart'; typedef OutlinedWidgetBuilder = Widget Function(BuildContext context, bool isShadow); class OutlinedText extends StatelessWidget { - final OutlinedWidgetBuilder leadingBuilder; + final OutlinedWidgetBuilder leadingBuilder, trailingBuilder; final String text; final TextStyle style; final double outlineWidth; final Color outlineColor; - static const leadingAlignment = PlaceholderAlignment.middle; + static const widgetSpanAlignment = PlaceholderAlignment.middle; const OutlinedText({ Key key, this.leadingBuilder, @required this.text, + this.trailingBuilder, @required this.style, double outlineWidth, Color outlineColor, @@ -31,7 +32,7 @@ class OutlinedText extends StatelessWidget { children: [ if (leadingBuilder != null) WidgetSpan( - alignment: leadingAlignment, + alignment: widgetSpanAlignment, child: leadingBuilder(context, true), ), TextSpan( @@ -43,6 +44,11 @@ class OutlinedText extends StatelessWidget { ..color = outlineColor, ), ), + if (trailingBuilder != null) + WidgetSpan( + alignment: widgetSpanAlignment, + child: trailingBuilder(context, true), + ), ], ), ), @@ -51,13 +57,18 @@ class OutlinedText extends StatelessWidget { children: [ if (leadingBuilder != null) WidgetSpan( - alignment: leadingAlignment, + alignment: widgetSpanAlignment, child: leadingBuilder(context, false), ), TextSpan( text: text, style: style, ), + if (trailingBuilder != null) + WidgetSpan( + alignment: widgetSpanAlignment, + child: trailingBuilder(context, false), + ), ], ), ), diff --git a/lib/widgets/debug_page.dart b/lib/widgets/debug_page.dart index a88230036..7d3bdde33 100644 --- a/lib/widgets/debug_page.dart +++ b/lib/widgets/debug_page.dart @@ -50,7 +50,7 @@ class DebugPageState extends State { padding: const EdgeInsets.all(8), children: [ const Text('Storage'), - ...AndroidFileUtils.storageVolumes.map((v) => Text('${v.description}: ${v.path} (removable: ${v.isRemovable}))')), + ...AndroidFileUtils.storageVolumes.map((v) => Text('${v.description}: ${v.path} (removable: ${v.isRemovable})')), const Divider(), Row( children: [