info: use overlay buttons and added map style button

This commit is contained in:
Thibault Deckers 2020-08-17 12:40:49 +09:00
parent 2528370f73
commit 5bca875f83
8 changed files with 312 additions and 215 deletions

View 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,
),
);
}

View file

@ -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;

View file

@ -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),

View 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 {}

View file

@ -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...',
),
]),
)
],
);
}

View file

@ -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;

View file

@ -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();