From a937efe719cddb5564ce583d2af356801b0a1f92 Mon Sep 17 00:00:00 2001 From: shenlong <139912620+shalong-tanwen@users.noreply.github.com> Date: Thu, 28 Sep 2023 03:26:48 +0000 Subject: [PATCH] feat(mobile): use map settings from server-config (#4045) * feat(mobile): use map settings from server-config to enable / disable map * refactor(mobile): remove async await for server info update --- mobile/lib/modules/home/views/home_page.dart | 2 +- mobile/lib/modules/map/ui/map_thumbnail.dart | 6 ++- mobile/lib/modules/map/views/map_page.dart | 6 ++- .../modules/search/ui/curated_places_row.dart | 32 ++++++++++++--- .../lib/modules/search/views/search_page.dart | 4 ++ .../models/server_info_state.model.dart | 14 ++++++- .../providers/server_info.provider.dart | 41 +++++++++++++++++++ .../shared/services/server_info.service.dart | 20 ++++++++- 8 files changed, 113 insertions(+), 12 deletions(-) diff --git a/mobile/lib/modules/home/views/home_page.dart b/mobile/lib/modules/home/views/home_page.dart index e37491440b..7fcd88be44 100644 --- a/mobile/lib/modules/home/views/home_page.dart +++ b/mobile/lib/modules/home/views/home_page.dart @@ -54,7 +54,7 @@ class HomePage extends HookConsumerWidget { Future(() => ref.read(assetProvider.notifier).getAllAsset()); ref.read(albumProvider.notifier).getAllAlbums(); ref.read(sharedAlbumProvider.notifier).getAllSharedAlbums(); - ref.read(serverInfoProvider.notifier).getServerVersion(); + ref.read(serverInfoProvider.notifier).getServerInfo(); selectionEnabledHook.addListener(() { multiselectEnabled.state = selectionEnabledHook.value; diff --git a/mobile/lib/modules/map/ui/map_thumbnail.dart b/mobile/lib/modules/map/ui/map_thumbnail.dart index 78998276d8..14cc2a83b6 100644 --- a/mobile/lib/modules/map/ui/map_thumbnail.dart +++ b/mobile/lib/modules/map/ui/map_thumbnail.dart @@ -1,6 +1,7 @@ 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:latlong2/latlong.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -29,8 +30,9 @@ class MapThumbnail extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final tileLayer = TileLayer( - urlTemplate: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", - subdomains: const ['a', 'b', 'c'], + urlTemplate: ref.watch( + serverInfoProvider.select((v) => v.serverConfig.mapTileUrl), + ), ); return SizedBox( diff --git a/mobile/lib/modules/map/views/map_page.dart b/mobile/lib/modules/map/views/map_page.dart index d228c38735..c8fc7f5e9d 100644 --- a/mobile/lib/modules/map/views/map_page.dart +++ b/mobile/lib/modules/map/views/map_page.dart @@ -20,6 +20,7 @@ import 'package:immich_mobile/modules/map/ui/map_page_bottom_sheet.dart'; import 'package:immich_mobile/modules/map/ui/map_page_app_bar.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/shared/models/asset.dart'; +import 'package:immich_mobile/shared/providers/server_info.provider.dart'; import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; import 'package:immich_mobile/shared/ui/immich_toast.dart'; import 'package:immich_mobile/utils/color_filter_generator.dart'; @@ -358,8 +359,9 @@ class MapPageState extends ConsumerState { } final tileLayer = TileLayer( - urlTemplate: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", - subdomains: const ['a', 'b', 'c'], + urlTemplate: ref.watch( + serverInfoProvider.select((v) => v.serverConfig.mapTileUrl), + ), maxNativeZoom: 19, maxZoom: 19, ); diff --git a/mobile/lib/modules/search/ui/curated_places_row.dart b/mobile/lib/modules/search/ui/curated_places_row.dart index ef394b83b9..d62607a807 100644 --- a/mobile/lib/modules/search/ui/curated_places_row.dart +++ b/mobile/lib/modules/search/ui/curated_places_row.dart @@ -8,15 +8,21 @@ import 'package:immich_mobile/shared/models/store.dart'; import 'package:latlong2/latlong.dart'; class CuratedPlacesRow extends CuratedRow { + final bool isMapEnabled; + const CuratedPlacesRow({ super.key, required super.content, + this.isMapEnabled = true, super.imageSize, super.onTap, }); @override Widget build(BuildContext context) { + // Calculating the actual index of the content based on the whether map is enabled or not. + // If enabled, inject map as the first item in the list (index 0) and so the actual content will start from index 1 + final int actualContentIndex = isMapEnabled ? 1 : 0; Widget buildMapThumbnail() { return GestureDetector( onTap: () => AutoRouter.of(context).push( @@ -75,6 +81,24 @@ class CuratedPlacesRow extends CuratedRow { ); } + // Return empty thumbnail + if (!isMapEnabled && content.isEmpty) { + return Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: SizedBox( + width: imageSize, + height: imageSize, + child: ThumbnailWithInfo( + textInfo: '', + onTap: () {}, + ), + ), + ), + ); + } + return ListView.builder( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric( @@ -82,11 +106,10 @@ class CuratedPlacesRow extends CuratedRow { ), itemBuilder: (context, index) { // Injecting Map thumbnail as the first element - if (index == 0) { + if (isMapEnabled && index == 0) { return buildMapThumbnail(); } - // The actual index is 1 less than the virutal index since we inject map into the first position - final actualIndex = index - 1; + final actualIndex = index - actualContentIndex; final object = content[actualIndex]; final thumbnailRequestUrl = '${Store.get(StoreKey.serverEndpoint)}/asset/thumbnail/${object.id}'; @@ -103,8 +126,7 @@ class CuratedPlacesRow extends CuratedRow { ), ); }, - // Adding 1 to inject map thumbnail as first element - itemCount: content.length + 1, + itemCount: content.length + actualContentIndex, ); } } diff --git a/mobile/lib/modules/search/views/search_page.dart b/mobile/lib/modules/search/views/search_page.dart index e8ea40d2ae..1110555a3b 100644 --- a/mobile/lib/modules/search/views/search_page.dart +++ b/mobile/lib/modules/search/views/search_page.dart @@ -14,6 +14,7 @@ import 'package:immich_mobile/modules/search/ui/person_name_edit_form.dart'; import 'package:immich_mobile/modules/search/ui/search_row_title.dart'; import 'package:immich_mobile/modules/search/ui/search_suggestion_list.dart'; import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/shared/providers/server_info.provider.dart'; import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; // ignore: must_be_immutable @@ -27,6 +28,8 @@ class SearchPage extends HookConsumerWidget { final isSearchEnabled = ref.watch(searchPageStateProvider).isSearchEnabled; final curatedLocation = ref.watch(getCuratedLocationProvider); final curatedPeople = ref.watch(getCuratedPeopleProvider); + final isMapEnabled = + ref.watch(serverInfoProvider.select((v) => v.serverFeatures.map)); var isDarkTheme = Theme.of(context).brightness == Brightness.dark; double imageSize = math.min(MediaQuery.of(context).size.width / 3, 150); @@ -107,6 +110,7 @@ class SearchPage extends HookConsumerWidget { loading: () => const Center(child: ImmichLoadingIndicator()), error: (err, stack) => Center(child: Text('Error: $err')), data: (locations) => CuratedPlacesRow( + isMapEnabled: isMapEnabled, content: locations .map( (o) => CuratedContent( diff --git a/mobile/lib/shared/models/server_info_state.model.dart b/mobile/lib/shared/models/server_info_state.model.dart index 6623ac1666..61879a6c0b 100644 --- a/mobile/lib/shared/models/server_info_state.model.dart +++ b/mobile/lib/shared/models/server_info_state.model.dart @@ -2,22 +2,30 @@ import 'package:openapi/api.dart'; class ServerInfoState { final ServerVersionResponseDto serverVersion; + final ServerFeaturesDto serverFeatures; + final ServerConfigDto serverConfig; final bool isVersionMismatch; final String versionMismatchErrorMessage; ServerInfoState({ required this.serverVersion, + required this.serverFeatures, + required this.serverConfig, required this.isVersionMismatch, required this.versionMismatchErrorMessage, }); ServerInfoState copyWith({ ServerVersionResponseDto? serverVersion, + ServerFeaturesDto? serverFeatures, + ServerConfigDto? serverConfig, bool? isVersionMismatch, String? versionMismatchErrorMessage, }) { return ServerInfoState( serverVersion: serverVersion ?? this.serverVersion, + serverFeatures: serverFeatures ?? this.serverFeatures, + serverConfig: serverConfig ?? this.serverConfig, isVersionMismatch: isVersionMismatch ?? this.isVersionMismatch, versionMismatchErrorMessage: versionMismatchErrorMessage ?? this.versionMismatchErrorMessage, @@ -26,7 +34,7 @@ class ServerInfoState { @override String toString() { - return 'ServerInfoState( serverVersion: $serverVersion, isVersionMismatch: $isVersionMismatch, versionMismatchErrorMessage: $versionMismatchErrorMessage)'; + return 'ServerInfoState( serverVersion: $serverVersion, serverFeatures: $serverFeatures, serverConfig: $serverConfig, isVersionMismatch: $isVersionMismatch, versionMismatchErrorMessage: $versionMismatchErrorMessage)'; } @override @@ -35,6 +43,8 @@ class ServerInfoState { return other is ServerInfoState && other.serverVersion == serverVersion && + other.serverFeatures == serverFeatures && + other.serverConfig == serverConfig && other.isVersionMismatch == isVersionMismatch && other.versionMismatchErrorMessage == versionMismatchErrorMessage; } @@ -42,6 +52,8 @@ class ServerInfoState { @override int get hashCode { return serverVersion.hashCode ^ + serverFeatures.hashCode ^ + serverConfig.hashCode ^ isVersionMismatch.hashCode ^ versionMismatchErrorMessage.hashCode; } diff --git a/mobile/lib/shared/providers/server_info.provider.dart b/mobile/lib/shared/providers/server_info.provider.dart index ff0945ead0..e9f8d011d2 100644 --- a/mobile/lib/shared/providers/server_info.provider.dart +++ b/mobile/lib/shared/providers/server_info.provider.dart @@ -15,6 +15,24 @@ class ServerInfoNotifier extends StateNotifier { patch_: 0, minor: 0, ), + serverFeatures: ServerFeaturesDto( + clipEncode: true, + configFile: false, + facialRecognition: true, + map: true, + oauth: false, + oauthAutoLaunch: false, + passwordLogin: true, + search: true, + sidecar: true, + tagImage: true, + reverseGeocoding: true, + ), + serverConfig: ServerConfigDto( + loginPageMessage: "", + mapTileUrl: "https://tile.openstreetmap.org/{z}/{x}/{y}.png", + oauthButtonText: "", + ), isVersionMismatch: false, versionMismatchErrorMessage: "", ), @@ -22,6 +40,12 @@ class ServerInfoNotifier extends StateNotifier { final ServerInfoService _serverInfoService; + getServerInfo() { + getServerVersion(); + getServerFeatures(); + getServerConfig(); + } + getServerVersion() async { ServerVersionResponseDto? serverVersion = await _serverInfoService.getServerVersion(); @@ -66,6 +90,23 @@ class ServerInfoNotifier extends StateNotifier { ); } + getServerFeatures() async { + ServerFeaturesDto? serverFeatures = + await _serverInfoService.getServerFeatures(); + if (serverFeatures == null) { + return; + } + state = state.copyWith(serverFeatures: serverFeatures); + } + + getServerConfig() async { + ServerConfigDto? serverConfig = await _serverInfoService.getServerConfig(); + if (serverConfig == null) { + return; + } + state = state.copyWith(serverConfig: serverConfig); + } + Map _getDetailVersion(String version) { List detail = version.split("."); diff --git a/mobile/lib/shared/services/server_info.service.dart b/mobile/lib/shared/services/server_info.service.dart index bf923dfab0..3973dd9fb8 100644 --- a/mobile/lib/shared/services/server_info.service.dart +++ b/mobile/lib/shared/services/server_info.service.dart @@ -28,7 +28,25 @@ class ServerInfoService { try { return await _apiService.serverInfoApi.getServerVersion(); } catch (e) { - debugPrint("Error getting server info"); + debugPrint("Error [getServerVersion] ${e.toString()}"); + return null; + } + } + + Future getServerFeatures() async { + try { + return await _apiService.serverInfoApi.getServerFeatures(); + } catch (e) { + debugPrint("Error [getServerFeatures] ${e.toString()}"); + return null; + } + } + + Future getServerConfig() async { + try { + return await _apiService.serverInfoApi.getServerConfig(); + } catch (e) { + debugPrint("Error [getServerConfig] ${e.toString()}"); return null; } }