#98 info: owner value alignment, empty icon collapse
This commit is contained in:
parent
44ed934a8c
commit
988bc0093e
9 changed files with 183 additions and 185 deletions
|
@ -759,7 +759,7 @@
|
|||
"viewerInfoLabelUri": "URI",
|
||||
"viewerInfoLabelPath": "Path",
|
||||
"viewerInfoLabelDuration": "Duration",
|
||||
"viewerInfoLabelOwner": "Owned by",
|
||||
"viewerInfoLabelOwner": "Owner",
|
||||
"viewerInfoLabelCoordinates": "Coordinates",
|
||||
"viewerInfoLabelAddress": "Address",
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'package:aves/app_mode.dart';
|
||||
import 'package:aves/image_providers/app_icon_image_provider.dart';
|
||||
import 'package:aves/model/actions/entry_info_actions.dart';
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/favourites.dart';
|
||||
|
@ -7,16 +9,19 @@ import 'package:aves/model/filters/mime.dart';
|
|||
import 'package:aves/model/filters/rating.dart';
|
||||
import 'package:aves/model/filters/tag.dart';
|
||||
import 'package:aves/model/filters/type.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/model/source/collection_lens.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:aves/theme/colors.dart';
|
||||
import 'package:aves/theme/format.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:aves/utils/file_utils.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
|
||||
import 'package:aves/widgets/viewer/action/entry_info_action_delegate.dart';
|
||||
import 'package:aves/widgets/viewer/info/common.dart';
|
||||
import 'package:aves/widgets/viewer/info/owner.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
|
@ -36,46 +41,15 @@ class BasicSection extends StatelessWidget {
|
|||
required this.onFilter,
|
||||
});
|
||||
|
||||
int get megaPixels => entry.megaPixels;
|
||||
|
||||
bool get showMegaPixels => entry.isPhoto && megaPixels > 0;
|
||||
|
||||
String get rasterResolutionText => '${entry.resolutionText}${showMegaPixels ? ' • $megaPixels MP' : ''}';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
final infoUnknown = l10n.viewerInfoUnknown;
|
||||
final locale = l10n.localeName;
|
||||
final use24hour = context.select<MediaQueryData, bool>((v) => v.alwaysUse24HourFormat);
|
||||
|
||||
return AnimatedBuilder(
|
||||
animation: entry.metadataChangeNotifier,
|
||||
builder: (context, child) {
|
||||
// TODO TLAD line break on all characters for the following fields when this is fixed: https://github.com/flutter/flutter/issues/61081
|
||||
// inserting ZWSP (\u200B) between characters does help, but it messes with width and height computation (another Flutter issue)
|
||||
final title = entry.bestTitle ?? infoUnknown;
|
||||
final date = entry.bestDate;
|
||||
final dateText = date != null ? formatDateTime(date, locale, use24hour) : infoUnknown;
|
||||
final showResolution = !entry.isSvg && entry.isSized;
|
||||
final sizeText = entry.sizeBytes != null ? formatFileSize(locale, entry.sizeBytes!) : infoUnknown;
|
||||
final path = entry.path;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
InfoRowGroup(
|
||||
info: {
|
||||
l10n.viewerInfoLabelTitle: title,
|
||||
l10n.viewerInfoLabelDate: dateText,
|
||||
if (entry.isVideo) ..._buildVideoRows(context),
|
||||
if (showResolution) l10n.viewerInfoLabelResolution: rasterResolutionText,
|
||||
l10n.viewerInfoLabelSize: sizeText,
|
||||
if (!entry.trashed) l10n.viewerInfoLabelUri: entry.uri,
|
||||
if (path != null) l10n.viewerInfoLabelPath: path,
|
||||
},
|
||||
),
|
||||
if (!entry.trashed) OwnerProp(entry: entry),
|
||||
_BasicInfo(entry: entry),
|
||||
_buildChips(context),
|
||||
_buildEditButtons(context),
|
||||
],
|
||||
|
@ -184,10 +158,130 @@ class BasicSection extends StatelessWidget {
|
|||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _BasicInfo extends StatefulWidget {
|
||||
final AvesEntry entry;
|
||||
|
||||
const _BasicInfo({
|
||||
required this.entry,
|
||||
});
|
||||
|
||||
@override
|
||||
State<_BasicInfo> createState() => _BasicInfoState();
|
||||
}
|
||||
|
||||
class _BasicInfoState extends State<_BasicInfo> {
|
||||
Future<String?> _ownerPackageLoader = SynchronousFuture(null);
|
||||
Future<void> _appNameLoader = SynchronousFuture(null);
|
||||
|
||||
AvesEntry get entry => widget.entry;
|
||||
|
||||
int get megaPixels => entry.megaPixels;
|
||||
|
||||
bool get showMegaPixels => entry.isPhoto && megaPixels > 0;
|
||||
|
||||
String get rasterResolutionText => '${entry.resolutionText}${showMegaPixels ? ' • $megaPixels MP' : ''}';
|
||||
|
||||
static const ownerPackageNamePropKey = 'owner_package_name';
|
||||
static const iconSize = 20.0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (!entry.trashed) {
|
||||
final isMediaContent = entry.uri.startsWith('content://media/external/');
|
||||
if (isMediaContent) {
|
||||
_ownerPackageLoader = metadataFetchService.hasContentResolverProp(ownerPackageNamePropKey).then((exists) {
|
||||
return exists ? metadataFetchService.getContentResolverProp(entry, ownerPackageNamePropKey) : SynchronousFuture(null);
|
||||
});
|
||||
final isViewerMode = context.read<ValueNotifier<AppMode>>().value == AppMode.view;
|
||||
if (isViewerMode && settings.isInstalledAppAccessAllowed) {
|
||||
_appNameLoader = androidFileUtils.initAppNames();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
final infoUnknown = l10n.viewerInfoUnknown;
|
||||
final locale = l10n.localeName;
|
||||
final use24hour = context.select<MediaQueryData, bool>((v) => v.alwaysUse24HourFormat);
|
||||
|
||||
// TODO TLAD line break on all characters for the following fields when this is fixed: https://github.com/flutter/flutter/issues/61081
|
||||
// inserting ZWSP (\u200B) between characters does help, but it messes with width and height computation (another Flutter issue)
|
||||
final title = entry.bestTitle ?? infoUnknown;
|
||||
final date = entry.bestDate;
|
||||
final dateText = date != null ? formatDateTime(date, locale, use24hour) : infoUnknown;
|
||||
final showResolution = !entry.isSvg && entry.isSized;
|
||||
final sizeText = entry.sizeBytes != null ? formatFileSize(locale, entry.sizeBytes!) : infoUnknown;
|
||||
final path = entry.path;
|
||||
|
||||
return FutureBuilder<String?>(
|
||||
future: _ownerPackageLoader,
|
||||
builder: (context, snapshot) {
|
||||
final ownerPackage = snapshot.data;
|
||||
return FutureBuilder<void>(
|
||||
future: _appNameLoader,
|
||||
builder: (context, snapshot) {
|
||||
return InfoRowGroup(
|
||||
info: {
|
||||
l10n.viewerInfoLabelTitle: title,
|
||||
l10n.viewerInfoLabelDate: dateText,
|
||||
if (entry.isVideo) ..._buildVideoRows(context),
|
||||
if (showResolution) l10n.viewerInfoLabelResolution: rasterResolutionText,
|
||||
l10n.viewerInfoLabelSize: sizeText,
|
||||
if (!entry.trashed) l10n.viewerInfoLabelUri: entry.uri,
|
||||
if (path != null) l10n.viewerInfoLabelPath: path,
|
||||
if (ownerPackage != null) l10n.viewerInfoLabelOwner: ownerPackage,
|
||||
},
|
||||
spanBuilders: {
|
||||
l10n.viewerInfoLabelOwner: _ownerHandler(ownerPackage),
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, String> _buildVideoRows(BuildContext context) {
|
||||
return {
|
||||
context.l10n.viewerInfoLabelDuration: entry.durationText,
|
||||
};
|
||||
}
|
||||
|
||||
InfoValueSpanBuilder _ownerHandler(String? ownerPackage) {
|
||||
if (ownerPackage == null) return (context, key, value) => [];
|
||||
|
||||
final appName = androidFileUtils.getCurrentAppName(ownerPackage) ?? ownerPackage;
|
||||
return (context, key, value) => [
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(end: 4),
|
||||
child: ConstrainedBox(
|
||||
// use constraints instead of sizing `Image`,
|
||||
// so that it can collapse when handling an empty image
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: iconSize,
|
||||
maxHeight: iconSize,
|
||||
),
|
||||
child: Image(
|
||||
image: AppIconImage(
|
||||
packageName: ownerPackage,
|
||||
size: iconSize,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: appName,
|
||||
style: InfoRowGroup.valueStyle,
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ class SectionRow extends StatelessWidget {
|
|||
class InfoRowGroup extends StatefulWidget {
|
||||
final Map<String, String> info;
|
||||
final int maxValueLength;
|
||||
final Map<String, InfoLinkHandler>? linkHandlers;
|
||||
final Map<String, InfoValueSpanBuilder> spanBuilders;
|
||||
|
||||
static const keyValuePadding = 16;
|
||||
static const fontSize = 13.0;
|
||||
|
@ -56,11 +56,28 @@ class InfoRowGroup extends StatefulWidget {
|
|||
super.key,
|
||||
required this.info,
|
||||
this.maxValueLength = 0,
|
||||
this.linkHandlers,
|
||||
});
|
||||
Map<String, InfoValueSpanBuilder>? spanBuilders,
|
||||
}) : spanBuilders = spanBuilders ?? const {};
|
||||
|
||||
@override
|
||||
State<InfoRowGroup> createState() => _InfoRowGroupState();
|
||||
|
||||
static InfoValueSpanBuilder linkSpanBuilder({
|
||||
required String Function(BuildContext context) linkText,
|
||||
required void Function(BuildContext context) onTap,
|
||||
}) {
|
||||
return (context, key, value) {
|
||||
value = linkText(context);
|
||||
// open link on tap
|
||||
final recognizer = TapGestureRecognizer()..onTap = () => onTap(context);
|
||||
// `colorScheme.secondary` is overridden upstream as an `ExpansionTileCard` theming workaround,
|
||||
// so we use `colorScheme.primary` instead
|
||||
final linkColor = Theme.of(context).colorScheme.primary;
|
||||
final style = InfoRowGroup.valueStyle.copyWith(color: linkColor, decoration: TextDecoration.underline);
|
||||
|
||||
return [TextSpan(text: '${Constants.fsi}$value${Constants.pdi}', style: style, recognizer: recognizer)];
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class _InfoRowGroupState extends State<InfoRowGroup> {
|
||||
|
@ -70,7 +87,7 @@ class _InfoRowGroupState extends State<InfoRowGroup> {
|
|||
|
||||
int get maxValueLength => widget.maxValueLength;
|
||||
|
||||
Map<String, InfoLinkHandler>? get linkHandlers => widget.linkHandlers;
|
||||
Map<String, InfoValueSpanBuilder> get spanBuilders => widget.spanBuilders;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -94,34 +111,8 @@ class _InfoRowGroupState extends State<InfoRowGroup> {
|
|||
children: keyValues.entries.expand(
|
||||
(kv) {
|
||||
final key = kv.key;
|
||||
String value;
|
||||
TextStyle? style;
|
||||
GestureRecognizer? recognizer;
|
||||
|
||||
if (linkHandlers?.containsKey(key) == true) {
|
||||
final handler = linkHandlers![key]!;
|
||||
value = handler.linkText(context);
|
||||
// open link on tap
|
||||
recognizer = TapGestureRecognizer()..onTap = () => handler.onTap(context);
|
||||
// `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 {
|
||||
value = kv.value;
|
||||
// long values are clipped, and made expandable by tapping them
|
||||
final showPreviewOnly = maxValueLength > 0 && value.length > maxValueLength && !_expandedKeys.contains(key);
|
||||
if (showPreviewOnly) {
|
||||
value = '${value.substring(0, maxValueLength)}…';
|
||||
// show full value on tap
|
||||
recognizer = TapGestureRecognizer()..onTap = () => setState(() => _expandedKeys.add(key));
|
||||
}
|
||||
}
|
||||
|
||||
if (key != lastKey) {
|
||||
value = '$value\n';
|
||||
}
|
||||
|
||||
final value = kv.value;
|
||||
final spanBuilder = spanBuilders[key] ?? _buildTextValueSpans;
|
||||
final thisSpaceSize = max(0.0, (baseValueX - keySizes[key]!)) + InfoRowGroup.keyValuePadding;
|
||||
|
||||
// each text span embeds and pops a Bidi isolate,
|
||||
|
@ -138,7 +129,8 @@ class _InfoRowGroupState extends State<InfoRowGroup> {
|
|||
child: const Text(''),
|
||||
),
|
||||
),
|
||||
TextSpan(text: '${Constants.fsi}$value${Constants.pdi}', style: style, recognizer: recognizer),
|
||||
...spanBuilder(context, key, value),
|
||||
if (key != lastKey) const TextSpan(text: '\n'),
|
||||
];
|
||||
},
|
||||
).toList(),
|
||||
|
@ -157,14 +149,20 @@ class _InfoRowGroupState extends State<InfoRowGroup> {
|
|||
)..layout(const BoxConstraints(), parentUsesSize: true);
|
||||
return para.getMaxIntrinsicWidth(double.infinity);
|
||||
}
|
||||
|
||||
List<InlineSpan> _buildTextValueSpans(BuildContext context, String key, String value) {
|
||||
GestureRecognizer? recognizer;
|
||||
|
||||
// long values are clipped, and made expandable by tapping them
|
||||
final showPreviewOnly = maxValueLength > 0 && value.length > maxValueLength && !_expandedKeys.contains(key);
|
||||
if (showPreviewOnly) {
|
||||
value = '${value.substring(0, maxValueLength)}…';
|
||||
// show full value on tap
|
||||
recognizer = TapGestureRecognizer()..onTap = () => setState(() => _expandedKeys.add(key));
|
||||
}
|
||||
|
||||
return [TextSpan(text: '${Constants.fsi}$value${Constants.pdi}', recognizer: recognizer)];
|
||||
}
|
||||
}
|
||||
|
||||
class InfoLinkHandler {
|
||||
final String Function(BuildContext context) linkText;
|
||||
final void Function(BuildContext context) onTap;
|
||||
|
||||
const InfoLinkHandler({
|
||||
required this.linkText,
|
||||
required this.onTap,
|
||||
});
|
||||
}
|
||||
typedef InfoValueSpanBuilder = List<InlineSpan> Function(BuildContext context, String key, String value);
|
||||
|
|
|
@ -50,7 +50,7 @@ class MetadataDirTile extends StatelessWidget {
|
|||
initiallyExpanded: initiallyExpanded,
|
||||
);
|
||||
} else {
|
||||
Map<String, InfoLinkHandler>? linkHandlers;
|
||||
Map<String, InfoValueSpanBuilder>? linkHandlers;
|
||||
switch (dirName) {
|
||||
case SvgMetadataService.metadataDirectory:
|
||||
linkHandlers = getSvgLinkHandlers(tags);
|
||||
|
@ -79,7 +79,7 @@ class MetadataDirTile extends StatelessWidget {
|
|||
child: InfoRowGroup(
|
||||
info: tags,
|
||||
maxValueLength: Constants.infoGroupMaxValueLength,
|
||||
linkHandlers: linkHandlers,
|
||||
spanBuilders: linkHandlers,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -87,9 +87,9 @@ class MetadataDirTile extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
static Map<String, InfoLinkHandler> getSvgLinkHandlers(SplayTreeMap<String, String> tags) {
|
||||
static Map<String, InfoValueSpanBuilder> getSvgLinkHandlers(SplayTreeMap<String, String> tags) {
|
||||
return {
|
||||
'Metadata': InfoLinkHandler(
|
||||
'Metadata': InfoRowGroup.linkSpanBuilder(
|
||||
linkText: (context) => context.l10n.viewerInfoViewXmlLinkText,
|
||||
onTap: (context) {
|
||||
Navigator.push(
|
||||
|
@ -106,9 +106,9 @@ class MetadataDirTile extends StatelessWidget {
|
|||
};
|
||||
}
|
||||
|
||||
static Map<String, InfoLinkHandler> getVideoCoverLinkHandlers(SplayTreeMap<String, String> tags) {
|
||||
static Map<String, InfoValueSpanBuilder> getVideoCoverLinkHandlers(SplayTreeMap<String, String> tags) {
|
||||
return {
|
||||
'Image': InfoLinkHandler(
|
||||
'Image': InfoRowGroup.linkSpanBuilder(
|
||||
linkText: (context) => context.l10n.viewerInfoOpenLinkText,
|
||||
onTap: (context) => OpenEmbeddedDataNotification.videoCover().dispatch(context),
|
||||
),
|
||||
|
|
|
@ -145,7 +145,7 @@ class XmpNamespace extends Equatable {
|
|||
InfoRowGroup(
|
||||
info: Map.fromEntries(props.map((prop) => MapEntry(prop.displayKey, formatValue(prop)))),
|
||||
maxValueLength: Constants.infoGroupMaxValueLength,
|
||||
linkHandlers: linkifyValues(props),
|
||||
spanBuilders: linkifyValues(props),
|
||||
),
|
||||
...buildFromExtractedData(),
|
||||
];
|
||||
|
@ -194,7 +194,7 @@ class XmpNamespace extends Equatable {
|
|||
|
||||
String formatValue(XmpProp prop) => prop.value;
|
||||
|
||||
Map<String, InfoLinkHandler> linkifyValues(List<XmpProp> props) => {};
|
||||
Map<String, InfoValueSpanBuilder> linkifyValues(List<XmpProp> props) => {};
|
||||
}
|
||||
|
||||
class XmpProp {
|
||||
|
|
|
@ -13,7 +13,7 @@ abstract class XmpGoogleNamespace extends XmpNamespace {
|
|||
List<Tuple2<String, String>> get dataProps;
|
||||
|
||||
@override
|
||||
Map<String, InfoLinkHandler> linkifyValues(List<XmpProp> props) {
|
||||
Map<String, InfoValueSpanBuilder> linkifyValues(List<XmpProp> props) {
|
||||
return Map.fromEntries(dataProps.map((t) {
|
||||
final dataPropPath = t.item1;
|
||||
final mimePropPath = t.item2;
|
||||
|
@ -22,7 +22,7 @@ abstract class XmpGoogleNamespace extends XmpNamespace {
|
|||
return (dataProp != null && mimeProp != null)
|
||||
? MapEntry(
|
||||
dataProp.displayKey,
|
||||
InfoLinkHandler(
|
||||
InfoRowGroup.linkSpanBuilder(
|
||||
linkText: (context) => context.l10n.viewerInfoOpenLinkText,
|
||||
onTap: (context) => OpenEmbeddedDataNotification.xmp(
|
||||
propPath: dataProp.path,
|
||||
|
|
|
@ -29,7 +29,7 @@ class XmpBasicNamespace extends XmpNamespace {
|
|||
final struct = thumbnails[index]!;
|
||||
return {
|
||||
if (struct.containsKey(thumbnailDataDisplayKey))
|
||||
thumbnailDataDisplayKey: InfoLinkHandler(
|
||||
thumbnailDataDisplayKey: InfoRowGroup.linkSpanBuilder(
|
||||
linkText: (context) => context.l10n.viewerInfoOpenLinkText,
|
||||
onTap: (context) => OpenEmbeddedDataNotification.xmp(
|
||||
propPath: 'xmp:Thumbnails[$index]/xmpGImg:image',
|
||||
|
|
|
@ -13,7 +13,7 @@ import 'package:flutter/material.dart';
|
|||
class XmpStructArrayCard extends StatefulWidget {
|
||||
final String title;
|
||||
late final List<Map<String, String>> structs;
|
||||
final Map<String, InfoLinkHandler> Function(int index)? linkifier;
|
||||
final Map<String, InfoValueSpanBuilder> Function(int index)? linkifier;
|
||||
|
||||
XmpStructArrayCard({
|
||||
super.key,
|
||||
|
@ -95,7 +95,7 @@ class _XmpStructArrayCardState extends State<XmpStructArrayCard> {
|
|||
child: InfoRowGroup(
|
||||
info: structs[_index],
|
||||
maxValueLength: Constants.infoGroupMaxValueLength,
|
||||
linkHandlers: widget.linkifier?.call(_index + 1),
|
||||
spanBuilders: widget.linkifier?.call(_index + 1),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -108,7 +108,7 @@ class _XmpStructArrayCardState extends State<XmpStructArrayCard> {
|
|||
class XmpStructCard extends StatelessWidget {
|
||||
final String title;
|
||||
final Map<String, String> struct;
|
||||
final Map<String, InfoLinkHandler> Function()? linkifier;
|
||||
final Map<String, InfoValueSpanBuilder> Function()? linkifier;
|
||||
|
||||
static const cardMargin = EdgeInsets.symmetric(vertical: 8, horizontal: 0);
|
||||
|
||||
|
@ -137,7 +137,7 @@ class XmpStructCard extends StatelessWidget {
|
|||
InfoRowGroup(
|
||||
info: struct,
|
||||
maxValueLength: Constants.infoGroupMaxValueLength,
|
||||
linkHandlers: linkifier?.call(),
|
||||
spanBuilders: linkifier?.call(),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -1,94 +0,0 @@
|
|||
import 'package:aves/app_mode.dart';
|
||||
import 'package:aves/image_providers/app_icon_image_provider.dart';
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/viewer/info/common.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class OwnerProp extends StatefulWidget {
|
||||
final AvesEntry entry;
|
||||
|
||||
const OwnerProp({
|
||||
super.key,
|
||||
required this.entry,
|
||||
});
|
||||
|
||||
@override
|
||||
State<OwnerProp> createState() => _OwnerPropState();
|
||||
}
|
||||
|
||||
class _OwnerPropState extends State<OwnerProp> {
|
||||
Future<String?> _ownerPackageLoader = SynchronousFuture(null);
|
||||
Future<void> _appNameLoader = SynchronousFuture(null);
|
||||
|
||||
AvesEntry get entry => widget.entry;
|
||||
|
||||
static const ownerPackageNamePropKey = 'owner_package_name';
|
||||
static const iconSize = 20.0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final isMediaContent = entry.uri.startsWith('content://media/external/');
|
||||
if (isMediaContent) {
|
||||
_ownerPackageLoader = metadataFetchService.hasContentResolverProp(ownerPackageNamePropKey).then((exists) {
|
||||
return exists ? metadataFetchService.getContentResolverProp(entry, ownerPackageNamePropKey) : SynchronousFuture(null);
|
||||
});
|
||||
final isViewerMode = context.read<ValueNotifier<AppMode>>().value == AppMode.view;
|
||||
if (isViewerMode && settings.isInstalledAppAccessAllowed) {
|
||||
_appNameLoader = androidFileUtils.initAppNames();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder<String?>(
|
||||
future: _ownerPackageLoader,
|
||||
builder: (context, snapshot) {
|
||||
final ownerPackage = snapshot.data;
|
||||
if (ownerPackage == null) return const SizedBox();
|
||||
|
||||
return FutureBuilder<void>(
|
||||
future: _appNameLoader,
|
||||
builder: (context, snapshot) {
|
||||
final appName = androidFileUtils.getCurrentAppName(ownerPackage) ?? ownerPackage;
|
||||
return SelectableText.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: context.l10n.viewerInfoLabelOwner,
|
||||
style: InfoRowGroup.keyStyle(context),
|
||||
),
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Image(
|
||||
image: AppIconImage(
|
||||
packageName: ownerPackage,
|
||||
size: iconSize,
|
||||
),
|
||||
width: iconSize,
|
||||
height: iconSize,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: appName,
|
||||
style: InfoRowGroup.valueStyle,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue