diff --git a/frontend/lib/models/environment_review.dart b/frontend/lib/models/environment_review.dart index 4618aaf54..3bdd37b16 100644 --- a/frontend/lib/models/environment_review.dart +++ b/frontend/lib/models/environment_review.dart @@ -17,6 +17,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'environment.dart'; +import 'user.dart'; part 'environment_review.freezed.dart'; part 'environment_review.g.dart'; @@ -35,6 +36,7 @@ abstract class EnvironmentReview with _$EnvironmentReview { required String reviewComment, @JsonKey(name: EnvironmentReview.reviewDecisionJsonKey) required List reviewDecision, + @Default([]) List reviewers, }) = _EnvironmentReview; factory EnvironmentReview.fromJson(Map json) => diff --git a/frontend/lib/ui/artefact_page/environment_expandable.dart b/frontend/lib/ui/artefact_page/environment_expandable.dart index 087854826..6c9921426 100644 --- a/frontend/lib/ui/artefact_page/environment_expandable.dart +++ b/frontend/lib/ui/artefact_page/environment_expandable.dart @@ -23,6 +23,7 @@ import '../expandable.dart'; import '../spacing.dart'; import 'environment_issues/environment_issues_expandable.dart'; import 'environment_review_button.dart'; +import 'environment_reviewers_avatars.dart'; import 'test_plan_expandable.dart'; class EnvironmentExpandable extends StatelessWidget { @@ -94,6 +95,10 @@ class _EnvironmentExpandableTitle extends StatelessWidget { style: Theme.of(context).textTheme.titleLarge, ), const Spacer(), + EnvironmentReviewersAvatars( + reviewers: artefactEnvironment.review.reviewers, + ), + const SizedBox(width: Spacing.level3), EnvironmentReviewButton(environmentReview: artefactEnvironment.review), ], ); diff --git a/frontend/lib/ui/artefact_page/environment_reviewers_avatars.dart b/frontend/lib/ui/artefact_page/environment_reviewers_avatars.dart new file mode 100644 index 000000000..25f7bc6ee --- /dev/null +++ b/frontend/lib/ui/artefact_page/environment_reviewers_avatars.dart @@ -0,0 +1,86 @@ +// Copyright (C) 2023 Canonical Ltd. +// +// This file is part of Test Observer Frontend. +// +// Test Observer Frontend is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 3, as +// published by the Free Software Foundation. +// +// Test Observer Frontend is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:flutter/material.dart'; + +import '../../models/user.dart'; +import '../user_avatar.dart'; + +class EnvironmentReviewersAvatars extends StatelessWidget { + const EnvironmentReviewersAvatars({ + super.key, + required this.reviewers, + }); + + final List reviewers; + + @override + Widget build(BuildContext context) { + if (reviewers.isEmpty) { + return const SizedBox.shrink(); + } + + if (reviewers.length == 1) { + return _SimpleUserAvatar(user: reviewers.first); + } + + // Show stacked avatars for multiple reviewers + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + for (int i = 0; i < reviewers.length; i++) + Transform.translate( + offset: Offset(-8.0 * i, 0), + child: _SimpleUserAvatar(user: reviewers[i]), + ), + ], + ); + } +} + +class _SimpleUserAvatar extends StatelessWidget { + const _SimpleUserAvatar({required this.user}); + + final User user; + + @override + Widget build(BuildContext context) { + return Tooltip( + message: _tooltipMessage, + child: CircleAvatar( + backgroundColor: _avatarColor, + radius: 16, + child: Text( + user.initials, + style: Theme.of(context) + .textTheme + .labelMedium + ?.apply(fontWeightDelta: 4), + ), + ), + ); + } + + String get _tooltipMessage { + String result = user.name; + if (user.launchpadHandle != null) { + result = '$result\n${user.launchpadHandle}'; + } + return result; + } + + Color get _avatarColor => possibleColors[user.id % possibleColors.length]; +}