Skip to content

Commit

Permalink
feat(neon_framework): Allow providing an existing user status to Neon…
Browse files Browse the repository at this point in the history
…UserAvatar

Signed-off-by: provokateurin <[email protected]>
  • Loading branch information
provokateurin committed Nov 1, 2024
1 parent 9411ff7 commit c0b2fa0
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ class NeonRichObjectMention extends StatelessWidget {
child = NeonUserAvatar(
username: parameter.id,
account: NeonProvider.of<Account>(context),
userStatusBloc: null,
);
case core.RichObjectParameter_Type.call:
highlight = true;
Expand Down
60 changes: 42 additions & 18 deletions packages/neon_framework/lib/src/widgets/user_avatar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,31 @@ class NeonUserAvatar extends StatefulWidget {
/// Creates a new Neon user avatar.
const NeonUserAvatar({
required this.account,
required this.userStatusBloc,
this.userStatusBloc,
this.userStatus,
this.username,
this.size,
super.key,
});
}) : assert(
userStatusBloc == null || userStatus == null,
'One of userStatusBloc and userStatus must be null',
);

/// The account used to fetch the image.
final Account account;

/// {@template neon_framework.UserStatus.userStatusBloc}
/// The user status bloc used for displaying the user status.
///
/// If `null` no status will be displayed.
/// If `null` and [userStatus] is null too no status will be displayed.
/// {@endtemplate}
final UserStatusBloc? userStatusBloc;

/// The displayed user status.
///
/// If `null` and [userStatusBloc] is null too no status will be displayed.
final user_status.$PublicInterface? userStatus;

/// The user profile to display.
///
/// Defaults to the username of [account].
Expand Down Expand Up @@ -84,25 +93,40 @@ class _UserAvatarState extends State<NeonUserAvatar> {
),
);

if (widget.userStatusBloc == null) {
return avatar;
final userStatusBloc = widget.userStatusBloc;
if (userStatusBloc != null) {
return Stack(
alignment: Alignment.center,
children: [
avatar,
ResultBuilder(
stream: userStatusBloc.statuses.map(
(statuses) => statuses[username] ?? Result<user_status.$PublicInterface>.loading(),
),
builder: (context, result) => NeonUserStatusIndicator(
result: result,
size: size,
),
),
],
);
}

return Stack(
alignment: Alignment.center,
children: [
avatar,
ResultBuilder(
stream: widget.userStatusBloc!.statuses.map(
(statuses) => statuses[username] ?? Result<user_status.$PublicInterface>.loading(),
),
builder: (context, result) => NeonUserStatusIndicator(
result: result,
final userStatus = widget.userStatus;
if (userStatus != null) {
return Stack(
alignment: Alignment.center,
children: [
avatar,
NeonUserStatusIndicator(
result: Result.success(userStatus),
size: size,
),
),
],
);
],
);
}

return avatar;
},
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ class TalkActorAvatar extends StatelessWidget {
spreed.ActorType.users => NeonUserAvatar(
username: actorId,
account: NeonProvider.of<Account>(context),
userStatusBloc: null,
),
spreed.ActorType.groups || spreed.ActorType.circles => CircleAvatar(
child: Icon(AdaptiveIcons.group),
Expand Down
89 changes: 61 additions & 28 deletions packages/neon_framework/test/user_avatar_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,40 +15,73 @@ void main() {
});

group('NeonUserAvatar', () {
for (final (withStatus, matcher) in [(false, findsNothing), (true, findsOne)]) {
testWidgets('${withStatus ? 'With' : 'Without'} status', (tester) async {
final account = MockAccount();
testWidgets('With UserStatusBloc', (tester) async {
final account = MockAccount();

final userStatusBloc = MockUserStatusBloc();
when(() => userStatusBloc.statuses).thenAnswer(
(_) => BehaviorSubject.seeded(
BuiltMap({
'test': Result<user_status.$PublicInterface>(
user_status.Public(
(b) => b
..userId = 'test'
..status = user_status.$Type.online,
),
null,
isLoading: false,
isCached: false,
final userStatusBloc = MockUserStatusBloc();
when(() => userStatusBloc.statuses).thenAnswer(
(_) => BehaviorSubject.seeded(
BuiltMap({
'test': Result<user_status.$PublicInterface>(
user_status.Public(
(b) => b
..userId = 'test'
..status = user_status.$Type.online,
),
}),
null,
isLoading: false,
isCached: false,
),
}),
),
);

await tester.pumpWidgetWithAccessibility(
TestApp(
child: NeonUserAvatar(
account: account,
userStatusBloc: userStatusBloc,
),
);
),
);

await tester.pumpWidgetWithAccessibility(
TestApp(
child: NeonUserAvatar(
account: account,
userStatusBloc: withStatus ? userStatusBloc : null,
),
expect(find.byType(NeonUserStatusIndicator), findsOne);
});

testWidgets('With user status', (tester) async {
final account = MockAccount();

final userStatus = user_status.Public(
(b) => b
..userId = 'test'
..status = user_status.$Type.online,
);

await tester.pumpWidgetWithAccessibility(
TestApp(
child: NeonUserAvatar(
account: account,
userStatus: userStatus,
),
);
),
);

expect(find.byType(NeonUserStatusIndicator), matcher);
});
}
expect(find.byType(NeonUserStatusIndicator), findsOne);
});

testWidgets('Without UserStatusBloc and user status', (tester) async {
final account = MockAccount();

await tester.pumpWidgetWithAccessibility(
TestApp(
child: NeonUserAvatar(
account: account,
),
),
);

expect(find.byType(NeonUserStatusIndicator), findsNothing);
});
});

group('Status indicator', () {
Expand Down

0 comments on commit c0b2fa0

Please sign in to comment.