a147dee4b6
* maplibre on web, custom styles from server Actually use new vector tile server, custom style.json support multiple style files, light/dark mode cleanup, use new map everywhere send file directly instead of loading first better light/dark mode switching remove leaflet fix mapstyles dto, first draft of map settings delete and add styles fix delete default styles fix tests only allow one light and one dark style url revert config core changes fix server config store fix tests move axios fetches to repo fix package-lock fix tests * open api * add assets to docker container * web: use mapSettings color for style * style: add unique ids to map styles * mobile: use style json for vector / raster * do not use svelte-material-icons * add click events to markers, simplify asset detail map * improve map performance by using asset thumbnails for markers instead of original file * Remove custom attribution (by request) * mobile: update map attribution * style: map dark mode * style: map light mode * zoom level for state * styling * overflow gradient * Limit maxZoom to 14 * mobile: listen for mapStyle changes in MapThumbnail * mobile: update concurrency --------- Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> Co-authored-by: bo0tzz <git@bo0tzz.me> Co-authored-by: Alex <alex.tran1502@gmail.com>
142 lines
3.9 KiB
Dart
142 lines
3.9 KiB
Dart
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:immich_mobile/shared/models/store.dart';
|
|
import 'package:immich_mobile/utils/url_helper.dart';
|
|
import 'package:openapi/api.dart';
|
|
import 'package:http/http.dart';
|
|
|
|
class ApiService {
|
|
late ApiClient _apiClient;
|
|
|
|
late UserApi userApi;
|
|
late AuthenticationApi authenticationApi;
|
|
late OAuthApi oAuthApi;
|
|
late AlbumApi albumApi;
|
|
late AssetApi assetApi;
|
|
late SearchApi searchApi;
|
|
late ServerInfoApi serverInfoApi;
|
|
late PartnerApi partnerApi;
|
|
late PersonApi personApi;
|
|
late AuditApi auditApi;
|
|
late SharedLinkApi sharedLinkApi;
|
|
late SystemConfigApi systemConfigApi;
|
|
late ActivityApi activityApi;
|
|
|
|
ApiService() {
|
|
final endpoint = Store.tryGet(StoreKey.serverEndpoint);
|
|
if (endpoint != null && endpoint.isNotEmpty) {
|
|
setEndpoint(endpoint);
|
|
}
|
|
}
|
|
String? _authToken;
|
|
|
|
setEndpoint(String endpoint) {
|
|
_apiClient = ApiClient(basePath: endpoint);
|
|
if (_authToken != null) {
|
|
setAccessToken(_authToken!);
|
|
}
|
|
userApi = UserApi(_apiClient);
|
|
authenticationApi = AuthenticationApi(_apiClient);
|
|
oAuthApi = OAuthApi(_apiClient);
|
|
albumApi = AlbumApi(_apiClient);
|
|
assetApi = AssetApi(_apiClient);
|
|
serverInfoApi = ServerInfoApi(_apiClient);
|
|
searchApi = SearchApi(_apiClient);
|
|
partnerApi = PartnerApi(_apiClient);
|
|
personApi = PersonApi(_apiClient);
|
|
auditApi = AuditApi(_apiClient);
|
|
sharedLinkApi = SharedLinkApi(_apiClient);
|
|
systemConfigApi = SystemConfigApi(_apiClient);
|
|
activityApi = ActivityApi(_apiClient);
|
|
}
|
|
|
|
Future<String> resolveAndSetEndpoint(String serverUrl) async {
|
|
final endpoint = await _resolveEndpoint(serverUrl);
|
|
setEndpoint(endpoint);
|
|
|
|
// Save in hivebox for next startup
|
|
Store.put(StoreKey.serverEndpoint, endpoint);
|
|
return endpoint;
|
|
}
|
|
|
|
/// Takes a server URL and attempts to resolve the API endpoint.
|
|
///
|
|
/// Input: [schema://]host[:port][/path]
|
|
/// schema - optional (default: https)
|
|
/// host - required
|
|
/// port - optional (default: based on schema)
|
|
/// path - optional
|
|
Future<String> _resolveEndpoint(String serverUrl) async {
|
|
final url = sanitizeUrl(serverUrl);
|
|
|
|
if (!await _isEndpointAvailable(serverUrl)) {
|
|
throw ApiException(503, "Server is not reachable");
|
|
}
|
|
|
|
// Check for /.well-known/immich
|
|
final wellKnownEndpoint = await _getWellKnownEndpoint(url);
|
|
if (wellKnownEndpoint.isNotEmpty) return wellKnownEndpoint;
|
|
|
|
// Otherwise, assume the URL provided is the api endpoint
|
|
return url;
|
|
}
|
|
|
|
Future<bool> _isEndpointAvailable(String serverUrl) async {
|
|
final Client client = Client();
|
|
|
|
if (!serverUrl.endsWith('/api')) {
|
|
serverUrl += '/api';
|
|
}
|
|
|
|
// Throw Socket or Timeout exceptions,
|
|
// we do not care if the endpoints hits an HTTP error
|
|
try {
|
|
await client
|
|
.get(
|
|
Uri.parse(serverUrl),
|
|
)
|
|
.timeout(const Duration(seconds: 5));
|
|
} on TimeoutException catch (_) {
|
|
return false;
|
|
} on SocketException catch (_) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
Future<String> _getWellKnownEndpoint(String baseUrl) async {
|
|
final Client client = Client();
|
|
|
|
try {
|
|
final res = await client.get(
|
|
Uri.parse("$baseUrl/.well-known/immich"),
|
|
headers: {"Accept": "application/json"},
|
|
);
|
|
|
|
if (res.statusCode == 200) {
|
|
final data = jsonDecode(res.body);
|
|
final endpoint = data['api']['endpoint'].toString();
|
|
|
|
if (endpoint.startsWith('/')) {
|
|
// Full URL is relative to base
|
|
return "$baseUrl$endpoint";
|
|
}
|
|
return endpoint;
|
|
}
|
|
} catch (e) {
|
|
debugPrint("Could not locate /.well-known/immich at $baseUrl");
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
setAccessToken(String accessToken) {
|
|
_authToken = accessToken;
|
|
_apiClient.addDefaultHeader('Authorization', 'Bearer $accessToken');
|
|
}
|
|
|
|
ApiClient get apiClient => _apiClient;
|
|
}
|