Skip to content

Commit

Permalink
Rename TileImage active to readyToDisplay for clarity and prevent Til…
Browse files Browse the repository at this point in the history
…eImages with no errorImage from being marked as ready to display.

Also fixed handling of errors when tile fading is disabled.
  • Loading branch information
rorystephenson committed Jul 24, 2023
1 parent 436b7dc commit e60fa0c
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 45 deletions.
90 changes: 51 additions & 39 deletions lib/src/layer/tile_layer/tile_image.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ import 'package:flutter_map/src/layer/tile_layer/tile_display.dart';
class TileImage extends ChangeNotifier {
bool _disposed = false;

// Controls fade-in opacity.
AnimationController? _animationController;

// Whether the tile is displayable. See [readyToDisplay].
bool _readyToDisplay = false;

/// Used by animationController. Still required if animation is disabled in
/// case the tile display is changed at a later point.
final TickerProvider vsync;
Expand All @@ -13,8 +19,6 @@ class TileImage extends ChangeNotifier {
/// indicate the position of the tile at that zoom level.
final TileCoordinates coordinates;

AnimationController? _animationController;

/// Callback fired when loading finishes with or withut an error. This
/// callback is not triggered after this TileImage is disposed.
final void Function(TileCoordinates coordinates) onLoadComplete;
Expand All @@ -26,20 +30,14 @@ class TileImage extends ChangeNotifier {
onLoadError;

/// Options for how the tile image is displayed.
TileDisplay _display;
TileDisplay _tileDisplay;

/// An optional image to show when a loading error occurs.
final ImageProvider? errorImage;

ImageProvider imageProvider;

/// Whether the tile is displayable. This means that either:
/// * Loading errored but there is a tile error image.
/// * Loading succeeded and the fade animation has finished.
/// * Loading succeeded and there is no fade animation.
bool _active = false;

// True if an error occurred during loading.
/// True if an error occurred during loading.
bool loadError = false;

/// When loading started.
Expand All @@ -60,7 +58,7 @@ class TileImage extends ChangeNotifier {
required this.onLoadError,
required TileDisplay tileDisplay,
required this.errorImage,
}) : _display = tileDisplay,
}) : _tileDisplay = tileDisplay,
_animationController = tileDisplay.when(
instantaneous: (_) => null,
fadeIn: (fadeIn) => AnimationController(
Expand All @@ -69,25 +67,33 @@ class TileImage extends ChangeNotifier {
),
);

double get opacity => _display.when(
instantaneous: (instantaneous) => _active ? instantaneous.opacity : 0.0,
double get opacity => _tileDisplay.when(
instantaneous: (instantaneous) =>
_readyToDisplay ? instantaneous.opacity : 0.0,
fadeIn: (fadeIn) => _animationController!.value,
)!;

AnimationController? get animation => _animationController;

String get coordinatesKey => coordinates.key;

bool get active => _active;
/// Whether the tile is displayable. This means that either:
/// * Loading errored but an error image is configured.
/// * Loading succeeded and the fade animation has finished.
/// * Loading succeeded and there is no fade animation.
///
/// Note that [opacity] can be less than 1 when this is true if instantaneous
/// tile display is used with a maximum opacity less than 1.
bool get readyToDisplay => _readyToDisplay;

// Used to sort TileImages by their distance from the current zoom.
double zIndex(double maxZoom, int currentZoom) =>
maxZoom - (currentZoom - coordinates.z).abs();

// Change the tile display options.
set tileDisplay(TileDisplay newTileDisplay) {
final oldTileDisplay = _display;
_display = newTileDisplay;
final oldTileDisplay = _tileDisplay;
_tileDisplay = newTileDisplay;

// Handle disabling/enabling of animation controller if necessary
oldTileDisplay.when(
Expand All @@ -98,7 +104,7 @@ class TileImage extends ChangeNotifier {
_animationController = AnimationController(
duration: fadeIn.duration,
vsync: vsync,
value: _active ? 1.0 : 0.0,
value: _readyToDisplay ? 1.0 : 0.0,
);
},
);
Expand Down Expand Up @@ -146,7 +152,7 @@ class TileImage extends ChangeNotifier {
this.imageInfo = imageInfo;

if (!_disposed) {
_activate();
_display();
onLoadComplete(coordinates);
}
}
Expand All @@ -155,42 +161,49 @@ class TileImage extends ChangeNotifier {
loadError = true;

if (!_disposed) {
_activate();
if (errorImage != null) _display();
onLoadError(this, exception, stackTrace);
onLoadComplete(coordinates);
}
}

void _activate() {
// Initiates fading in and marks this TileImage as readyToDisplay when fading
// finishes. If fading is disabled or a loading error occurred this TileImage
// becomes readyToDisplay immediately.
void _display() {
final previouslyLoaded = loadFinishedAt != null;
loadFinishedAt = DateTime.now();

_display.when(
if (loadError) {
assert(
errorImage != null,
'A TileImage should not be displayed if loading errors and there is no '
'errorImage to show.',
);
_readyToDisplay = true;
if (!_disposed) notifyListeners();
return;
}

_tileDisplay.when(
instantaneous: (_) {
_active = true;
_readyToDisplay = true;
if (!_disposed) notifyListeners();
},
fadeIn: (fadeIn) {
if (loadError && errorImage != null) {
_active = true;
if (!_disposed) notifyListeners();
return;
}

final fadeStartOpacity =
previouslyLoaded ? fadeIn.reloadStartOpacity : fadeIn.startOpacity;

if (fadeStartOpacity == 1.0) {
_active = true;
_readyToDisplay = true;
if (!_disposed) notifyListeners();
return;
} else {
_animationController!.reset();
_animationController!.forward(from: fadeStartOpacity).then((_) {
_readyToDisplay = true;
if (!_disposed) notifyListeners();
});
}

_animationController!.reset();
_animationController!.forward(from: fadeStartOpacity).then((_) {
_active = true;
if (!_disposed) notifyListeners();
});
},
);
}
Expand All @@ -213,8 +226,7 @@ class TileImage extends ChangeNotifier {
}
}

// Mark the image as inactive.
_active = false;
_readyToDisplay = false;
_animationController?.stop(canceled: false);
_animationController?.value = 0.0;
notifyListeners();
Expand All @@ -234,6 +246,6 @@ class TileImage extends ChangeNotifier {

@override
String toString() {
return 'TileImage($coordinates, active: $_active)';
return 'TileImage($coordinates, readyToDisplay: $_readyToDisplay)';
}
}
12 changes: 6 additions & 6 deletions lib/src/layer/tile_layer/tile_removal_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class TileRemovalState {
final retain = Set<TileImage>.from(_keepTiles);

for (final tile in _tileImages.values) {
if (_keepTiles.contains(tile) && !tile.active) {
if (_keepTiles.contains(tile) && !tile.readyToDisplay) {
final coords = tile.coordinates;
if (!_retainAncestor(
retain, coords.x, coords.y, coords.z, coords.z - 5)) {
Expand All @@ -68,8 +68,8 @@ class TileRemovalState {
}

// Recurses through the ancestors of the Tile at the given coordinates adding
// them to [retain] if they are active or loaded. Returns true if any of the
// ancestor tiles were active.
// them to [retain] if they are ready to display or loaded. Returns true if
// any of the ancestor tiles were ready to display.
bool _retainAncestor(
Set<TileImage> retain,
int x,
Expand All @@ -84,7 +84,7 @@ class TileRemovalState {

final tile = _tileImages[coords2];
if (tile != null) {
if (tile.active) {
if (tile.readyToDisplay) {
retain.add(tile);
return true;
} else if (tile.loadFinishedAt != null) {
Expand All @@ -100,7 +100,7 @@ class TileRemovalState {
}

// Recurses through the descendants of the Tile at the given coordinates
// adding them to [retain] if they are active or loaded.
// adding them to [retain] if they are ready to display or loaded.
void _retainChildren(
Set<TileImage> retain,
int x,
Expand All @@ -114,7 +114,7 @@ class TileRemovalState {

final tile = _tileImages[coords];
if (tile != null) {
if (tile.active || tile.loadFinishedAt != null) {
if (tile.readyToDisplay || tile.loadFinishedAt != null) {
retain.add(tile);
}
}
Expand Down

0 comments on commit e60fa0c

Please sign in to comment.