Skip to content

Commit

Permalink
feat: add map options and stadia lines and labels to esri maps
Browse files Browse the repository at this point in the history
  • Loading branch information
ArthurBeaulieu committed Jul 17, 2024
1 parent 78c0963 commit 2f7137a
Show file tree
Hide file tree
Showing 7 changed files with 210 additions and 14 deletions.
14 changes: 14 additions & 0 deletions lib/src/data/data_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class DataController with ChangeNotifier {
late bool _startupHelpFlag;
late String _userMainCity;
// Map and marks utils
late String _mapLayer;
final Map<String, List<dynamic>> _citiesBounds = {};
final Map<String, dynamic> _citiesMarkers = {};

Expand All @@ -31,6 +32,7 @@ class DataController with ChangeNotifier {
Locale get appLocale => _appLocale;
bool get startupHelpFlag => _startupHelpFlag;
String get userMainCity => _userMainCity;
String get mapLayer => _mapLayer;
Map<String, List<dynamic>> get citiesBounds => _citiesBounds;
Map<String, dynamic> get citiesMarkers => _citiesMarkers;

Expand All @@ -40,6 +42,7 @@ class DataController with ChangeNotifier {
_appLocale = await _dataService.getAppLocale();
_startupHelpFlag = await _dataService.getStartupHelpFlag();
_userMainCity = await _dataService.getUserMainCity();
_mapLayer = await _dataService.getMapLayer();

for (var i = 0; i < AppConst.citiesCode.length; ++i) {
// Get cities boundaries from assets
Expand Down Expand Up @@ -97,4 +100,15 @@ class DataController with ChangeNotifier {
await _dataService.setUserMainCity(_userMainCity); // Save into storage
notifyListeners();
}
// Update map layer
Future<void> setMapLayer(
String? newValue,
) async {
if (newValue == null) return; // Avoid null exception
if (newValue == _mapLayer) return; // Avoid setting same value
if (['osm', 'esri'].contains(newValue) == false) return; // Avoid writing value not supported
_mapLayer = newValue; // Update controller value
await _dataService.setMapLayer(_mapLayer); // Save into storage
notifyListeners();
}
}
26 changes: 26 additions & 0 deletions lib/src/data/data_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,22 @@ class DataService {
// Return stored value
return value;
}
// The user map layer preference
Future<String> getMapLayer() async {
final SharedPreferences appPreferences = await SharedPreferences.getInstance();
// Read preference from device shared preference storage
String? value = appPreferences.getString('md-map-layer');
// First app launch, set pref value to 'BRX' and return it
if (value == null) {
await appPreferences.setString(
'md-map-layer',
'osm',
);
return 'osm';
}
// Return stored value
return value;
}

/* Data service setters */

Expand Down Expand Up @@ -153,4 +169,14 @@ class DataService {
newValue,
);
}
// Persists the user's selected map layer
Future<void> setMapLayer(
String newValue,
) async {
final SharedPreferences appPreferences = await SharedPreferences.getInstance();
await appPreferences.setString(
'md-map-layer',
newValue,
);
}
}
4 changes: 0 additions & 4 deletions lib/src/localization/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,6 @@
"mapOptionsLayerStyle": "Style du fond de carte",
"mapOptionsLayerPlan": "Plan",
"mapOptionsLayerSatellite": "Satellite",
"mapOptionsDisplayedMarkers": "Visibilité des marqueurs",
"mapOptionsDisplaySpots": "Afficher les spots",
"mapOptionsDisplayShops": "Afficher les magasins",
"mapOptionsDisplayBars": "Afficher les bars",
"mapOSMContributors": "Contributeurs OpenStreeMap",
"mapEsriContributors": "Alimenté par Esri",
"mapFlutterMapContributors": "Développeurs de Flutter Maps",
Expand Down
4 changes: 0 additions & 4 deletions lib/src/localization/app_fr.arb
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,6 @@
"mapOptionsLayerStyle": "Style du fond de carte",
"mapOptionsLayerPlan": "Plan",
"mapOptionsLayerSatellite": "Satellite",
"mapOptionsDisplayedMarkers": "Visibilité des marqueurs",
"mapOptionsDisplaySpots": "Afficher les spots",
"mapOptionsDisplayShops": "Afficher les magasins",
"mapOptionsDisplayBars": "Afficher les bars",
"mapOSMContributors": "Contributeurs OpenStreeMap",
"mapEsriContributors": "Alimenté par Esri",
"mapFlutterMapContributors": "Développeurs de Flutter Maps",
Expand Down
1 change: 1 addition & 0 deletions lib/src/utils/app_const.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:flutter_dotenv/flutter_dotenv.dart';

class AppConst {
static String? osrApiKey = dotenv.env['OSR_API_KEY'];
static String? stadiaMapsApiKey = dotenv.env['STADIA_MAPS_KEY'];
static const int maxDistanceForRoute = 30000; // 30km max range to trace routes
static const List<String> supportedLang = ['en', 'fr'];

Expand Down
115 changes: 115 additions & 0 deletions lib/src/view/fragment/map_options_fragment_view.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:toggle_switch/toggle_switch.dart';

import '/src/data/data_controller.dart';
import '/src/utils/size_config.dart';

class MapOptionsFragmentView {
static void showModal(
BuildContext context,
DataController dataController,
Function setter,
) {
showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
barrierColor: Colors.black.withOpacity(0.1),
builder: (
BuildContext context,
) {
SizeConfig().init(context);
MediaQueryData mediaQueryData = MediaQuery.of(context);
// Local working values
String mapLayer = dataController.mapLayer;

return StatefulBuilder(
builder: (
BuildContext context,
StateSetter setModalState,
) {
return Container(
height: (25 * mediaQueryData.size.height) / 100,
color: Theme.of(context).colorScheme.background,
// Modal inner padding
padding: EdgeInsets.symmetric(
horizontal: SizeConfig.padding,
),
child: Center(
child: ListView(
children: [
Column(
children: [
SizedBox(
height: SizeConfig.padding,
),
// Modal title
Text(
AppLocalizations.of(context)!.mapOptionsTitle,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: SizeConfig.fontTextTitleSize,
),
),
SizedBox(
height: SizeConfig.padding,
),
// Map layer type subtitle
Text(
AppLocalizations.of(context)!.mapOptionsLayerStyle,
style: TextStyle(
fontSize: SizeConfig.fontTextLargeSize,
),
),
SizedBox(
height: SizeConfig.paddingSmall,
),
// Map layer type switch
ToggleSwitch(
customWidths: [
mediaQueryData.size.width / 3,
mediaQueryData.size.width / 3,
],
initialLabelIndex: (mapLayer == 'osm')
? 0
: 1,
totalSwitches: 2,
labels: [
AppLocalizations.of(context)!.mapOptionsLayerPlan,
AppLocalizations.of(context)!.mapOptionsLayerSatellite,
],
onToggle: (
index,
) {
if (index == 0) {
mapLayer = 'osm';
setter(
'mapLayer',
mapLayer,
);
setModalState(() {});
} else {
mapLayer = 'esri';
setter(
'mapLayer',
mapLayer,
);
setModalState(() {});
}
},
),
SizedBox(
height: SizeConfig.paddingLarge,
),
],
),
],
),
),
);
},
);
},
);
}
}
60 changes: 54 additions & 6 deletions lib/src/view/map_explore_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import '/src/data/data_controller.dart';
import '/src/utils/app_const.dart';
import '/src/utils/map_utils.dart';
import '/src/utils/size_config.dart';
import '/src/view/fragment/map_options_fragment_view.dart';
import '/src/view/settings_view.dart';
// Hold the main widget map view, that contains
// all spots, shops and bars saved on server. Handle
Expand All @@ -42,8 +43,6 @@ class MapExploreViewState extends State<MapExploreView> with TickerProviderState
// Map data
late List<Polygon> citiesBoundsPolygons;
late List<Marker> citiesMarkers;
// Map user session settings (not saved upon restart)
String mapLayer = 'osm';
bool doubleTap = false; // Enter double tap mode
bool doubleTapPerformed = false; // Double tap actually happened
final OpenRouteService ors = OpenRouteService(
Expand Down Expand Up @@ -84,7 +83,6 @@ class MapExploreViewState extends State<MapExploreView> with TickerProviderState
_alignPositionStreamController.close();
super.dispose();
}

// Navigation routing
void computeRouteToMark(
LatLng destination,
Expand Down Expand Up @@ -211,6 +209,16 @@ class MapExploreViewState extends State<MapExploreView> with TickerProviderState
}
}
}
// Calback function to set MapView internal values according to option changed
void mapOptionsSetter(
String type,
dynamic value,
) {
if (type == 'mapLayer') {
widget.dataController.setMapLayer(value);
}
setState(() {});
}
// Map widget builing
@override
Widget build(
Expand Down Expand Up @@ -301,11 +309,32 @@ class MapExploreViewState extends State<MapExploreView> with TickerProviderState
),
children: [
TileLayer(
urlTemplate: (mapLayer == 'osm')
urlTemplate: (widget.dataController.mapLayer == 'osm')
? 'https://tile.openstreetmap.org/{z}/{x}/{y}.png'
: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
userAgentPackageName: 'com.messebasseproduction.mondourdannais',
),
// For satelite layer, adding lines and labels tile overlays
(widget.dataController.mapLayer == 'esri')
? TileLayer(//&api_key=${AppConst.stadiaMapsApiKey}
urlTemplate: 'https://tiles-eu.stadiamaps.com/tiles/stamen_terrain-lines/{z}/{x}/{y}{r}.png?api_key=${AppConst.stadiaMapsApiKey}',
userAgentPackageName: 'com.messebasseproduction.mondourdannais',
retinaMode: true,
additionalOptions: {
'api_key': AppConst.stadiaMapsApiKey!,
},
)
: const SizedBox.shrink(),
(widget.dataController.mapLayer == 'esri')
? TileLayer(
urlTemplate: 'https://tiles-eu.stadiamaps.com/tiles/stamen_terrain-labels/{z}/{x}/{y}{r}.png?api_key=${AppConst.stadiaMapsApiKey}',
userAgentPackageName: 'com.messebasseproduction.mondourdannais',
retinaMode: true,
additionalOptions: {
'api_key': AppConst.stadiaMapsApiKey!,
},
)
: const SizedBox.shrink(),
CurrentLocationLayer(
alignPositionStream: _alignPositionStreamController.stream,
alignPositionOnUpdate: _alignPositionOnUpdate,
Expand Down Expand Up @@ -362,15 +391,15 @@ class MapExploreViewState extends State<MapExploreView> with TickerProviderState
},
attributions: [
TextSourceAttribution(
(mapLayer == 'osm')
(widget.dataController.mapLayer == 'osm')
? AppLocalizations.of(context)!.mapOSMContributors
: AppLocalizations.of(context)!.mapEsriContributors,
textStyle: TextStyle(
color: Theme.of(context).colorScheme.surface,
fontStyle: FontStyle.italic,
),
onTap: () => launchUrl(
(mapLayer == 'osm')
(widget.dataController.mapLayer == 'osm')
? Uri.parse('https://openstreetmap.org/copyright')
: Uri.parse('https://www.esri.com'),
),
Expand Down Expand Up @@ -425,6 +454,25 @@ class MapExploreViewState extends State<MapExploreView> with TickerProviderState
),
),
),
// Map filtering operations
Container(
margin: EdgeInsets.symmetric(
vertical: SizeConfig.paddingTiny,
),
child: FloatingActionButton(
heroTag: 'filterButton',
onPressed: () => MapOptionsFragmentView.showModal(
context,
widget.dataController,
mapOptionsSetter,
),
foregroundColor: null,
backgroundColor: null,
child: const Icon(
Icons.map,
),
),
),
// Zoom out
Container(
margin: EdgeInsets.symmetric(
Expand Down

0 comments on commit 2f7137a

Please sign in to comment.