diff --git a/mobile/assets/i18n/en-US.json b/mobile/assets/i18n/en-US.json index 082a3f6de9..074f3e1648 100644 --- a/mobile/assets/i18n/en-US.json +++ b/mobile/assets/i18n/en-US.json @@ -217,6 +217,7 @@ "search_page_selfies": "Selfies", "search_page_things": "Things", "search_page_videos": "Videos", + "search_page_people": "People", "search_page_view_all_button": "View all", "search_page_your_activity": "Your activity", "search_result_page_new_search_hint": "New Search", @@ -285,5 +286,6 @@ "version_announcement_overlay_text_1": "Hi friend, there is a new release of", "version_announcement_overlay_text_2": "please take your time to visit the ", "version_announcement_overlay_text_3": " and ensure your docker-compose and .env setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your server application automatically.", - "version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89" -} \ No newline at end of file + "version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89", + "all_people_page_title": "People" +} diff --git a/mobile/ios/Podfile.lock b/mobile/ios/Podfile.lock index 44760c9c87..f1d5b2169b 100644 --- a/mobile/ios/Podfile.lock +++ b/mobile/ios/Podfile.lock @@ -39,7 +39,7 @@ PODS: - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS - - sqflite (0.0.2): + - sqflite (0.0.3): - Flutter - FMDB (>= 2.7.5) - Toast (4.0.0) @@ -128,21 +128,21 @@ SPEC CHECKSUMS: flutter_web_auth: c25208760459cec375a3c39f6a8759165ca0fa4d fluttertoast: eb263d302cc92e04176c053d2385237e9f43fad0 FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a - image_picker_ios: 58b9c4269cb176f89acea5e5d043c9358f2d25f8 + image_picker_ios: 4a8aadfbb6dc30ad5141a2ce3832af9214a705b5 integration_test: 13825b8a9334a850581300559b8839134b124670 isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073 package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e - path_provider_foundation: c68054786f1b4f3343858c1e1d0caaded73f0be9 + path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8 path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02 permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce photo_manager: 4f6810b7dfc4feb03b461ac1a70dacf91fba7604 SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68 - shared_preferences_foundation: 986fc17f3d3251412d18b0265f9c64113a8c2472 - sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 + shared_preferences_foundation: e2dae3258e06f44cc55f49d42024fd8dd03c590c + sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a Toast: 91b396c56ee72a5790816f40d3a94dd357abc196 url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4 - video_player_avfoundation: 6d971a232d72e6ee25368378d48a079dea01f1cf + video_player_avfoundation: 81e49bb3d9fb63dccf9fa0f6d877dc3ddbeac126 wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f PODFILE CHECKSUM: 599d8aeb73728400c15364e734525722250a5382 diff --git a/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid.dart b/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid.dart index b628273bf6..21a33b51c6 100644 --- a/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid.dart +++ b/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid.dart @@ -28,6 +28,7 @@ class ImmichAssetGrid extends HookConsumerWidget { final bool showMultiSelectIndicator; final void Function(ItemPosition start, ItemPosition end)? visibleItemsListener; + final Widget? topWidget; const ImmichAssetGrid({ super.key, @@ -44,6 +45,7 @@ class ImmichAssetGrid extends HookConsumerWidget { this.dynamicLayout, this.showMultiSelectIndicator = true, this.visibleItemsListener, + this.topWidget, }); @override @@ -125,6 +127,7 @@ class ImmichAssetGrid extends HookConsumerWidget { settings.getSetting(AppSettingsEnum.dynamicLayout), showMultiSelectIndicator: showMultiSelectIndicator, visibleItemsListener: visibleItemsListener, + topWidget: topWidget, ), ), ), diff --git a/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid_view.dart b/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid_view.dart index 79dca40f9d..691a5b27f4 100644 --- a/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid_view.dart +++ b/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid_view.dart @@ -206,6 +206,8 @@ class ImmichAssetGridViewState extends State { key: ValueKey(section.offset), crossAxisAlignment: CrossAxisAlignment.start, children: [ + if (section.offset == 0 && widget.topWidget != null) + widget.topWidget!, if (section.type == RenderAssetGridElementType.monthTitle) _buildMonthTitle(context, section.date), if (section.type == RenderAssetGridElementType.groupDividerTitle || @@ -401,6 +403,7 @@ class ImmichAssetGridView extends StatefulWidget { final bool showMultiSelectIndicator; final void Function(ItemPosition start, ItemPosition end)? visibleItemsListener; + final Widget? topWidget; const ImmichAssetGridView({ super.key, @@ -416,6 +419,7 @@ class ImmichAssetGridView extends StatefulWidget { this.dynamicLayout = true, this.showMultiSelectIndicator = true, this.visibleItemsListener, + this.topWidget, }); @override diff --git a/mobile/lib/modules/search/providers/people.provider.dart b/mobile/lib/modules/search/providers/people.provider.dart new file mode 100644 index 0000000000..e40ff3fc83 --- /dev/null +++ b/mobile/lib/modules/search/providers/people.provider.dart @@ -0,0 +1,44 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart'; +import 'package:immich_mobile/modules/search/services/person.service.dart'; +import 'package:openapi/api.dart'; + +final personAssetsProvider = FutureProvider.family + .autoDispose((ref, personId) async { + final PersonService personService = ref.watch(personServiceProvider); + + final assets = await personService.getPersonAssets(personId); + + if (assets == null) { + return RenderList.empty(); + } + + return RenderList.fromAssets(assets, GroupAssetsBy.auto); +}); + +final getCuratedPeopleProvider = + FutureProvider.autoDispose>((ref) async { + final PersonService personService = ref.watch(personServiceProvider); + + final curatedPeople = await personService.getCuratedPeople(); + + return curatedPeople ?? []; +}); + +class UpdatePersonName { + final String id; + final String name; + + UpdatePersonName(this.id, this.name); +} + +final updatePersonNameProvider = + StateProvider.family((ref, dto) async { + final PersonService personService = ref.watch(personServiceProvider); + + final person = await personService.updateName(dto.id, dto.name); + + if (person != null && person.name == dto.name) { + ref.invalidate(getCuratedPeopleProvider); + } +}); diff --git a/mobile/lib/modules/search/services/person.service.dart b/mobile/lib/modules/search/services/person.service.dart new file mode 100644 index 0000000000..5813653cc7 --- /dev/null +++ b/mobile/lib/modules/search/services/person.service.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/shared/models/asset.dart'; +import 'package:immich_mobile/shared/providers/api.provider.dart'; +import 'package:immich_mobile/shared/services/api.service.dart'; +import 'package:openapi/api.dart'; + +final personServiceProvider = Provider( + (ref) => PersonService( + ref.watch(apiServiceProvider), + ), +); + +class PersonService { + final ApiService _apiService; + + PersonService(this._apiService); + + Future?> getCuratedPeople() async { + try { + return await _apiService.personApi.getAllPeople(); + } catch (e) { + debugPrint("Error [getCuratedPeople] ${e.toString()}"); + return null; + } + } + + Future?> getPersonAssets(String id) async { + try { + final assets = await _apiService.personApi.getPersonAssets(id); + + if (assets == null) { + return null; + } + + return assets.map((e) => Asset.remote(e)).toList(); + } catch (e) { + debugPrint("Error [getPersonAssets] ${e.toString()}"); + return null; + } + } + + Future updateName(String id, String name) async { + try { + return await _apiService.personApi.updatePerson( + id, + PersonUpdateDto( + name: name, + ), + ); + } catch (e) { + debugPrint("Error [updateName] ${e.toString()}"); + return null; + } + } +} diff --git a/mobile/lib/modules/search/ui/curated_people_row.dart b/mobile/lib/modules/search/ui/curated_people_row.dart new file mode 100644 index 0000000000..18a1da2c38 --- /dev/null +++ b/mobile/lib/modules/search/ui/curated_people_row.dart @@ -0,0 +1,114 @@ +import 'package:flutter/material.dart'; +import 'package:immich_mobile/modules/search/models/curated_content.dart'; +import 'package:immich_mobile/modules/search/ui/thumbnail_with_info.dart'; +import 'package:immich_mobile/shared/models/store.dart'; +import 'package:immich_mobile/utils/image_url_builder.dart'; + +class CuratedPeopleRow extends StatelessWidget { + final List content; + + /// Callback with the content and the index when tapped + final Function(CuratedContent, int)? onTap; + final Function(CuratedContent, int)? onNameTap; + + const CuratedPeopleRow({ + super.key, + required this.content, + this.onTap, + required this.onNameTap, + }); + + @override + Widget build(BuildContext context) { + const imageSize = 85.0; + + // Guard empty [content] + if (content.isEmpty) { + // Return empty thumbnail + 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.only( + left: 16, + top: 8, + ), + itemBuilder: (context, index) { + final person = content[index]; + final headers = { + "Authorization": "Bearer ${Store.get(StoreKey.accessToken)}" + }; + return Padding( + padding: const EdgeInsets.only(right: 18.0), + child: SizedBox( + width: imageSize, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + GestureDetector( + onTap: () => onTap?.call(person, index), + child: SizedBox( + height: imageSize, + child: Material( + shape: const CircleBorder(side: BorderSide.none), + elevation: 3, + child: CircleAvatar( + maxRadius: imageSize / 2, + backgroundImage: NetworkImage( + getFaceThumbnailUrl(person.id), + headers: headers, + ), + ), + ), + ), + ), + if (person.label == "") + GestureDetector( + onTap: () => onNameTap?.call(person, index), + child: Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + "Add name", + style: TextStyle( + fontWeight: FontWeight.bold, + color: Theme.of(context).primaryColor, + ), + ), + ), + ) + else + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + person.label, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 13.0, + ), + ), + ) + ], + ), + ), + ); + }, + itemCount: content.length, + ); + } +} diff --git a/mobile/lib/modules/search/ui/explore_grid.dart b/mobile/lib/modules/search/ui/explore_grid.dart index 788a05dd75..12f8ec6a1e 100644 --- a/mobile/lib/modules/search/ui/explore_grid.dart +++ b/mobile/lib/modules/search/ui/explore_grid.dart @@ -4,12 +4,16 @@ import 'package:immich_mobile/modules/search/models/curated_content.dart'; import 'package:immich_mobile/modules/search/ui/thumbnail_with_info.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/shared/models/store.dart'; +import 'package:immich_mobile/utils/image_url_builder.dart'; class ExploreGrid extends StatelessWidget { final List curatedContent; + final bool isPeople; + const ExploreGrid({ super.key, required this.curatedContent, + this.isPeople = false, }); @override @@ -36,16 +40,25 @@ class ExploreGrid extends StatelessWidget { ), itemBuilder: (context, index) { final content = curatedContent[index]; - final thumbnailRequestUrl = - '${Store.get(StoreKey.serverEndpoint)}/asset/thumbnail/${content.id}'; + final thumbnailRequestUrl = isPeople + ? getFaceThumbnailUrl(content.id) + : '${Store.get(StoreKey.serverEndpoint)}/asset/thumbnail/${content.id}'; + return ThumbnailWithInfo( imageUrl: thumbnailRequestUrl, textInfo: content.label, borderRadius: 0, onTap: () { - AutoRouter.of(context).push( - SearchResultRoute(searchTerm: 'm:${content.label}'), - ); + isPeople + ? AutoRouter.of(context).push( + PersonResultRoute( + personId: content.id, + personName: content.label, + ), + ) + : AutoRouter.of(context).push( + SearchResultRoute(searchTerm: 'm:${content.label}'), + ); }, ); }, diff --git a/mobile/lib/modules/search/ui/person_name_edit_form.dart b/mobile/lib/modules/search/ui/person_name_edit_form.dart new file mode 100644 index 0000000000..f60824b5fb --- /dev/null +++ b/mobile/lib/modules/search/ui/person_name_edit_form.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/modules/search/providers/people.provider.dart'; + +class PersonNameEditFormResult { + final bool success; + final String updatedName; + + PersonNameEditFormResult(this.success, this.updatedName); +} + +class PersonNameEditForm extends HookConsumerWidget { + final String personId; + final String personName; + + const PersonNameEditForm({ + super.key, + required this.personId, + required this.personName, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final controller = useTextEditingController(text: personName); + + return AlertDialog( + title: const Text( + "Add a name", + style: TextStyle(fontWeight: FontWeight.bold), + ), + content: SingleChildScrollView( + child: TextFormField( + controller: controller, + autofocus: true, + decoration: const InputDecoration( + hintText: 'Name', + ), + ), + ), + actions: [ + TextButton( + style: TextButton.styleFrom(), + onPressed: () { + Navigator.of(context, rootNavigator: true) + .pop( + PersonNameEditFormResult(false, ''), + ); + }, + child: Text( + "Cancel", + style: TextStyle( + color: Colors.red[300], + fontWeight: FontWeight.bold, + ), + ), + ), + TextButton( + onPressed: () { + ref.read( + updatePersonNameProvider( + UpdatePersonName(personId, controller.text), + ), + ); + + Navigator.of(context, rootNavigator: true) + .pop( + PersonNameEditFormResult(true, controller.text), + ); + }, + child: Text( + "Save", + style: TextStyle( + color: Theme.of(context).primaryColor, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ); + } +} diff --git a/mobile/lib/modules/search/ui/search_row_title.dart b/mobile/lib/modules/search/ui/search_row_title.dart new file mode 100644 index 0000000000..5448874e30 --- /dev/null +++ b/mobile/lib/modules/search/ui/search_row_title.dart @@ -0,0 +1,46 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; + +class SearchRowTitle extends StatelessWidget { + final Function() onViewAllPressed; + final String title; + final double top; + + const SearchRowTitle({ + super.key, + required this.onViewAllPressed, + required this.title, + this.top = 12, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.only( + left: 16.0, + right: 16.0, + top: top, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + title, + style: Theme.of(context).textTheme.titleSmall, + ), + TextButton( + onPressed: onViewAllPressed, + child: Text( + 'search_page_view_all_button', + style: TextStyle( + color: Theme.of(context).primaryColor, + fontWeight: FontWeight.bold, + fontSize: 14.0, + ), + ).tr(), + ), + ], + ), + ); + } +} diff --git a/mobile/lib/modules/search/views/all_people_page.dart b/mobile/lib/modules/search/views/all_people_page.dart new file mode 100644 index 0000000000..d3361fc30a --- /dev/null +++ b/mobile/lib/modules/search/views/all_people_page.dart @@ -0,0 +1,51 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/modules/search/models/curated_content.dart'; +import 'package:immich_mobile/modules/search/providers/people.provider.dart'; +import 'package:immich_mobile/modules/search/ui/explore_grid.dart'; +import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; + +class AllPeoplePage extends HookConsumerWidget { + const AllPeoplePage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final curatedPeople = ref.watch(getCuratedPeopleProvider); + + return Scaffold( + appBar: AppBar( + title: Text( + 'all_people_page_title', + style: TextStyle( + color: Theme.of(context).primaryColor, + fontWeight: FontWeight.bold, + fontSize: 16.0, + ), + ).tr(), + leading: IconButton( + onPressed: () => AutoRouter.of(context).pop(), + icon: const Icon(Icons.arrow_back_ios_rounded), + ), + ), + body: curatedPeople.when( + loading: () => const Center(child: ImmichLoadingIndicator()), + error: (err, stack) => Center( + child: Text('Error: $err'), + ), + data: (people) => ExploreGrid( + isPeople: true, + curatedContent: people + .map( + (person) => CuratedContent( + label: person.name, + id: person.id, + ), + ) + .toList(), + ), + ), + ); + } +} diff --git a/mobile/lib/modules/search/views/person_result_page.dart b/mobile/lib/modules/search/views/person_result_page.dart new file mode 100644 index 0000000000..bcdae61854 --- /dev/null +++ b/mobile/lib/modules/search/views/person_result_page.dart @@ -0,0 +1,152 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart'; +import 'package:immich_mobile/modules/search/providers/people.provider.dart'; +import 'package:immich_mobile/modules/search/ui/person_name_edit_form.dart'; +import 'package:immich_mobile/shared/models/store.dart' as isar_store; +import 'package:immich_mobile/utils/image_url_builder.dart'; + +class PersonResultPage extends HookConsumerWidget { + final String personId; + final String personName; + + const PersonResultPage({ + super.key, + required this.personId, + required this.personName, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final name = useState(personName); + + showEditNameDialog() { + showDialog( + context: context, + builder: (BuildContext context) { + return PersonNameEditForm( + personId: personId, + personName: personName, + ); + }, + ).then((result) { + if (result != null && result.success) { + name.value = result.updatedName; + } + }); + } + + void buildBottomSheet() { + showModalBottomSheet( + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + isScrollControlled: false, + context: context, + useSafeArea: true, + builder: (context) { + return SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + leading: const Icon(Icons.edit_outlined), + title: const Text( + 'Edit name', + style: TextStyle(fontWeight: FontWeight.bold), + ), + onTap: showEditNameDialog, + ) + ], + ), + ); + }, + ); + } + + buildTitleBlock() { + if (name.value == "") { + return GestureDetector( + onTap: showEditNameDialog, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Add a name', + style: Theme.of(context).textTheme.titleSmall?.copyWith( + color: Theme.of(context).colorScheme.secondary, + ), + ), + Text( + 'Find them fast by name with search', + style: Theme.of(context).textTheme.labelSmall, + ), + ], + ), + ); + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + name.value, + style: Theme.of(context).textTheme.titleLarge, + ), + ], + ); + } + + return Scaffold( + appBar: AppBar( + title: Text(name.value), + leading: IconButton( + onPressed: () => AutoRouter.of(context).pop(), + icon: const Icon(Icons.arrow_back_ios_rounded), + ), + actions: [ + IconButton( + onPressed: buildBottomSheet, + icon: const Icon(Icons.more_vert_rounded), + ), + ], + ), + body: ref.watch(personAssetsProvider(personId)).when( + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, stackTrace) => Center( + child: Text( + error.toString(), + ), + ), + data: (data) => data.isEmpty + ? const Center( + child: Text('Opps'), + ) + : ImmichAssetGrid( + renderList: data, + topWidget: Padding( + padding: const EdgeInsets.only(left: 8.0, top: 24), + child: Row( + children: [ + CircleAvatar( + radius: 36, + backgroundImage: NetworkImage( + getFaceThumbnailUrl(personId), + headers: { + "Authorization": + "Bearer ${isar_store.Store.get(isar_store.StoreKey.accessToken)}" + }, + ), + ), + Padding( + padding: const EdgeInsets.only(left: 16.0), + child: buildTitleBlock(), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/mobile/lib/modules/search/views/search_page.dart b/mobile/lib/modules/search/views/search_page.dart index fc87f08403..e3e8b133a6 100644 --- a/mobile/lib/modules/search/views/search_page.dart +++ b/mobile/lib/modules/search/views/search_page.dart @@ -4,13 +4,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart' hide Store; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/modules/search/models/curated_content.dart'; +import 'package:immich_mobile/modules/search/providers/people.provider.dart'; import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart'; +import 'package:immich_mobile/modules/search/ui/curated_people_row.dart'; import 'package:immich_mobile/modules/search/ui/curated_row.dart'; import 'package:immich_mobile/modules/search/ui/immich_search_bar.dart'; +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/ui/immich_loading_indicator.dart'; -import 'package:openapi/api.dart'; // ignore: must_be_immutable class SearchPage extends HookConsumerWidget { @@ -21,10 +24,9 @@ class SearchPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final isSearchEnabled = ref.watch(searchPageStateProvider).isSearchEnabled; - AsyncValue> curatedLocation = - ref.watch(getCuratedLocationProvider); - AsyncValue> curatedObjects = - ref.watch(getCuratedObjectProvider); + final curatedLocation = ref.watch(getCuratedLocationProvider); + final curatedObjects = ref.watch(getCuratedObjectProvider); + final curatedPeople = ref.watch(getCuratedPeopleProvider); var isDarkTheme = Theme.of(context).brightness == Brightness.dark; double imageSize = MediaQuery.of(context).size.width / 3; @@ -54,6 +56,50 @@ class SearchPage extends HookConsumerWidget { ); } + showNameEditModel( + String personId, + String personName, + ) { + return showDialog( + context: context, + builder: (BuildContext context) { + return PersonNameEditForm(personId: personId, personName: personName); + }, + ); + } + + buildPeople() { + return SizedBox( + height: MediaQuery.of(context).size.width / 3, + child: curatedPeople.when( + loading: () => const Center(child: ImmichLoadingIndicator()), + error: (err, stack) => Center(child: Text('Error: $err')), + data: (people) => CuratedPeopleRow( + content: people + .map( + (person) => CuratedContent( + id: person.id, + label: person.name, + ), + ) + .take(12) + .toList(), + onTap: (content, index) { + AutoRouter.of(context).push( + PersonResultRoute( + personId: content.id, + personName: content.label, + ), + ); + }, + onNameTap: (person, index) => { + showNameEditModel(person.id, person.label), + }, + ), + ), + ); + } + buildPlaces() { return SizedBox( height: imageSize, @@ -130,63 +176,25 @@ class SearchPage extends HookConsumerWidget { children: [ ListView( children: [ - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16.0, - vertical: 4.0, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "search_page_places", - style: Theme.of(context).textTheme.titleSmall, - ).tr(), - TextButton( - child: Text( - 'search_page_view_all_button', - style: TextStyle( - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.bold, - fontSize: 14.0, - ), - ).tr(), - onPressed: () => AutoRouter.of(context).push( - const CuratedLocationRoute(), - ), - ), - ], + SearchRowTitle( + title: "search_page_people".tr(), + onViewAllPressed: () => AutoRouter.of(context).push( + const AllPeopleRoute(), ), ), - buildPlaces(), - Padding( - padding: const EdgeInsets.only( - top: 24.0, - bottom: 4.0, - left: 16.0, - right: 16.0, + buildPeople(), + SearchRowTitle( + title: "search_page_places".tr(), + onViewAllPressed: () => AutoRouter.of(context).push( + const CuratedLocationRoute(), ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "search_page_things", - style: Theme.of(context).textTheme.titleSmall, - ).tr(), - TextButton( - child: Text( - 'search_page_view_all_button', - style: TextStyle( - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.bold, - fontSize: 14.0, - ), - ).tr(), - onPressed: () => AutoRouter.of(context).push( - const CuratedObjectRoute(), - ), - ), - ], + top: 0, + ), + buildPlaces(), + SearchRowTitle( + title: "search_page_things".tr(), + onViewAllPressed: () => AutoRouter.of(context).push( + const CuratedObjectRoute(), ), ), buildThings(), @@ -200,7 +208,7 @@ class SearchPage extends HookConsumerWidget { ), ListTile( leading: Icon( - Icons.favorite_border, + Icons.star_outline, color: categoryIconColor, ), title: diff --git a/mobile/lib/routing/router.dart b/mobile/lib/routing/router.dart index cd731dc109..200d357dc0 100644 --- a/mobile/lib/routing/router.dart +++ b/mobile/lib/routing/router.dart @@ -25,9 +25,11 @@ import 'package:immich_mobile/modules/login/views/login_page.dart'; import 'package:immich_mobile/modules/onboarding/providers/gallery_permission.provider.dart'; import 'package:immich_mobile/modules/onboarding/views/permission_onboarding_page.dart'; import 'package:immich_mobile/modules/search/views/all_motion_videos_page.dart'; +import 'package:immich_mobile/modules/search/views/all_people_page.dart'; import 'package:immich_mobile/modules/search/views/all_videos_page.dart'; import 'package:immich_mobile/modules/search/views/curated_location_page.dart'; import 'package:immich_mobile/modules/search/views/curated_object_page.dart'; +import 'package:immich_mobile/modules/search/views/person_result_page.dart'; import 'package:immich_mobile/modules/search/views/recently_added_page.dart'; import 'package:immich_mobile/modules/search/views/search_page.dart'; import 'package:immich_mobile/modules/search/views/search_result_page.dart'; @@ -37,8 +39,8 @@ import 'package:immich_mobile/routing/duplicate_guard.dart'; import 'package:immich_mobile/routing/gallery_permission_guard.dart'; import 'package:immich_mobile/shared/models/asset.dart'; import 'package:immich_mobile/shared/models/album.dart'; -import 'package:immich_mobile/shared/models/user.dart'; import 'package:immich_mobile/shared/models/logger_message.model.dart'; +import 'package:immich_mobile/shared/models/user.dart'; import 'package:immich_mobile/shared/providers/api.provider.dart'; import 'package:immich_mobile/shared/services/api.service.dart'; import 'package:immich_mobile/shared/views/app_log_detail_page.dart'; @@ -140,7 +142,15 @@ part 'router.gr.dart'; ], ), AutoRoute(page: PartnerPage, guards: [AuthGuard, DuplicateGuard]), - AutoRoute(page: PartnerDetailPage, guards: [AuthGuard, DuplicateGuard]) + AutoRoute(page: PartnerDetailPage, guards: [AuthGuard, DuplicateGuard]), + AutoRoute( + page: PersonResultPage, + guards: [ + AuthGuard, + DuplicateGuard, + ], + ), + AutoRoute(page: AllPeoplePage, guards: [AuthGuard, DuplicateGuard]), ], ) class AppRouter extends _$AppRouter { diff --git a/mobile/lib/routing/router.gr.dart b/mobile/lib/routing/router.gr.dart index d79aac5f5d..7c03a9b88d 100644 --- a/mobile/lib/routing/router.gr.dart +++ b/mobile/lib/routing/router.gr.dart @@ -84,6 +84,7 @@ class _$AppRouter extends RootStackRouter { onVideoEnded: args.onVideoEnded, onPlaying: args.onPlaying, onPaused: args.onPaused, + placeholder: args.placeholder, ), ); }, @@ -272,6 +273,23 @@ class _$AppRouter extends RootStackRouter { ), ); }, + PersonResultRoute.name: (routeData) { + final args = routeData.argsAs(); + return MaterialPageX( + routeData: routeData, + child: PersonResultPage( + key: args.key, + personId: args.personId, + personName: args.personName, + ), + ); + }, + AllPeopleRoute.name: (routeData) { + return MaterialPageX( + routeData: routeData, + child: const AllPeoplePage(), + ); + }, HomeRoute.name: (routeData) { return MaterialPageX( routeData: routeData, @@ -555,6 +573,22 @@ class _$AppRouter extends RootStackRouter { duplicateGuard, ], ), + RouteConfig( + PersonResultRoute.name, + path: '/person-result-page', + guards: [ + authGuard, + duplicateGuard, + ], + ), + RouteConfig( + AllPeopleRoute.name, + path: '/all-people-page', + guards: [ + authGuard, + duplicateGuard, + ], + ), ]; } @@ -670,9 +704,10 @@ class VideoViewerRoute extends PageRouteInfo { Key? key, required Asset asset, required bool isMotionVideo, - required dynamic onVideoEnded, - dynamic onPlaying, - dynamic onPaused, + required void Function() onVideoEnded, + void Function()? onPlaying, + void Function()? onPaused, + Widget? placeholder, }) : super( VideoViewerRoute.name, path: '/video-viewer-page', @@ -683,6 +718,7 @@ class VideoViewerRoute extends PageRouteInfo { onVideoEnded: onVideoEnded, onPlaying: onPlaying, onPaused: onPaused, + placeholder: placeholder, ), ); @@ -697,6 +733,7 @@ class VideoViewerRouteArgs { required this.onVideoEnded, this.onPlaying, this.onPaused, + this.placeholder, }); final Key? key; @@ -705,15 +742,17 @@ class VideoViewerRouteArgs { final bool isMotionVideo; - final dynamic onVideoEnded; + final void Function() onVideoEnded; - final dynamic onPlaying; + final void Function()? onPlaying; - final dynamic onPaused; + final void Function()? onPaused; + + final Widget? placeholder; @override String toString() { - return 'VideoViewerRouteArgs{key: $key, asset: $asset, isMotionVideo: $isMotionVideo, onVideoEnded: $onVideoEnded, onPlaying: $onPlaying, onPaused: $onPaused}'; + return 'VideoViewerRouteArgs{key: $key, asset: $asset, isMotionVideo: $isMotionVideo, onVideoEnded: $onVideoEnded, onPlaying: $onPlaying, onPaused: $onPaused, placeholder: $placeholder}'; } } @@ -1191,6 +1230,57 @@ class PartnerDetailRouteArgs { } } +/// generated route for +/// [PersonResultPage] +class PersonResultRoute extends PageRouteInfo { + PersonResultRoute({ + Key? key, + required String personId, + required String personName, + }) : super( + PersonResultRoute.name, + path: '/person-result-page', + args: PersonResultRouteArgs( + key: key, + personId: personId, + personName: personName, + ), + ); + + static const String name = 'PersonResultRoute'; +} + +class PersonResultRouteArgs { + const PersonResultRouteArgs({ + this.key, + required this.personId, + required this.personName, + }); + + final Key? key; + + final String personId; + + final String personName; + + @override + String toString() { + return 'PersonResultRouteArgs{key: $key, personId: $personId, personName: $personName}'; + } +} + +/// generated route for +/// [AllPeoplePage] +class AllPeopleRoute extends PageRouteInfo { + const AllPeopleRoute() + : super( + AllPeopleRoute.name, + path: '/all-people-page', + ); + + static const String name = 'AllPeopleRoute'; +} + /// generated route for /// [HomePage] class HomeRoute extends PageRouteInfo { diff --git a/mobile/lib/routing/tab_navigation_observer.dart b/mobile/lib/routing/tab_navigation_observer.dart index 1085082713..7a9f4a5a82 100644 --- a/mobile/lib/routing/tab_navigation_observer.dart +++ b/mobile/lib/routing/tab_navigation_observer.dart @@ -1,6 +1,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/modules/album/providers/album.provider.dart'; +import 'package:immich_mobile/modules/search/providers/people.provider.dart'; import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart'; import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart'; @@ -32,6 +33,7 @@ class TabNavigationObserver extends AutoRouterObserver { // Refresh Location State ref.invalidate(getCuratedLocationProvider); ref.invalidate(getCuratedObjectProvider); + ref.invalidate(getCuratedPeopleProvider); } if (route.name == 'SharingRoute') { diff --git a/mobile/lib/shared/services/api.service.dart b/mobile/lib/shared/services/api.service.dart index fc960a52e9..a3984b1200 100644 --- a/mobile/lib/shared/services/api.service.dart +++ b/mobile/lib/shared/services/api.service.dart @@ -17,6 +17,7 @@ class ApiService { late SearchApi searchApi; late ServerInfoApi serverInfoApi; late PartnerApi partnerApi; + late PersonApi personApi; ApiService() { final endpoint = Store.tryGet(StoreKey.serverEndpoint); @@ -39,6 +40,7 @@ class ApiService { serverInfoApi = ServerInfoApi(_apiClient); searchApi = SearchApi(_apiClient); partnerApi = PartnerApi(_apiClient); + personApi = PersonApi(_apiClient); } Future resolveAndSetEndpoint(String serverUrl) async { diff --git a/mobile/lib/utils/image_url_builder.dart b/mobile/lib/utils/image_url_builder.dart index ea249bb984..3fe68f131d 100644 --- a/mobile/lib/utils/image_url_builder.dart +++ b/mobile/lib/utils/image_url_builder.dart @@ -59,3 +59,7 @@ String _getThumbnailUrl( }) { return '${Store.get(StoreKey.serverEndpoint)}/asset/thumbnail/$id?format=${type.value}'; } + +String getFaceThumbnailUrl(final String personId) { + return '${Store.get(StoreKey.serverEndpoint)}/person/$personId/thumbnail'; +} diff --git a/mobile/lib/utils/immich_app_theme.dart b/mobile/lib/utils/immich_app_theme.dart index b7e8559f1e..aecf102d89 100644 --- a/mobile/lib/utils/immich_app_theme.dart +++ b/mobile/lib/utils/immich_app_theme.dart @@ -32,6 +32,8 @@ ThemeData immichLightTheme = ThemeData( primarySwatch: Colors.indigo, primaryColor: Colors.indigo, hintColor: Colors.indigo, + focusColor: Colors.indigo, + splashColor: Colors.indigo.withOpacity(0.15), fontFamily: 'WorkSans', scaffoldBackgroundColor: immichBackgroundColor, snackBarTheme: const SnackBarThemeData( @@ -119,6 +121,26 @@ ThemeData immichLightTheme = ThemeData( ), ), ), + dialogTheme: const DialogTheme( + surfaceTintColor: Colors.transparent, + ), + inputDecorationTheme: const InputDecorationTheme( + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.indigo, + ), + ), + labelStyle: TextStyle( + color: Colors.indigo, + ), + hintStyle: TextStyle( + fontSize: 14.0, + fontWeight: FontWeight.normal, + ), + ), + textSelectionTheme: const TextSelectionThemeData( + cursorColor: Colors.indigo, + ), ); ThemeData immichDarkTheme = ThemeData( @@ -217,4 +239,24 @@ ThemeData immichDarkTheme = ThemeData( ), ), ), + dialogTheme: const DialogTheme( + surfaceTintColor: Colors.transparent, + ), + inputDecorationTheme: InputDecorationTheme( + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: immichDarkThemePrimaryColor, + ), + ), + labelStyle: TextStyle( + color: immichDarkThemePrimaryColor, + ), + hintStyle: const TextStyle( + fontSize: 14.0, + fontWeight: FontWeight.normal, + ), + ), + textSelectionTheme: TextSelectionThemeData( + cursorColor: immichDarkThemePrimaryColor, + ), ); diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 1be8f40576..7363a24444 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -21,18 +21,18 @@ packages: dependency: transitive description: name: archive - sha256: "80e5141fafcb3361653ce308776cfd7d45e6e9fbb429e14eec571382c0c5fecb" + sha256: "0c8368c9b3f0abbc193b9d6133649a614204b528982bebc7026372d61677ce3a" url: "https://pub.dev" source: hosted - version: "3.3.2" + version: "3.3.7" args: dependency: transitive description: name: args - sha256: "4cab82a83ffef80b262ddedf47a0a8e56ee6fbf7fe21e6e768b02792034dd440" + sha256: c372bb384f273f0c2a8aaaa226dad84dc27c8519a691b888725dec59518ad53a url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" async: dependency: transitive description: @@ -77,10 +77,10 @@ packages: dependency: transitive description: name: build - sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777" + sha256: "43865b79fbb78532e4bff7c33087aa43b1d488c4fdef014eaef568af6d8016dc" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.0" build_config: dependency: transitive description: @@ -93,34 +93,34 @@ packages: dependency: transitive description: name: build_daemon - sha256: "6bc5544ea6ce4428266e7ea680e945c68806c4aae2da0eb5e9ccf38df8d6acbf" + sha256: "5f02d73eb2ba16483e693f80bee4f088563a820e47d1027d4cdfe62b5bb43e65" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "4.0.0" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: "687cf90a3951affac1bd5f9ecb5e3e90b60487f3d9cdc359bb310f8876bb02a6" + sha256: db49b8609ef8c81cca2b310618c3017c00f03a92af44c04d310b907b2d692d95 url: "https://pub.dev" source: hosted - version: "2.0.10" + version: "2.2.0" build_runner: dependency: "direct dev" description: name: build_runner - sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727 + sha256: "220ae4553e50d7c21a17c051afc7b183d28a24a420502e842f303f8e4e6edced" url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.4.4" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: "14febe0f5bac5ae474117a36099b4de6f1dbc52df6c5e55534b3da9591bf4292" + sha256: "30859c90e9ddaccc484f56303931f477b1f1ba2bab74aa32ed5d6ce15870f8cf" url: "https://pub.dev" source: hosted - version: "7.2.7" + version: "7.2.8" built_collection: dependency: transitive description: @@ -133,10 +133,10 @@ packages: dependency: transitive description: name: built_value - sha256: "169565c8ad06adb760c3645bf71f00bff161b00002cace266cad42c5d22a7725" + sha256: "2f17434bd5d52a26762043d6b43bb53b3acd029b4d9071a329f46d67ef297e6d" url: "https://pub.dev" source: hosted - version: "8.4.3" + version: "8.5.0" cached_network_image: dependency: "direct main" description: @@ -165,18 +165,18 @@ packages: dependency: transitive description: name: cancellation_token - sha256: "44891ef71d605bc59ef7974c403630d8e8506fcd897a29c3e38466ef69e5c4eb" + sha256: "7bacc556338b9f84e4db991805fdfa37fa1eda3689b94185bdc7459099455d71" url: "https://pub.dev" source: hosted - version: "1.6.1" + version: "2.0.0" cancellation_token_http: dependency: "direct main" description: name: cancellation_token_http - sha256: e0396730db74d96522cb7162cb390c73b3e30aa24450001a24374cd09f8484ea + sha256: bb91655e2e47d6274b681261ee6a687b7aa9023f49cfc28f42d095b2f86febc3 url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" characters: dependency: transitive description: @@ -189,18 +189,18 @@ packages: dependency: transitive description: name: checked_yaml - sha256: "3d1505d91afa809d177efd4eed5bb0eb65805097a1463abdd2add076effae311" + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.0.3" chewie: dependency: "direct main" description: name: chewie - sha256: e9da4898ee4859825404f507969f57113c04ca0060e152b95c9afd73934126ad + sha256: "745e81e84c6d7f3835f89f85bb49771c0a66099e4caf8f8e9e9a372bc66fb2c1" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.5.0" clock: dependency: transitive description: @@ -269,10 +269,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "7a03456c3490394c8e7665890333e91ae8a49be43542b616e414449ac358acd4" + sha256: f4f1f73ab3fd2afcbcca165ee601fe980d966af6a21b5970c6c9376955c528ad url: "https://pub.dev" source: hosted - version: "2.2.4" + version: "2.3.1" dartx: dependency: transitive description: @@ -285,10 +285,10 @@ packages: dependency: "direct main" description: name: device_info_plus - sha256: "1d6e5a61674ba3a68fb048a7c7b4ff4bebfed8d7379dbe8f2b718231be9a7c95" + sha256: f52ab3b76b36ede4d135aab80194df8925b553686f0fa12226b4e2d658e45903 url: "https://pub.dev" source: hosted - version: "8.1.0" + version: "8.2.2" device_info_plus_platform_interface: dependency: transitive description: @@ -309,10 +309,10 @@ packages: dependency: "direct main" description: name: easy_localization - sha256: f30e9b20ed4d1b890171c30241d9b9c43efe21fee55dee7bd68f94daf269ea75 + sha256: "30ebf25448ffe169e0bd9bc4b5da94faa8398967a2ad2ca09f438be8b6953645" url: "https://pub.dev" source: hosted - version: "3.0.2-dev.2" + version: "3.0.2" easy_logger: dependency: transitive description: @@ -436,18 +436,18 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "4bef634684b2c7f3468c77c766c831229af829a0cd2d4ee6c1b99558bd14e5d2" + sha256: "950e77c2bbe1692bc0874fc7fb491b96a4dc340457f4ea1641443d0a6c1ea360" url: "https://pub.dev" source: hosted - version: "2.0.8" + version: "2.0.15" flutter_riverpod: dependency: transitive description: name: flutter_riverpod - sha256: "46a27b7a11dc13738054093076f2dc65692ddcd463979b15092accf5681aea20" + sha256: b83ac5827baadefd331ea1d85110f34645827ea234ccabf53a655f41901a9bf4 url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.6" flutter_test: dependency: "direct dev" description: flutter @@ -507,26 +507,26 @@ packages: dependency: transitive description: name: graphs - sha256: f9e130f3259f52d26f0cfc0e964513796dafed572fa52e45d2f8d6ca14db39b2 + sha256: "772db3d53d23361d4ffcf5a9bb091cf3ee9b22f2be52cd107cd7a2683a89ba0e" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.0" hooks_riverpod: dependency: "direct main" description: name: hooks_riverpod - sha256: a596bcb1eaf48eae6da1ce8b9e60ec9538ef7d15725e941c3626f29dfcc01d96 + sha256: be68cf7653fcab798500f9047ac58c3f109287a1595012412f4a0d654a9bb9c5 url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.6" html: dependency: transitive description: name: html - sha256: d9793e10dbe0e6c364f4c59bf3e01fb33a9b2a674bc7a1081693dba0614b6269 + sha256: "58e3491f7bf0b6a4ea5110c0c688877460d1a6366731155c4a4580e7ded773e8" url: "https://pub.dev" source: hosted - version: "0.15.1" + version: "0.15.3" http: dependency: "direct main" description: @@ -563,34 +563,34 @@ packages: dependency: "direct main" description: name: image_picker - sha256: "22207768556b82d55ec70166824350fee32298732d5efa4d6e756f848f51f66a" + sha256: "9978d3510af4e6a902e545ce19229b926e6de6a1828d6134d3aab2e129a4d270" url: "https://pub.dev" source: hosted - version: "0.8.6+3" + version: "0.8.7+5" image_picker_android: dependency: transitive description: name: image_picker_android - sha256: "68d067baf7f6e401b1124ee83dd6967e67847314250fd68012aab34a69beb344" + sha256: "364967c8d581f5d75fc05f6c79fcf1115e3c05db3d3eee1aaca52e0da3f7501c" url: "https://pub.dev" source: hosted - version: "0.8.5+7" + version: "0.8.6+15" image_picker_for_web: dependency: transitive description: name: image_picker_for_web - sha256: "66fc6e3877bbde82c33d122f3588777c3784ac5bd7d1cdd79213ef7aecb85b34" + sha256: "98f50d6b9f294c8ba35e25cc0d13b04bfddd25dbc8d32fa9d566a6572f2c081c" url: "https://pub.dev" source: hosted - version: "2.1.11" + version: "2.1.12" image_picker_ios: dependency: transitive description: name: image_picker_ios - sha256: "39aa70b5f1e5e7c94585b9738632d5fdb764a5655e40cd9e7b95fbd2fc50c519" + sha256: d779210bda268a03b57e923fb1e410f32f5c5e708ad256348bcbf1f44f558fd0 url: "https://pub.dev" source: hosted - version: "0.8.6+9" + version: "0.8.7+4" image_picker_platform_interface: dependency: transitive description: @@ -656,10 +656,10 @@ packages: dependency: transitive description: name: json_annotation - sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317 + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 url: "https://pub.dev" source: hosted - version: "4.8.0" + version: "4.8.1" latlong2: dependency: "direct main" description: @@ -672,10 +672,10 @@ packages: dependency: transitive description: name: lints - sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593" + sha256: "6b0206b0bf4f04961fc5438198ccb3a885685cd67d4d4a32cc20ad7f8adbe015" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.1.0" lists: dependency: transitive description: @@ -799,26 +799,26 @@ packages: dependency: "direct main" description: name: path_provider - sha256: "04890b994ee89bfa80bf3080bfec40d5a92c5c7a785ebb02c13084a099d2b6f9" + sha256: "3087813781ab814e4157b172f1a11c46be20179fcc9bea043e0fba36bc0acaa2" url: "https://pub.dev" source: hosted - version: "2.0.13" + version: "2.0.15" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "7623b7d4be0f0f7d9a8b5ee6879fc13e4522d4c875ab86801dee4af32b54b83e" + sha256: "2cec049d282c7f13c594b4a73976b0b4f2d7a1838a6dd5aaf7bd9719196bee86" url: "https://pub.dev" source: hosted - version: "2.0.23" + version: "2.0.27" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: eec003594f19fe2456ea965ae36b3fc967bc5005f508890aafe31fa75e41d972 + sha256: "1995d88ec2948dac43edf8fe58eb434d35d22a2940ecee1a9fefcd62beee6eb3" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.2.3" path_provider_ios: dependency: "direct main" description: @@ -831,10 +831,10 @@ packages: dependency: transitive description: name: path_provider_linux - sha256: "525ad5e07622d19447ad740b1ed5070031f7a5437f44355ae915ff56e986429a" + sha256: "2ae08f2216225427e64ad224a24354221c2c7907e448e6e0e8b57b1eb9f10ad1" url: "https://pub.dev" source: hosted - version: "2.1.9" + version: "2.1.10" path_provider_platform_interface: dependency: transitive description: @@ -847,10 +847,10 @@ packages: dependency: transitive description: name: path_provider_windows - sha256: "642ddf65fde5404f83267e8459ddb4556316d3ee6d511ed193357e25caa3632d" + sha256: d3f80b32e83ec208ac95253e0cd4d298e104fbc63cb29c5c69edaed43b0c69d6 url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.6" pedantic: dependency: transitive description: @@ -871,18 +871,18 @@ packages: dependency: transitive description: name: permission_handler_android - sha256: "8028362b40c4a45298f1cbfccd227c8dd6caf0e27088a69f2ba2ab15464159e2" + sha256: d8cc6a62ded6d0f49c6eac337e080b066ee3bce4d405bd9439a61e1f1927bfe8 url: "https://pub.dev" source: hosted - version: "10.2.0" + version: "10.2.1" permission_handler_apple: dependency: transitive description: name: permission_handler_apple - sha256: "9c370ef6a18b1c4b2f7f35944d644a56aa23576f23abee654cf73968de93f163" + sha256: ee96ac32f5a8e6f80756e25b25b9f8e535816c8e6665a96b6d70681f8c4f7e85 url: "https://pub.dev" source: hosted - version: "9.0.7" + version: "9.0.8" permission_handler_platform_interface: dependency: transitive description: @@ -903,18 +903,18 @@ packages: dependency: transitive description: name: petitparser - sha256: "49392a45ced973e8d94a85fdb21293fbb40ba805fc49f2965101ae748a3683b4" + sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 url: "https://pub.dev" source: hosted - version: "5.1.0" + version: "5.4.0" photo_manager: dependency: "direct main" description: name: photo_manager - sha256: "55d50ad1b8f984c57fa7c4bd4980f4760e80d3d9355263cf72624a6ff1bf2b5b" + sha256: bdc4ab1fa9fb064d8ccfea6ab44119f55b220293d7ce2e19eb5a5f998db86c88 url: "https://pub.dev" source: hosted - version: "2.5.2" + version: "2.6.0" platform: dependency: transitive description: @@ -931,6 +931,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" + url: "https://pub.dev" + source: hosted + version: "3.7.3" polylabel: dependency: transitive description: @@ -975,26 +983,26 @@ packages: dependency: transitive description: name: pub_semver - sha256: "307de764d305289ff24ad257ad5c5793ce56d04947599ad68b3baa124105fc17" + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" pubspec_parse: dependency: transitive description: name: pubspec_parse - sha256: "75f6614d6dde2dc68948dffbaa4fe5dae32cd700eb9fb763fe11dfb45a3c4d0a" + sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.3" riverpod: dependency: transitive description: name: riverpod - sha256: "59a48de9c757aa61aa28e9fd625ffb360d43b6b54606f12536622c55be9e8c4b" + sha256: "80e48bebc83010d5e67a11c9514af6b44bbac1ec77b4333c8ea65dbc79e2d8ef" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.6" rxdart: dependency: transitive description: @@ -1007,98 +1015,98 @@ packages: dependency: "direct main" description: name: scrollable_positioned_list - sha256: ca7fcaa743db712d4f7b1580526f494d0093c77a721a65705ee51fbeac7a2bd3 + sha256: "1b54d5f1329a1e263269abc9e2543d90806131aa14fe7c6062a8054d57249287" url: "https://pub.dev" source: hosted - version: "0.3.5" + version: "0.3.8" share_plus: dependency: "direct main" description: name: share_plus - sha256: "8c6892037b1824e2d7e8f59d54b3105932899008642e6372e5079c6939b4b625" + sha256: b1f15232d41e9701ab2f04181f21610c36c83a12ae426b79b4bd011c567934b1 url: "https://pub.dev" source: hosted - version: "6.3.1" + version: "6.3.4" share_plus_platform_interface: dependency: transitive description: name: share_plus_platform_interface - sha256: "82ddd4ab9260c295e6e39612d4ff00390b9a7a21f1bb1da771e2f232d80ab8a1" + sha256: "0c6e61471bd71b04a138b8b588fa388e66d8b005e6f2deda63371c5c505a0981" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.1" shared_preferences: dependency: transitive description: name: shared_preferences - sha256: ee6257848f822b8481691f20c3e6d2bfee2e9eccb2a3d249907fcfb198c55b41 + sha256: "16d3fb6b3692ad244a695c0183fca18cf81fd4b821664394a781de42386bf022" url: "https://pub.dev" source: hosted - version: "2.0.18" + version: "2.1.1" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: a51a4f9375097f94df1c6e0a49c0374440d31ab026b59d58a7e7660675879db4 + sha256: "6478c6bbbecfe9aced34c483171e90d7c078f5883558b30ec3163cf18402c749" url: "https://pub.dev" source: hosted - version: "2.0.16" + version: "2.1.4" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "6b84fdf06b32bb336f972d373cd38b63734f3461ba56ac2ba01b56d052796259" + sha256: e014107bb79d6d3297196f4f2d0db54b5d1f85b8ea8ff63b8e8b391a02700feb url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.2" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux - sha256: d7fb71e6e20cd3dfffcc823a28da3539b392e53ed5fc5c2b90b55fdaa8a7e8fa + sha256: "9d387433ca65717bbf1be88f4d5bb18f10508917a8fa2fb02e0fd0d7479a9afa" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" shared_preferences_platform_interface: dependency: transitive description: name: shared_preferences_platform_interface - sha256: "824bfd02713e37603b2bdade0842e47d56e7db32b1dcdd1cae533fb88e2913fc" + sha256: fb5cf25c0235df2d0640ac1b1174f6466bd311f621574997ac59018a6664548d url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.2.0" shared_preferences_web: dependency: transitive description: name: shared_preferences_web - sha256: "6737b757e49ba93de2a233df229d0b6a87728cea1684da828cbc718b65dcf9d7" + sha256: "74083203a8eae241e0de4a0d597dbedab3b8fef5563f33cf3c12d7e93c655ca5" url: "https://pub.dev" source: hosted - version: "2.0.5" + version: "2.1.0" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows - sha256: bd014168e8484837c39ef21065b78f305810ceabc1d4f90be6e3b392ce81b46d + sha256: "5e588e2efef56916a3b229c3bfe81e6a525665a454519ca51dbcc4236a274173" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" shelf: dependency: transitive description: name: shelf - sha256: c24a96135a2ccd62c64b69315a14adc5c3419df63b4d7c05832a346fdb73682c + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - sha256: a988c0e8d8ffbdb8a28aa7ec8e449c260f3deb808781fe1284d22c5bba7156e8 + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.4" sky_engine: dependency: transitive description: flutter @@ -1108,26 +1116,26 @@ packages: dependency: "direct main" description: name: socket_io_client - sha256: a9c589d3fe2658506be38ddb36f23348daab73a00ff1645533669d07a5111cfc + sha256: "5d2a0a12c2a4a5f48d14e5b6aef7db78d3b425a9b084071059fa54bd12d2576c" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" socket_io_common: dependency: transitive description: name: socket_io_common - sha256: "5a218a784df4d1927ae713e17af619caa736cb2ebac287c59e4e24228b22da29" + sha256: "2ab92f8ff3ebbd4b353bf4a98bee45cc157e3255464b2f90f66e09c4472047eb" url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.0.3" source_gen: dependency: transitive description: name: source_gen - sha256: "2d79738b6bbf38a43920e2b8d189e9a3ce6cc201f4b8fc76be5e4fe377b1c38d" + sha256: "373f96cf5a8744bc9816c1ff41cf5391bbdbe3d7a96fe98c622b6738a8a7bd33" url: "https://pub.dev" source: hosted - version: "1.2.6" + version: "1.3.2" source_span: dependency: transitive description: @@ -1140,18 +1148,18 @@ packages: dependency: transitive description: name: sqflite - sha256: "78324387dc81df14f78df06019175a86a2ee0437624166c382e145d0a7fd9a4f" + sha256: b4d6710e1200e96845747e37338ea8a819a12b51689a3bcf31eff0003b37a0b9 url: "https://pub.dev" source: hosted - version: "2.2.4+1" + version: "2.2.8+4" sqflite_common: dependency: transitive description: name: sqflite_common - sha256: bfd6973aaeeb93475bc0d875ac9aefddf7965ef22ce09790eb963992ffc5183f + sha256: e77abf6ff961d69dfef41daccbb66b51e9983cdd5cb35bf30733598057401555 url: "https://pub.dev" source: hosted - version: "2.4.2+2" + version: "2.4.5" stack_trace: dependency: transitive description: @@ -1204,10 +1212,10 @@ packages: dependency: transitive description: name: synchronized - sha256: "33b31b6beb98100bf9add464a36a8dd03eb10c7a8cf15aeec535e9b054aaf04b" + sha256: "5fcbd27688af6082f5abd611af56ee575342c30e87541d0245f7ff99faa02c60" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.1.0" term_glyph: dependency: transitive description: @@ -1252,10 +1260,10 @@ packages: dependency: transitive description: name: typed_data - sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" unicode: dependency: transitive description: @@ -1276,42 +1284,42 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: "75f2846facd11168d007529d6cd8fcb2b750186bea046af9711f10b907e1587e" + sha256: eb1e00ab44303d50dd487aab67ebc575456c146c6af44422f9c13889984c00f3 url: "https://pub.dev" source: hosted - version: "6.1.10" + version: "6.1.11" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "1f4d9ebe86f333c15d318f81dcdc08b01d45da44af74552608455ebdc08d9732" + sha256: "1a5848f598acc5b7d8f7c18b8cb834ab667e59a13edc3c93e9d09cf38cc6bc87" url: "https://pub.dev" source: hosted - version: "6.0.24" + version: "6.0.34" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: c9cd648d2f7ab56968e049d4e9116f96a85517f1dd806b96a86ea1018a3a82e5 + sha256: "9af7ea73259886b92199f9e42c116072f05ff9bea2dcb339ab935dfc957392c2" url: "https://pub.dev" source: hosted - version: "6.1.1" + version: "6.1.4" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: e29039160ab3730e42f3d811dc2a6d5f2864b90a70fb765ea60144b03307f682 + sha256: "207f4ddda99b95b4d4868320a352d374b0b7e05eefad95a4a26f57da413443f5" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "2dddb3291a57b074dade66b5e07e64401dd2487caefd4e9e2f467138d8c7eb06" + sha256: "91ee3e75ea9dadf38036200c5d3743518f4a5eb77a8d13fda1ee5764373f185e" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" url_launcher_platform_interface: dependency: transitive description: @@ -1324,18 +1332,18 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "574cfbe2390666003c3a1d129bdc4574aaa6728f0c00a4829a81c316de69dd9b" + sha256: "81fe91b6c4f84f222d186a9d23c73157dc4c8e1c71489c4d08be1ad3b228f1aa" url: "https://pub.dev" source: hosted - version: "2.0.15" + version: "2.0.16" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "97c9067950a0d09cbd93e2e3f0383d1403989362b97102fbf446473a48079a4b" + sha256: "254708f17f7c20a9c8c471f67d86d76d4a3f9c1591aad1e15292008aceb82771" url: "https://pub.dev" source: hosted - version: "3.0.4" + version: "3.0.6" uuid: dependency: transitive description: @@ -1356,42 +1364,42 @@ packages: dependency: "direct main" description: name: video_player - sha256: "6cec15c21974282994577ffcfb5b42e64a699d38583138ec8dcb3d0a6902a41c" + sha256: de95f0e9405f29b5582573d4166132e71f83b3158aac14e8ee5767a54f4f1fbd url: "https://pub.dev" source: hosted - version: "2.5.2" + version: "2.6.1" video_player_android: dependency: transitive description: name: video_player_android - sha256: "0fc42778d794465f12456ccdade3e729e4339c8a112f9e58d170dc00f17b75f2" + sha256: ae1c7d9a71c236a1bf9e567bd7ed4c90887e389a5f233b2192593f7f7395005c url: "https://pub.dev" source: hosted - version: "2.3.11" + version: "2.4.8" video_player_avfoundation: dependency: transitive description: name: video_player_avfoundation - sha256: "5df5411ff9d316f1dcbfee284e9838aa686e314f2a722b15c02cb7ce40ef9446" + sha256: "4c274e439f349a0ee5cb3c42978393ede173a443b98f50de6ffe6900eaa19216" url: "https://pub.dev" source: hosted - version: "2.3.9" + version: "2.4.6" video_player_platform_interface: dependency: transitive description: name: video_player_platform_interface - sha256: "72ba04ad0eff76123c6d782ac46621cb8be476a89c33c89173fce982b6ec049b" + sha256: a8c4dcae2a7a6e7cc1d7f9808294d968eca1993af34a98e95b9bdfa959bec684 url: "https://pub.dev" source: hosted - version: "6.0.2" + version: "6.1.0" video_player_web: dependency: transitive description: name: video_player_web - sha256: d635bb2834f2b14cfd52c7fc9307a95dffbe768d116dd6047a4ecbba203289c8 + sha256: "44ce41424d104dfb7cf6982cc6b84af2b007a24d126406025bf40de5d481c74c" url: "https://pub.dev" source: hosted - version: "2.0.14" + version: "2.0.16" vm_service: dependency: transitive description: @@ -1444,18 +1452,18 @@ packages: dependency: transitive description: name: watcher - sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0" + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.1.0" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: ca49c0bc209c687b887f30527fb6a9d80040b072cc2990f34b9bec3e7663101b + sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.4.0" webdriver: dependency: transitive description: @@ -1468,10 +1476,10 @@ packages: dependency: transitive description: name: win32 - sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46 + sha256: a6f0236dbda0f63aa9a25ad1ff9a9d8a4eaaa5012da0dc59d21afdb1dc361ca4 url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "3.1.4" wkt_parser: dependency: transitive description: @@ -1492,10 +1500,10 @@ packages: dependency: transitive description: name: xml - sha256: "979ee37d622dec6365e2efa4d906c37470995871fe9ae080d967e192d88286b5" + sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" url: "https://pub.dev" source: hosted - version: "6.2.2" + version: "6.3.0" xxh3: dependency: transitive description: @@ -1508,10 +1516,10 @@ packages: dependency: transitive description: name: yaml - sha256: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370" + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.2" sdks: - dart: ">=3.0.0-0 <4.0.0" + dart: ">=3.0.0 <4.0.0" flutter: ">=3.3.0"