info: use overlay buttons and added map style button
This commit is contained in:
parent
2528370f73
commit
5bca875f83
8 changed files with 312 additions and 215 deletions
51
lib/widgets/common/action_delegates/map_style_dialog.dart
Normal file
51
lib/widgets/common/action_delegates/map_style_dialog.dart
Normal file
|
@ -0,0 +1,51 @@
|
|||
import 'package:aves/model/settings.dart';
|
||||
import 'package:aves/widgets/fullscreen/info/location_section.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import '../dialog.dart';
|
||||
|
||||
class MapStyleDialog extends StatefulWidget {
|
||||
@override
|
||||
_MapStyleDialogState createState() => _MapStyleDialogState();
|
||||
}
|
||||
|
||||
class _MapStyleDialogState extends State<MapStyleDialog> {
|
||||
EntryMapStyle _selectedStyle;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_selectedStyle = settings.infoMapStyle;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AvesDialog(
|
||||
title: 'Map Style',
|
||||
scrollableContent: EntryMapStyle.values.map((style) => _buildRadioListTile(style, style.name)).toList(),
|
||||
actions: [
|
||||
FlatButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text('Cancel'.toUpperCase()),
|
||||
),
|
||||
FlatButton(
|
||||
onPressed: () => Navigator.pop(context, _selectedStyle),
|
||||
child: Text('Apply'.toUpperCase()),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRadioListTile(EntryMapStyle style, String title) => RadioListTile<EntryMapStyle>(
|
||||
value: style,
|
||||
groupValue: _selectedStyle,
|
||||
onChanged: (style) => setState(() => _selectedStyle = style),
|
||||
title: Text(
|
||||
title,
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.fade,
|
||||
maxLines: 1,
|
||||
),
|
||||
);
|
||||
}
|
|
@ -46,6 +46,7 @@ class AIcons {
|
|||
static const IconData share = OMIcons.share;
|
||||
static const IconData sort = OMIcons.sort;
|
||||
static const IconData stats = OMIcons.pieChart;
|
||||
static const IconData style = OMIcons.palette;
|
||||
static const IconData zoomIn = OMIcons.add;
|
||||
static const IconData zoomOut = OMIcons.remove;
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'package:aves/utils/geo_utils.dart';
|
|||
import 'package:aves/widgets/common/aves_filter_chip.dart';
|
||||
import 'package:aves/widgets/common/icons.dart';
|
||||
import 'package:aves/widgets/fullscreen/info/info_page.dart';
|
||||
import 'package:aves/widgets/fullscreen/info/maps/buttons.dart';
|
||||
import 'package:aves/widgets/fullscreen/info/maps/google_map.dart';
|
||||
import 'package:aves/widgets/fullscreen/info/maps/leaflet_map.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -94,20 +95,25 @@ class _LocationSectionState extends State<LocationSection> {
|
|||
padding: EdgeInsets.only(bottom: 8),
|
||||
child: SectionRow(AIcons.location),
|
||||
),
|
||||
if (settings.infoMapStyle == EntryMapStyle.google)
|
||||
EntryGoogleMap(
|
||||
NotificationListener(
|
||||
onNotification: (notification) {
|
||||
if (notification is MapStyleChangedNotification) setState(() {});
|
||||
return false;
|
||||
},
|
||||
child: settings.infoMapStyle == EntryMapStyle.google
|
||||
? EntryGoogleMap(
|
||||
markerId: entry.uri ?? entry.path,
|
||||
latLng: entry.latLng,
|
||||
geoUri: entry.geoUri,
|
||||
initialZoom: settings.infoMapZoom,
|
||||
)
|
||||
else
|
||||
EntryLeafletMap(
|
||||
: EntryLeafletMap(
|
||||
latLng: entry.latLng,
|
||||
geoUri: entry.geoUri,
|
||||
initialZoom: settings.infoMapZoom,
|
||||
style: settings.infoMapStyle,
|
||||
),
|
||||
),
|
||||
if (location.isNotEmpty)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 8),
|
||||
|
|
112
lib/widgets/fullscreen/info/maps/buttons.dart
Normal file
112
lib/widgets/fullscreen/info/maps/buttons.dart
Normal file
|
@ -0,0 +1,112 @@
|
|||
import 'package:aves/model/settings.dart';
|
||||
import 'package:aves/services/android_app_service.dart';
|
||||
import 'package:aves/widgets/common/action_delegates/map_style_dialog.dart';
|
||||
import 'package:aves/widgets/common/fx/blurred.dart';
|
||||
import 'package:aves/widgets/common/icons.dart';
|
||||
import 'package:aves/widgets/fullscreen/info/location_section.dart';
|
||||
import 'package:aves/widgets/fullscreen/overlay/common.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MapButtonPanel extends StatelessWidget {
|
||||
final String geoUri;
|
||||
final void Function(double amount) zoomBy;
|
||||
|
||||
static const BorderRadius mapBorderRadius = BorderRadius.all(Radius.circular(24)); // to match button circles
|
||||
static const double padding = 4;
|
||||
|
||||
const MapButtonPanel({
|
||||
@required this.geoUri,
|
||||
@required this.zoomBy,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Positioned.fill(
|
||||
child: Align(
|
||||
alignment: AlignmentDirectional.centerEnd,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(padding),
|
||||
child: TooltipTheme(
|
||||
data: TooltipTheme.of(context).copyWith(
|
||||
preferBelow: false,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
MapOverlayButton(
|
||||
icon: AIcons.style,
|
||||
onPressed: () async {
|
||||
final style = await showDialog<EntryMapStyle>(
|
||||
context: context,
|
||||
builder: (context) => MapStyleDialog(),
|
||||
);
|
||||
if (style != null) {
|
||||
settings.infoMapStyle = style;
|
||||
MapStyleChangedNotification().dispatch(context);
|
||||
}
|
||||
},
|
||||
tooltip: 'Style map...',
|
||||
),
|
||||
SizedBox(height: padding),
|
||||
MapOverlayButton(
|
||||
icon: AIcons.openInNew,
|
||||
onPressed: () => AndroidAppService.openMap(geoUri),
|
||||
tooltip: 'Show on map...',
|
||||
),
|
||||
Spacer(),
|
||||
MapOverlayButton(
|
||||
icon: AIcons.zoomIn,
|
||||
onPressed: () => zoomBy(1),
|
||||
tooltip: 'Zoom in',
|
||||
),
|
||||
SizedBox(height: padding),
|
||||
MapOverlayButton(
|
||||
icon: AIcons.zoomOut,
|
||||
onPressed: () => zoomBy(-1),
|
||||
tooltip: 'Zoom out',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MapOverlayButton extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String tooltip;
|
||||
final VoidCallback onPressed;
|
||||
|
||||
const MapOverlayButton({
|
||||
@required this.icon,
|
||||
@required this.tooltip,
|
||||
@required this.onPressed,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlurredOval(
|
||||
child: Material(
|
||||
type: MaterialType.circle,
|
||||
color: FullscreenOverlay.backgroundColor,
|
||||
child: Ink(
|
||||
decoration: BoxDecoration(
|
||||
border: FullscreenOverlay.buildBorder(context),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: IconButton(
|
||||
iconSize: 20,
|
||||
visualDensity: VisualDensity.compact,
|
||||
icon: Icon(icon),
|
||||
onPressed: onPressed,
|
||||
tooltip: tooltip,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MapStyleChangedNotification extends Notification {}
|
|
@ -1,6 +1,5 @@
|
|||
import 'package:aves/model/settings.dart';
|
||||
import 'package:aves/services/android_app_service.dart';
|
||||
import 'package:aves/widgets/common/icons.dart';
|
||||
import 'package:aves/widgets/fullscreen/info/maps/buttons.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
@ -38,17 +37,31 @@ class EntryGoogleMapState extends State<EntryGoogleMap> with AutomaticKeepAliveC
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
final accentHue = HSVColor.fromColor(Theme.of(context).accentColor).hue;
|
||||
return Row(
|
||||
return Stack(
|
||||
children: [
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
_buildMap(),
|
||||
Positioned.fill(
|
||||
child: Align(
|
||||
alignment: AlignmentDirectional.centerEnd,
|
||||
child: MapButtonPanel(
|
||||
geoUri: widget.geoUri,
|
||||
zoomBy: _zoomBy,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMap() {
|
||||
final accentHue = HSVColor.fromColor(Theme.of(context).accentColor).hue;
|
||||
return GestureDetector(
|
||||
onScaleStart: (details) {
|
||||
// absorb scale gesture here to prevent scrolling
|
||||
// and triggering by mistake a move to the image page above
|
||||
},
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
borderRadius: MapButtonPanel.mapBorderRadius,
|
||||
child: Container(
|
||||
color: Colors.white70,
|
||||
height: 200,
|
||||
|
@ -77,32 +90,6 @@ class EntryGoogleMapState extends State<EntryGoogleMap> with AutomaticKeepAliveC
|
|||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
TooltipTheme(
|
||||
data: TooltipTheme.of(context).copyWith(
|
||||
preferBelow: false,
|
||||
),
|
||||
child: Column(children: [
|
||||
IconButton(
|
||||
icon: Icon(AIcons.zoomIn),
|
||||
onPressed: _controller == null ? null : () => _zoomBy(1),
|
||||
tooltip: 'Zoom in',
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(AIcons.zoomOut),
|
||||
onPressed: _controller == null ? null : () => _zoomBy(-1),
|
||||
tooltip: 'Zoom out',
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(AIcons.openInNew),
|
||||
onPressed: () => AndroidAppService.openMap(widget.geoUri),
|
||||
tooltip: 'Show on map...',
|
||||
),
|
||||
]),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import 'package:aves/model/settings.dart';
|
||||
import 'package:aves/services/android_app_service.dart';
|
||||
import 'package:aves/widgets/common/icons.dart';
|
||||
import 'package:aves/widgets/fullscreen/info/maps/buttons.dart';
|
||||
import 'package:aves/widgets/fullscreen/info/maps/scale_layer.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
|
@ -46,20 +45,31 @@ class EntryLeafletMapState extends State<EntryLeafletMap> with AutomaticKeepAliv
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
final accentColor = Theme.of(context).accentColor;
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
Stack(
|
||||
children: [
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
_buildMap(),
|
||||
MapButtonPanel(
|
||||
geoUri: widget.geoUri,
|
||||
zoomBy: _zoomBy,
|
||||
),
|
||||
],
|
||||
),
|
||||
_buildAttribution(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMap() {
|
||||
return GestureDetector(
|
||||
onScaleStart: (details) {
|
||||
// absorb scale gesture here to prevent scrolling
|
||||
// and triggering by mistake a move to the image page above
|
||||
},
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
borderRadius: MapButtonPanel.mapBorderRadius,
|
||||
child: Container(
|
||||
color: Colors.white70,
|
||||
height: 200,
|
||||
|
@ -85,7 +95,7 @@ class EntryLeafletMapState extends State<EntryLeafletMap> with AutomaticKeepAliv
|
|||
return Icon(
|
||||
Icons.place,
|
||||
size: markerSize,
|
||||
color: accentColor,
|
||||
color: Theme.of(context).accentColor,
|
||||
);
|
||||
},
|
||||
anchorPos: AnchorPos.align(AnchorAlign.top),
|
||||
|
@ -98,35 +108,6 @@ class EntryLeafletMapState extends State<EntryLeafletMap> with AutomaticKeepAliv
|
|||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
TooltipTheme(
|
||||
data: TooltipTheme.of(context).copyWith(
|
||||
preferBelow: false,
|
||||
),
|
||||
child: Column(children: [
|
||||
IconButton(
|
||||
icon: Icon(AIcons.zoomIn),
|
||||
onPressed: _mapController == null ? null : () => _zoomBy(1),
|
||||
tooltip: 'Zoom in',
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(AIcons.zoomOut),
|
||||
onPressed: _mapController == null ? null : () => _zoomBy(-1),
|
||||
tooltip: 'Zoom out',
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(AIcons.openInNew),
|
||||
onPressed: () => AndroidAppService.openMap(widget.geoUri),
|
||||
tooltip: 'Show on map...',
|
||||
),
|
||||
]),
|
||||
)
|
||||
],
|
||||
),
|
||||
_buildAttribution(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -144,39 +125,38 @@ class EntryLeafletMapState extends State<EntryLeafletMap> with AutomaticKeepAliv
|
|||
}
|
||||
|
||||
Widget _buildAttribution() {
|
||||
final attribution = _getAttributionMarkdown();
|
||||
return attribution != null
|
||||
? Markdown(
|
||||
data: attribution,
|
||||
switch (widget.style) {
|
||||
case EntryMapStyle.osmHot:
|
||||
return _buildAttributionMarkdown('© [OpenStreetMap](https://www.openstreetmap.org/copyright) contributors, Tiles style by [Humanitarian OpenStreetMap Team](https://www.hotosm.org/) hosted by [OpenStreetMap France](https://openstreetmap.fr/)');
|
||||
case EntryMapStyle.stamenToner:
|
||||
case EntryMapStyle.stamenWatercolor:
|
||||
return _buildAttributionMarkdown('Map tiles by [Stamen Design](http://stamen.com), [CC BY 3.0](http://creativecommons.org/licenses/by/3.0) — Map data © [OpenStreetMap](https://www.openstreetmap.org/copyright) contributors');
|
||||
default:
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildAttributionMarkdown(String data) {
|
||||
return Markdown(
|
||||
data: data,
|
||||
selectable: true,
|
||||
styleSheet: MarkdownStyleSheet(
|
||||
a: TextStyle(color: Theme.of(context).accentColor),
|
||||
p: TextStyle(fontSize: 13, fontFamily: 'Concourse'),
|
||||
p: TextStyle(color: Colors.white70, fontSize: 13, fontFamily: 'Concourse'),
|
||||
),
|
||||
onTapLink: (url) async {
|
||||
if (await canLaunch(url)) {
|
||||
await launch(url);
|
||||
}
|
||||
},
|
||||
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 8),
|
||||
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 0),
|
||||
shrinkWrap: true,
|
||||
)
|
||||
: SizedBox.shrink();
|
||||
}
|
||||
|
||||
String _getAttributionMarkdown() {
|
||||
switch (widget.style) {
|
||||
case EntryMapStyle.osmHot:
|
||||
return '© [OpenStreetMap](https://www.openstreetmap.org/copyright) contributors, Tiles style by [Humanitarian OpenStreetMap Team](https://www.hotosm.org/) hosted by [OpenStreetMap France](https://openstreetmap.fr/)';
|
||||
case EntryMapStyle.stamenToner:
|
||||
case EntryMapStyle.stamenWatercolor:
|
||||
return 'Map tiles by [Stamen Design](http://stamen.com), [CC BY 3.0](http://creativecommons.org/licenses/by/3.0) — Map data © [OpenStreetMap](https://www.openstreetmap.org/copyright) contributors';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void _zoomBy(double amount) {
|
||||
if (_mapController == null) return;
|
||||
|
||||
final endZoom = (settings.infoMapZoom + amount).clamp(1.0, 16.0);
|
||||
settings.infoMapZoom = endZoom;
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import 'package:aves/model/settings.dart';
|
||||
import 'package:aves/utils/constants.dart';
|
||||
import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart';
|
||||
import 'package:aves/widgets/fullscreen/info/location_section.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SettingsPage extends StatelessWidget {
|
||||
|
@ -27,16 +26,6 @@ class SettingsPage extends StatelessWidget {
|
|||
Flexible(child: LaunchPageSelector()),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text('Maps', style: Constants.titleTextStyle),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text('Storage:'),
|
||||
SizedBox(width: 8),
|
||||
Flexible(child: InfoMapStyleSelector()),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -46,35 +35,6 @@ class SettingsPage extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
class InfoMapStyleSelector extends StatefulWidget {
|
||||
@override
|
||||
_InfoMapStyleSelectorState createState() => _InfoMapStyleSelectorState();
|
||||
}
|
||||
|
||||
class _InfoMapStyleSelectorState extends State<InfoMapStyleSelector> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DropdownButton<EntryMapStyle>(
|
||||
items: EntryMapStyle.values
|
||||
.map((selected) => DropdownMenuItem(
|
||||
value: selected,
|
||||
child: Text(
|
||||
selected.name,
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.fade,
|
||||
maxLines: 1,
|
||||
),
|
||||
))
|
||||
.toList(),
|
||||
value: settings.infoMapStyle,
|
||||
onChanged: (selected) {
|
||||
settings.infoMapStyle = selected;
|
||||
setState(() {});
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LaunchPageSelector extends StatefulWidget {
|
||||
@override
|
||||
_LaunchPageSelectorState createState() => _LaunchPageSelectorState();
|
||||
|
|
Loading…
Reference in a new issue