Skip to content

Commit

Permalink
Merge pull request #2621 from nextcloud/feat/talk_app/use-existing-us…
Browse files Browse the repository at this point in the history
…er-status-data

feat(talk_app): Use existing user status data
  • Loading branch information
provokateurin authored Dec 10, 2024
2 parents 75dc994 + f31b2da commit ad17370
Show file tree
Hide file tree
Showing 11 changed files with 171 additions and 88 deletions.
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 @@ -31,7 +31,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
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ class _TalkBloc extends InteractiveBloc implements TalkBloc {
await RequestManager.instance.wrap(
account: account,
subject: rooms,
getRequest: account.client.spreed.room.$getRooms_Request,
getRequest: () => account.client.spreed.room.$getRooms_Request(
includeStatus: spreed.RoomGetRoomsIncludeStatus.$1,
),
converter: ResponseConverter(account.client.spreed.room.$getRooms_Serializer()),
unwrap: (response) => response.body.ocs.data.rebuild(
(b) => b.sort((a, b) => b.lastActivity.compareTo(a.lastActivity)),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import 'package:flutter/material.dart';
import 'package:neon_framework/blocs.dart';
import 'package:neon_framework/models.dart';
import 'package:neon_framework/theme.dart';
import 'package:neon_framework/utils.dart';
import 'package:neon_framework/widgets.dart';
import 'package:nextcloud/core.dart' as core;
import 'package:nextcloud/spreed.dart' as spreed;
import 'package:nextcloud/user_status.dart' as user_status;
import 'package:talk_app/l10n/localizations.dart';

/// The data that will be returned when the [TalkCreateRoomDialog] is closed.
Expand Down Expand Up @@ -165,11 +165,21 @@ class _TalkCreateRoomDialogState extends State<TalkCreateRoomDialog> {
core.AutocompleteResult result,
void Function(core.AutocompleteResult)? onSelected,
) {
final status = result.status.autocompleteResultStatus0;
final icon = switch (result.source) {
'users' => NeonUserAvatar(
username: result.id,
account: NeonProvider.of<Account>(context),
userStatusBloc: NeonProvider.of<UserStatusBloc>(context),
userStatus: status != null
? user_status.Public(
(b) => b
..userId = result.id
..message = status.message
..icon = status.icon
..clearAt = status.clearAt
..status = user_status.$Type.valueOf(status.status),
)
: null,
),
'groups' => CircleAvatar(
child: Icon(
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
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:neon_framework/blocs.dart';
import 'package:neon_framework/models.dart';
import 'package:neon_framework/theme.dart';
import 'package:neon_framework/utils.dart';
import 'package:neon_framework/widgets.dart';
import 'package:nextcloud/spreed.dart' as spreed;
import 'package:nextcloud/user_status.dart' as user_status;
import 'package:talk_app/l10n/localizations.dart';
import 'package:talk_app/src/blocs/room.dart';
import 'package:talk_app/src/widgets/message.dart';
Expand Down Expand Up @@ -187,6 +187,7 @@ class _TalkMessageInputState extends State<TalkMessageInput> {
token: bloc.room.value.requireData.token,
search: matchingPart.substring(1),
limit: 5,
includeStatus: spreed.ChatMentionsIncludeStatus.$1,
);

return response.body.ocs.data
Expand Down Expand Up @@ -257,8 +258,17 @@ class _TalkMessageInputState extends State<TalkMessageInput> {
final icon = switch (suggestion.mention.source) {
'users' => NeonUserAvatar(
account: NeonProvider.of<Account>(context),
userStatusBloc: NeonProvider.of<UserStatusBloc>(context),
username: suggestion.mention.id,
userStatus: suggestion.mention.status != null
? user_status.Public(
(b) => b
..userId = suggestion.mention.id
..message = suggestion.mention.statusMessage
..icon = suggestion.mention.statusIcon
..clearAt = suggestion.mention.statusClearAt
..status = user_status.$Type.valueOf(suggestion.mention.status!),
)
: null,
),
'groups' || 'calls' => CircleAvatar(
child: Icon(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import 'package:flutter/material.dart';
import 'package:neon_framework/blocs.dart';
import 'package:neon_framework/models.dart';
import 'package:neon_framework/theme.dart';
import 'package:neon_framework/utils.dart';
import 'package:neon_framework/widgets.dart';
import 'package:nextcloud/spreed.dart' as spreed;
import 'package:nextcloud/user_status.dart' as user_status;

/// Displays the avatar of the [room].
///
Expand Down Expand Up @@ -50,7 +50,16 @@ class TalkRoomAvatar extends StatelessWidget {
spreed.RoomType.oneToOne => NeonUserAvatar(
username: room.name,
account: NeonProvider.of<Account>(context),
userStatusBloc: NeonProvider.of<UserStatusBloc>(context),
userStatus: room.status != null
? user_status.Public(
(b) => b
..userId = room.name
..message = room.statusMessage
..icon = room.statusIcon
..clearAt = room.statusClearAt
..status = user_status.$Type.valueOf(room.status!),
)
: null,
),
spreed.RoomType.group => _buildIconAvatar(AdaptiveIcons.group),
// coverage:ignore-start
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ Account mockAutocompleteAccount() {
'label': 'Test',
'icon': '',
'source': source,
'status': '',
'status': source == 'users'
? {
'status': 'online',
}
: '',
'subline': '',
'shareWithDisplayNameUnique': '',
},
Expand Down Expand Up @@ -69,21 +73,24 @@ void main() {
});

final localizations = TalkLocalizationsEn();
for (final (name, type, typeName, iconMatcher, iconCount, source) in [
for (final (name, type, typeName, matchers, source) in [
(
'One-to-one',
spreed.RoomType.oneToOne,
localizations.roomType(spreed.RoomType.oneToOne.name),
find.byType(NeonUserAvatar),
findsOne,
{
find.byType(NeonUserAvatar): findsOne,
find.byType(NeonUserStatusIcon): findsOne,
},
'users',
),
(
'Group',
spreed.RoomType.group,
localizations.roomType(spreed.RoomType.group.name),
find.byIcon(AdaptiveIcons.group),
findsExactly(2),
{
find.byIcon(AdaptiveIcons.group): findsExactly(2),
},
'groups',
),
]) {
Expand Down Expand Up @@ -129,7 +136,9 @@ void main() {
await tester.enterText(find.byType(TextFormField), 't');
await tester.pumpAndSettle();

expect(iconMatcher, iconCount);
for (final entry in matchers.entries) {
expect(entry.key, entry.value);
}
await tester.tap(find.text('Test'));
await tester.pumpAndSettle();

Expand All @@ -147,7 +156,17 @@ void main() {
..label = 'Test'
..icon = ''
..source = source
..status = (autocompleteResultStatus0: null, string: '')
..status = source == 'users'
? (
autocompleteResultStatus0: core.AutocompleteResult_Status0(
(b) => b.status = 'online',
),
string: null,
)
: (
autocompleteResultStatus0: null,
string: '',
)
..subline = ''
..shareWithDisplayNameUnique = '',
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import 'package:neon_framework/theme.dart';
import 'package:neon_framework/utils.dart';
import 'package:neon_framework/widgets.dart';
import 'package:nextcloud/spreed.dart' as spreed;
import 'package:nextcloud/user_status.dart' as user_status;
import 'package:provider/provider.dart';
import 'package:rxdart/rxdart.dart';
import 'package:talk_app/l10n/localizations.dart';
Expand Down Expand Up @@ -47,6 +46,7 @@ Account mockTalkAccount() {
'id': 'id2',
'label': 'label2',
'source': 'users',
'status': 'online',
},
],
},
Expand Down Expand Up @@ -162,27 +162,11 @@ void main() {
});

testWidgets('Mention suggestions', (tester) async {
final userStatusBloc = MockUserStatusBloc();
when(() => userStatusBloc.statuses).thenAnswer(
(_) => BehaviorSubject.seeded(
BuiltMap({
'id2': Result.success(
user_status.Public(
(b) => b
..userId = 'id1'
..status = user_status.$Type.online,
),
),
}),
),
);

await tester.pumpWidgetWithAccessibility(
TestApp(
localizationsDelegates: TalkLocalizations.localizationsDelegates,
supportedLocales: TalkLocalizations.supportedLocales,
providers: [
NeonProvider<UserStatusBloc>.value(value: userStatusBloc),
NeonProvider<TalkRoomBloc>.value(value: bloc),
Provider<Account>.value(value: account),
],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import 'package:built_collection/built_collection.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:neon_framework/blocs.dart';
import 'package:neon_framework/models.dart';
import 'package:neon_framework/testing.dart';
import 'package:neon_framework/theme.dart';
import 'package:neon_framework/utils.dart';
import 'package:neon_framework/widgets.dart';
import 'package:nextcloud/spreed.dart' as spreed;
import 'package:provider/provider.dart';
import 'package:rxdart/subjects.dart';
import 'package:talk_app/src/widgets/room_avatar.dart';

import 'testing.dart';
Expand Down Expand Up @@ -43,18 +39,15 @@ void main() {
testWidgets('One to one', (tester) async {
final account = MockAccount();

final userStatusBloc = MockUserStatusBloc();
when(() => userStatusBloc.statuses).thenAnswer((_) => BehaviorSubject.seeded(BuiltMap()));

final room = MockRoom();
when(() => room.isCustomAvatar).thenReturn(false);
when(() => room.type).thenReturn(spreed.RoomType.oneToOne.value);
when(() => room.name).thenReturn('');
when(() => room.status).thenReturn('online');

await tester.pumpWidgetWithAccessibility(
TestApp(
providers: [
NeonProvider<UserStatusBloc>.value(value: userStatusBloc),
Provider<Account>.value(value: account),
],
child: TalkRoomAvatar(
Expand All @@ -63,6 +56,7 @@ void main() {
),
);
expect(find.byType(NeonUserAvatar), findsOne);
expect(find.byType(NeonUserStatusIcon), findsOne);
});

testWidgets('Other', (tester) async {
Expand Down
Loading

0 comments on commit ad17370

Please sign in to comment.