SD card storage indicator in drawer and section headers

This commit is contained in:
Thibault Deckers 2020-04-13 14:32:18 +09:00
parent 23eac7c3c7
commit 53dfe85e07
6 changed files with 69 additions and 18 deletions

View file

@ -32,6 +32,10 @@ class AndroidFileUtils {
bool isDownloadPath(String path) => path == downloadPath; 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) { AlbumType getAlbumType(String albumDirectory) {
if (albumDirectory != null) { if (albumDirectory != null) {
if (androidFileUtils.isCameraPath(albumDirectory)) return AlbumType.Camera; if (androidFileUtils.isCameraPath(albumDirectory)) return AlbumType.Camera;

View file

@ -93,13 +93,23 @@ class _CollectionDrawerState extends State<CollectionDrawer> {
title: 'Favourites', title: 'Favourites',
filter: FavouriteFilter(), filter: FavouriteFilter(),
); );
final buildAlbumEntry = (String album) => _FilteredCollectionNavTile( final buildAlbumEntry = (String album) {
final uniqueName = source.getUniqueAlbumName(album);
return _FilteredCollectionNavTile(
source: source, source: source,
leading: IconUtils.getAlbumIcon(context: context, album: album), leading: IconUtils.getAlbumIcon(context: context, album: album),
title: source.getUniqueAlbumName(album), title: uniqueName,
trailing: androidFileUtils.isOnSD(album)
? const Icon(
OMIcons.sdStorage,
size: 16,
color: Colors.grey,
)
: null,
dense: true, dense: true,
filter: AlbumFilter(album, source.getUniqueAlbumName(album)), filter: AlbumFilter(album, uniqueName),
); );
};
final buildTagEntry = (String tag) => _FilteredCollectionNavTile( final buildTagEntry = (String tag) => _FilteredCollectionNavTile(
source: source, source: source,
leading: Icon( leading: Icon(
@ -311,6 +321,7 @@ class _FilteredCollectionNavTile extends StatelessWidget {
final CollectionSource source; final CollectionSource source;
final Widget leading; final Widget leading;
final String title; final String title;
final Widget trailing;
final bool dense; final bool dense;
final CollectionFilter filter; final CollectionFilter filter;
@ -318,6 +329,7 @@ class _FilteredCollectionNavTile extends StatelessWidget {
@required this.source, @required this.source,
@required this.leading, @required this.leading,
@required this.title, @required this.title,
this.trailing,
bool dense, bool dense,
@required this.filter, @required this.filter,
}) : dense = dense ?? false; }) : dense = dense ?? false;
@ -330,6 +342,7 @@ class _FilteredCollectionNavTile extends StatelessWidget {
child: ListTile( child: ListTile(
leading: leading, leading: leading,
title: Text(title), title: Text(title),
trailing: trailing,
dense: dense, dense: dense,
onTap: () => _goToCollection(context), onTap: () => _goToCollection(context),
), ),

View file

@ -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/album/grid/header_generic.dart';
import 'package:aves/widgets/common/icons.dart'; import 'package:aves/widgets/common/icons.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:outline_material_icons/outline_material_icons.dart';
class AlbumSectionHeader extends StatelessWidget { class AlbumSectionHeader extends StatelessWidget {
final String folderPath, albumName; final String folderPath, albumName;
@ -26,6 +28,13 @@ class AlbumSectionHeader extends StatelessWidget {
return TitleSectionHeader( return TitleSectionHeader(
leading: albumIcon, leading: albumIcon,
title: albumName, title: albumName,
trailing: androidFileUtils.isOnSD(folderPath)
? const Icon(
OMIcons.sdStorage,
size: 16,
color: Color(0xFF757575),
)
: null,
); );
} }
} }

View file

@ -70,17 +70,19 @@ class SectionHeader extends StatelessWidget {
var headerExtent = 0.0; var headerExtent = 0.0;
if (sectionKey is String) { if (sectionKey is String) {
// only compute height for album headers, as they're the only likely ones to split on multiple lines // 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 text = source.getUniqueAlbumName(sectionKey);
final maxWidth = scrollableWidth - TitleSectionHeader.padding.horizontal; final maxWidth = scrollableWidth - TitleSectionHeader.padding.horizontal;
final para = RenderParagraph( final para = RenderParagraph(
TextSpan( TextSpan(
children: [ children: [
if (hasIcon) if (hasLeading)
// `RenderParagraph` fails to lay out `WidgetSpan` offscreen as of Flutter v1.17.0 // `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 // 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), // 23 hair spaces match a width of 40.0
TextSpan(text: '\u200A' * 23), if (hasTrailing)
TextSpan(text: '\u200A' * 17),
TextSpan( TextSpan(
text: text, text: text,
style: Constants.titleTextStyle, style: Constants.titleTextStyle,
@ -97,13 +99,19 @@ class SectionHeader extends StatelessWidget {
} }
class TitleSectionHeader extends StatelessWidget { class TitleSectionHeader extends StatelessWidget {
final Widget leading; final Widget leading, trailing;
final String title; 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 leadingDimension = 32.0;
static const leadingPadding = EdgeInsets.only(right: 8, bottom: 4); static const leadingPadding = EdgeInsets.only(right: 8, bottom: 4);
static const trailingPadding = EdgeInsets.only(left: 8, bottom: 4);
static const padding = EdgeInsets.all(16); static const padding = EdgeInsets.all(16);
@override @override
@ -122,6 +130,12 @@ class TitleSectionHeader extends StatelessWidget {
) )
: null, : null,
text: title, text: title,
trailingBuilder: trailing != null
? (context, isShadow) => Container(
padding: trailingPadding,
child: isShadow ? null : trailing,
)
: null,
style: Constants.titleTextStyle, style: Constants.titleTextStyle,
outlineWidth: 2, outlineWidth: 2,
), ),

View file

@ -3,18 +3,19 @@ import 'package:flutter/material.dart';
typedef OutlinedWidgetBuilder = Widget Function(BuildContext context, bool isShadow); typedef OutlinedWidgetBuilder = Widget Function(BuildContext context, bool isShadow);
class OutlinedText extends StatelessWidget { class OutlinedText extends StatelessWidget {
final OutlinedWidgetBuilder leadingBuilder; final OutlinedWidgetBuilder leadingBuilder, trailingBuilder;
final String text; final String text;
final TextStyle style; final TextStyle style;
final double outlineWidth; final double outlineWidth;
final Color outlineColor; final Color outlineColor;
static const leadingAlignment = PlaceholderAlignment.middle; static const widgetSpanAlignment = PlaceholderAlignment.middle;
const OutlinedText({ const OutlinedText({
Key key, Key key,
this.leadingBuilder, this.leadingBuilder,
@required this.text, @required this.text,
this.trailingBuilder,
@required this.style, @required this.style,
double outlineWidth, double outlineWidth,
Color outlineColor, Color outlineColor,
@ -31,7 +32,7 @@ class OutlinedText extends StatelessWidget {
children: [ children: [
if (leadingBuilder != null) if (leadingBuilder != null)
WidgetSpan( WidgetSpan(
alignment: leadingAlignment, alignment: widgetSpanAlignment,
child: leadingBuilder(context, true), child: leadingBuilder(context, true),
), ),
TextSpan( TextSpan(
@ -43,6 +44,11 @@ class OutlinedText extends StatelessWidget {
..color = outlineColor, ..color = outlineColor,
), ),
), ),
if (trailingBuilder != null)
WidgetSpan(
alignment: widgetSpanAlignment,
child: trailingBuilder(context, true),
),
], ],
), ),
), ),
@ -51,13 +57,18 @@ class OutlinedText extends StatelessWidget {
children: [ children: [
if (leadingBuilder != null) if (leadingBuilder != null)
WidgetSpan( WidgetSpan(
alignment: leadingAlignment, alignment: widgetSpanAlignment,
child: leadingBuilder(context, false), child: leadingBuilder(context, false),
), ),
TextSpan( TextSpan(
text: text, text: text,
style: style, style: style,
), ),
if (trailingBuilder != null)
WidgetSpan(
alignment: widgetSpanAlignment,
child: trailingBuilder(context, false),
),
], ],
), ),
), ),

View file

@ -50,7 +50,7 @@ class DebugPageState extends State<DebugPage> {
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
children: [ children: [
const Text('Storage'), 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(), const Divider(),
Row( Row(
children: [ children: [