mirror of
https://github.com/ente-io/ente.git
synced 2025-08-08 07:28:26 +00:00
[mob][photos] Show user avatars in email section of save or edit person screen to match figma design
This commit is contained in:
parent
96e8b09555
commit
ba53da4a69
@ -12,6 +12,7 @@ class AlbumSharesIcons extends StatelessWidget {
|
||||
final bool removeBorder;
|
||||
final EdgeInsets padding;
|
||||
final Widget? trailingWidget;
|
||||
final Alignment stackAlignment;
|
||||
|
||||
const AlbumSharesIcons({
|
||||
super.key,
|
||||
@ -21,13 +22,14 @@ class AlbumSharesIcons extends StatelessWidget {
|
||||
this.removeBorder = true,
|
||||
this.trailingWidget,
|
||||
this.padding = const EdgeInsets.only(left: 10.0, top: 10, bottom: 10),
|
||||
this.stackAlignment = Alignment.topLeft,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final displayCount = min(sharees.length, limitCountTo);
|
||||
final hasMore = sharees.length > limitCountTo;
|
||||
final double overlapPadding = type == AvatarType.tiny ? 14.0 : 20.0;
|
||||
final double overlapPadding = getOverlapPadding(type);
|
||||
final widgets = List<Widget>.generate(
|
||||
displayCount,
|
||||
(index) => Positioned(
|
||||
@ -46,9 +48,7 @@ class AlbumSharesIcons extends StatelessWidget {
|
||||
left: (overlapPadding * displayCount),
|
||||
child: MoreCountWidget(
|
||||
sharees.length - displayCount,
|
||||
type: type == AvatarType.tiny
|
||||
? MoreCountType.tiny
|
||||
: MoreCountType.mini,
|
||||
type: moreCountTypeFromAvatarType(type),
|
||||
thumbnailView: removeBorder,
|
||||
),
|
||||
),
|
||||
@ -67,9 +67,36 @@ class AlbumSharesIcons extends StatelessWidget {
|
||||
return Padding(
|
||||
padding: padding,
|
||||
child: Stack(
|
||||
alignment: stackAlignment,
|
||||
clipBehavior: Clip.none,
|
||||
children: widgets,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
double getOverlapPadding(AvatarType type) {
|
||||
switch (type) {
|
||||
case AvatarType.extra:
|
||||
return 14.0;
|
||||
case AvatarType.tiny:
|
||||
return 14.0;
|
||||
case AvatarType.mini:
|
||||
return 20.0;
|
||||
case AvatarType.small:
|
||||
return 28.0;
|
||||
}
|
||||
}
|
||||
|
||||
MoreCountType moreCountTypeFromAvatarType(AvatarType type) {
|
||||
switch (type) {
|
||||
case AvatarType.extra:
|
||||
return MoreCountType.extra;
|
||||
case AvatarType.tiny:
|
||||
return MoreCountType.tiny;
|
||||
case AvatarType.mini:
|
||||
return MoreCountType.mini;
|
||||
case AvatarType.small:
|
||||
return MoreCountType.small;
|
||||
}
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ class MoreCountWidget extends StatelessWidget {
|
||||
final enteTextTheme = getEnteTextTheme(context);
|
||||
switch (type) {
|
||||
case MoreCountType.small:
|
||||
return Tuple2(36.0, enteTextTheme.small);
|
||||
return Tuple2(32.0, enteTextTheme.small);
|
||||
case MoreCountType.mini:
|
||||
return Tuple2(24.0, enteTextTheme.mini);
|
||||
case MoreCountType.tiny:
|
||||
|
@ -28,6 +28,7 @@ class UserAvatarWidget extends StatefulWidget {
|
||||
|
||||
@override
|
||||
State<UserAvatarWidget> createState() => _UserAvatarWidgetState();
|
||||
static const strokeWidth = 1.0;
|
||||
}
|
||||
|
||||
class _UserAvatarWidgetState extends State<UserAvatarWidget> {
|
||||
@ -67,7 +68,7 @@ class _UserAvatarWidgetState extends State<UserAvatarWidget> {
|
||||
color: widget.thumbnailView
|
||||
? strokeMutedDark
|
||||
: getEnteColorScheme(context).strokeMuted,
|
||||
width: 1,
|
||||
width: UserAvatarWidget.strokeWidth,
|
||||
strokeAlign: BorderSide.strokeAlignOutside,
|
||||
),
|
||||
),
|
||||
@ -114,21 +115,6 @@ class _UserAvatarWidgetState extends State<UserAvatarWidget> {
|
||||
type: widget.type,
|
||||
);
|
||||
}
|
||||
|
||||
double getAvatarSize(
|
||||
AvatarType type,
|
||||
) {
|
||||
switch (type) {
|
||||
case AvatarType.small:
|
||||
return 36.0;
|
||||
case AvatarType.mini:
|
||||
return 24.0;
|
||||
case AvatarType.tiny:
|
||||
return 18.0;
|
||||
case AvatarType.extra:
|
||||
return 18.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _FirstLetterAvatar extends StatefulWidget {
|
||||
@ -178,7 +164,7 @@ class _FirstLetterAvatarState extends State<_FirstLetterAvatar> {
|
||||
color: widget.thumbnailView
|
||||
? strokeMutedDark
|
||||
: getEnteColorScheme(context).strokeMuted,
|
||||
width: 1.0,
|
||||
width: UserAvatarWidget.strokeWidth,
|
||||
strokeAlign: BorderSide.strokeAlignOutside,
|
||||
),
|
||||
),
|
||||
@ -204,7 +190,7 @@ class _FirstLetterAvatarState extends State<_FirstLetterAvatar> {
|
||||
final enteTextTheme = getEnteTextTheme(context);
|
||||
switch (type) {
|
||||
case AvatarType.small:
|
||||
return Tuple2(36.0, enteTextTheme.small);
|
||||
return Tuple2(32.0, enteTextTheme.small);
|
||||
case AvatarType.mini:
|
||||
return Tuple2(24.0, enteTextTheme.mini);
|
||||
case AvatarType.tiny:
|
||||
@ -214,3 +200,18 @@ class _FirstLetterAvatarState extends State<_FirstLetterAvatar> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double getAvatarSize(
|
||||
AvatarType type,
|
||||
) {
|
||||
switch (type) {
|
||||
case AvatarType.small:
|
||||
return 32.0;
|
||||
case AvatarType.mini:
|
||||
return 24.0;
|
||||
case AvatarType.tiny:
|
||||
return 18.0;
|
||||
case AvatarType.extra:
|
||||
return 18.0;
|
||||
}
|
||||
}
|
||||
|
@ -16,18 +16,23 @@ import "package:photos/events/people_changed_event.dart";
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import "package:photos/generated/protos/ente/common/vector.pb.dart";
|
||||
import "package:photos/l10n/l10n.dart";
|
||||
import "package:photos/models/api/collection/user.dart";
|
||||
import "package:photos/models/file/file.dart";
|
||||
import "package:photos/models/ml/face/person.dart";
|
||||
import "package:photos/services/collections_service.dart";
|
||||
import "package:photos/services/machine_learning/face_ml/feedback/cluster_feedback.dart";
|
||||
import "package:photos/services/machine_learning/face_ml/person/person_service.dart";
|
||||
import "package:photos/services/machine_learning/ml_result.dart";
|
||||
import "package:photos/services/search_service.dart";
|
||||
import "package:photos/services/user_service.dart";
|
||||
import "package:photos/theme/ente_theme.dart";
|
||||
import "package:photos/ui/common/date_input.dart";
|
||||
import "package:photos/ui/common/loading_widget.dart";
|
||||
import "package:photos/ui/components/action_sheet_widget.dart";
|
||||
import "package:photos/ui/components/buttons/button_widget.dart";
|
||||
import "package:photos/ui/components/models/button_type.dart";
|
||||
import "package:photos/ui/sharing/album_share_info_widget.dart";
|
||||
import "package:photos/ui/sharing/user_avator_widget.dart";
|
||||
import "package:photos/ui/viewer/file/no_thumbnail_widget.dart";
|
||||
import "package:photos/ui/viewer/gallery/hooks/pick_person_avatar.dart";
|
||||
import "package:photos/ui/viewer/people/link_email_screen.dart";
|
||||
@ -778,12 +783,14 @@ class _EmailSectionState extends State<_EmailSection> {
|
||||
String? _email;
|
||||
final _logger = Logger("_EmailSectionState");
|
||||
bool _initialEmailIsUserEmail = false;
|
||||
late final List<User> _contacts;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_email = widget.email;
|
||||
_initialEmailIsUserEmail = Configuration.instance.getEmail() == _email;
|
||||
_contacts = _getContacts();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -798,6 +805,9 @@ class _EmailSectionState extends State<_EmailSection> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const limitCountTo = 5;
|
||||
final avatarSize = getAvatarSize(AvatarType.small);
|
||||
final overlapPadding = getOverlapPadding(AvatarType.small);
|
||||
if (_email == null || _email!.isEmpty) {
|
||||
return AnimatedSize(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
@ -810,80 +820,108 @@ class _EmailSectionState extends State<_EmailSection> {
|
||||
color: getEnteColorScheme(context).fillFaint,
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: FutureBuilder<bool>(
|
||||
future: _isMeAssigned(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
final isMeAssigned = snapshot.data!;
|
||||
if (!isMeAssigned || _initialEmailIsUserEmail) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: ButtonWidget(
|
||||
buttonType: ButtonType.secondary,
|
||||
labelText: "This is me!",
|
||||
onTap: () async {
|
||||
_updateEmailField(
|
||||
Configuration.instance.getEmail(),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: ButtonWidget(
|
||||
buttonType: ButtonType.primary,
|
||||
labelText: "Link email",
|
||||
shouldSurfaceExecutionStates: false,
|
||||
onTap: () async {
|
||||
final newEmail = await routeToPage(
|
||||
context,
|
||||
LinkEmailScreen(
|
||||
widget.personID,
|
||||
isFromSaveOrEditPerson: true,
|
||||
),
|
||||
);
|
||||
if (newEmail != null) {
|
||||
_updateEmailField(newEmail as String);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return ButtonWidget(
|
||||
buttonType: ButtonType.primary,
|
||||
labelText: "Link email",
|
||||
shouldSurfaceExecutionStates: false,
|
||||
onTap: () async {
|
||||
final newEmail = await routeToPage(
|
||||
context,
|
||||
LinkEmailScreen(
|
||||
widget.personID,
|
||||
isFromSaveOrEditPerson: true,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (_contacts.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 1.0),
|
||||
height: 32 + 2 * UserAvatarWidget.strokeWidth,
|
||||
width: ((avatarSize) * (limitCountTo + 1)) -
|
||||
(((avatarSize) - overlapPadding) * limitCountTo) +
|
||||
(2 * UserAvatarWidget.strokeWidth),
|
||||
child: AlbumSharesIcons(
|
||||
sharees: _contacts,
|
||||
limitCountTo: limitCountTo,
|
||||
type: AvatarType.small,
|
||||
padding: EdgeInsets.zero,
|
||||
stackAlignment: Alignment.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_contacts.isNotEmpty) const SizedBox(height: 38),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: FutureBuilder<bool>(
|
||||
future: _isMeAssigned(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
final isMeAssigned = snapshot.data!;
|
||||
if (!isMeAssigned || _initialEmailIsUserEmail) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: ButtonWidget(
|
||||
buttonType: ButtonType.secondary,
|
||||
labelText: "This is me!",
|
||||
onTap: () async {
|
||||
_updateEmailField(
|
||||
Configuration.instance.getEmail(),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: ButtonWidget(
|
||||
buttonType: ButtonType.primary,
|
||||
labelText: "Link email",
|
||||
shouldSurfaceExecutionStates: false,
|
||||
onTap: () async {
|
||||
final newEmail = await routeToPage(
|
||||
context,
|
||||
LinkEmailScreen(
|
||||
widget.personID,
|
||||
isFromSaveOrEditPerson: true,
|
||||
),
|
||||
);
|
||||
if (newEmail != null) {
|
||||
_updateEmailField(newEmail as String);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
if (newEmail != null) {
|
||||
_updateEmailField(newEmail as String);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
} else if (snapshot.hasError) {
|
||||
_logger.severe(
|
||||
"Error getting isMeAssigned",
|
||||
snapshot.error,
|
||||
);
|
||||
return const RepaintBoundary(child: EnteLoadingWidget());
|
||||
} else {
|
||||
return const RepaintBoundary(child: EnteLoadingWidget());
|
||||
}
|
||||
},
|
||||
),
|
||||
} else {
|
||||
return ButtonWidget(
|
||||
buttonType: ButtonType.primary,
|
||||
labelText: "Link email",
|
||||
shouldSurfaceExecutionStates: false,
|
||||
onTap: () async {
|
||||
final newEmail = await routeToPage(
|
||||
context,
|
||||
LinkEmailScreen(
|
||||
widget.personID,
|
||||
isFromSaveOrEditPerson: true,
|
||||
),
|
||||
);
|
||||
if (newEmail != null) {
|
||||
_updateEmailField(newEmail as String);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
} else if (snapshot.hasError) {
|
||||
_logger.severe(
|
||||
"Error getting isMeAssigned",
|
||||
snapshot.error,
|
||||
);
|
||||
return const RepaintBoundary(
|
||||
child: EnteLoadingWidget(),
|
||||
);
|
||||
} else {
|
||||
return const RepaintBoundary(
|
||||
child: EnteLoadingWidget(),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
).animate().fadeIn(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
@ -946,4 +984,42 @@ class _EmailSectionState extends State<_EmailSection> {
|
||||
saveOrEditPersonState._email = newEmail;
|
||||
});
|
||||
}
|
||||
|
||||
List<User> _getContacts() {
|
||||
final List<User> suggestedUsers = [];
|
||||
final int ownerID = Configuration.instance.getUserID()!;
|
||||
final cachedEmailToPartialPersonDataMap =
|
||||
PersonService.instance.emailToPartialPersonDataMapCache;
|
||||
|
||||
for (final c in CollectionsService.instance.getActiveCollections()) {
|
||||
if (c.owner?.id == ownerID) {
|
||||
for (final User? u in c.sharees ?? []) {
|
||||
if (u != null && u.id != null && u.email.isNotEmpty) {
|
||||
if (!suggestedUsers.any((user) => user.email == u.email) &&
|
||||
cachedEmailToPartialPersonDataMap[u.email] == null) {
|
||||
suggestedUsers.add(u);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (c.owner?.id != null && c.owner!.email.isNotEmpty) {
|
||||
if (!suggestedUsers.any((user) => user.email == c.owner!.email) &&
|
||||
cachedEmailToPartialPersonDataMap[c.owner!.email] == null) {
|
||||
suggestedUsers.add(c.owner!);
|
||||
}
|
||||
}
|
||||
}
|
||||
final cachedUserDetails = UserService.instance.getCachedUserDetails();
|
||||
if (cachedUserDetails?.familyData?.members?.isNotEmpty ?? false) {
|
||||
for (final member in cachedUserDetails!.familyData!.members!) {
|
||||
if (!suggestedUsers.any((user) => user.email == member.email) &&
|
||||
cachedEmailToPartialPersonDataMap[member.email] == null) {
|
||||
suggestedUsers.add(User(email: member.email));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suggestedUsers.sort((a, b) => a.email.compareTo(b.email));
|
||||
|
||||
return suggestedUsers;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user