diff --git a/android/app/src/main/java/deckers/thibault/aves/MainActivity.java b/android/app/src/main/java/deckers/thibault/aves/MainActivity.java index 2ca377c26..25a47efa2 100644 --- a/android/app/src/main/java/deckers/thibault/aves/MainActivity.java +++ b/android/app/src/main/java/deckers/thibault/aves/MainActivity.java @@ -6,6 +6,7 @@ import android.os.Build; import android.os.Bundle; import android.util.Log; +import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; import androidx.core.content.pm.ShortcutInfoCompat; import androidx.core.content.pm.ShortcutManagerCompat; @@ -25,19 +26,23 @@ import deckers.thibault.aves.channel.calls.MetadataHandler; import deckers.thibault.aves.channel.calls.StorageHandler; import deckers.thibault.aves.channel.streams.ImageByteStreamHandler; import deckers.thibault.aves.channel.streams.ImageOpStreamHandler; +import deckers.thibault.aves.channel.streams.IntentStreamHandler; import deckers.thibault.aves.channel.streams.MediaStoreStreamHandler; import deckers.thibault.aves.channel.streams.StorageAccessStreamHandler; import deckers.thibault.aves.utils.PermissionManager; import deckers.thibault.aves.utils.Utils; import io.flutter.embedding.android.FlutterActivity; import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.EventChannel; import io.flutter.plugin.common.MethodChannel; public class MainActivity extends FlutterActivity { private static final String LOG_TAG = Utils.createLogTag(MainActivity.class); + public static final String INTENT_CHANNEL = "deckers.thibault/aves/intent"; public static final String VIEWER_CHANNEL = "deckers.thibault/aves/viewer"; + private IntentStreamHandler intentStreamHandler; private Map intentDataMap; @Override @@ -79,6 +84,8 @@ public class MainActivity extends FlutterActivity { finish(); } }); + intentStreamHandler = new IntentStreamHandler(); + new EventChannel(messenger, INTENT_CHANNEL).setStreamHandler(intentStreamHandler); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { setupShortcuts(); @@ -107,6 +114,13 @@ public class MainActivity extends FlutterActivity { ShortcutManagerCompat.setDynamicShortcuts(this, Arrays.asList(videos, search)); } + @Override + protected void onNewIntent(@NonNull Intent intent) { + super.onNewIntent(intent); + handleIntent(intent); + intentStreamHandler.notifyNewIntent(); + } + private void handleIntent(Intent intent) { Log.i(LOG_TAG, "handleIntent intent=" + intent); if (intent == null) return; diff --git a/android/app/src/main/java/deckers/thibault/aves/channel/streams/ImageByteStreamHandler.java b/android/app/src/main/java/deckers/thibault/aves/channel/streams/ImageByteStreamHandler.java index 31cb5c924..4e1b81720 100644 --- a/android/app/src/main/java/deckers/thibault/aves/channel/streams/ImageByteStreamHandler.java +++ b/android/app/src/main/java/deckers/thibault/aves/channel/streams/ImageByteStreamHandler.java @@ -44,7 +44,7 @@ public class ImageByteStreamHandler implements EventChannel.StreamHandler { } @Override - public void onListen(Object o, final EventChannel.EventSink eventSink) { + public void onListen(Object args, EventChannel.EventSink eventSink) { this.eventSink = eventSink; this.handler = new Handler(Looper.getMainLooper()); new Thread(this::getImage).start(); diff --git a/android/app/src/main/java/deckers/thibault/aves/channel/streams/ImageOpStreamHandler.java b/android/app/src/main/java/deckers/thibault/aves/channel/streams/ImageOpStreamHandler.java index c6d3e3fc3..cdf7fa5c4 100644 --- a/android/app/src/main/java/deckers/thibault/aves/channel/streams/ImageOpStreamHandler.java +++ b/android/app/src/main/java/deckers/thibault/aves/channel/streams/ImageOpStreamHandler.java @@ -47,7 +47,7 @@ public class ImageOpStreamHandler implements EventChannel.StreamHandler { } @Override - public void onListen(Object o, final EventChannel.EventSink eventSink) { + public void onListen(Object args, EventChannel.EventSink eventSink) { this.eventSink = eventSink; this.handler = new Handler(Looper.getMainLooper()); if ("delete".equals(op)) { diff --git a/android/app/src/main/java/deckers/thibault/aves/channel/streams/IntentStreamHandler.java b/android/app/src/main/java/deckers/thibault/aves/channel/streams/IntentStreamHandler.java new file mode 100644 index 000000000..a02446d29 --- /dev/null +++ b/android/app/src/main/java/deckers/thibault/aves/channel/streams/IntentStreamHandler.java @@ -0,0 +1,20 @@ +package deckers.thibault.aves.channel.streams; + +import io.flutter.plugin.common.EventChannel; + +public class IntentStreamHandler implements EventChannel.StreamHandler { + private EventChannel.EventSink eventSink; + + @Override + public void onListen(Object args, EventChannel.EventSink eventSink) { + this.eventSink = eventSink; + } + + @Override + public void onCancel(Object arguments) { + } + + public void notifyNewIntent() { + eventSink.success(true); + } +} \ No newline at end of file diff --git a/android/app/src/main/java/deckers/thibault/aves/channel/streams/MediaStoreStreamHandler.java b/android/app/src/main/java/deckers/thibault/aves/channel/streams/MediaStoreStreamHandler.java index 819f83afb..f837895d5 100644 --- a/android/app/src/main/java/deckers/thibault/aves/channel/streams/MediaStoreStreamHandler.java +++ b/android/app/src/main/java/deckers/thibault/aves/channel/streams/MediaStoreStreamHandler.java @@ -27,7 +27,7 @@ public class MediaStoreStreamHandler implements EventChannel.StreamHandler { } @Override - public void onListen(Object args, final EventChannel.EventSink eventSink) { + public void onListen(Object args, EventChannel.EventSink eventSink) { this.eventSink = eventSink; this.handler = new Handler(Looper.getMainLooper()); new Thread(this::fetchAll).start(); diff --git a/android/app/src/main/java/deckers/thibault/aves/channel/streams/StorageAccessStreamHandler.java b/android/app/src/main/java/deckers/thibault/aves/channel/streams/StorageAccessStreamHandler.java index d7dac8d63..88c630c2d 100644 --- a/android/app/src/main/java/deckers/thibault/aves/channel/streams/StorageAccessStreamHandler.java +++ b/android/app/src/main/java/deckers/thibault/aves/channel/streams/StorageAccessStreamHandler.java @@ -29,7 +29,7 @@ public class StorageAccessStreamHandler implements EventChannel.StreamHandler { } @Override - public void onListen(Object o, final EventChannel.EventSink eventSink) { + public void onListen(Object args, EventChannel.EventSink eventSink) { this.eventSink = eventSink; this.handler = new Handler(Looper.getMainLooper()); Runnable onGranted = () -> success(true); // user gave access to a directory, with no guarantee that it matches the specified `path` diff --git a/lib/main.dart b/lib/main.dart index 20f111378..bf915ed7f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,6 +5,7 @@ import 'package:aves/model/settings/settings.dart'; import 'package:aves/utils/route_tracker.dart'; import 'package:aves/widgets/common/data_providers/settings_provider.dart'; import 'package:aves/widgets/common/icons.dart'; +import 'package:aves/widgets/common/routes.dart'; import 'package:aves/widgets/home_page.dart'; import 'package:aves/widgets/welcome_page.dart'; import 'package:firebase_core/firebase_core.dart'; @@ -32,7 +33,7 @@ void main() { enum AppMode { main, pick, view } class AvesApp extends StatefulWidget { - static AppMode mode = AppMode.main; + static AppMode mode; @override _AvesAppState createState() => _AvesAppState(); @@ -41,6 +42,8 @@ class AvesApp extends StatefulWidget { class _AvesAppState extends State { Future _appSetup; final NavigatorObserver _routeTracker = CrashlyticsRouteTracker(); + final _newIntentChannel = EventChannel('deckers.thibault/aves/intent'); + final _navigatorKey = GlobalKey(); static const accentColor = Colors.indigoAccent; @@ -64,10 +67,13 @@ class _AvesAppState extends State { ), ); + Widget get firstPage => settings.hasAcceptedTerms ? HomePage() : WelcomePage(); + @override void initState() { super.initState(); _appSetup = _setup(); + _newIntentChannel.receiveBroadcastStream().listen((_) => _onNewIntent()); } Future _setup() async { @@ -87,6 +93,14 @@ class _AvesAppState extends State { await settings.init(); } + void _onNewIntent() { + FirebaseCrashlytics.instance.log('New intent'); + _navigatorKey.currentState.pushReplacement(DirectMaterialPageRoute( + settings: RouteSettings(name: HomePage.routeName), + builder: (_) => firstPage, + )); + } + @override Widget build(BuildContext context) { // place the settings provider above `MaterialApp` @@ -95,23 +109,10 @@ class _AvesAppState extends State { future: _appSetup, builder: (context, snapshot) { if (!snapshot.hasError && snapshot.connectionState == ConnectionState.done) { - return settings.hasAcceptedTerms ? HomePage() : WelcomePage(); + return firstPage; } return Scaffold( - body: snapshot.hasError - ? Container( - alignment: Alignment.center, - padding: EdgeInsets.all(16), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(AIcons.error), - SizedBox(height: 16), - Text(snapshot.error.toString()), - ], - ), - ) - : SizedBox.shrink(), + body: snapshot.hasError ? _buildError(snapshot.error) : SizedBox.shrink(), ); }, ); @@ -121,6 +122,7 @@ class _AvesAppState extends State { future: _appSetup, builder: (context, snapshot) { return MaterialApp( + navigatorKey: _navigatorKey, home: home, navigatorObservers: [ if (!snapshot.hasError && snapshot.connectionState == ConnectionState.done) _routeTracker, @@ -134,4 +136,19 @@ class _AvesAppState extends State { ), ); } + + Widget _buildError(Object error) { + return Container( + alignment: Alignment.center, + padding: EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(AIcons.error), + SizedBox(height: 16), + Text(error.toString()), + ], + ), + ); + } } diff --git a/lib/model/filters/album.dart b/lib/model/filters/album.dart index 5a1f454ca..2c64b5985 100644 --- a/lib/model/filters/album.dart +++ b/lib/model/filters/album.dart @@ -83,7 +83,7 @@ class AlbumFilter extends CollectionFilter { } @override - int get hashCode => hashValues('AlbumFilter', album); + int get hashCode => hashValues(type, album); @override String toString() { diff --git a/lib/model/filters/favourite.dart b/lib/model/filters/favourite.dart index fb703e733..cee2aff50 100644 --- a/lib/model/filters/favourite.dart +++ b/lib/model/filters/favourite.dart @@ -31,5 +31,5 @@ class FavouriteFilter extends CollectionFilter { } @override - int get hashCode => 'FavouriteFilter'.hashCode; + int get hashCode => type.hashCode; } diff --git a/lib/model/filters/location.dart b/lib/model/filters/location.dart index 74ea8d1e8..83f8b60c0 100644 --- a/lib/model/filters/location.dart +++ b/lib/model/filters/location.dart @@ -4,7 +4,7 @@ import 'package:aves/widgets/common/icons.dart'; import 'package:flutter/widgets.dart'; class LocationFilter extends CollectionFilter { - static const type = 'country'; + static const type = 'location'; static const locationSeparator = ';'; final LocationLevel level; @@ -57,7 +57,7 @@ class LocationFilter extends CollectionFilter { } @override - int get hashCode => hashValues('LocationFilter', level, _location); + int get hashCode => hashValues(type, level, _location); @override String toString() { diff --git a/lib/model/filters/mime.dart b/lib/model/filters/mime.dart index cd1000439..3ed29003a 100644 --- a/lib/model/filters/mime.dart +++ b/lib/model/filters/mime.dart @@ -80,5 +80,5 @@ class MimeFilter extends CollectionFilter { } @override - int get hashCode => hashValues('MimeFilter', mime); + int get hashCode => hashValues(type, mime); } diff --git a/lib/model/filters/query.dart b/lib/model/filters/query.dart index 78937d159..00ac31fde 100644 --- a/lib/model/filters/query.dart +++ b/lib/model/filters/query.dart @@ -68,5 +68,5 @@ class QueryFilter extends CollectionFilter { } @override - int get hashCode => hashValues('QueryFilter', query); + int get hashCode => hashValues(type, query); } diff --git a/lib/model/filters/tag.dart b/lib/model/filters/tag.dart index 96bab615a..d75b67da2 100644 --- a/lib/model/filters/tag.dart +++ b/lib/model/filters/tag.dart @@ -43,7 +43,7 @@ class TagFilter extends CollectionFilter { } @override - int get hashCode => hashValues('TagFilter', tag); + int get hashCode => hashValues(type, tag); @override String toString() { diff --git a/lib/services/image_file_service.dart b/lib/services/image_file_service.dart index 684026a30..8011b97da 100644 --- a/lib/services/image_file_service.dart +++ b/lib/services/image_file_service.dart @@ -233,7 +233,7 @@ class ImageOpEvent { } @override - int get hashCode => hashValues('ImageOpEvent', success, uri); + int get hashCode => hashValues(success, uri); @override String toString() { diff --git a/lib/widgets/common/image_providers/thumbnail_provider.dart b/lib/widgets/common/image_providers/thumbnail_provider.dart index 53b718075..b3f1875c7 100644 --- a/lib/widgets/common/image_providers/thumbnail_provider.dart +++ b/lib/widgets/common/image_providers/thumbnail_provider.dart @@ -66,24 +66,27 @@ class ThumbnailProviderKey { final ImageEntry entry; final double extent; final double scale; + // do not access `contentId` via `entry` for hashCode and equality purposes + // as an entry is not constant and its contentId can change + final int contentId; - const ThumbnailProviderKey({ + ThumbnailProviderKey({ @required this.entry, @required this.extent, this.scale, - }); + }) : contentId = entry.contentId; @override bool operator ==(Object other) { if (other.runtimeType != runtimeType) return false; - return other is ThumbnailProviderKey && other.entry.contentId == entry.contentId && other.extent == extent && other.scale == scale; + return other is ThumbnailProviderKey && other.contentId == contentId && other.extent == extent && other.scale == scale; } @override - int get hashCode => hashValues(entry.contentId, extent, scale); + int get hashCode => hashValues(contentId, extent, scale); @override String toString() { - return 'ThumbnailProviderKey{contentId=${entry.contentId}, extent=$extent, scale=$scale}'; + return 'ThumbnailProviderKey{contentId=$contentId, extent=$extent, scale=$scale}'; } } diff --git a/lib/widgets/filter_grids/common/chip_action_delegate.dart b/lib/widgets/filter_grids/common/chip_action_delegate.dart index 4fdf14035..cdbdd389e 100644 --- a/lib/widgets/filter_grids/common/chip_action_delegate.dart +++ b/lib/widgets/filter_grids/common/chip_action_delegate.dart @@ -23,12 +23,10 @@ class ChipActionDelegate { switch (action) { case ChipAction.pin: - final pinnedFilters = settings.pinnedFilters..add(filter); - settings.pinnedFilters = pinnedFilters; + settings.pinnedFilters = settings.pinnedFilters..add(filter); break; case ChipAction.unpin: - final pinnedFilters = settings.pinnedFilters..remove(filter); - settings.pinnedFilters = pinnedFilters; + settings.pinnedFilters = settings.pinnedFilters..remove(filter); break; default: break; @@ -78,12 +76,17 @@ class AlbumChipActionDelegate extends ChipActionDelegate with FeedbackMixin, Per await source.moveEntry(entry, newFields); } }); + final newAlbum = path.join(path.dirname(album), newName); source.updateAfterMove( entries: movedEntries, fromAlbums: {album}, - toAlbum: path.join(path.dirname(album), newName), + toAlbum: newAlbum, copy: false, ); + final newFilter = AlbumFilter(newAlbum, source.getUniqueAlbumName(newAlbum)); + settings.pinnedFilters = settings.pinnedFilters + ..remove(filter) + ..add(newFilter); final failed = bySuccess[false]?.length ?? 0; if (failed > 0) { diff --git a/lib/widgets/filter_grids/common/filter_grid_page.dart b/lib/widgets/filter_grids/common/filter_grid_page.dart index 9acc8a89d..ecc8ae752 100644 --- a/lib/widgets/filter_grids/common/filter_grid_page.dart +++ b/lib/widgets/filter_grids/common/filter_grid_page.dart @@ -15,6 +15,7 @@ import 'package:aves/widgets/common/data_providers/media_query_data_provider.dar import 'package:aves/widgets/common/double_back_pop.dart'; import 'package:aves/widgets/common/icons.dart'; import 'package:aves/widgets/common/menu_row.dart'; +import 'package:aves/widgets/common/scroll_thumb.dart'; import 'package:aves/widgets/common/search_button.dart'; import 'package:aves/widgets/drawer/app_drawer.dart'; import 'package:aves/widgets/filter_grids/common/chip_action_delegate.dart'; @@ -22,6 +23,7 @@ import 'package:aves/widgets/filter_grids/common/chip_actions.dart'; import 'package:aves/widgets/filter_grids/common/chip_set_action_delegate.dart'; import 'package:aves/widgets/filter_grids/common/decorated_filter_chip.dart'; import 'package:collection/collection.dart'; +import 'package:draggable_scrollbar/draggable_scrollbar.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:pedantic/pedantic.dart'; @@ -168,7 +170,6 @@ class FilterGridPage extends StatelessWidget { @override Widget build(BuildContext context) { - final pinnedFilters = settings.pinnedFilters; return MediaQueryDataProvider( child: Scaffold( body: DoubleBackPopScope( @@ -177,63 +178,9 @@ class FilterGridPage extends StatelessWidget { selector: (c, mq) => mq.size.width, builder: (c, mqWidth, child) { final columnCount = (mqWidth / maxCrossAxisExtent).ceil(); + final scrollView = _buildScrollView(context, columnCount); return AnimationLimiter( - child: CustomScrollView( - slivers: [ - appBar, - filterKeys.isEmpty - ? SliverFillRemaining( - child: emptyBuilder(), - hasScrollBody: false, - ) - : SliverPadding( - padding: EdgeInsets.all(AvesFilterChip.outlineWidth), - sliver: SliverGrid( - delegate: SliverChildBuilderDelegate( - (context, i) { - final key = filterKeys[i]; - final filter = filterBuilder(key); - final child = DecoratedFilterChip( - key: Key(key), - source: source, - filter: filter, - entry: filterEntries[key], - pinned: pinnedFilters.contains(filter), - onTap: onTap, - onLongPress: onLongPress, - ); - return AnimationConfiguration.staggeredGrid( - position: i, - columnCount: columnCount, - duration: Durations.staggeredAnimation, - delay: Durations.staggeredAnimationDelay, - child: SlideAnimation( - verticalOffset: 50.0, - child: FadeInAnimation( - child: child, - ), - ), - ); - }, - childCount: filterKeys.length, - ), - gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: maxCrossAxisExtent, - mainAxisSpacing: 8, - crossAxisSpacing: 8, - ), - ), - ), - SliverToBoxAdapter( - child: Selector( - selector: (context, mq) => mq.viewInsets.bottom, - builder: (context, mqViewInsetsBottom, child) { - return SizedBox(height: mqViewInsetsBottom); - }, - ), - ), - ], - ), + child: _buildDraggableScrollView(scrollView), ); }, ), @@ -246,4 +193,86 @@ class FilterGridPage extends StatelessWidget { ), ); } + + Widget _buildDraggableScrollView(ScrollView scrollView) { + return Selector( + selector: (context, mq) => mq.viewInsets.bottom, + builder: (context, mqViewInsetsBottom, child) => DraggableScrollbar( + heightScrollThumb: avesScrollThumbHeight, + backgroundColor: Colors.white, + scrollThumbBuilder: avesScrollThumbBuilder( + height: avesScrollThumbHeight, + backgroundColor: Colors.white, + ), + controller: PrimaryScrollController.of(context), + padding: EdgeInsets.only( + // padding to keep scroll thumb between app bar above and nav bar below + top: kToolbarHeight, + bottom: mqViewInsetsBottom, + ), + child: scrollView, + ), + ); + } + + ScrollView _buildScrollView(BuildContext context, int columnCount) { + final pinnedFilters = settings.pinnedFilters; + return CustomScrollView( + controller: PrimaryScrollController.of(context), + slivers: [ + appBar, + filterKeys.isEmpty + ? SliverFillRemaining( + child: emptyBuilder(), + hasScrollBody: false, + ) + : SliverPadding( + padding: EdgeInsets.all(AvesFilterChip.outlineWidth), + sliver: SliverGrid( + delegate: SliverChildBuilderDelegate( + (context, i) { + final key = filterKeys[i]; + final filter = filterBuilder(key); + final child = DecoratedFilterChip( + key: Key(key), + source: source, + filter: filter, + entry: filterEntries[key], + pinned: pinnedFilters.contains(filter), + onTap: onTap, + onLongPress: onLongPress, + ); + return AnimationConfiguration.staggeredGrid( + position: i, + columnCount: columnCount, + duration: Durations.staggeredAnimation, + delay: Durations.staggeredAnimationDelay, + child: SlideAnimation( + verticalOffset: 50.0, + child: FadeInAnimation( + child: child, + ), + ), + ); + }, + childCount: filterKeys.length, + ), + gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: maxCrossAxisExtent, + mainAxisSpacing: 8, + crossAxisSpacing: 8, + ), + ), + ), + SliverToBoxAdapter( + child: Selector( + selector: (context, mq) => mq.viewInsets.bottom, + builder: (context, mqViewInsetsBottom, child) { + return SizedBox(height: mqViewInsetsBottom); + }, + ), + ), + ], + ); + } } diff --git a/lib/widgets/fullscreen/info/info_page.dart b/lib/widgets/fullscreen/info/info_page.dart index 9e0eb11e0..cc8450dfb 100644 --- a/lib/widgets/fullscreen/info/info_page.dart +++ b/lib/widgets/fullscreen/info/info_page.dart @@ -8,6 +8,7 @@ import 'package:aves/widgets/common/icons.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'; +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:tuple/tuple.dart'; @@ -190,10 +191,25 @@ class SectionRow extends StatelessWidget { } } -class InfoRowGroup extends StatelessWidget { +class InfoRowGroup extends StatefulWidget { final Map keyValues; + final int maxValueLength; - const InfoRowGroup(this.keyValues); + const InfoRowGroup( + this.keyValues, { + this.maxValueLength = 0, + }); + + @override + _InfoRowGroupState createState() => _InfoRowGroupState(); +} + +class _InfoRowGroupState extends State { + final List _expandedKeys = []; + + Map get keyValues => widget.keyValues; + + int get maxValueLength => widget.maxValueLength; @override Widget build(BuildContext context) { @@ -204,20 +220,30 @@ class InfoRowGroup extends StatelessWidget { children: [ SelectableText.rich( TextSpan( - children: keyValues.entries - .expand( - (kv) => [ - TextSpan(text: '${kv.key} ', style: TextStyle(color: Colors.white70, height: 1.7)), - TextSpan(text: '${kv.value}${kv.key == lastKey ? '' : '\n'}'), - ], - ) - .toList(), + children: keyValues.entries.expand( + (kv) { + final key = kv.key; + var value = kv.value; + final showPreviewOnly = maxValueLength > 0 && value.length > maxValueLength && !_expandedKeys.contains(key); + if (showPreviewOnly) { + value = '${value.substring(0, maxValueLength)}…'; + } + return [ + TextSpan(text: '$key ', style: TextStyle(color: Colors.white70, height: 1.7)), + TextSpan(text: '$value${key == lastKey ? '' : '\n'}', recognizer: showPreviewOnly ? _buildTapRecognizer(key) : null), + ]; + }, + ).toList(), ), style: TextStyle(fontFamily: 'Concourse'), ), ], ); } + + GestureRecognizer _buildTapRecognizer(String key) { + return TapGestureRecognizer()..onTap = () => setState(() => _expandedKeys.add(key)); + } } class BackUpNotification extends Notification {} diff --git a/lib/widgets/fullscreen/info/location_section.dart b/lib/widgets/fullscreen/info/location_section.dart index 8c53dc01f..0d25b2cff 100644 --- a/lib/widgets/fullscreen/info/location_section.dart +++ b/lib/widgets/fullscreen/info/location_section.dart @@ -108,8 +108,11 @@ class _LocationSectionState extends State { style: settings.infoMapStyle, ), ), - if (entry.hasGps) InfoRowGroup({'Coordinates': settings.coordinateFormat.format(entry.latLng)}), - if (location.isNotEmpty) InfoRowGroup({'Address': location}), + if (entry.hasGps) + InfoRowGroup(Map.fromEntries([ + MapEntry('Coordinates', settings.coordinateFormat.format(entry.latLng)), + if (location.isNotEmpty) MapEntry('Address', location), + ])), if (filters.isNotEmpty) Padding( padding: EdgeInsets.symmetric(horizontal: AvesFilterChip.outlineWidth / 2) + EdgeInsets.only(top: 8), diff --git a/lib/widgets/fullscreen/info/metadata_section.dart b/lib/widgets/fullscreen/info/metadata_section.dart index 38107d084..004b6793e 100644 --- a/lib/widgets/fullscreen/info/metadata_section.dart +++ b/lib/widgets/fullscreen/info/metadata_section.dart @@ -85,7 +85,7 @@ class _MetadataSectionSliverState extends State with Auto } if (index < untitledDirectoryCount + 1) { final dir = directoriesWithoutTitle[index - 1]; - return InfoRowGroup(dir.tags); + return InfoRowGroup(dir.tags, maxValueLength: maxValueLength); } final dir = directoriesWithTitle[index - 1 - untitledDirectoryCount]; return Theme( @@ -109,7 +109,7 @@ class _MetadataSectionSliverState extends State with Auto Container( alignment: Alignment.topLeft, padding: EdgeInsets.only(left: 8, right: 8, bottom: 8), - child: InfoRowGroup(dir.tags), + child: InfoRowGroup(dir.tags, maxValueLength: maxValueLength), ), ], baseColor: Colors.grey[900], @@ -133,7 +133,7 @@ class _MetadataSectionSliverState extends State with Auto final value = tagKV.value as String ?? ''; if (value.isEmpty) return null; final tagName = tagKV.key as String ?? ''; - return MapEntry(tagName, value.length > maxValueLength ? '${value.substring(0, maxValueLength)}…' : value); + return MapEntry(tagName, value); }).where((kv) => kv != null))); return _MetadataDirectory(directoryName, tags); }).toList() diff --git a/lib/widgets/home_page.dart b/lib/widgets/home_page.dart index cb5fe69be..8b7eeeba5 100644 --- a/lib/widgets/home_page.dart +++ b/lib/widgets/home_page.dart @@ -63,19 +63,18 @@ class _HomePageState extends State { await androidFileUtils.init(); unawaited(androidFileUtils.initAppNames()); + AvesApp.mode = AppMode.main; final intentData = await ViewerService.getIntentData(); if (intentData != null) { final action = intentData['action']; switch (action) { case 'view': - AvesApp.mode = AppMode.view; _viewerEntry = await _initViewerEntry( uri: intentData['uri'], mimeType: intentData['mimeType'], ); - if (_viewerEntry == null) { - // fallback to default mode when we fail to retrieve the entry - AvesApp.mode = AppMode.main; + if (_viewerEntry != null) { + AvesApp.mode = AppMode.view; } break; case 'pick': diff --git a/pubspec.yaml b/pubspec.yaml index 918637ed5..1e15c3027 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.1.12+24 +version: 1.1.13+25 # video_player (as of v0.10.8+2, backed by ExoPlayer): # - does not support content URIs (by default, but trivial by fork)