Skip to content
Open
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
107 changes: 94 additions & 13 deletions app/src/main/java/dev/wander/android/opentagviewer/MapsActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,12 @@ public void setZIndex(String markerId, float zIndex) {

private Optional<UserMapCameraPosition> lastCameraPositionOnLoad;

/** Whether the opening camera decision was already made - see applyOpeningCameraIfReady. */
private boolean openingCameraApplied;

/** The opening view came from the stored position, so marker auto-navigation must not stomp it. */
private boolean openingCameraRestored;

private int windowWidth = 0;

private final ActivityResultLauncher<Intent> pickZipActivityLauncher = registerForActivityResult(
Expand Down Expand Up @@ -569,17 +575,10 @@ protected void onCreate(Bundle savedInstanceState) {
var async = this.getLastCameraPosition()
.subscribe(pos -> {
Log.d(TAG, "Got previous camera position to reset us to: " + pos);

pos.ifPresent(userMapCameraPosition -> {
if (this.mapProvider != null) {
this.mapProvider.moveCamera(
userMapCameraPosition.getLat(),
userMapCameraPosition.getLon(),
userMapCameraPosition.getZoom()
);
}
});

// The camera position arrives off a different async path than the
// provider - whichever lands second has to make the move, or a saved
// view is silently dropped whenever the datastore answers first.
this.applyOpeningCameraIfReady();
}, error -> Log.e(TAG, "Failed to get last camera position!", error));

this.handleAuthAndShowDevices();
Expand Down Expand Up @@ -633,6 +632,7 @@ public void onMapReady(IMapProvider provider) {
mapProvider.setMapStyle(this.getPreferredMapStyle());

this.enableMyLocation(false);
this.applyOpeningCameraIfReady();

// 地图准备好后,刷新一次当前显示的信标位置
this.showLastDeviceLocations();
Expand Down Expand Up @@ -1115,6 +1115,86 @@ public void onClickMyLocation(View view) {
this.animateCameraToMyLocation();
}

/**
* Where the map opens, decided once both async sides have arrived: the provider
* (onMapReady) and the persisted camera position (getLastCameraPosition). The
* decision is made exactly once - the second call, from whichever side landed
* last, is the one that moves the camera.
*/
private void applyOpeningCameraIfReady() {
if (this.openingCameraApplied || this.mapProvider == null
|| this.lastCameraPositionOnLoad == null) {
return;
}
this.openingCameraApplied = true;

final Optional<UserMapCameraPosition> saved = this.lastCameraPositionOnLoad;
if (saved.isPresent()) {
final UserMapCameraPosition pos = saved.get();
this.mapProvider.moveCamera(pos.getLat(), pos.getLon(), pos.getZoom());
this.openingCameraRestored = true;
return;
}

// Nothing was ever stored. The provider's own default zooms out until the
// planet tiles across the screen, so pick a deliberate opening view instead:
// the phone's position when permission allows, else a country-scale view for
// the app language, else a zoom that shows the world exactly once. Markers
// that exist already own the opening view through their own navigation.
if (!this.currentMarkers.isEmpty()) {
return;
}
if (ActivityCompat.checkSelfPermission(this, ACCESS_FINE_LOCATION) == PERMISSION_GRANTED
|| ActivityCompat.checkSelfPermission(this, ACCESS_COARSE_LOCATION) == PERMISSION_GRANTED) {
this.fusedLocationClient.getLastLocation()
.addOnSuccessListener(this, location -> {
if (this.mapProvider == null) {
return;
}
// Markers may have arrived while the fix was in flight - they own
// the opening view, and a belated phone position must not stomp it.
if (!this.currentMarkers.isEmpty()) {
return;
}
if (location != null) {
this.mapProvider.animateCamera(location.getLatitude(),
location.getLongitude(), this.initialZoom(), null);
} else {
this.applyLocaleOpeningView();
}
})
.addOnFailureListener(this, e -> {
// A failed fix is the same empty answer as a null one: fall back to
// the locale view unless markers claimed the opening meanwhile.
if (this.mapProvider != null && this.currentMarkers.isEmpty()) {
this.applyLocaleOpeningView();
}
});
return;
}
this.applyLocaleOpeningView();
}

/**
* The opening view when neither a stored camera position nor a phone fix exists:
* a country-scale frame for the app language, or a world view tight enough that
* the planet appears once rather than tiled.
*/
private void applyLocaleOpeningView() {
if (this.mapProvider == null) {
return;
}
// The activity's configured locale, not Locale.getDefault(): a per-app language set
// through locales_config never rewrites the default, and the tag is "de" with no
// country - so it is the language that decides, not the region.
final Locale appLocale = this.getResources().getConfiguration().getLocales().get(0);
if ("de".equals(appLocale.getLanguage())) {
this.mapProvider.moveCamera(50.96, 10.42, 5.5f);
} else {
this.mapProvider.moveCamera(30.0, 10.0, 2.5f);
}
}

private void animateCameraToMyLocation() {
if (ActivityCompat.checkSelfPermission(this, ACCESS_FINE_LOCATION) == PERMISSION_GRANTED
|| ActivityCompat.checkSelfPermission(this, ACCESS_COARSE_LOCATION) == PERMISSION_GRANTED) {
Expand Down Expand Up @@ -2511,8 +2591,9 @@ private synchronized void showBeaconOnMap(final BeaconInformation beacon, final
String markerId = this.mapProvider.addMarker(marker);
this.currentMarkers.put(beaconId, markerId); // 存储markerId而不是Marker对象

if (this.currentMarkers.size() == 1) {
// for the first marker, navigate to it smoothly on the map!
if (this.currentMarkers.size() == 1 && !this.openingCameraRestored) {
// The first marker steers the camera - unless the user's own stored view
// was just put back, which outranks any pin.
this.goToBeaconOnMap(beaconId, this.initialZoom());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -289,11 +289,30 @@ public void clearPolylines() {
this.mapView.invalidate();
}

/**
* Runs {@code action} once the map view has a size - immediately when it already has one.
*
* <p>osmdroid scrolls in pixels, and a camera call on a zero-sized MapView centers half a
* screen wrong: the restored view then opens consistently off to one side. onMapReady
* fires straight out of {@link #initialize}, before the first layout, so the camera
* restore is the call that reliably hits this.
*/
private void whenLaidOut(final Runnable action) {
if (this.mapView.getWidth() == 0) {
this.mapView.addOnFirstLayoutListener(
(v, left, top, right, bottom) -> action.run());
} else {
action.run();
}
}

@Override
public void moveCamera(double latitude, double longitude, float zoom) {
if (this.controller == null) return;
this.controller.setCenter(new GeoPoint(latitude, longitude));
this.controller.setZoom((double) zoom);
this.whenLaidOut(() -> {
this.controller.setCenter(new GeoPoint(latitude, longitude));
this.controller.setZoom((double) zoom);
});
}

@Override
Expand All @@ -308,7 +327,8 @@ public void animateCamera(double latitude, double longitude, float zoom, Runnabl
// API (unlike Google's AMap.CancelableCallback), so the caller's callback runs once the
// animation has had time to finish rather than on a true completion event. 300ms is
// osmdroid's own default MapView animation speed.
this.controller.animateTo(new GeoPoint(latitude, longitude), (double) zoom, 300L);
this.whenLaidOut(() -> this.controller.animateTo(
new GeoPoint(latitude, longitude), (double) zoom, 300L));
if (callback != null) {
this.mapView.postDelayed(callback, 300);
}
Expand Down
Loading