tv: stat donut legend focus, map captioned buttons, viewer button focus
This commit is contained in:
parent
4d226e6e38
commit
bcced35e66
25 changed files with 219 additions and 110 deletions
|
@ -25,6 +25,7 @@ class MediaSessionHandler(private val context: Context, private val mediaCommand
|
|||
|
||||
private var session: MediaSessionCompat? = null
|
||||
private var wasPlaying = false
|
||||
private var isNoisyAudioReceiverRegistered = false
|
||||
private val noisyAudioReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (intent.action == AudioManager.ACTION_AUDIO_BECOMING_NOISY) {
|
||||
|
@ -34,7 +35,10 @@ class MediaSessionHandler(private val context: Context, private val mediaCommand
|
|||
}
|
||||
|
||||
fun dispose() {
|
||||
context.unregisterReceiver(noisyAudioReceiver)
|
||||
if (isNoisyAudioReceiverRegistered) {
|
||||
context.unregisterReceiver(noisyAudioReceiver)
|
||||
isNoisyAudioReceiverRegistered = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||
|
@ -110,8 +114,10 @@ class MediaSessionHandler(private val context: Context, private val mediaCommand
|
|||
val isPlaying = state == PlaybackStateCompat.STATE_PLAYING
|
||||
if (!wasPlaying && isPlaying) {
|
||||
context.registerReceiver(noisyAudioReceiver, IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY))
|
||||
isNoisyAudioReceiverRegistered = true
|
||||
} else if (wasPlaying && !isPlaying) {
|
||||
context.unregisterReceiver(noisyAudioReceiver)
|
||||
isNoisyAudioReceiverRegistered = false
|
||||
}
|
||||
wasPlaying = isPlaying
|
||||
}
|
||||
|
|
35
lib/model/actions/map_actions.dart
Normal file
35
lib/model/actions/map_actions.dart
Normal file
|
@ -0,0 +1,35 @@
|
|||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
enum MapAction {
|
||||
selectStyle,
|
||||
zoomIn,
|
||||
zoomOut,
|
||||
}
|
||||
|
||||
extension ExtraMapAction on MapAction {
|
||||
String getText(BuildContext context) {
|
||||
switch (this) {
|
||||
case MapAction.selectStyle:
|
||||
return context.l10n.mapStyleTooltip;
|
||||
case MapAction.zoomIn:
|
||||
return context.l10n.mapZoomInTooltip;
|
||||
case MapAction.zoomOut:
|
||||
return context.l10n.mapZoomOutTooltip;
|
||||
}
|
||||
}
|
||||
|
||||
Widget getIcon() => Icon(_getIconData());
|
||||
|
||||
IconData _getIconData() {
|
||||
switch (this) {
|
||||
case MapAction.selectStyle:
|
||||
return AIcons.layers;
|
||||
case MapAction.zoomIn:
|
||||
return AIcons.zoomIn;
|
||||
case MapAction.zoomOut:
|
||||
return AIcons.zoomOut;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -27,8 +27,6 @@ class Device {
|
|||
|
||||
bool get isDynamicColorAvailable => _isDynamicColorAvailable;
|
||||
|
||||
bool get isReadOnly => _isTelevision;
|
||||
|
||||
bool get isTelevision => _isTelevision;
|
||||
|
||||
bool get showPinShortcutFeedback => _showPinShortcutFeedback;
|
||||
|
|
|
@ -3,7 +3,6 @@ import 'dart:io';
|
|||
import 'dart:ui';
|
||||
|
||||
import 'package:aves/geo/countries.dart';
|
||||
import 'package:aves/model/device.dart';
|
||||
import 'package:aves/model/entry_cache.dart';
|
||||
import 'package:aves/model/entry_dirs.dart';
|
||||
import 'package:aves/model/favourites.dart';
|
||||
|
@ -12,6 +11,7 @@ import 'package:aves/model/metadata/address.dart';
|
|||
import 'package:aves/model/metadata/catalog.dart';
|
||||
import 'package:aves/model/metadata/trash.dart';
|
||||
import 'package:aves/model/multipage.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/model/source/trash.dart';
|
||||
import 'package:aves/model/video/metadata.dart';
|
||||
import 'package:aves/ref/mime_types.dart';
|
||||
|
@ -281,7 +281,7 @@ class AvesEntry {
|
|||
|
||||
bool get isMediaStoreMediaContent => isMediaStoreContent && {'/external/images/', '/external/video/'}.any(uri.contains);
|
||||
|
||||
bool get canEdit => !device.isReadOnly && path != null && !trashed && isMediaStoreContent;
|
||||
bool get canEdit => !settings.isReadOnly && path != null && !trashed && isMediaStoreContent;
|
||||
|
||||
bool get canEditDate => canEdit && (canEditExif || canEditXmp);
|
||||
|
||||
|
|
|
@ -403,6 +403,8 @@ class Settings extends ChangeNotifier {
|
|||
|
||||
bool get useTvLayout => device.isTelevision || forceTvLayout;
|
||||
|
||||
bool get isReadOnly => useTvLayout;
|
||||
|
||||
// navigation
|
||||
|
||||
bool get mustBackTwiceToExit => getBool(mustBackTwiceToExitKey) ?? SettingsDefaults.mustBackTwiceToExit;
|
||||
|
|
|
@ -2,7 +2,6 @@ import 'dart:async';
|
|||
|
||||
import 'package:aves/app_mode.dart';
|
||||
import 'package:aves/model/actions/entry_set_actions.dart';
|
||||
import 'package:aves/model/device.dart';
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/model/filters/query.dart';
|
||||
|
@ -392,7 +391,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
|||
...(isSelecting ? selectionMenuActions : browsingMenuActions).where(isVisible).map(
|
||||
(action) => _toMenuItem(action, enabled: canApply(action), selection: selection),
|
||||
),
|
||||
if (isSelecting && !device.isReadOnly && appMode == AppMode.main && !isTrash)
|
||||
if (isSelecting && !settings.isReadOnly && appMode == AppMode.main && !isTrash)
|
||||
PopupMenuItem<EntrySetAction>(
|
||||
enabled: hasSelection,
|
||||
padding: EdgeInsets.zero,
|
||||
|
|
|
@ -55,7 +55,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
|||
required int selectedItemCount,
|
||||
required bool isTrash,
|
||||
}) {
|
||||
final canWrite = !device.isReadOnly;
|
||||
final canWrite = !settings.isReadOnly;
|
||||
final isMain = appMode == AppMode.main;
|
||||
switch (action) {
|
||||
// general
|
||||
|
|
|
@ -34,17 +34,16 @@ class SectionHeader<T> extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
Widget child = _buildContent(context);
|
||||
if (settings.useTvLayout) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
final primaryColor = Theme.of(context).colorScheme.primary;
|
||||
child = Material(
|
||||
type: MaterialType.transparency,
|
||||
child: InkResponse(
|
||||
onTap: _onTap(context),
|
||||
onHover: (_) {},
|
||||
containedInkWell: true,
|
||||
highlightShape: BoxShape.rectangle,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(123)),
|
||||
containedInkWell: true,
|
||||
splashColor: colors.primary.withOpacity(0.12),
|
||||
hoverColor: colors.primary.withOpacity(0.04),
|
||||
hoverColor: primaryColor.withOpacity(0.04),
|
||||
splashColor: primaryColor.withOpacity(0.12),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
|
|
|
@ -73,7 +73,7 @@ class _OverlayButtonState extends State<OverlayButton> {
|
|||
builder: (context, focused, child) {
|
||||
final border = AvesBorder.border(
|
||||
context,
|
||||
width: AvesBorder.curvedBorderWidth * (focused ? 2 : 1),
|
||||
width: AvesBorder.curvedBorderWidth * (focused ? 3 : 1),
|
||||
);
|
||||
return borderRadius != null
|
||||
? BlurredRRect(
|
||||
|
|
|
@ -1,28 +1,27 @@
|
|||
import 'package:aves/model/settings/enums/map_style.dart';
|
||||
import 'package:aves/model/actions/map_actions.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/map/buttons/button.dart';
|
||||
import 'package:aves/widgets/common/map/buttons/coordinate_filter.dart';
|
||||
import 'package:aves/widgets/common/map/compass.dart';
|
||||
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
|
||||
import 'package:aves/widgets/common/map/map_action_delegate.dart';
|
||||
import 'package:aves_map/aves_map.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class MapButtonPanel extends StatelessWidget {
|
||||
final AvesMapController? controller;
|
||||
final ValueNotifier<ZoomedBounds> boundsNotifier;
|
||||
final Future<void> Function(double amount)? zoomBy;
|
||||
final void Function(BuildContext context)? openMapPage;
|
||||
final VoidCallback? resetRotation;
|
||||
|
||||
const MapButtonPanel({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.boundsNotifier,
|
||||
this.zoomBy,
|
||||
this.openMapPage,
|
||||
this.resetRotation,
|
||||
});
|
||||
|
@ -123,21 +122,8 @@ class MapButtonPanel extends StatelessWidget {
|
|||
: const Spacer(),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: padding),
|
||||
child: MapOverlayButton(
|
||||
// key is expected by test driver
|
||||
buttonKey: const Key('map-menu-layers'),
|
||||
icon: const Icon(AIcons.layers),
|
||||
onPressed: () => showSelectionDialog<EntryMapStyle>(
|
||||
context: context,
|
||||
builder: (context) => AvesSelectionDialog<EntryMapStyle?>(
|
||||
initialValue: settings.mapStyle,
|
||||
options: Map.fromEntries(availability.mapStyles.map((v) => MapEntry(v, v.getName(context)))),
|
||||
title: context.l10n.mapStyleDialogTitle,
|
||||
),
|
||||
onSelection: (v) => settings.mapStyle = v,
|
||||
),
|
||||
tooltip: context.l10n.mapStyleTooltip,
|
||||
),
|
||||
// key is expected by test driver
|
||||
child: _buildButton(context, MapAction.selectStyle, buttonKey: const Key('map-menu-layers')),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -148,17 +134,9 @@ class MapButtonPanel extends StatelessWidget {
|
|||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
MapOverlayButton(
|
||||
icon: const Icon(AIcons.zoomIn),
|
||||
onPressed: zoomBy != null ? () => zoomBy?.call(1) : null,
|
||||
tooltip: context.l10n.mapZoomInTooltip,
|
||||
),
|
||||
_buildButton(context, MapAction.zoomIn),
|
||||
SizedBox(height: padding),
|
||||
MapOverlayButton(
|
||||
icon: const Icon(AIcons.zoomOut),
|
||||
onPressed: zoomBy != null ? () => zoomBy?.call(-1) : null,
|
||||
tooltip: context.l10n.mapZoomOutTooltip,
|
||||
),
|
||||
_buildButton(context, MapAction.zoomOut),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -168,4 +146,11 @@ class MapButtonPanel extends StatelessWidget {
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildButton(BuildContext context, MapAction action, {Key? buttonKey}) => MapOverlayButton(
|
||||
buttonKey: buttonKey,
|
||||
icon: action.getIcon(),
|
||||
onPressed: () => MapActionDelegate(controller).onActionSelected(context, action),
|
||||
tooltip: action.getText(context),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -144,6 +144,7 @@ class _GeoMapState extends State<GeoMap> {
|
|||
);
|
||||
bool _isMarkerImageReady(MarkerKey<AvesEntry> key) => key.entry.isThumbnailReady(extent: MapThemeData.markerImageExtent);
|
||||
|
||||
final controller = widget.controller;
|
||||
Widget child = const SizedBox();
|
||||
if (mapStyle != null) {
|
||||
switch (mapStyle) {
|
||||
|
@ -153,7 +154,7 @@ class _GeoMapState extends State<GeoMap> {
|
|||
case EntryMapStyle.hmsNormal:
|
||||
case EntryMapStyle.hmsTerrain:
|
||||
child = mobileServices.buildMap<AvesEntry>(
|
||||
controller: widget.controller,
|
||||
controller: controller,
|
||||
clusterListenable: _clusterChangeNotifier,
|
||||
boundsNotifier: _boundsNotifier,
|
||||
style: mapStyle,
|
||||
|
@ -175,7 +176,7 @@ class _GeoMapState extends State<GeoMap> {
|
|||
case EntryMapStyle.stamenToner:
|
||||
case EntryMapStyle.stamenWatercolor:
|
||||
child = EntryLeafletMap<AvesEntry>(
|
||||
controller: widget.controller,
|
||||
controller: controller,
|
||||
clusterListenable: _clusterChangeNotifier,
|
||||
boundsNotifier: _boundsNotifier,
|
||||
minZoom: 2,
|
||||
|
@ -260,6 +261,7 @@ class _GeoMapState extends State<GeoMap> {
|
|||
children: [
|
||||
const MapDecorator(),
|
||||
MapButtonPanel(
|
||||
controller: controller,
|
||||
boundsNotifier: _boundsNotifier,
|
||||
openMapPage: widget.openMapPage,
|
||||
),
|
||||
|
@ -485,14 +487,13 @@ class _GeoMapState extends State<GeoMap> {
|
|||
|
||||
Widget _decorateMap(BuildContext context, Widget? child) => MapDecorator(child: child);
|
||||
|
||||
Widget _buildButtonPanel(
|
||||
Future<void> Function(double amount) zoomBy,
|
||||
VoidCallback resetRotation,
|
||||
) =>
|
||||
MapButtonPanel(
|
||||
boundsNotifier: _boundsNotifier,
|
||||
zoomBy: zoomBy,
|
||||
openMapPage: widget.openMapPage,
|
||||
resetRotation: resetRotation,
|
||||
);
|
||||
Widget _buildButtonPanel(VoidCallback resetRotation) {
|
||||
if (settings.useTvLayout) return const SizedBox();
|
||||
return MapButtonPanel(
|
||||
controller: widget.controller,
|
||||
boundsNotifier: _boundsNotifier,
|
||||
openMapPage: widget.openMapPage,
|
||||
resetRotation: resetRotation,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -95,6 +95,7 @@ class _EntryLeafletMapState<T> extends State<EntryLeafletMap<T>> with TickerProv
|
|||
final avesMapController = widget.controller;
|
||||
if (avesMapController != null) {
|
||||
_subscriptions.add(avesMapController.moveCommands.listen((event) => _moveTo(event.latLng)));
|
||||
_subscriptions.add(avesMapController.zoomCommands.listen((event) => _zoomBy(event.delta)));
|
||||
}
|
||||
_subscriptions.add(_leafletMapController.mapEventStream.listen((event) => _updateVisibleRegion()));
|
||||
widget.clusterListenable.addListener(_updateMarkers);
|
||||
|
@ -114,7 +115,7 @@ class _EntryLeafletMapState<T> extends State<EntryLeafletMap<T>> with TickerProv
|
|||
return Stack(
|
||||
children: [
|
||||
widget.decoratorBuilder(context, _buildMap()),
|
||||
widget.buttonPanelBuilder(_zoomBy, _resetRotation),
|
||||
widget.buttonPanelBuilder(_resetRotation),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
37
lib/widgets/common/map/map_action_delegate.dart
Normal file
37
lib/widgets/common/map/map_action_delegate.dart
Normal file
|
@ -0,0 +1,37 @@
|
|||
import 'package:aves/model/actions/map_actions.dart';
|
||||
import 'package:aves/model/settings/enums/map_style.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
|
||||
import 'package:aves_map/aves_map.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class MapActionDelegate {
|
||||
final AvesMapController? controller;
|
||||
|
||||
const MapActionDelegate(this.controller);
|
||||
|
||||
void onActionSelected(BuildContext context, MapAction action) {
|
||||
switch (action) {
|
||||
case MapAction.selectStyle:
|
||||
showSelectionDialog<EntryMapStyle>(
|
||||
context: context,
|
||||
builder: (context) => AvesSelectionDialog<EntryMapStyle?>(
|
||||
initialValue: settings.mapStyle,
|
||||
options: Map.fromEntries(availability.mapStyles.map((v) => MapEntry(v, v.getName(context)))),
|
||||
title: context.l10n.mapStyleDialogTitle,
|
||||
),
|
||||
onSelection: (v) => settings.mapStyle = v,
|
||||
);
|
||||
break;
|
||||
case MapAction.zoomIn:
|
||||
controller?.zoomBy(1);
|
||||
break;
|
||||
case MapAction.zoomOut:
|
||||
controller?.zoomBy(-1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,7 +3,6 @@ import 'dart:io';
|
|||
import 'package:aves/app_mode.dart';
|
||||
import 'package:aves/model/actions/chip_set_actions.dart';
|
||||
import 'package:aves/model/actions/move_type.dart';
|
||||
import 'package:aves/model/device.dart';
|
||||
import 'package:aves/model/filters/album.dart';
|
||||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/model/highlight.dart';
|
||||
|
@ -76,10 +75,10 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate<AlbumFilter> with
|
|||
}) {
|
||||
switch (action) {
|
||||
case ChipSetAction.createAlbum:
|
||||
return !device.isReadOnly && appMode == AppMode.main && !isSelecting;
|
||||
return !settings.isReadOnly && appMode == AppMode.main && !isSelecting;
|
||||
case ChipSetAction.delete:
|
||||
case ChipSetAction.rename:
|
||||
return !device.isReadOnly && appMode == AppMode.main && isSelecting;
|
||||
return !settings.isReadOnly && appMode == AppMode.main && isSelecting;
|
||||
default:
|
||||
return super.isVisible(
|
||||
action,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:aves/app_mode.dart';
|
||||
import 'package:aves/model/actions/map_actions.dart';
|
||||
import 'package:aves/model/actions/map_cluster_actions.dart';
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/filters/coordinate.dart';
|
||||
|
@ -16,11 +17,14 @@ import 'package:aves/theme/icons.dart';
|
|||
import 'package:aves/utils/debouncer.dart';
|
||||
import 'package:aves/widgets/collection/collection_page.dart';
|
||||
import 'package:aves/widgets/collection/entry_set_action_delegate.dart';
|
||||
import 'package:aves/widgets/common/basic/insets.dart';
|
||||
import 'package:aves/widgets/common/basic/menu.dart';
|
||||
import 'package:aves/widgets/common/behaviour/routes.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/identity/buttons/captioned_button.dart';
|
||||
import 'package:aves/widgets/common/identity/empty.dart';
|
||||
import 'package:aves/widgets/common/map/geo_map.dart';
|
||||
import 'package:aves/widgets/common/map/map_action_delegate.dart';
|
||||
import 'package:aves/widgets/common/providers/highlight_info_provider.dart';
|
||||
import 'package:aves/widgets/common/providers/map_theme_provider.dart';
|
||||
import 'package:aves/widgets/common/thumbnail/scroller.dart';
|
||||
|
@ -231,7 +235,7 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
|
|||
}
|
||||
|
||||
Widget _buildMap() {
|
||||
return MapTheme(
|
||||
Widget child = MapTheme(
|
||||
interactive: true,
|
||||
showCoordinateFilter: true,
|
||||
navigationButton: MapNavigationButton.back,
|
||||
|
@ -259,6 +263,37 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
|
|||
onMarkerLongPress: _onMarkerLongPress,
|
||||
),
|
||||
);
|
||||
if (settings.useTvLayout) {
|
||||
child = DirectionalSafeArea(
|
||||
top: false,
|
||||
end: false,
|
||||
bottom: false,
|
||||
child: Row(
|
||||
children: [
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
MapAction.selectStyle,
|
||||
MapAction.zoomIn,
|
||||
MapAction.zoomOut,
|
||||
]
|
||||
.map((action) => Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: CaptionedButton(
|
||||
icon: action.getIcon(),
|
||||
caption: action.getText(context),
|
||||
onPressed: () => MapActionDelegate(_mapController).onActionSelected(context, action),
|
||||
),
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(child: child),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
Widget _buildOverlayController() {
|
||||
|
|
|
@ -111,35 +111,45 @@ class _MimeDonutState extends State<MimeDonut> with AutomaticKeepAliveClientMixi
|
|||
],
|
||||
),
|
||||
);
|
||||
final primaryColor = Theme.of(context).colorScheme.primary;
|
||||
final legend = SizedBox(
|
||||
width: dim,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: seriesData
|
||||
.map((d) => GestureDetector(
|
||||
onTap: () => widget.onFilterSelection(MimeFilter(d.mimeType)),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(AIcons.disc, color: d.color),
|
||||
const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: Text(
|
||||
d.displayText,
|
||||
overflow: TextOverflow.fade,
|
||||
softWrap: false,
|
||||
maxLines: 1,
|
||||
.map((d) => Material(
|
||||
type: MaterialType.transparency,
|
||||
child: InkResponse(
|
||||
onTap: () => widget.onFilterSelection(MimeFilter(d.mimeType)),
|
||||
containedInkWell: true,
|
||||
highlightShape: BoxShape.rectangle,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(123)),
|
||||
hoverColor: primaryColor.withOpacity(0.04),
|
||||
splashColor: primaryColor.withOpacity(0.12),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(AIcons.disc, color: d.color),
|
||||
const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: Text(
|
||||
d.displayText,
|
||||
overflow: TextOverflow.fade,
|
||||
softWrap: false,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
numberFormat.format(d.entryCount),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).textTheme.bodySmall!.color,
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
numberFormat.format(d.entryCount),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).textTheme.bodySmall!.color,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(width: 4),
|
||||
],
|
||||
),
|
||||
),
|
||||
))
|
||||
.toList(),
|
||||
|
|
|
@ -281,7 +281,7 @@ class _StatsPageState extends State<StatsPage> {
|
|||
style: Constants.knownTitleTextStyle,
|
||||
);
|
||||
if (settings.useTvLayout) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
final primaryColor = Theme.of(context).colorScheme.primary;
|
||||
header = Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
|
@ -289,12 +289,11 @@ class _StatsPageState extends State<StatsPage> {
|
|||
type: MaterialType.transparency,
|
||||
child: InkResponse(
|
||||
onTap: onHeaderPressed,
|
||||
onHover: (_) {},
|
||||
containedInkWell: true,
|
||||
highlightShape: BoxShape.rectangle,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(123)),
|
||||
containedInkWell: true,
|
||||
splashColor: colors.primary.withOpacity(0.12),
|
||||
hoverColor: colors.primary.withOpacity(0.04),
|
||||
hoverColor: primaryColor.withOpacity(0.04),
|
||||
splashColor: primaryColor.withOpacity(0.12),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: header,
|
||||
|
|
|
@ -67,7 +67,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
|||
}
|
||||
} else {
|
||||
final targetEntry = EntryActions.pageActions.contains(action) ? pageEntry : mainEntry;
|
||||
final canWrite = !device.isReadOnly;
|
||||
final canWrite = !settings.isReadOnly;
|
||||
switch (action) {
|
||||
case EntryAction.toggleFavourite:
|
||||
return collection != null;
|
||||
|
|
|
@ -3,12 +3,12 @@ import 'dart:convert';
|
|||
|
||||
import 'package:aves/model/actions/entry_actions.dart';
|
||||
import 'package:aves/model/actions/events.dart';
|
||||
import 'package:aves/model/device.dart';
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/entry_info.dart';
|
||||
import 'package:aves/model/entry_metadata_edition.dart';
|
||||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/model/geotiff.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/model/source/collection_lens.dart';
|
||||
import 'package:aves/ref/mime_types.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
|
@ -30,7 +30,7 @@ class EntryInfoActionDelegate with FeedbackMixin, PermissionAwareMixin, EntryEdi
|
|||
Stream<ActionEvent<EntryAction>> get eventStream => _eventStreamController.stream;
|
||||
|
||||
bool isVisible(AvesEntry targetEntry, EntryAction action) {
|
||||
final canWrite = !device.isReadOnly;
|
||||
final canWrite = !settings.isReadOnly;
|
||||
switch (action) {
|
||||
// general
|
||||
case EntryAction.editDate:
|
||||
|
|
|
@ -135,6 +135,7 @@ class _BottomOverlayContentState extends State<_BottomOverlayContent> {
|
|||
void initState() {
|
||||
super.initState();
|
||||
_registerWidget(widget);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _requestFocus());
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -162,11 +163,10 @@ class _BottomOverlayContentState extends State<_BottomOverlayContent> {
|
|||
parent: animationController,
|
||||
curve: Curves.easeOutQuad,
|
||||
);
|
||||
animationController.addStatusListener(_onAnimationStatusChanged);
|
||||
}
|
||||
|
||||
void _unregisterWidget(_BottomOverlayContent widget) {
|
||||
widget.animationController.removeStatusListener(_onAnimationStatusChanged);
|
||||
// nothing
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -266,11 +266,7 @@ class _BottomOverlayContentState extends State<_BottomOverlayContent> {
|
|||
);
|
||||
}
|
||||
|
||||
void _onAnimationStatusChanged(AnimationStatus status) {
|
||||
if (status == AnimationStatus.completed) {
|
||||
_buttonRowFocusScopeNode.children.firstOrNull?.requestFocus();
|
||||
}
|
||||
}
|
||||
void _requestFocus() => _buttonRowFocusScopeNode.children.firstOrNull?.requestFocus();
|
||||
}
|
||||
|
||||
class ExtraBottomOverlay extends StatelessWidget {
|
||||
|
|
|
@ -36,6 +36,7 @@ class _SlideshowButtonsState extends State<SlideshowButtons> {
|
|||
void initState() {
|
||||
super.initState();
|
||||
_registerWidget(widget);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _requestFocus());
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -53,17 +54,15 @@ class _SlideshowButtonsState extends State<SlideshowButtons> {
|
|||
}
|
||||
|
||||
void _registerWidget(SlideshowButtons widget) {
|
||||
final animationController = widget.animationController;
|
||||
_buttonScale = CurvedAnimation(
|
||||
parent: animationController,
|
||||
parent: widget.animationController,
|
||||
// a little bounce at the top
|
||||
curve: Curves.easeOutBack,
|
||||
);
|
||||
animationController.addStatusListener(_onAnimationStatusChanged);
|
||||
}
|
||||
|
||||
void _unregisterWidget(SlideshowButtons widget) {
|
||||
widget.animationController.removeStatusListener(_onAnimationStatusChanged);
|
||||
// nothing
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -111,9 +110,5 @@ class _SlideshowButtonsState extends State<SlideshowButtons> {
|
|||
|
||||
void _onAction(BuildContext context, SlideshowAction action) => SlideshowActionNotification(action).dispatch(context);
|
||||
|
||||
void _onAnimationStatusChanged(AnimationStatus status) {
|
||||
if (status == AnimationStatus.completed) {
|
||||
_buttonRowFocusScopeNode.children.firstOrNull?.requestFocus();
|
||||
}
|
||||
}
|
||||
void _requestFocus() => _buttonRowFocusScopeNode.children.firstOrNull?.requestFocus();
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ class AvesMapController {
|
|||
|
||||
Stream<MapControllerMoveEvent> get moveCommands => _events.where((event) => event is MapControllerMoveEvent).cast<MapControllerMoveEvent>();
|
||||
|
||||
Stream<MapControllerZoomEvent> get zoomCommands => _events.where((event) => event is MapControllerZoomEvent).cast<MapControllerZoomEvent>();
|
||||
|
||||
Stream<MapIdleUpdate> get idleUpdates => _events.where((event) => event is MapIdleUpdate).cast<MapIdleUpdate>();
|
||||
|
||||
Stream<MapMarkerLocationChangeEvent> get markerLocationChanges => _events.where((event) => event is MapMarkerLocationChangeEvent).cast<MapMarkerLocationChangeEvent>();
|
||||
|
@ -20,6 +22,8 @@ class AvesMapController {
|
|||
|
||||
void moveTo(LatLng latLng) => _streamController.add(MapControllerMoveEvent(latLng));
|
||||
|
||||
void zoomBy(double delta) => _streamController.add(MapControllerZoomEvent(delta));
|
||||
|
||||
void notifyIdle(ZoomedBounds bounds) => _streamController.add(MapIdleUpdate(bounds));
|
||||
|
||||
void notifyMarkerLocationChange() => _streamController.add(MapMarkerLocationChangeEvent());
|
||||
|
@ -31,6 +35,12 @@ class MapControllerMoveEvent {
|
|||
MapControllerMoveEvent(this.latLng);
|
||||
}
|
||||
|
||||
class MapControllerZoomEvent {
|
||||
final double delta;
|
||||
|
||||
MapControllerZoomEvent(this.delta);
|
||||
}
|
||||
|
||||
class MapIdleUpdate {
|
||||
final ZoomedBounds bounds;
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import 'package:aves_map/src/marker/key.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
typedef ButtonPanelBuilder = Widget Function(Future<void> Function(double amount) zoomBy, VoidCallback resetRotation);
|
||||
typedef ButtonPanelBuilder = Widget Function(VoidCallback resetRotation);
|
||||
typedef MarkerClusterBuilder<T> = Map<MarkerKey<T>, GeoEntry<T>> Function();
|
||||
typedef MarkerWidgetBuilder<T> = Widget Function(MarkerKey<T> key);
|
||||
typedef MarkerImageReadyChecker<T> = bool Function(MarkerKey<T> key);
|
||||
|
|
|
@ -95,6 +95,7 @@ class _EntryGoogleMapState<T> extends State<EntryGoogleMap<T>> with WidgetsBindi
|
|||
final avesMapController = widget.controller;
|
||||
if (avesMapController != null) {
|
||||
_subscriptions.add(avesMapController.moveCommands.listen((event) => _moveTo(_toServiceLatLng(event.latLng))));
|
||||
_subscriptions.add(avesMapController.zoomCommands.listen((event) => _zoomBy(event.delta)));
|
||||
}
|
||||
widget.clusterListenable.addListener(_updateMarkers);
|
||||
}
|
||||
|
@ -139,7 +140,7 @@ class _EntryGoogleMapState<T> extends State<EntryGoogleMap<T>> with WidgetsBindi
|
|||
},
|
||||
),
|
||||
widget.decoratorBuilder(context, _buildMap()),
|
||||
widget.buttonPanelBuilder(_zoomBy, _resetRotation),
|
||||
widget.buttonPanelBuilder(_resetRotation),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -89,6 +89,7 @@ class _EntryHmsMapState<T> extends State<EntryHmsMap<T>> {
|
|||
final avesMapController = widget.controller;
|
||||
if (avesMapController != null) {
|
||||
_subscriptions.add(avesMapController.moveCommands.listen((event) => _moveTo(_toServiceLatLng(event.latLng))));
|
||||
_subscriptions.add(avesMapController.zoomCommands.listen((event) => _zoomBy(event.delta)));
|
||||
}
|
||||
widget.clusterListenable.addListener(_updateMarkers);
|
||||
}
|
||||
|
@ -118,7 +119,7 @@ class _EntryHmsMapState<T> extends State<EntryHmsMap<T>> {
|
|||
},
|
||||
),
|
||||
widget.decoratorBuilder(context, _buildMap()),
|
||||
widget.buttonPanelBuilder(_zoomBy, _resetRotation),
|
||||
widget.buttonPanelBuilder(_resetRotation),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue