Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve path tracing #88

Merged
merged 7 commits into from
Jul 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/constants/values.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,5 @@ const BATTERY_SAVER_ENABLED_MINIMUM_TIME_BETWEEN_HEADLESS_RUNS =
const LIVE_LOCATION_STALE_DURATION = Duration(minutes: 1);

const LOCATION_POLYLINE_OPAQUE_AMOUNT_THRESHOLD = 100;

const LOCATION_MERGE_DISTANCE_THRESHOLD = 50.0;
4 changes: 4 additions & 0 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,10 @@
"sharesOverviewScreen_importTask_importLabel": "Import",
"locationsOverview_viewSelection_all": "Show all locations",
"locationsOverview_activeShares_amount": "{count, plural, =0{No active shares} =1{One share active} other{{count} shares active}}",
"locationsOverview_mapAction_goToCurrentPosition": "Go to current position",
"locationsOverview_mapAction_alignNorth": "Align North",
"locationsOverview_mapAction_detailedLocations_show": "Show detailed locations",
"locationsOverview_mapAction_detailedLocations_hide": "Merge nearby locations",
"createTask_title": "Define a name for your Share",
"createTask_fields_name_label": "Name",
"createTask_sameTaskNameAlreadyExists": "A Share with this name already exists. You can create the Share, but you will have two Shares with the same name.",
Expand Down
239 changes: 167 additions & 72 deletions lib/screens/LocationsOverviewScreen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:collection/collection.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_expandable_fab/flutter_expandable_fab.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_logs/flutter_logs.dart';
Expand Down Expand Up @@ -57,6 +58,9 @@ import 'ViewDetailScreen.dart';
import 'locations_overview_screen_widgets/ViewDetailsSheet.dart';
import 'locations_overview_screen_widgets/constants.dart';

// After this threshold, locations will not be merged together anymore
const LOCATION_DETAILS_ZOOM_THRESHOLD = 17;

enum LocationStatus {
stale,
active,
Expand Down Expand Up @@ -87,6 +91,9 @@ class _LocationsOverviewScreenState extends State<LocationsOverviewScreen>
bool showFAB = true;
bool isNorth = true;

bool showDetailedLocations = false;
bool disableShowDetailedLocations = false;

Stream<Position>? _positionStream;

// Dummy stream to trigger updates to out of bound markers
Expand All @@ -109,6 +116,8 @@ class _LocationsOverviewScreenState extends State<LocationsOverviewScreen>

bool _hasGoneToInitialPosition = false;

Map<TaskView, List<LocationPointService>> _cachedMergedLocations = {};

TaskView? get selectedView {
if (selectedViewID == null) {
return null;
Expand Down Expand Up @@ -177,6 +186,11 @@ class _LocationsOverviewScreenState extends State<LocationsOverviewScreen>
event is MapEventDoubleTapZoom ||
event is MapEventScrollWheelZoom) {
mapEventStream.add(null);

setState(() {
showDetailedLocations =
event.zoom >= LOCATION_DETAILS_ZOOM_THRESHOLD;
});
}
});

Expand Down Expand Up @@ -249,6 +263,29 @@ class _LocationsOverviewScreenState extends State<LocationsOverviewScreen>
);
}

List<LocationPointService> mergeLocationsIfRequired(
final TaskView view,
) {
final locations = _fetchers.locations[view] ?? [];

if (showDetailedLocations && !disableShowDetailedLocations) {
return locations;
}

if (_cachedMergedLocations.containsKey(selectedView)) {
return _cachedMergedLocations[selectedView]!;
}

final mergedLocations = mergeLocations(
locations,
distanceThreshold: LOCATION_MERGE_DISTANCE_THRESHOLD,
);

_cachedMergedLocations[view] = mergedLocations;

return mergedLocations;
}

void _createLocationFetcher() {
final viewService = context.read<ViewService>();

Expand Down Expand Up @@ -686,8 +723,13 @@ class _LocationsOverviewScreenState extends State<LocationsOverviewScreen>
myLocationButtonEnabled: true,
myLocationEnabled: true,
compassEnabled: true,
onCameraMove: (_) {
onCameraMove: (movement) {
mapEventStream.add(null);

setState(() {
showDetailedLocations =
movement.zoom >= LOCATION_DETAILS_ZOOM_THRESHOLD;
});
},
onMapCreated: (controller) {
appleMapController = controller;
Expand All @@ -712,7 +754,7 @@ class _LocationsOverviewScreenState extends State<LocationsOverviewScreen>
.where(
(view) => selectedViewID == null || view.id == selectedViewID)
.map(
(view) => (_fetchers.locations[view] ?? [])
(view) => mergeLocationsIfRequired(view)
.map(
(location) => AppleMaps.Circle(
circleId: AppleMaps.CircleId(location.id),
Expand Down Expand Up @@ -751,12 +793,15 @@ class _LocationsOverviewScreenState extends State<LocationsOverviewScreen>
selectedViewID = view.id;
});
},
points: List<AppleMaps.LatLng>.from(
locations.reversed.map(
(location) =>
AppleMaps.LatLng(location.latitude, location.longitude),
),
),
points: mergeLocationsIfRequired(entry.key)
.reversed
.map(
(location) => AppleMaps.LatLng(
location.latitude,
location.longitude,
),
)
.toList(),
);
},
),
Expand All @@ -783,10 +828,10 @@ class _LocationsOverviewScreenState extends State<LocationsOverviewScreen>
.where(
(view) => selectedViewID == null || view.id == selectedViewID)
.map(
(view) => (_fetchers.locations[view] ?? [])
(view) => mergeLocationsIfRequired(view)
.mapIndexed(
(index, location) => CircleMarker(
radius: 10,
radius: location.accuracy,
useRadiusInMeter: true,
point: LatLng(location.latitude, location.longitude),
borderStrokeWidth: 1,
Expand Down Expand Up @@ -819,12 +864,13 @@ class _LocationsOverviewScreenState extends State<LocationsOverviewScreen>
: List<Color>.generate(
9, (index) => view.color.withOpacity(0.9)) +
[view.color.withOpacity(.3)],
points: List<LatLng>.from(
locations.reversed.map(
(location) =>
LatLng(location.latitude, location.longitude),
),
),
points: mergeLocationsIfRequired(entry.key)
.reversed
.map(
(location) =>
LatLng(location.latitude, location.longitude),
)
.toList(),
);
},
),
Expand Down Expand Up @@ -1222,9 +1268,11 @@ class _LocationsOverviewScreenState extends State<LocationsOverviewScreen>
}

Widget buildMapActions() {
const dimension = 50;
const margin = 10.0;
const dimension = 50.0;
const diff = FAB_SIZE - dimension;

final l10n = AppLocalizations.of(context);
final settings = context.watch<SettingsService>();
final shades = getPrimaryColorShades(context);

Expand All @@ -1237,81 +1285,128 @@ class _LocationsOverviewScreenState extends State<LocationsOverviewScreen>
(isCupertino(context) ? LARGE_SPACE : SMALL_SPACE),
child: Column(
children: [
SizedBox.square(
dimension: 50,
child: Center(
child: PlatformWidget(
material: (context, _) => Paper(
width: null,
borderRadius: BorderRadius.circular(HUGE_SPACE),
padding: EdgeInsets.zero,
child: IconButton(
color: isNorth ? shades[200] : shades[400],
icon: AnimatedBuilder(
animation: rotationAnimation,
builder: (context, child) => Transform.rotate(
angle: rotationAnimation.value,
child: child,
),
child: PlatformFlavorWidget(
material: (context, _) => Transform.rotate(
angle: -pi / 4,
child: const Icon(MdiIcons.compass),
AnimatedScale(
scale: showDetailedLocations ? 1 : 0,
duration:
showDetailedLocations ? 1200.milliseconds : 100.milliseconds,
curve: showDetailedLocations ? Curves.elasticOut : Curves.easeIn,
child: Tooltip(
message: disableShowDetailedLocations
? l10n.locationsOverview_mapAction_detailedLocations_show
: l10n.locationsOverview_mapAction_detailedLocations_hide,
preferBelow: false,
margin: const EdgeInsets.only(bottom: margin),
child: SizedBox.square(
dimension: dimension,
child: Center(
child: Paper(
width: null,
borderRadius: BorderRadius.circular(HUGE_SPACE),
padding: EdgeInsets.zero,
child: IconButton(
color: shades[400],
icon: Icon(disableShowDetailedLocations
? MdiIcons.mapMarkerMultipleOutline
: MdiIcons.mapMarkerMultiple),
onPressed: () {
setState(() {
disableShowDetailedLocations =
!disableShowDetailedLocations;
});
},
),
),
),
),
),
),
const SizedBox(height: SMALL_SPACE),
Tooltip(
message: l10n.locationsOverview_mapAction_alignNorth,
preferBelow: false,
margin: const EdgeInsets.only(bottom: margin),
child: SizedBox.square(
dimension: dimension,
child: Center(
child: PlatformWidget(
material: (context, _) => Paper(
width: null,
borderRadius: BorderRadius.circular(HUGE_SPACE),
padding: EdgeInsets.zero,
child: IconButton(
color: isNorth ? shades[200] : shades[400],
icon: AnimatedBuilder(
animation: rotationAnimation,
builder: (context, child) => Transform.rotate(
angle: rotationAnimation.value,
child: child,
),
child: PlatformFlavorWidget(
material: (context, _) => Transform.rotate(
angle: -pi / 4,
child: const Icon(MdiIcons.compass),
),
cupertino: (context, _) =>
const Icon(CupertinoIcons.location_north_fill),
),
cupertino: (context, _) =>
const Icon(CupertinoIcons.location_north_fill),
),
onPressed: () {
if (flutterMapController != null) {
flutterMapController!.rotate(0);
}
},
),
),
cupertino: (context, _) => CupertinoButton(
color: isNorth ? shades[200] : shades[400],
padding: EdgeInsets.zero,
borderRadius: BorderRadius.circular(HUGE_SPACE),
onPressed: () {
if (flutterMapController != null) {
flutterMapController!.rotate(0);
}
},
),
),
cupertino: (context, _) => CupertinoButton(
color: isNorth ? shades[200] : shades[400],
padding: EdgeInsets.zero,
borderRadius: BorderRadius.circular(HUGE_SPACE),
onPressed: () {
if (flutterMapController != null) {
flutterMapController!.rotate(0);
}
},
child: AnimatedBuilder(
animation: rotationAnimation,
builder: (context, child) => Transform.rotate(
angle: rotationAnimation.value,
child: child,
child: AnimatedBuilder(
animation: rotationAnimation,
builder: (context, child) => Transform.rotate(
angle: rotationAnimation.value,
child: child,
),
child: const Icon(CupertinoIcons.location_north_fill),
),
child: const Icon(CupertinoIcons.location_north_fill),
),
),
),
),
),
const SizedBox(height: SMALL_SPACE),
SizedBox.square(
dimension: 50,
child: Center(
child: PlatformWidget(
material: (context, _) => Paper(
width: null,
borderRadius: BorderRadius.circular(HUGE_SPACE),
padding: EdgeInsets.zero,
child: IconButton(
Tooltip(
message: l10n.locationsOverview_mapAction_goToCurrentPosition,
preferBelow: false,
margin: const EdgeInsets.only(bottom: margin),
child: SizedBox.square(
dimension: dimension,
child: Center(
child: PlatformWidget(
material: (context, _) => Paper(
width: null,
borderRadius: BorderRadius.circular(HUGE_SPACE),
padding: EdgeInsets.zero,
child: IconButton(
color: shades[400],
icon: const Icon(Icons.my_location),
onPressed: () =>
goToCurrentPosition(askPermissions: true),
),
),
cupertino: (context, _) => CupertinoButton(
color: shades[400],
icon: const Icon(Icons.my_location),
padding: EdgeInsets.zero,
onPressed: () =>
goToCurrentPosition(askPermissions: true),
child: const Icon(Icons.my_location),
),
),
cupertino: (context, _) => CupertinoButton(
color: shades[400],
padding: EdgeInsets.zero,
onPressed: () => goToCurrentPosition(askPermissions: true),
child: const Icon(Icons.my_location),
),
),
),
),
Expand Down
Loading