[mob][photos] Refactor

The contacts section was moved to the shared tab from the search tab in a hacky way before this change. Have now refactored code around it for better readability and consistancy
This commit is contained in:
ashilkn 2025-01-24 16:18:08 +05:30
parent a1b0e82d56
commit cf977a7fa1
7 changed files with 342 additions and 124 deletions

View File

@ -21,7 +21,6 @@ import "package:photos/ui/viewer/location/add_location_sheet.dart";
import "package:photos/ui/viewer/location/pick_center_point_widget.dart";
import "package:photos/utils/dialog_util.dart";
import "package:photos/utils/navigation_util.dart";
import "package:photos/utils/share_util.dart";
enum ResultType {
collection,
@ -46,8 +45,6 @@ enum SectionType {
// includes year, month , day, event ResultType
moment,
album,
// People section shows the files shared by other persons
contacts,
fileTypesAndExtension,
}
@ -63,8 +60,6 @@ extension SectionTypeExtensions on SectionType {
return S.of(context).moments;
case SectionType.location:
return S.of(context).locations;
case SectionType.contacts:
return S.of(context).contacts;
case SectionType.album:
return S.of(context).albums;
case SectionType.fileTypesAndExtension:
@ -82,8 +77,6 @@ extension SectionTypeExtensions on SectionType {
return S.of(context).searchDatesEmptySection;
case SectionType.location:
return S.of(context).searchLocationEmptySection;
case SectionType.contacts:
return S.of(context).searchPeopleEmptySection;
case SectionType.album:
return S.of(context).searchAlbumsEmptySection;
case SectionType.fileTypesAndExtension:
@ -103,8 +96,6 @@ extension SectionTypeExtensions on SectionType {
return false;
case SectionType.location:
return true;
case SectionType.contacts:
return true;
case SectionType.album:
return true;
case SectionType.fileTypesAndExtension:
@ -124,8 +115,6 @@ extension SectionTypeExtensions on SectionType {
return false;
case SectionType.location:
return true;
case SectionType.contacts:
return true;
case SectionType.album:
return true;
case SectionType.fileTypesAndExtension:
@ -145,8 +134,6 @@ extension SectionTypeExtensions on SectionType {
return S.of(context).addNew;
case SectionType.location:
return S.of(context).addNew;
case SectionType.contacts:
return S.of(context).invite;
case SectionType.album:
return S.of(context).addNew;
case SectionType.fileTypesAndExtension:
@ -164,8 +151,6 @@ extension SectionTypeExtensions on SectionType {
return null;
case SectionType.location:
return Icons.add_location_alt_outlined;
case SectionType.contacts:
return Icons.adaptive.share;
case SectionType.album:
return Icons.add;
case SectionType.fileTypesAndExtension:
@ -175,12 +160,6 @@ extension SectionTypeExtensions on SectionType {
FutureVoidCallback ctaOnTap(BuildContext context) {
switch (this) {
case SectionType.contacts:
return () async {
await shareText(
S.of(context).shareTextRecommendUsingEnte,
);
};
case SectionType.location:
return () async {
final centerPoint = await showPickCenterPointSheet(context);
@ -249,9 +228,6 @@ extension SectionTypeExtensions on SectionType {
case SectionType.location:
return SearchService.instance.getAllLocationTags(limit);
case SectionType.contacts:
return SearchService.instance.getAllContactsSearchResults(limit);
case SectionType.album:
return SearchService.instance.getAllCollectionSearchResults(limit);

View File

@ -14,9 +14,9 @@ class SectionTitle extends StatelessWidget {
this.title,
this.titleWithBrand,
this.mutedTitle = false,
Key? key,
super.key,
this.padding,
}) : super(key: key);
});
@override
Widget build(BuildContext context) {

View File

@ -0,0 +1,198 @@
import "dart:async";
import "package:collection/collection.dart";
import "package:dotted_border/dotted_border.dart";
import "package:flutter/material.dart";
import "package:flutter_animate/flutter_animate.dart";
import "package:photos/generated/l10n.dart";
import "package:photos/models/search/generic_search_result.dart";
import "package:photos/models/search/search_result.dart";
import "package:photos/services/search_service.dart";
import "package:photos/theme/ente_theme.dart";
import "package:photos/ui/common/loading_widget.dart";
import "package:photos/ui/components/title_bar_title_widget.dart";
import "package:photos/ui/viewer/search/result/searchable_item.dart";
import "package:photos/utils/share_util.dart";
class ContactsSectionAllPage extends StatefulWidget {
const ContactsSectionAllPage({super.key});
@override
State<ContactsSectionAllPage> createState() => _ContactsSectionAllPageState();
}
class _ContactsSectionAllPageState extends State<ContactsSectionAllPage> {
late Future<List<SearchResult>> resutls;
final streamSubscriptions = <StreamSubscription>[];
@override
void initState() {
super.initState();
resutls = SearchService.instance.getAllContactsSearchResults(null);
}
@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,
),
),
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TitleBarTitleWidget(
title: S.of(context).contacts,
),
FutureBuilder(
future: resutls,
builder: (context, snapshot) {
if (snapshot.hasData) {
final sectionResults = snapshot.data!;
return Text(sectionResults.length.toString())
.animate()
.fadeIn(
duration: const Duration(milliseconds: 150),
curve: Curves.easeIn,
);
} else {
return const SizedBox.shrink();
}
},
),
],
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 20,
horizontal: 16,
),
child: FutureBuilder(
future: resutls,
builder: (context, snapshot) {
if (snapshot.hasData) {
final sectionResults = snapshot.data!;
sectionResults.sort(
(a, b) =>
compareAsciiLowerCaseNatural(b.name(), a.name()),
);
return ListView.separated(
itemBuilder: (context, index) {
if (sectionResults.length == index) {
return const _SearchableItemPlaceholder();
}
final result =
sectionResults[index] as GenericSearchResult;
return SearchableItemWidget(
sectionResults[index],
onResultTap: result.onResultTap != null
? () => result.onResultTap!(context)
: null,
);
},
separatorBuilder: (context, index) {
return const SizedBox(height: 10);
},
itemCount: sectionResults.length + 1,
physics: const BouncingScrollPhysics(),
)
.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();
}
},
),
),
),
],
),
);
}
}
class _SearchableItemPlaceholder extends StatelessWidget {
const _SearchableItemPlaceholder();
@override
Widget build(BuildContext context) {
final colorScheme = getEnteColorScheme(context);
final textTheme = getEnteTextTheme(context);
return Padding(
padding: const EdgeInsets.only(right: 1),
child: GestureDetector(
onTap: () async {
await shareText(
S.of(context).shareTextRecommendUsingEnte,
);
},
child: DottedBorder(
strokeWidth: 2,
borderType: BorderType.RRect,
radius: const Radius.circular(4),
padding: EdgeInsets.zero,
dashPattern: const [4, 4],
color: colorScheme.strokeFainter,
child: Row(
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
borderRadius:
const BorderRadius.horizontal(left: Radius.circular(4)),
color: colorScheme.fillFaint,
),
child: Icon(
Icons.adaptive.share,
color: colorScheme.strokeMuted,
),
),
const SizedBox(width: 12),
Text(
S.of(context).invite,
style: textTheme.body,
),
],
),
),
),
);
}
}

View File

@ -1,69 +1,24 @@
import "dart:async";
import "package:dotted_border/dotted_border.dart";
import "package:flutter/material.dart";
import "package:photos/core/constants.dart";
import "package:photos/events/event.dart";
import "package:photos/generated/l10n.dart";
import "package:photos/models/search/generic_search_result.dart";
import "package:photos/models/search/recent_searches.dart";
import "package:photos/models/search/search_types.dart";
import "package:photos/theme/ente_theme.dart";
import "package:photos/ui/tabs/shared/contacts_all_page.dart";
import "package:photos/ui/viewer/file/no_thumbnail_widget.dart";
import "package:photos/ui/viewer/file/thumbnail_widget.dart";
import "package:photos/ui/viewer/search/result/contact_result_page.dart";
import "package:photos/ui/viewer/search/search_section_cta.dart";
import "package:photos/ui/viewer/search_tab/section_header.dart";
import "package:photos/utils/navigation_util.dart";
import "package:photos/utils/share_util.dart";
class ContactsSection extends StatefulWidget {
class ContactsSection extends StatelessWidget {
final List<GenericSearchResult> contactSearchResults;
const ContactsSection(this.contactSearchResults, {super.key});
@override
State<ContactsSection> createState() => _ContactsSectionState();
}
class _ContactsSectionState extends State<ContactsSection> {
late List<GenericSearchResult> _contactSearchResults;
final streamSubscriptions = <StreamSubscription>[];
@override
void initState() {
super.initState();
_contactSearchResults = widget.contactSearchResults;
final streamsToListenTo = SectionType.contacts.sectionUpdateEvents();
for (Stream<Event> stream in streamsToListenTo) {
streamSubscriptions.add(
stream.listen((event) async {
_contactSearchResults = (await SectionType.contacts.getData(
context,
limit: kSearchSectionLimit,
)) as List<GenericSearchResult>;
setState(() {});
}),
);
}
}
@override
void dispose() {
for (var subscriptions in streamSubscriptions) {
subscriptions.cancel();
}
super.dispose();
}
@override
void didUpdateWidget(covariant ContactsSection oldWidget) {
super.didUpdateWidget(oldWidget);
_contactSearchResults = widget.contactSearchResults;
}
@override
Widget build(BuildContext context) {
if (_contactSearchResults.isEmpty) {
if (contactSearchResults.isEmpty) {
final textTheme = getEnteTextTheme(context);
return Padding(
padding: const EdgeInsets.only(left: 12, right: 8),
@ -74,14 +29,14 @@ class _ContactsSectionState extends State<ContactsSection> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
SectionType.contacts.sectionTitle(context),
S.of(context).contacts,
style: textTheme.largeBold,
),
const SizedBox(height: 24),
Padding(
padding: const EdgeInsets.only(left: 4),
child: Text(
SectionType.contacts.getEmptyStateText(context),
S.of(context).searchPeopleEmptySection,
style: textTheme.smallMuted,
),
),
@ -89,13 +44,13 @@ class _ContactsSectionState extends State<ContactsSection> {
),
),
const SizedBox(width: 8),
const SearchSectionEmptyCTAIcon(SectionType.contacts),
const _ContactsSectionEmptyCTAIcon(),
],
),
);
} else {
final recommendations = <Widget>[
..._contactSearchResults.map(
...contactSearchResults.map(
(contactSearchResult) => ContactRecommendation(contactSearchResult),
),
const ContactCTA(),
@ -103,9 +58,8 @@ class _ContactsSectionState extends State<ContactsSection> {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SectionHeader(
SectionType.contacts,
hasMore: (_contactSearchResults.length >= kSearchSectionLimit - 1),
_SectionHeader(
hasMore: (contactSearchResults.length >= kSearchSectionLimit - 1),
),
SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 4.5),
@ -209,7 +163,11 @@ class ContactCTA extends StatelessWidget {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 2.5),
child: GestureDetector(
onTap: SectionType.contacts.ctaOnTap(context),
onTap: () async {
await shareText(
S.of(context).shareTextRecommendUsingEnte,
);
},
child: ConstrainedBox(
constraints: const BoxConstraints(
maxHeight: double.infinity,
@ -253,3 +211,96 @@ class ContactCTA extends StatelessWidget {
);
}
}
class _ContactsSectionEmptyCTAIcon extends StatelessWidget {
const _ContactsSectionEmptyCTAIcon();
@override
Widget build(BuildContext context) {
final textTheme = getEnteTextTheme(context);
final colorScheme = getEnteColorScheme(context);
return GestureDetector(
onTap: () async {
await shareText(
S.of(context).shareTextRecommendUsingEnte,
);
},
child: Padding(
padding: const EdgeInsets.fromLTRB(8, 14, 8, 0),
child: Column(
children: [
DottedBorder(
color: colorScheme.strokeFaint,
dashPattern: const [3.875, 3.875],
borderType: BorderType.Circle,
strokeWidth: 1.5,
radius: const Radius.circular(33.25),
child: SizedBox(
width: 62.5,
height: 62.5,
child: Icon(
Icons.adaptive.share,
color: colorScheme.strokeFaint,
size: 20,
),
),
),
const SizedBox(
height: 10,
),
Text(
S.of(context).invite,
maxLines: 2,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
style: textTheme.miniFaint,
),
],
),
),
);
}
}
class _SectionHeader extends StatelessWidget {
final bool hasMore;
const _SectionHeader({required this.hasMore});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
if (hasMore) {
routeToPage(
context,
const ContactsSectionAllPage(),
);
}
},
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.all(12),
child: Text(
S.of(context).contacts,
style: getEnteTextTheme(context).largeBold,
),
),
hasMore
? Container(
color: Colors.transparent,
child: Padding(
padding: const EdgeInsets.fromLTRB(24, 12, 12, 12),
child: Icon(
Icons.chevron_right_outlined,
color: getEnteColorScheme(context).strokeMuted,
),
),
)
: const SizedBox.shrink(),
],
),
);
}
}

View File

@ -19,11 +19,11 @@ import 'package:photos/ui/common/loading_widget.dart';
import "package:photos/ui/components/buttons/icon_button_widget.dart";
import 'package:photos/ui/tabs/section_title.dart';
import "package:photos/ui/tabs/shared/all_quick_links_page.dart";
import "package:photos/ui/tabs/shared/contacts_section.dart";
import "package:photos/ui/tabs/shared/empty_state.dart";
import "package:photos/ui/tabs/shared/quick_link_album_item.dart";
import "package:photos/ui/viewer/gallery/collect_photos_card_widget.dart";
import "package:photos/ui/viewer/gallery/collection_page.dart";
import "package:photos/ui/viewer/search_tab/contacts_section.dart";
import "package:photos/utils/debouncer.dart";
import "package:photos/utils/navigation_util.dart";

View File

@ -143,12 +143,6 @@ class _AllSearchSectionsState extends State<AllSearchSections> {
snapshot.data!.elementAt(index)
as List<GenericSearchResult>,
);
case SectionType.contacts:
// return ContactsSection(
// snapshot.data!.elementAt(index)
// as List<GenericSearchResult>,
// );
return const SizedBox.shrink();
case SectionType.fileTypesAndExtension:
return FileTypeSection(
snapshot.data!.elementAt(index)

View File

@ -5,10 +5,10 @@ packages:
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab"
sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834
url: "https://pub.dev"
source: hosted
version: "76.0.0"
version: "72.0.0"
_flutterfire_internals:
dependency: transitive
description:
@ -21,7 +21,7 @@ packages:
dependency: transitive
description: dart
source: sdk
version: "0.3.3"
version: "0.3.2"
adaptive_theme:
dependency: "direct main"
description:
@ -34,10 +34,10 @@ packages:
dependency: transitive
description:
name: analyzer
sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e"
sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139
url: "https://pub.dev"
source: hosted
version: "6.11.0"
version: "6.7.0"
android_intent_plus:
dependency: "direct main"
description:
@ -284,10 +284,10 @@ packages:
dependency: "direct main"
description:
name: collection
sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev"
source: hosted
version: "1.19.0"
version: "1.18.0"
computer:
dependency: "direct main"
description:
@ -585,12 +585,11 @@ packages:
figma_squircle:
dependency: "direct main"
description:
path: "."
ref: HEAD
resolved-ref: "5f1ad5aaccdf31fc398fc141979ea845a0f45383"
url: "https://github.com/Ax0elz/figma_squircle.git"
source: git
version: "0.5.5"
name: figma_squircle
sha256: "790b91a9505e90d246f6efe2fa065ff7fffe658c7b44fe9b5b20c7b0ad3818c0"
url: "https://pub.dev"
source: hosted
version: "0.5.3"
file:
dependency: transitive
description:
@ -1359,18 +1358,18 @@ packages:
dependency: transitive
description:
name: leak_tracker
sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06"
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
url: "https://pub.dev"
source: hosted
version: "10.0.7"
version: "10.0.5"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379"
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
url: "https://pub.dev"
source: hosted
version: "3.0.8"
version: "3.0.5"
leak_tracker_testing:
dependency: transitive
description:
@ -1487,10 +1486,10 @@ packages:
dependency: transitive
description:
name: macros
sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656"
sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536"
url: "https://pub.dev"
source: hosted
version: "0.1.3-main.0"
version: "0.1.2-main.4"
maps_launcher:
dependency: "direct main"
description:
@ -2293,7 +2292,7 @@ packages:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
version: "0.0.99"
source_gen:
dependency: transitive
description:
@ -2418,10 +2417,10 @@ packages:
dependency: transitive
description:
name: stack_trace
sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev"
source: hosted
version: "1.12.0"
version: "1.11.1"
step_progress_indicator:
dependency: "direct main"
description:
@ -2450,10 +2449,10 @@ packages:
dependency: transitive
description:
name: string_scanner
sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
url: "https://pub.dev"
source: hosted
version: "1.3.0"
version: "1.2.0"
styled_text:
dependency: "direct main"
description:
@ -2514,26 +2513,26 @@ packages:
dependency: "direct dev"
description:
name: test
sha256: "713a8789d62f3233c46b4a90b174737b2c04cb6ae4500f2aa8b1be8f03f5e67f"
sha256: "7ee44229615f8f642b68120165ae4c2a75fe77ae2065b1e55ae4711f6cf0899e"
url: "https://pub.dev"
source: hosted
version: "1.25.8"
version: "1.25.7"
test_api:
dependency: transitive
description:
name: test_api
sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
url: "https://pub.dev"
source: hosted
version: "0.7.3"
version: "0.7.2"
test_core:
dependency: transitive
description:
name: test_core
sha256: "12391302411737c176b0b5d6491f466b0dd56d4763e347b6714efbaa74d7953d"
sha256: "55ea5a652e38a1dfb32943a7973f3681a60f872f8c3a05a14664ad54ef9c6696"
url: "https://pub.dev"
source: hosted
version: "0.6.5"
version: "0.6.4"
timezone:
dependency: transitive
description:
@ -2820,10 +2819,10 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
url: "https://pub.dev"
source: hosted
version: "14.3.0"
version: "14.2.5"
volume_controller:
dependency: transitive
description:
@ -2892,10 +2891,10 @@ packages:
dependency: transitive
description:
name: webdriver
sha256: "3d773670966f02a646319410766d3b5e1037efb7f07cc68f844d5e06cd4d61c8"
sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e"
url: "https://pub.dev"
source: hosted
version: "3.0.4"
version: "3.0.3"
webkit_inspection_protocol:
dependency: transitive
description: