2023-11-09 18:10:56 +02:00
|
|
|
import 'dart:convert';
|
|
|
|
import 'dart:io';
|
|
|
|
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:flutter_map/flutter_map.dart';
|
2023-08-27 07:07:35 +02:00
|
|
|
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';
|
2023-11-09 18:10:56 +02:00
|
|
|
import 'package:immich_mobile/shared/providers/api.provider.dart';
|
|
|
|
import 'package:immich_mobile/shared/services/api.service.dart';
|
|
|
|
import 'package:immich_mobile/shared/ui/immich_loading_indicator.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';
|
2023-08-27 07:07:35 +02:00
|
|
|
|
|
|
|
class MapStateNotifier extends StateNotifier<MapState> {
|
2023-11-09 18:10:56 +02:00
|
|
|
MapStateNotifier(this._appSettingsProvider, this._apiService)
|
2023-08-27 07:07:35 +02:00
|
|
|
: super(
|
|
|
|
MapState(
|
2023-10-22 22:15:34 +02:00
|
|
|
isDarkTheme: _appSettingsProvider
|
2023-08-27 07:07:35 +02:00
|
|
|
.getSetting<bool>(AppSettingsEnum.mapThemeMode),
|
2023-10-22 22:15:34 +02:00
|
|
|
showFavoriteOnly: _appSettingsProvider
|
2023-08-27 07:07:35 +02:00
|
|
|
.getSetting<bool>(AppSettingsEnum.mapShowFavoriteOnly),
|
2023-10-22 22:15:34 +02:00
|
|
|
includeArchived: _appSettingsProvider
|
2023-10-04 15:51:07 +02:00
|
|
|
.getSetting<bool>(AppSettingsEnum.mapIncludeArchived),
|
2023-10-22 22:15:34 +02:00
|
|
|
relativeTime: _appSettingsProvider
|
2023-08-27 07:07:35 +02:00
|
|
|
.getSetting<int>(AppSettingsEnum.mapRelativeDate),
|
2023-11-09 18:10:56 +02:00
|
|
|
isLoading: true,
|
2023-08-27 07:07:35 +02:00
|
|
|
),
|
2023-11-09 18:10:56 +02:00
|
|
|
) {
|
|
|
|
_fetchStyleFromServer(
|
|
|
|
_appSettingsProvider.getSetting<bool>(AppSettingsEnum.mapThemeMode),
|
|
|
|
);
|
|
|
|
}
|
2023-08-27 07:07:35 +02:00
|
|
|
|
2023-10-22 22:15:34 +02:00
|
|
|
final AppSettingsService _appSettingsProvider;
|
2023-11-09 18:10:56 +02:00
|
|
|
final ApiService _apiService;
|
|
|
|
final Logger _log = Logger("MapStateNotifier");
|
|
|
|
|
|
|
|
bool get isRaster =>
|
|
|
|
state.mapStyle != null && state.mapStyle!.rasterTileProvider != null;
|
|
|
|
|
|
|
|
double get maxZoom =>
|
2023-12-14 15:40:56 +02:00
|
|
|
(isRaster ? state.mapStyle!.rasterTileProvider!.maximumZoom : 18)
|
2023-11-09 18:10:56 +02:00
|
|
|
.toDouble();
|
2023-08-27 07:07:35 +02:00
|
|
|
|
|
|
|
void switchTheme(bool isDarkTheme) {
|
2023-11-09 18:10:56 +02:00
|
|
|
_updateThemeMode(isDarkTheme);
|
|
|
|
_fetchStyleFromServer(isDarkTheme);
|
|
|
|
}
|
|
|
|
|
|
|
|
void _updateThemeMode(bool isDarkTheme) {
|
2023-10-22 22:15:34 +02:00
|
|
|
_appSettingsProvider.setSetting(
|
2023-08-27 07:07:35 +02:00
|
|
|
AppSettingsEnum.mapThemeMode,
|
|
|
|
isDarkTheme,
|
|
|
|
);
|
2023-11-09 18:10:56 +02:00
|
|
|
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,
|
|
|
|
);
|
2023-08-27 07:07:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void switchFavoriteOnly(bool isFavoriteOnly) {
|
2023-10-22 22:15:34 +02:00
|
|
|
_appSettingsProvider.setSetting(
|
2023-08-27 07:07:35 +02:00
|
|
|
AppSettingsEnum.mapShowFavoriteOnly,
|
2023-10-04 15:51:07 +02:00
|
|
|
isFavoriteOnly,
|
2023-08-27 07:07:35 +02:00
|
|
|
);
|
|
|
|
state = state.copyWith(showFavoriteOnly: isFavoriteOnly);
|
|
|
|
}
|
|
|
|
|
2023-10-04 15:51:07 +02:00
|
|
|
void switchIncludeArchived(bool isIncludeArchived) {
|
2023-10-22 22:15:34 +02:00
|
|
|
_appSettingsProvider.setSetting(
|
2023-10-04 15:51:07 +02:00
|
|
|
AppSettingsEnum.mapIncludeArchived,
|
|
|
|
isIncludeArchived,
|
|
|
|
);
|
|
|
|
state = state.copyWith(includeArchived: isIncludeArchived);
|
|
|
|
}
|
|
|
|
|
2023-08-27 07:07:35 +02:00
|
|
|
void setRelativeTime(int relativeTime) {
|
2023-10-22 22:15:34 +02:00
|
|
|
_appSettingsProvider.setSetting(
|
2023-08-27 07:07:35 +02:00
|
|
|
AppSettingsEnum.mapRelativeDate,
|
|
|
|
relativeTime,
|
|
|
|
);
|
|
|
|
state = state.copyWith(relativeTime: relativeTime);
|
|
|
|
}
|
2023-11-09 18:10:56 +02:00
|
|
|
|
|
|
|
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,
|
|
|
|
concurrency: 6,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return const Center(child: ImmichLoadingIndicator());
|
|
|
|
}
|
2023-08-27 07:07:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
final mapStateNotifier =
|
|
|
|
StateNotifierProvider<MapStateNotifier, MapState>((ref) {
|
2023-11-09 18:10:56 +02:00
|
|
|
return MapStateNotifier(
|
|
|
|
ref.watch(appSettingsServiceProvider),
|
|
|
|
ref.watch(apiServiceProvider),
|
|
|
|
);
|
2023-08-27 07:07:35 +02:00
|
|
|
});
|