diff --git a/mobile/ios/Podfile.lock b/mobile/ios/Podfile.lock index 3e4684855a..05b45bbd44 100644 --- a/mobile/ios/Podfile.lock +++ b/mobile/ios/Podfile.lock @@ -223,6 +223,8 @@ PODS: - sqlite3/fts5 - sqlite3/perf-threadsafe - sqlite3/rtree + - system_info_plus (0.0.1): + - Flutter - Toast (4.1.1) - ua_client_hints (1.4.0): - Flutter @@ -290,6 +292,7 @@ DEPENDENCIES: - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) - sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/ios`) + - system_info_plus (from `.symlinks/plugins/system_info_plus/ios`) - ua_client_hints (from `.symlinks/plugins/ua_client_hints/ios`) - uni_links (from `.symlinks/plugins/uni_links/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) @@ -418,6 +421,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/sqflite_darwin/darwin" sqlite3_flutter_libs: :path: ".symlinks/plugins/sqlite3_flutter_libs/ios" + system_info_plus: + :path: ".symlinks/plugins/system_info_plus/ios" ua_client_hints: :path: ".symlinks/plugins/ua_client_hints/ios" uni_links: @@ -501,6 +506,7 @@ SPEC CHECKSUMS: sqflite_darwin: a553b1fd6fe66f53bbb0fe5b4f5bab93f08d7a13 sqlite3: 0bb0e6389d824e40296f531b858a2a0b71c0d2fb sqlite3_flutter_libs: c00457ebd31e59fa6bb830380ddba24d44fbcd3b + system_info_plus: 5393c8da281d899950d751713575fbf91c7709aa Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e ua_client_hints: 46bb5817a868f9e397c0ba7e3f2f5c5d90c35156 uni_links: d97da20c7701486ba192624d99bffaaffcfc298a diff --git a/mobile/ios/Runner.xcodeproj/project.pbxproj b/mobile/ios/Runner.xcodeproj/project.pbxproj index 142efc1059..86d7aa2535 100644 --- a/mobile/ios/Runner.xcodeproj/project.pbxproj +++ b/mobile/ios/Runner.xcodeproj/project.pbxproj @@ -334,6 +334,7 @@ "${BUILT_PRODUCTS_DIR}/sqflite_darwin/sqflite_darwin.framework", "${BUILT_PRODUCTS_DIR}/sqlite3/sqlite3.framework", "${BUILT_PRODUCTS_DIR}/sqlite3_flutter_libs/sqlite3_flutter_libs.framework", + "${BUILT_PRODUCTS_DIR}/system_info_plus/system_info_plus.framework", "${BUILT_PRODUCTS_DIR}/ua_client_hints/ua_client_hints.framework", "${BUILT_PRODUCTS_DIR}/uni_links/uni_links.framework", "${BUILT_PRODUCTS_DIR}/url_launcher_ios/url_launcher_ios.framework", @@ -428,6 +429,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqflite_darwin.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqlite3.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqlite3_flutter_libs.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/system_info_plus.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ua_client_hints.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/uni_links.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher_ios.framework", diff --git a/mobile/lib/ente_theme_data.dart b/mobile/lib/ente_theme_data.dart index 8e8ed61523..6ee3384ac4 100644 --- a/mobile/lib/ente_theme_data.dart +++ b/mobile/lib/ente_theme_data.dart @@ -16,6 +16,7 @@ final lightThemeData = ThemeData( primary: Colors.black, secondary: Color.fromARGB(255, 163, 163, 163), background: Colors.white, + surfaceTint: Colors.transparent, ), outlinedButtonTheme: buildOutlinedButtonThemeData( bgDisabled: const Color.fromRGBO(158, 158, 158, 1), @@ -94,6 +95,7 @@ final darkThemeData = ThemeData( primary: Colors.white, background: Color.fromRGBO(0, 0, 0, 1), secondary: Color.fromARGB(255, 163, 163, 163), + surfaceTint: Colors.transparent, ), buttonTheme: const ButtonThemeData().copyWith( buttonColor: const Color.fromRGBO(45, 194, 98, 1.0), diff --git a/mobile/lib/ui/viewer/people/add_person_action_sheet.dart b/mobile/lib/ui/viewer/people/add_person_action_sheet.dart index b63034c440..4c04f39c1b 100644 --- a/mobile/lib/ui/viewer/people/add_person_action_sheet.dart +++ b/mobile/lib/ui/viewer/people/add_person_action_sheet.dart @@ -313,7 +313,7 @@ class _PersonActionSheetState extends State { await PersonService.instance.addPerson(text, clusterID); final bool extraPhotosFound = await ClusterFeedbackService.instance .checkAndDoAutomaticMerges(personEntity!, - personClusterID: clusterID); + personClusterID: clusterID,); if (extraPhotosFound) { showShortToast(context, S.of(context).extraPhotosFound); } diff --git a/mobile/lib/ui/viewer/search/result/search_people_all_page.dart b/mobile/lib/ui/viewer/search/result/search_people_all_page.dart new file mode 100644 index 0000000000..cf1ded9212 --- /dev/null +++ b/mobile/lib/ui/viewer/search/result/search_people_all_page.dart @@ -0,0 +1,128 @@ +import "dart:async"; + +import "package:collection/collection.dart"; +import "package:flutter/material.dart"; +import "package:flutter_animate/flutter_animate.dart"; +import "package:photos/events/event.dart"; +import "package:photos/models/search/generic_search_result.dart"; +import "package:photos/models/search/search_result.dart"; +import "package:photos/models/search/search_types.dart"; +import "package:photos/ui/common/loading_widget.dart"; +import "package:photos/ui/viewer/search/result/searchable_item.dart"; +import "package:photos/ui/viewer/search_tab/people_section.dart"; + +class PeopleAllPage extends StatefulWidget { + final SectionType sectionType; + const PeopleAllPage({required this.sectionType, super.key}); + + @override + State createState() => _PeopleAllPageState(); +} + +class _PeopleAllPageState extends State { + late Future> sectionData; + final streamSubscriptions = []; + + @override + void initState() { + super.initState(); + sectionData = widget.sectionType.getData(context); + final streamsToListenTo = widget.sectionType.viewAllUpdateEvents(); + for (Stream stream in streamsToListenTo) { + streamSubscriptions.add( + stream.listen((event) async { + setState(() { + sectionData = widget.sectionType.getData(context); + }); + }), + ); + } + } + + @override + void dispose() { + for (var subscriptions in streamSubscriptions) { + subscriptions.cancel(); + } + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + toolbarHeight: 48, + leadingWidth: 48, + leading: GestureDetector( + onTap: () { + Navigator.pop(context); + }, + child: const Icon( + Icons.arrow_back_outlined, + ), + ), + title: Text(widget.sectionType.sectionTitle(context)), + centerTitle: false, + ), + body: Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8), + child: FutureBuilder( + future: sectionData, + builder: (context, snapshot) { + if (snapshot.hasData) { + final List sectionResults = snapshot.data!; + if (widget.sectionType.sortByName) { + sectionResults.sort( + (a, b) => compareAsciiLowerCaseNatural(b.name(), a.name()), + ); + } + return GridView.builder( + padding: const EdgeInsets.all(0), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: MediaQuery.of(context).size.width > 600 + ? 4 + : 3, // Dynamically adjust columns based on screen width + crossAxisSpacing: 8, + mainAxisSpacing: 0, + childAspectRatio: + 0.78, // Adjust this value to control item height ratio + ), + itemCount: sectionResults.length, + physics: const BouncingScrollPhysics(), + cacheExtent: + widget.sectionType == SectionType.album ? 400 : null, + itemBuilder: (context, index) { + Widget resultWidget; + if (sectionResults[index] is GenericSearchResult) { + resultWidget = PeopleRowItem( + searchResult: sectionResults[index], + ); + } else { + resultWidget = SearchableItemWidget( + sectionResults[index], + ); + } + return resultWidget + .animate() + .fadeIn( + duration: const Duration(milliseconds: 225), + curve: Curves.easeIn, + ) + .slide( + begin: const Offset(0, -0.01), + curve: Curves.easeIn, + duration: const Duration(milliseconds: 225), + ); + }, + ); + } else { + return const EnteLoadingWidget(); + } + }, + ), + ), + ), + ); + } +} diff --git a/mobile/lib/ui/viewer/search/result/searchable_item.dart b/mobile/lib/ui/viewer/search/result/searchable_item.dart index f8e2ed1acb..805b3f5f43 100644 --- a/mobile/lib/ui/viewer/search/result/searchable_item.dart +++ b/mobile/lib/ui/viewer/search/result/searchable_item.dart @@ -15,10 +15,10 @@ class SearchableItemWidget extends StatelessWidget { final Function? onResultTap; const SearchableItemWidget( this.searchResult, { - Key? key, + super.key, this.resultCount, this.onResultTap, - }) : super(key: key); + }); @override Widget build(BuildContext context) { diff --git a/mobile/lib/ui/viewer/search_tab/people_section.dart b/mobile/lib/ui/viewer/search_tab/people_section.dart index 33e7a6740e..947cef124e 100644 --- a/mobile/lib/ui/viewer/search_tab/people_section.dart +++ b/mobile/lib/ui/viewer/search_tab/people_section.dart @@ -19,8 +19,8 @@ import "package:photos/ui/viewer/file/thumbnail_widget.dart"; import "package:photos/ui/viewer/people/add_person_action_sheet.dart"; import "package:photos/ui/viewer/people/people_page.dart"; import 'package:photos/ui/viewer/search/result/person_face_widget.dart'; +import "package:photos/ui/viewer/search/result/search_people_all_page.dart"; import "package:photos/ui/viewer/search/result/search_result_page.dart"; -import 'package:photos/ui/viewer/search/result/search_section_all_page.dart'; import "package:photos/ui/viewer/search/search_section_cta.dart"; import "package:photos/utils/navigation_util.dart"; @@ -88,7 +88,7 @@ class _PeopleSectionState extends State { if (shouldShowMore) { routeToPage( context, - SearchSectionAllPage( + PeopleAllPage( sectionType: widget.sectionType, ), ); @@ -119,7 +119,7 @@ class _PeopleSectionState extends State { ], ), const SizedBox(height: 2), - SearchExampleRow(_examples, widget.sectionType), + PeopleRow(_examples, widget.sectionType), ], ), ) @@ -163,11 +163,11 @@ class _PeopleSectionState extends State { } } -class SearchExampleRow extends StatelessWidget { +class PeopleRow extends StatelessWidget { final SectionType sectionType; final List examples; - const SearchExampleRow(this.examples, this.sectionType, {super.key}); + const PeopleRow(this.examples, this.sectionType, {super.key}); @override Widget build(BuildContext context) { @@ -175,7 +175,7 @@ class SearchExampleRow extends StatelessWidget { final scrollableExamples = []; examples.forEachIndexed((index, element) { scrollableExamples.add( - SearchExample( + PeopleRowItem( searchResult: examples.elementAt(index), ), ); @@ -193,9 +193,9 @@ class SearchExampleRow extends StatelessWidget { } } -class SearchExample extends StatelessWidget { +class PeopleRowItem extends StatelessWidget { final SearchResult searchResult; - const SearchExample({required this.searchResult, super.key}); + const PeopleRowItem({required this.searchResult, super.key}); @override Widget build(BuildContext context) { @@ -204,9 +204,9 @@ class SearchExample extends StatelessWidget { int.tryParse(searchResult.name()) != null); late final double width; if (textScaleFactor <= 1.0) { - width = 85.0; + width = 120.0; } else { - width = 85.0 + ((textScaleFactor - 1.0) * 64); + width = 120.0 + ((textScaleFactor - 1.0) * 64); } final heroTag = searchResult.heroTag() + (searchResult.previewThumbnail()?.tag ?? ""); @@ -238,19 +238,20 @@ class SearchExample extends StatelessWidget { child: SizedBox( width: width, child: Padding( - padding: const EdgeInsets.only(left: 6, right: 6, top: 8), + padding: const EdgeInsets.only(left: 4, right: 4, top: 8), child: Column( mainAxisSize: MainAxisSize.min, children: [ SizedBox( - width: 64, - height: 64, + width: 100, + height: 100, child: searchResult.previewThumbnail() != null ? Hero( tag: heroTag, child: ClipRRect( - borderRadius: - const BorderRadius.all(Radius.elliptical(16, 12)), + borderRadius: const BorderRadius.all( + Radius.elliptical(16, 12), + ), child: searchResult.type() != ResultType.faces ? ThumbnailWidget( searchResult.previewThumbnail()!, @@ -285,7 +286,7 @@ class SearchExample extends StatelessWidget { } }, child: Padding( - padding: const EdgeInsets.only(top: 10, bottom: 16), + padding: const EdgeInsets.only(top: 10, bottom: 10), child: Text( "Add name", maxLines: 1, @@ -296,7 +297,7 @@ class SearchExample extends StatelessWidget { ), ) : Padding( - padding: const EdgeInsets.only(top: 10, bottom: 16), + padding: const EdgeInsets.only(top: 10, bottom: 10), child: Text( searchResult.name(), maxLines: 2,