Skip to content

Commit

Permalink
mobile: use style json for vector / raster
Browse files Browse the repository at this point in the history
  • Loading branch information
shenlong-tanwen authored and danieldietzler committed Oct 30, 2023
1 parent e147f04 commit 5e28194
Show file tree
Hide file tree
Showing 10 changed files with 244 additions and 127 deletions.
20 changes: 17 additions & 3 deletions mobile/lib/modules/map/models/map_state.model.dart
Original file line number Diff line number Diff line change
@@ -1,33 +1,43 @@
import 'package:vector_map_tiles/vector_map_tiles.dart';

class MapState {
final bool isDarkTheme;
final bool showFavoriteOnly;
final bool includeArchived;
final int relativeTime;
final Style? mapStyle;
final bool isLoading;

MapState({
this.isDarkTheme = false,
this.showFavoriteOnly = false,
this.includeArchived = false,
this.relativeTime = 0,
this.mapStyle,
this.isLoading = false,
});

MapState copyWith({
bool? isDarkTheme,
bool? showFavoriteOnly,
bool? includeArchived,
int? relativeTime,
Style? mapStyle,
bool? isLoading,
}) {
return MapState(
isDarkTheme: isDarkTheme ?? this.isDarkTheme,
showFavoriteOnly: showFavoriteOnly ?? this.showFavoriteOnly,
includeArchived: includeArchived ?? this.includeArchived,
relativeTime: relativeTime ?? this.relativeTime,
mapStyle: mapStyle ?? this.mapStyle,
isLoading: isLoading ?? this.isLoading,
);
}

@override
String toString() {
return 'MapSettingsState(isDarkTheme: $isDarkTheme, showFavoriteOnly: $showFavoriteOnly, relativeTime: $relativeTime, includeArchived: $includeArchived)';
return 'MapSettingsState(isDarkTheme: $isDarkTheme, showFavoriteOnly: $showFavoriteOnly, relativeTime: $relativeTime, includeArchived: $includeArchived, mapStyle: $mapStyle, isLoading: $isLoading)';
}

@override
Expand All @@ -38,14 +48,18 @@ class MapState {
other.isDarkTheme == isDarkTheme &&
other.showFavoriteOnly == showFavoriteOnly &&
other.relativeTime == relativeTime &&
other.includeArchived == includeArchived;
other.includeArchived == includeArchived &&
other.mapStyle == mapStyle &&
other.isLoading == isLoading;
}

@override
int get hashCode {
return isDarkTheme.hashCode ^
showFavoriteOnly.hashCode ^
relativeTime.hashCode ^
includeArchived.hashCode;
includeArchived.hashCode ^
mapStyle.hashCode ^
isLoading.hashCode;
}
}
107 changes: 103 additions & 4 deletions mobile/lib/modules/map/providers/map_state.provider.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
import 'dart:convert';
import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/map/models/map_state.model.dart';
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
import 'package:immich_mobile/shared/providers/api.provider.dart';
import 'package:immich_mobile/shared/services/api.service.dart';
import 'package:immich_mobile/utils/color_filter_generator.dart';
import 'package:logging/logging.dart';
import 'package:openapi/api.dart';
import 'package:vector_map_tiles/vector_map_tiles.dart';

class MapStateNotifier extends StateNotifier<MapState> {
MapStateNotifier(this._appSettingsProvider)
MapStateNotifier(this._appSettingsProvider, this._apiService)
: super(
MapState(
isDarkTheme: _appSettingsProvider
Expand All @@ -15,17 +27,69 @@ class MapStateNotifier extends StateNotifier<MapState> {
.getSetting<bool>(AppSettingsEnum.mapIncludeArchived),
relativeTime: _appSettingsProvider
.getSetting<int>(AppSettingsEnum.mapRelativeDate),
isLoading: true,
),
);
) {
_fetchStyleFromServer(
_appSettingsProvider.getSetting<bool>(AppSettingsEnum.mapThemeMode),
);
}

final AppSettingsService _appSettingsProvider;
final ApiService _apiService;
final Logger _log = Logger("MapStateNotifier");

bool get isRaster =>
state.mapStyle != null && state.mapStyle!.rasterTileProvider != null;

double get maxZoom =>
(isRaster ? state.mapStyle!.rasterTileProvider!.maximumZoom : 18)
.toDouble();

void switchTheme(bool isDarkTheme) {
_updateThemeMode(isDarkTheme);
_fetchStyleFromServer(isDarkTheme);
}

void _updateThemeMode(bool isDarkTheme) {
_appSettingsProvider.setSetting(
AppSettingsEnum.mapThemeMode,
isDarkTheme,
);
state = state.copyWith(isDarkTheme: isDarkTheme);
state = state.copyWith(isDarkTheme: isDarkTheme, isLoading: true);
}

void _fetchStyleFromServer(bool isDarkTheme) async {
final styleResponse = await _apiService.systemConfigApi
.getMapStyleWithHttpInfo(isDarkTheme ? MapTheme.dark : MapTheme.light);
if (styleResponse.statusCode >= HttpStatus.badRequest) {
throw ApiException(styleResponse.statusCode, styleResponse.body);
}
final styleJsonString = styleResponse.body.isNotEmpty &&
styleResponse.statusCode != HttpStatus.noContent
? styleResponse.body
: null;

if (styleJsonString == null) {
_log.severe('Style JSON from server is empty');
return;
}
final styleJson = await compute(jsonDecode, styleJsonString);
if (styleJson is! Map<String, dynamic>) {
_log.severe('Style JSON from server is invalid');
return;
}
final styleReader = StyleReader(uri: '');
Style? style;
try {
style = await styleReader.readFromMap(styleJson);
} finally {
// Consume all error
}
state = state.copyWith(
mapStyle: style,
isLoading: false,
);
}

void switchFavoriteOnly(bool isFavoriteOnly) {
Expand All @@ -51,9 +115,44 @@ class MapStateNotifier extends StateNotifier<MapState> {
);
state = state.copyWith(relativeTime: relativeTime);
}

Widget getTileLayer([bool forceDark = false]) {
if (isRaster) {
final rasterProvider = state.mapStyle!.rasterTileProvider;
final rasterLayer = TileLayer(
urlTemplate: rasterProvider!.url,
maxNativeZoom: rasterProvider.maximumZoom,
maxZoom: rasterProvider.maximumZoom.toDouble(),
);
return state.isDarkTheme || forceDark
? InvertionFilter(
child: SaturationFilter(
saturation: -1,
child: BrightnessFilter(
brightness: -1,
child: rasterLayer,
),
),
)
: rasterLayer;
}
if (state.mapStyle != null && !isRaster) {
return VectorTileLayer(
// Tiles and themes will be set for vector providers
tileProviders: state.mapStyle!.providers!,
theme: state.mapStyle!.theme!,
sprites: state.mapStyle!.sprites,
maximumTileSubstitutionDifference: 0,
);
}
return const SizedBox.shrink();
}
}

final mapStateNotifier =
StateNotifierProvider<MapStateNotifier, MapState>((ref) {
return MapStateNotifier(ref.watch(appSettingsServiceProvider));
return MapStateNotifier(
ref.watch(appSettingsServiceProvider),
ref.watch(apiServiceProvider),
);
});
31 changes: 12 additions & 19 deletions mobile/lib/modules/map/ui/map_page_bottom_sheet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import 'package:immich_mobile/shared/ui/drag_sheet.dart';
import 'package:immich_mobile/utils/color_filter_generator.dart';
import 'package:immich_mobile/utils/debounce.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import 'package:url_launcher/url_launcher.dart';

class MapPageBottomSheet extends StatefulHookConsumerWidget {
final Stream mapPageEventStream;
Expand Down Expand Up @@ -320,24 +319,18 @@ class AssetsInBoundBottomSheetState extends ConsumerState<MapPageBottomSheet> {
Positioned(
bottom: maxHeight * currentExtend.value,
left: 0,
child: GestureDetector(
onTap: () => launchUrl(
Uri.parse('https://openstreetmap.org/copyright'),
),
child: ColoredBox(
color: (widget.isDarkTheme
? Colors.grey[900]
: Colors.grey[100])!,
child: Padding(
padding: const EdgeInsets.all(3),
child: Text(
'© OpenStreetMap contributors',
style: TextStyle(
fontSize: 6,
color: !widget.isDarkTheme
? Colors.grey[900]
: Colors.grey[100],
),
child: ColoredBox(
color:
(widget.isDarkTheme ? Colors.grey[900] : Colors.grey[100])!,
child: Padding(
padding: const EdgeInsets.all(3),
child: Text(
'Thanks to Cofractal for the tile servers',
style: TextStyle(
fontSize: 6,
color: !widget.isDarkTheme
? Colors.grey[900]
: Colors.grey[100],
),
),
),
Expand Down
25 changes: 4 additions & 21 deletions mobile/lib/modules/map/ui/map_thumbnail.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_map/plugin_api.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
import 'package:immich_mobile/utils/color_filter_generator.dart';
import 'package:immich_mobile/modules/map/providers/map_state.provider.dart';
import 'package:latlong2/latlong.dart';
import 'package:url_launcher/url_launcher.dart';

// A non-interactive thumbnail of a map in the given coordinates with optional markers
class MapThumbnail extends HookConsumerWidget {
Expand All @@ -29,12 +27,6 @@ class MapThumbnail extends HookConsumerWidget {

@override
Widget build(BuildContext context, WidgetRef ref) {
final tileLayer = TileLayer(
urlTemplate: ref.watch(
serverInfoProvider.select((v) => v.serverConfig.mapTileUrl),
),
);

return SizedBox(
height: height,
child: ClipRRect(
Expand All @@ -52,23 +44,14 @@ class MapThumbnail extends HookConsumerWidget {
animationConfig: const ScaleRAWA(),
attributions: [
TextSourceAttribution(
'OpenStreetMap contributors',
onTap: () => launchUrl(
Uri.parse('https://openstreetmap.org/copyright'),
),
'Thanks to Cofractal for the tile servers',
onTap: () => {},
),
],
),
],
children: [
isDarkTheme
? InvertionFilter(
child: SaturationFilter(
saturation: -1,
child: tileLayer,
),
)
: tileLayer,
ref.read(mapStateNotifier.notifier).getTileLayer(isDarkTheme),
if (markers.isNotEmpty) MarkerLayer(markers: markers),
],
),
Expand Down

0 comments on commit 5e28194

Please sign in to comment.