From 922430da36a1a2432e7c6ad102ffbc0d0fa20876 Mon Sep 17 00:00:00 2001 From: Matej Kramny Date: Wed, 26 Jun 2024 15:31:55 -0400 Subject: [PATCH] feat(mobile): add additional request headers (#10588) * add additional request headers * improve interface * move headers under advanced settings * refactor * refactor --------- Co-authored-by: Alex --- mobile/assets/i18n/en-US.json | 10 +- mobile/lib/entities/store.entity.dart | 1 + .../pages/common/headers_settings.page.dart | 183 ++++++++++++++++++ .../lib/pages/search/person_result.page.dart | 6 +- .../video_player_controller_provider.dart | 5 +- .../video_player_controller_provider.g.dart | Bin 4820 -> 4820 bytes .../providers/image/cache/image_loader.dart | 6 +- mobile/lib/providers/websocket.provider.dart | 4 +- mobile/lib/routing/router.dart | 5 + mobile/lib/routing/router.gr.dart | 20 ++ mobile/lib/services/api.service.dart | 47 ++++- mobile/lib/services/backup.service.dart | 3 +- .../album/album_thumbnail_listtile.dart | 6 +- mobile/lib/widgets/common/user_avatar.dart | 3 +- .../widgets/common/user_circle_avatar.dart | 5 +- .../map/positioned_asset_marker_icon.dart | 6 +- .../widgets/search/curated_people_row.dart | 6 +- .../search/search_filter/people_picker.dart | 7 +- .../widgets/search/thumbnail_with_info.dart | 6 +- .../widgets/settings/advanced_settings.dart | 2 + .../custome_proxy_headers_settings.dart | 30 +++ .../settings/local_storage_settings.dart | 3 + mobile/makefile | 2 + 23 files changed, 319 insertions(+), 47 deletions(-) create mode 100644 mobile/lib/pages/common/headers_settings.page.dart create mode 100644 mobile/lib/widgets/settings/custom_proxy_headers_settings/custome_proxy_headers_settings.dart diff --git a/mobile/assets/i18n/en-US.json b/mobile/assets/i18n/en-US.json index 5f86e4064c..fe9da72c93 100644 --- a/mobile/assets/i18n/en-US.json +++ b/mobile/assets/i18n/en-US.json @@ -13,6 +13,9 @@ "advanced_settings_self_signed_ssl_title": "Allow self-signed SSL certificates", "advanced_settings_tile_subtitle": "Advanced user's settings", "advanced_settings_tile_title": "Advanced", + "headers_settings_tile_title": "Custom proxy headers", + "headers_settings_tile_subtitle": "Define proxy headers the app should send with each network request", + "advanced_settings_troubleshooting_subtitle": "Enable additional features for troubleshooting", "advanced_settings_troubleshooting_title": "Troubleshooting", "album_info_card_backup_album_excluded": "EXCLUDED", @@ -522,5 +525,8 @@ "version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89", "viewer_remove_from_stack": "Remove from Stack", "viewer_stack_use_as_main_asset": "Use as Main Asset", - "viewer_unstack": "Un-Stack" -} \ No newline at end of file + "viewer_unstack": "Un-Stack", + "header_settings_header_name_input": "Header name", + "header_settings_header_value_input": "Header value", + "header_settings_page_title": "Proxy Headers" +} diff --git a/mobile/lib/entities/store.entity.dart b/mobile/lib/entities/store.entity.dart index e7f1a6bce0..55d252b307 100644 --- a/mobile/lib/entities/store.entity.dart +++ b/mobile/lib/entities/store.entity.dart @@ -193,6 +193,7 @@ enum StoreKey { mapThemeMode(124, type: int), mapwithPartners(125, type: bool), enableHapticFeedback(126, type: bool), + customHeaders(127, type: String), ; const StoreKey( diff --git a/mobile/lib/pages/common/headers_settings.page.dart b/mobile/lib/pages/common/headers_settings.page.dart new file mode 100644 index 0000000000..1a6a10d903 --- /dev/null +++ b/mobile/lib/pages/common/headers_settings.page.dart @@ -0,0 +1,183 @@ +import 'dart:convert'; + +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:immich_mobile/entities/store.entity.dart' as store_keys; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +class SettingsHeader { + String key = ""; + String value = ""; +} + +@RoutePage() +class HeaderSettingsPage extends HookConsumerWidget { + const HeaderSettingsPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + // final apiService = ref.watch(apiServiceProvider); + final headers = useState>([]); + final setInitialHeaders = useState(false); + + var headersStr = + store_keys.Store.get(store_keys.StoreKey.customHeaders, ""); + if (!setInitialHeaders.value) { + if (headersStr.isNotEmpty) { + var customHeaders = jsonDecode(headersStr) as Map; + customHeaders.forEach((k, v) { + final header = SettingsHeader(); + header.key = k; + header.value = v; + headers.value.add(header); + }); + } + + // add first one to help the user + if (headers.value.isEmpty) { + final header = SettingsHeader(); + header.key = ''; + header.value = ''; + + headers.value.add(header); + } + } + setInitialHeaders.value = true; + + var list = [ + ...headers.value.map((headerValue) { + return HeaderKeyValueSettings( + header: headerValue, + onRemove: () { + headers.value.remove(headerValue); + headers.value = headers.value.toList(); + }, + ); + }), + ]; + + return Scaffold( + appBar: AppBar( + title: const Text('header_settings_page_title').tr(), + centerTitle: false, + actions: [ + IconButton( + onPressed: () { + headers.value.add(SettingsHeader()); + headers.value = headers.value.toList(); + }, + icon: const Icon(Icons.add_outlined), + tooltip: 'Add Header', + ), + ], + ), + body: PopScope( + onPopInvoked: (_) => saveHeaders(headers.value), + child: ListView.separated( + padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 16.0), + itemCount: list.length, + itemBuilder: (ctx, index) => list[index], + separatorBuilder: (context, index) => const Padding( + padding: EdgeInsets.only(bottom: 16.0, left: 8, right: 8), + child: Divider(), + ), + ), + ), + ); + } + + saveHeaders(List headers) { + final headersMap = {}; + for (var header in headers) { + final key = header.key.trim(); + final value = header.value.trim(); + + if (key.isEmpty || value.isEmpty) continue; + headersMap[key] = value; + } + + var encoded = jsonEncode(headersMap); + store_keys.Store.put(store_keys.StoreKey.customHeaders, encoded); + } +} + +class HeaderKeyValueSettings extends StatelessWidget { + final TextEditingController keyController; + final TextEditingController valueController; + final SettingsHeader header; + final Function() onRemove; + + HeaderKeyValueSettings({ + super.key, + required this.header, + required this.onRemove, + }) : keyController = TextEditingController(text: header.key), + valueController = TextEditingController(text: header.value); + + String? emptyFieldValidator(String? value) { + if (value == null || value.isEmpty) { + return 'Value cannot be empty'; + } + + return null; + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Padding( + padding: const EdgeInsets.only(left: 8, right: 8, bottom: 12.0), + child: Row( + children: [ + Expanded( + child: TextFormField( + controller: keyController, + decoration: InputDecoration( + labelText: 'header_settings_header_name_input'.tr(), + border: const OutlineInputBorder(), + ), + autocorrect: false, + onChanged: (headerKey) { + header.key = headerKey; + }, + validator: emptyFieldValidator, + textInputAction: TextInputAction.next, + ), + ), + Padding( + padding: const EdgeInsets.only(left: 8), + child: IconButton( + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 12), + ), + color: Colors.red[400], + onPressed: onRemove, + icon: const Icon(Icons.delete_outline), + ), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.only(left: 8, right: 8, bottom: 12.0), + child: TextFormField( + controller: valueController, + decoration: InputDecoration( + labelText: 'header_settings_header_name_input'.tr(), + border: const OutlineInputBorder(), + ), + autocorrect: false, + onChanged: (headerValue) { + header.value = headerValue; + }, + validator: emptyFieldValidator, + textInputAction: TextInputAction.done, + ), + ), + ], + ); + } +} diff --git a/mobile/lib/pages/search/person_result.page.dart b/mobile/lib/pages/search/person_result.page.dart index 1fec9a4a33..55824b8db9 100644 --- a/mobile/lib/pages/search/person_result.page.dart +++ b/mobile/lib/pages/search/person_result.page.dart @@ -5,8 +5,8 @@ import 'package:flutter_hooks/flutter_hooks.dart' hide Store; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/providers/search/people.provider.dart'; +import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/widgets/search/person_name_edit_form.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart'; import 'package:immich_mobile/utils/image_url_builder.dart'; @@ -122,9 +122,7 @@ class PersonResultPage extends HookConsumerWidget { radius: 36, backgroundImage: NetworkImage( getFaceThumbnailUrl(personId), - headers: { - "x-immich-user-token": Store.get(StoreKey.accessToken), - }, + headers: ApiService.getRequestHeaders(), ), ), Padding( diff --git a/mobile/lib/providers/asset_viewer/video_player_controller_provider.dart b/mobile/lib/providers/asset_viewer/video_player_controller_provider.dart index 4f6c6f30ea..969e181cbb 100644 --- a/mobile/lib/providers/asset_viewer/video_player_controller_provider.dart +++ b/mobile/lib/providers/asset_viewer/video_player_controller_provider.dart @@ -1,5 +1,6 @@ import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/services/api.service.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:video_player/video_player.dart'; @@ -26,11 +27,9 @@ Future videoPlayerController( : '$serverEndpoint/assets/${asset.remoteId}/video/playback'; final url = Uri.parse(videoUrl); - final accessToken = Store.get(StoreKey.accessToken); - controller = VideoPlayerController.networkUrl( url, - httpHeaders: {"x-immich-user-token": accessToken}, + httpHeaders: ApiService.getRequestHeaders(), videoPlayerOptions: asset.livePhotoVideoId != null ? VideoPlayerOptions(mixWithOthers: true) : VideoPlayerOptions(mixWithOthers: false), diff --git a/mobile/lib/providers/asset_viewer/video_player_controller_provider.g.dart b/mobile/lib/providers/asset_viewer/video_player_controller_provider.g.dart index cfef55457d76f2f127babb5158ba2397123e34c0..00ad37648a85e3bb6510919c7dbd153a69c0e755 100644 GIT binary patch delta 53 zcmcbjdPQ}EEu)5oNs^JJnPGCWQDSOhnq`WCv5~21N>Z9-lBJ1-iJ2i#B-zL$&2)1r HW4<5&x^@qn delta 53 zcmcbjdPQ}EEu)5+iIIt!Wul3Rk%hUTg+-!yvax|-nrU)kig8MsajKDVvV}>Cg=zBU IRK|Qk0HECuI{*Lx diff --git a/mobile/lib/providers/image/cache/image_loader.dart b/mobile/lib/providers/image/cache/image_loader.dart index 5124494255..6e83e9af64 100644 --- a/mobile/lib/providers/image/cache/image_loader.dart +++ b/mobile/lib/providers/image/cache/image_loader.dart @@ -4,7 +4,7 @@ import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:immich_mobile/providers/image/exceptions/image_loading_exception.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/services/api.service.dart'; /// Loads the codec from the URI and sends the events to the [chunkEvents] stream /// @@ -17,9 +17,7 @@ class ImageLoader { required ImageDecoderCallback decode, StreamController? chunkEvents, }) async { - final headers = { - 'x-immich-user-token': Store.get(StoreKey.accessToken), - }; + final headers = ApiService.getRequestHeaders(); final stream = cache.getFileStream( uri, diff --git a/mobile/lib/providers/websocket.provider.dart b/mobile/lib/providers/websocket.provider.dart index 4f722ec48b..6216a5de64 100644 --- a/mobile/lib/providers/websocket.provider.dart +++ b/mobile/lib/providers/websocket.provider.dart @@ -11,6 +11,7 @@ import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/providers/asset.provider.dart'; import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; +import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/sync.service.dart'; import 'package:immich_mobile/utils/debounce.dart'; import 'package:logging/logging.dart'; @@ -105,10 +106,9 @@ class WebsocketNotifier extends StateNotifier { final authenticationState = _ref.read(authenticationProvider); if (authenticationState.isAuthenticated) { - final accessToken = Store.get(StoreKey.accessToken); try { final endpoint = Uri.parse(Store.get(StoreKey.serverEndpoint)); - final headers = {"x-immich-user-token": accessToken}; + final headers = ApiService.getRequestHeaders(); if (endpoint.userInfo.isNotEmpty) { headers["Authorization"] = "Basic ${base64.encode(utf8.encode(endpoint.userInfo))}"; diff --git a/mobile/lib/routing/router.dart b/mobile/lib/routing/router.dart index f51f11230b..a9068316a2 100644 --- a/mobile/lib/routing/router.dart +++ b/mobile/lib/routing/router.dart @@ -24,6 +24,7 @@ import 'package:immich_mobile/pages/common/app_log.page.dart'; import 'package:immich_mobile/pages/common/app_log_detail.page.dart'; import 'package:immich_mobile/pages/common/create_album.page.dart'; import 'package:immich_mobile/pages/common/gallery_viewer.page.dart'; +import 'package:immich_mobile/pages/common/headers_settings.page.dart'; import 'package:immich_mobile/pages/common/settings.page.dart'; import 'package:immich_mobile/pages/common/splash_screen.page.dart'; import 'package:immich_mobile/pages/common/tab_controller.page.dart'; @@ -222,6 +223,10 @@ class AppRouter extends _$AppRouter { guards: [_authGuard, _duplicateGuard], transitionsBuilder: TransitionsBuilders.noTransition, ), + AutoRoute( + page: HeaderSettingsRoute.page, + guards: [_duplicateGuard], + ), ]; } diff --git a/mobile/lib/routing/router.gr.dart b/mobile/lib/routing/router.gr.dart index 806dd7f6ff..e602007df9 100644 --- a/mobile/lib/routing/router.gr.dart +++ b/mobile/lib/routing/router.gr.dart @@ -191,6 +191,12 @@ abstract class _$AppRouter extends RootStackRouter { ), ); }, + HeaderSettingsRoute.name: (routeData) { + return AutoRoutePage( + routeData: routeData, + child: const HeaderSettingsPage(), + ); + }, LibraryRoute.name: (routeData) { return AutoRoutePage( routeData: routeData, @@ -917,6 +923,20 @@ class GalleryViewerRouteArgs { } } +/// generated route for +/// [HeaderSettingsPage] +class HeaderSettingsRoute extends PageRouteInfo { + const HeaderSettingsRoute({List? children}) + : super( + HeaderSettingsRoute.name, + initialChildren: children, + ); + + static const String name = 'HeaderSettingsRoute'; + + static const PageInfo page = PageInfo(name); +} + /// generated route for /// [LibraryPage] class LibraryRoute extends PageRouteInfo { diff --git a/mobile/lib/services/api.service.dart b/mobile/lib/services/api.service.dart index 5381580e39..c128a2c2fc 100644 --- a/mobile/lib/services/api.service.dart +++ b/mobile/lib/services/api.service.dart @@ -9,7 +9,7 @@ import 'package:logging/logging.dart'; import 'package:openapi/api.dart'; import 'package:http/http.dart'; -class ApiService { +class ApiService implements Authentication { late ApiClient _apiClient; late UsersApi usersApi; @@ -40,7 +40,7 @@ class ApiService { final _log = Logger("ApiService"); setEndpoint(String endpoint) { - _apiClient = ApiClient(basePath: endpoint); + _apiClient = ApiClient(basePath: endpoint, authentication: this); if (_accessToken != null) { setAccessToken(_accessToken!); } @@ -103,7 +103,10 @@ class ApiService { try { final response = await client - .get(Uri.parse("$serverUrl/server-info/ping")) + .get( + Uri.parse("$serverUrl/server-info/ping"), + headers: getRequestHeaders(), + ) .timeout(const Duration(seconds: 5)); _log.info("Pinging server with response code ${response.statusCode}"); @@ -132,9 +135,12 @@ class ApiService { final Client client = Client(); try { + var headers = {"Accept": "application/json"}; + headers.addAll(getRequestHeaders()); + final res = await client.get( Uri.parse("$baseUrl/.well-known/immich"), - headers: {"Accept": "application/json"}, + headers: headers, ); if (res.statusCode == 200) { @@ -156,7 +162,38 @@ class ApiService { setAccessToken(String accessToken) { _accessToken = accessToken; - _apiClient.addDefaultHeader('x-immich-user-token', accessToken); + Store.put(StoreKey.accessToken, accessToken); + } + + static Map getRequestHeaders() { + var accessToken = Store.get(StoreKey.accessToken, ""); + var customHeadersStr = Store.get(StoreKey.customHeaders, ""); + var header = {}; + if (accessToken.isNotEmpty) { + header['x-immich-user-token'] = accessToken; + } + + if (customHeadersStr.isEmpty) { + return header; + } + + var customHeaders = jsonDecode(customHeadersStr) as Map; + customHeaders.forEach((key, value) { + header[key] = value; + }); + + return header; + } + + @override + Future applyToParams( + List queryParams, + Map headerParams, + ) { + return Future(() { + var headers = ApiService.getRequestHeaders(); + headerParams.addAll(headers); + }); } ApiClient get apiClient => _apiClient; diff --git a/mobile/lib/services/backup.service.dart b/mobile/lib/services/backup.service.dart index 025e475474..e7cb4d27ee 100644 --- a/mobile/lib/services/backup.service.dart +++ b/mobile/lib/services/backup.service.dart @@ -303,8 +303,7 @@ class BackupService { onProgress: ((bytes, totalBytes) => uploadProgressCb(bytes, totalBytes)), ); - baseRequest.headers["x-immich-user-token"] = - Store.get(StoreKey.accessToken); + baseRequest.headers.addAll(ApiService.getRequestHeaders()); baseRequest.headers["Transfer-Encoding"] = "chunked"; baseRequest.fields['deviceAssetId'] = entity.id; diff --git a/mobile/lib/widgets/album/album_thumbnail_listtile.dart b/mobile/lib/widgets/album/album_thumbnail_listtile.dart index 0018ceebcb..3c1e3a8ee0 100644 --- a/mobile/lib/widgets/album/album_thumbnail_listtile.dart +++ b/mobile/lib/widgets/album/album_thumbnail_listtile.dart @@ -5,7 +5,7 @@ import 'package:flutter/material.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/entities/album.entity.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/utils/image_url_builder.dart'; import 'package:openapi/api.dart'; @@ -48,9 +48,7 @@ class AlbumThumbnailListTile extends StatelessWidget { album, type: AssetMediaSize.thumbnail, ), - httpHeaders: { - "x-immich-user-token": Store.get(StoreKey.accessToken), - }, + httpHeaders: ApiService.getRequestHeaders(), cacheKey: getAlbumThumbNailCacheKey(album, type: AssetMediaSize.thumbnail), errorWidget: (context, url, error) => diff --git a/mobile/lib/widgets/common/user_avatar.dart b/mobile/lib/widgets/common/user_avatar.dart index 8dfe00b2b9..9a577d94b3 100644 --- a/mobile/lib/widgets/common/user_avatar.dart +++ b/mobile/lib/widgets/common/user_avatar.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/user.entity.dart'; +import 'package:immich_mobile/services/api.service.dart'; Widget userAvatar(BuildContext context, User u, {double? radius}) { final url = @@ -13,7 +14,7 @@ Widget userAvatar(BuildContext context, User u, {double? radius}) { backgroundColor: context.primaryColor.withAlpha(50), foregroundImage: CachedNetworkImageProvider( url, - headers: {"x-immich-user-token": Store.get(StoreKey.accessToken)}, + headers: ApiService.getRequestHeaders(), cacheKey: "user-${u.id}-profile", ), // silence errors if user has no profile image, use initials as fallback diff --git a/mobile/lib/widgets/common/user_circle_avatar.dart b/mobile/lib/widgets/common/user_circle_avatar.dart index 9dd924d98e..bf3bd8a5a8 100644 --- a/mobile/lib/widgets/common/user_circle_avatar.dart +++ b/mobile/lib/widgets/common/user_circle_avatar.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/user.entity.dart'; +import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/widgets/common/transparent_image.dart'; // ignore: must_be_immutable @@ -50,9 +51,7 @@ class UserCircleAvatar extends ConsumerWidget { height: size, placeholder: (_, __) => Image.memory(kTransparentImage), imageUrl: profileImageUrl, - httpHeaders: { - "x-immich-user-token": Store.get(StoreKey.accessToken), - }, + httpHeaders: ApiService.getRequestHeaders(), fadeInDuration: const Duration(milliseconds: 300), errorWidget: (context, error, stackTrace) => textIcon, ), diff --git a/mobile/lib/widgets/map/positioned_asset_marker_icon.dart b/mobile/lib/widgets/map/positioned_asset_marker_icon.dart index 4002c371cd..ac176b4701 100644 --- a/mobile/lib/widgets/map/positioned_asset_marker_icon.dart +++ b/mobile/lib/widgets/map/positioned_asset_marker_icon.dart @@ -4,7 +4,7 @@ import 'dart:math'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/utils/image_url_builder.dart'; class PositionedAssetMarkerIcon extends StatelessWidget { @@ -88,9 +88,7 @@ class _AssetMarkerIcon extends StatelessWidget { backgroundImage: CachedNetworkImageProvider( imageUrl, cacheKey: cacheKey, - headers: { - "x-immich-user-token": Store.get(StoreKey.accessToken), - }, + headers: ApiService.getRequestHeaders(), errorListener: (_) => const Icon(Icons.image_not_supported_outlined), ), diff --git a/mobile/lib/widgets/search/curated_people_row.dart b/mobile/lib/widgets/search/curated_people_row.dart index f8e4fbe3c0..2ec57d6a1a 100644 --- a/mobile/lib/widgets/search/curated_people_row.dart +++ b/mobile/lib/widgets/search/curated_people_row.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/models/search/search_curated_content.model.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/utils/image_url_builder.dart'; class CuratedPeopleRow extends StatelessWidget { @@ -33,9 +33,7 @@ class CuratedPeopleRow extends StatelessWidget { separatorBuilder: (context, index) => const SizedBox(width: 16), itemBuilder: (context, index) { final person = content[index]; - final headers = { - "x-immich-user-token": Store.get(StoreKey.accessToken), - }; + final headers = ApiService.getRequestHeaders(); return Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ diff --git a/mobile/lib/widgets/search/search_filter/people_picker.dart b/mobile/lib/widgets/search/search_filter/people_picker.dart index a8308cb9d1..d79ae5bd95 100644 --- a/mobile/lib/widgets/search/search_filter/people_picker.dart +++ b/mobile/lib/widgets/search/search_filter/people_picker.dart @@ -4,7 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/providers/search/people.provider.dart'; -import 'package:immich_mobile/entities/store.entity.dart' as local_store; +import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/utils/image_url_builder.dart'; import 'package:openapi/api.dart'; @@ -18,10 +18,7 @@ class PeoplePicker extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { var imageSize = 45.0; final people = ref.watch(getAllPeopleProvider); - final headers = { - "x-immich-user-token": - local_store.Store.get(local_store.StoreKey.accessToken), - }; + final headers = ApiService.getRequestHeaders(); final selectedPeople = useState>(filter ?? {}); return people.widgetWhen( diff --git a/mobile/lib/widgets/search/thumbnail_with_info.dart b/mobile/lib/widgets/search/thumbnail_with_info.dart index 035bc4420f..8722bf8db8 100644 --- a/mobile/lib/widgets/search/thumbnail_with_info.dart +++ b/mobile/lib/widgets/search/thumbnail_with_info.dart @@ -1,8 +1,8 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/widgets/search/thumbnail_with_info_container.dart'; +import 'package:immich_mobile/services/api.service.dart'; class ThumbnailWithInfo extends StatelessWidget { const ThumbnailWithInfo({ @@ -36,9 +36,7 @@ class ThumbnailWithInfo extends StatelessWidget { height: double.infinity, fit: BoxFit.cover, imageUrl: imageUrl!, - httpHeaders: { - "x-immich-user-token": Store.get(StoreKey.accessToken), - }, + httpHeaders: ApiService.getRequestHeaders(), errorWidget: (context, url, error) => const Icon(Icons.image_not_supported_outlined), ), diff --git a/mobile/lib/widgets/settings/advanced_settings.dart b/mobile/lib/widgets/settings/advanced_settings.dart index b0727feb0c..60ad4ea3d3 100644 --- a/mobile/lib/widgets/settings/advanced_settings.dart +++ b/mobile/lib/widgets/settings/advanced_settings.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart' hide Store; +import 'package:immich_mobile/widgets/settings/custom_proxy_headers_settings/custome_proxy_headers_settings.dart'; import 'package:immich_mobile/widgets/settings/local_storage_settings.dart'; import 'package:immich_mobile/widgets/settings/settings_slider_list_tile.dart'; import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart'; @@ -62,6 +63,7 @@ class AdvancedSettings extends HookConsumerWidget { subtitle: "advanced_settings_self_signed_ssl_subtitle".tr(), onChanged: (_) => HttpOverrides.global = HttpSSLCertOverride(), ), + const CustomeProxyHeaderSettings(), ]; return SettingsSubPageScaffold(settings: advancedSettings); diff --git a/mobile/lib/widgets/settings/custom_proxy_headers_settings/custome_proxy_headers_settings.dart b/mobile/lib/widgets/settings/custom_proxy_headers_settings/custome_proxy_headers_settings.dart new file mode 100644 index 0000000000..12efa52b2d --- /dev/null +++ b/mobile/lib/widgets/settings/custom_proxy_headers_settings/custome_proxy_headers_settings.dart @@ -0,0 +1,30 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/routing/router.dart'; + +class CustomeProxyHeaderSettings extends StatelessWidget { + const CustomeProxyHeaderSettings({super.key}); + + @override + Widget build(BuildContext context) { + return ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 20), + dense: true, + title: Text( + "headers_settings_tile_title".tr(), + style: context.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.w500, + ), + ), + subtitle: Text( + "headers_settings_tile_subtitle".tr(), + style: const TextStyle( + fontSize: 14, + ), + ), + onTap: () => context.pushRoute(const HeaderSettingsRoute()), + ); + } +} diff --git a/mobile/lib/widgets/settings/local_storage_settings.dart b/mobile/lib/widgets/settings/local_storage_settings.dart index f0413c228f..6e7723cbff 100644 --- a/mobile/lib/widgets/settings/local_storage_settings.dart +++ b/mobile/lib/widgets/settings/local_storage_settings.dart @@ -37,6 +37,9 @@ class LocalStorageSettings extends HookConsumerWidget { ).tr(args: ["${cacheItemCount.value}"]), subtitle: const Text( "cache_settings_duplicated_assets_subtitle", + style: TextStyle( + fontSize: 14, + ), ).tr(), trailing: TextButton( onPressed: cacheItemCount.value > 0 ? clearCache : null, diff --git a/mobile/makefile b/mobile/makefile index a04fe36215..43bc59c7d4 100644 --- a/mobile/makefile +++ b/mobile/makefile @@ -1,3 +1,5 @@ +.PHONY: build watch create_app_icon create_splash build_release_android + build: dart run build_runner build --delete-conflicting-outputs