Skip to content

Commit

Permalink
TW-1827: online status is not updated correctly (#1879)
Browse files Browse the repository at this point in the history
* TW-1827: online status isnot updated correctly

* TW-1827: online status is not updated correctly

* TW-1827: update online status correctly

* TW-1827: write widget test for chat_app_bar_title
  • Loading branch information
sherlockvn authored Jun 25, 2024
1 parent f5728fd commit b63fcbe
Show file tree
Hide file tree
Showing 7 changed files with 487 additions and 45 deletions.
12 changes: 12 additions & 0 deletions assets/l10n/intl_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -1180,6 +1180,7 @@
"type": "text",
"placeholders": {}
},
"loading": "Loading status...",
"loadMore": "Load more…",
"@loadMore": {
"type": "text",
Expand Down Expand Up @@ -1376,6 +1377,11 @@
"type": "text",
"placeholders": {}
},
"aWhileAgo": "a while ago",
"@aWhileAgo": {
"type": "text",
"placeholders": {}
},
"ok": "Ok",
"@ok": {
"type": "text",
Expand Down Expand Up @@ -2566,6 +2572,12 @@
"hour": {}
}
},
"onlineDayAgo": "online {day}d ago",
"@onlineDayAgo": {
"placeholders": {
"day": {}
}
},
"noMessageHereYet": "No message here yet...",
"@noMessageHereYet": {},
"sendMessageGuide": "Send a message or tap on the greeting bellow.",
Expand Down
37 changes: 37 additions & 0 deletions lib/pages/chat/chat.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'package:fluffychat/presentation/model/chat/view_event_list_ui_state.dart
import 'package:fluffychat/utils/extension/basic_event_extension.dart';
import 'package:fluffychat/utils/extension/event_status_custom_extension.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/filtered_timeline_extension.dart';
import 'package:fluffychat/utils/room_status_extension.dart';
import 'package:fluffychat/widgets/context_menu/context_menu_action.dart';
import 'package:fluffychat/widgets/mixins/popup_menu_widget_style.dart';
import 'package:fluffychat/widgets/mixins/twake_context_menu_mixin.dart';
Expand Down Expand Up @@ -258,6 +259,39 @@ class ChatController extends State<Chat>
SuggestionsController<Map<String, String?>> suggestionsController =
SuggestionsController();

ValueNotifier<CachedPresence?> cachedPresenceNotifier = ValueNotifier(null);

StreamController<CachedPresence> cachedPresenceStreamController =
StreamController.broadcast();

Future<void> initCachedPresence() async {
cachedPresenceNotifier.value = room?.directChatPresence;
if (room?.directChatMatrixID != null) {
Matrix.of(context).client.onlatestPresenceChanged.stream.listen((event) {
if (event.userid == room!.directChatMatrixID) {
Logs().v(
'onlatestPresenceChanged: ${event.presence}, ${event.lastActiveTimestamp}',
);
cachedPresenceStreamController.add(event);
}
});
try {
final getPresenceResponse = await client.getPresence(
room!.directChatMatrixID!,
);

cachedPresenceNotifier.value = CachedPresence.fromPresenceResponse(
getPresenceResponse,
room!.directChatMatrixID!,
);
} catch (e) {
Logs().e('Failed to get presence', e);
cachedPresenceNotifier.value =
CachedPresence.neverSeen(room!.directChatMatrixID!);
}
}
}

bool isUnpinEvent(Event event) =>
room?.pinnedEventIds
.firstWhereOrNull((eventId) => eventId == event.eventId) !=
Expand Down Expand Up @@ -1923,6 +1957,7 @@ class ChatController extends State<Chat>
}
_handleReceivedShareFiles();
_listenRoomUpdateEvent();
initCachedPresence();
});
}

Expand Down Expand Up @@ -1968,6 +2003,8 @@ class ChatController extends State<Chat>
keyboardVisibilitySubscription?.cancel();
InViewNotifierListCustom.of(context)?.dispose();
replyEventNotifier.dispose();
cachedPresenceStreamController.close();
cachedPresenceNotifier.dispose();
super.dispose();
}

Expand Down
106 changes: 66 additions & 40 deletions lib/pages/chat/chat_app_bar_title.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:async';

import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:fluffychat/pages/chat/chat_app_bar_title_style.dart';
import 'package:fluffychat/resource/image_paths.dart';
Expand All @@ -11,7 +13,6 @@ import 'package:lottie/lottie.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
import 'package:fluffychat/widgets/avatar/avatar.dart';
import 'package:rxdart/rxdart.dart';

class ChatAppBarTitle extends StatelessWidget {
final Widget? actions;
Expand All @@ -22,6 +23,8 @@ class ChatAppBarTitle extends StatelessWidget {
final Stream<ConnectivityResult> connectivityResultStream;
final VoidCallback onPushDetails;
final String? roomName;
final ValueNotifier<CachedPresence?> cachedPresenceNotifier;
final StreamController<CachedPresence>? cachedPresenceStreamController;

const ChatAppBarTitle({
super.key,
Expand All @@ -33,6 +36,8 @@ class ChatAppBarTitle extends StatelessWidget {
required this.sendController,
required this.connectivityResultStream,
required this.onPushDetails,
required this.cachedPresenceNotifier,
this.cachedPresenceStreamController,
});

@override
Expand Down Expand Up @@ -106,6 +111,9 @@ class ChatAppBarTitle extends StatelessWidget {
_ChatAppBarStatusContent(
connectivityResultStream: connectivityResultStream,
room: room!,
cachedPresenceNotifier: cachedPresenceNotifier,
cachedPresenceStreamController:
cachedPresenceStreamController,
),
],
),
Expand All @@ -120,17 +128,23 @@ class _ChatAppBarStatusContent extends StatelessWidget {
const _ChatAppBarStatusContent({
required this.connectivityResultStream,
required this.room,
required this.cachedPresenceNotifier,
this.cachedPresenceStreamController,
});

final Stream<ConnectivityResult> connectivityResultStream;
final Room room;
final ValueNotifier<CachedPresence?> cachedPresenceNotifier;
final StreamController<CachedPresence>? cachedPresenceStreamController;

@override
Widget build(BuildContext context) {
if (room.isDirectChat) {
return _DirectChatAppBarStatusContent(
connectivityResultStream: connectivityResultStream,
room: room,
cachedPresenceNotifier: cachedPresenceNotifier,
cachedPresenceStreamController: cachedPresenceStreamController!,
);
}

Expand All @@ -145,50 +159,61 @@ class _DirectChatAppBarStatusContent extends StatelessWidget {
const _DirectChatAppBarStatusContent({
required this.connectivityResultStream,
required this.room,
required this.cachedPresenceNotifier,
required this.cachedPresenceStreamController,
});

final Stream<ConnectivityResult> connectivityResultStream;
final Room room;
final ValueNotifier<CachedPresence?> cachedPresenceNotifier;
final StreamController<CachedPresence> cachedPresenceStreamController;

@override
Widget build(BuildContext context) {
CachedPresence? directChatPresence = room.directChatPresence;
return FutureBuilder<GetPresenceResponse>(
future: room.client.getPresence(room.directChatMatrixID!),
builder: (context, futureSnapshot) {
if (futureSnapshot.hasData) {
directChatPresence = CachedPresence.fromPresenceResponse(
futureSnapshot.data!,
room.directChatMatrixID!,
);
}
return StreamBuilder<List>(
stream: CombineLatestStream.list(
[connectivityResultStream, room.directChatPresenceStream],
),
builder: (context, snapshot) {
final connectivityResult = tryCast<ConnectivityResult>(
snapshot.data?[0],
fallback: ConnectivityResult.none,
);
directChatPresence = tryCast<CachedPresence>(
snapshot.data?[1],
fallback: directChatPresence,
return ValueListenableBuilder(
valueListenable: cachedPresenceNotifier,
builder: (context, directChatCachedPresence, child) {
return StreamBuilder(
stream: connectivityResultStream,
builder: (context, connectivitySnapshot) {
return StreamBuilder(
stream: cachedPresenceStreamController.stream,
builder: (context, cachedPresenceSnapshot) {
final connectivityResult = tryCast<ConnectivityResult>(
connectivitySnapshot.data,
fallback: ConnectivityResult.none,
);
directChatPresence = tryCast<CachedPresence>(
cachedPresenceSnapshot.data,
fallback: directChatCachedPresence,
);
if (connectivitySnapshot.hasData &&
connectivityResult == ConnectivityResult.none) {
return ChatAppBarTitleText(
text: L10n.of(context)!.noConnection,
);
}
if (directChatPresence == null) {
return ChatAppBarTitleText(
text: L10n.of(context)!.loading,
);
}
final typingText = room.getLocalizedTypingText(context);
if (typingText.isEmpty) {
return ChatAppBarTitleText(
text: room
.getLocalizedStatus(
context,
presence: directChatPresence,
)
.capitalize(context),
);
} else {
return _ChatAppBarTitleTyping(typingText: typingText);
}
},
);
if (snapshot.hasData &&
connectivityResult == ConnectivityResult.none) {
return _ChatAppBarTitleText(text: L10n.of(context)!.noConnection);
}
final typingText = room.getLocalizedTypingText(context);
if (typingText.isEmpty) {
return _ChatAppBarTitleText(
text: room
.getLocalizedStatus(context, presence: directChatPresence)
.capitalize(context),
);
} else {
return _ChatAppBarTitleTyping(typingText: typingText);
}
},
);
},
Expand Down Expand Up @@ -216,11 +241,11 @@ class _GroupChatAppBarStatusContent extends StatelessWidget {
);

if (snapshot.hasData && connectivityResult == ConnectivityResult.none) {
return _ChatAppBarTitleText(text: L10n.of(context)!.noConnection);
return ChatAppBarTitleText(text: L10n.of(context)!.noConnection);
}
final typingText = room.getLocalizedTypingText(context);
if (typingText.isEmpty) {
return _ChatAppBarTitleText(
return ChatAppBarTitleText(
text: room.getLocalizedStatus(context).capitalize(context),
);
} else {
Expand All @@ -231,8 +256,9 @@ class _GroupChatAppBarStatusContent extends StatelessWidget {
}
}

class _ChatAppBarTitleText extends StatelessWidget {
const _ChatAppBarTitleText({
class ChatAppBarTitleText extends StatelessWidget {
const ChatAppBarTitleText({
super.key,
required this.text,
});

Expand Down
4 changes: 4 additions & 0 deletions lib/pages/chat/chat_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ class ChatView extends StatelessWidget with MessageContentMixin {
actions: _appBarActions(context),
onPushDetails: controller.onPushDetails,
roomName: controller.roomName,
cachedPresenceNotifier:
controller.cachedPresenceNotifier,
cachedPresenceStreamController:
controller.cachedPresenceStreamController,
),
),
],
Expand Down
9 changes: 7 additions & 2 deletions lib/utils/date_time_extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,14 @@ extension DateTimeExtension on DateTime {
return other.difference(this) < const Duration(hours: 1);
}

bool isLessThanTenHoursAgo({DateTime? other}) {
bool isLessThanADayAgo({DateTime? other}) {
other ??= DateTime.now();
return other.difference(this) < const Duration(hours: 10);
return other.difference(this) < const Duration(hours: 24);
}

bool isLessThan30DaysAgo({DateTime? other}) {
other ??= DateTime.now();
return other.difference(this) < const Duration(days: 30);
}

String _formatDateWithLocale(BuildContext context, String pattern) {
Expand Down
12 changes: 9 additions & 3 deletions lib/utils/room_status_extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ extension RoomStatusExtension on Room {

String getLocalizedStatus(BuildContext context, {CachedPresence? presence}) {
if (isDirectChat) {
return _getLocalizedStatusDirectChat(presence, context);
return getLocalizedStatusDirectChat(presence, context);
}

return _getLocalizedStatusGroupChat(context);
Expand Down Expand Up @@ -98,7 +98,7 @@ extension RoomStatusExtension on Room {
return L10n.of(context)!.countMembers(totalMembers);
}

String _getLocalizedStatusDirectChat(
String getLocalizedStatusDirectChat(
CachedPresence? directChatPresence,
BuildContext context,
) {
Expand All @@ -115,11 +115,17 @@ extension RoomStatusExtension on Room {
return L10n.of(context)!.onlineMinAgo(
currentDateTime.difference(lastActiveDateTime).inMinutes,
);
} else if (lastActiveDateTime.isLessThanTenHoursAgo()) {
} else if (lastActiveDateTime.isLessThanADayAgo()) {
final timeOffline = currentDateTime.difference(lastActiveDateTime);
return L10n.of(context)!.onlineHourAgo(
(timeOffline.inMinutes / 60).round(),
);
} else if (lastActiveDateTime.isLessThan30DaysAgo()) {
return L10n.of(context)!.onlineDayAgo(
currentDateTime.difference(lastActiveDateTime).inDays,
);
} else {
return L10n.of(context)!.aWhileAgo;
}
}
}
Expand Down
Loading

0 comments on commit b63fcbe

Please sign in to comment.