Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TW-1827: online status is not updated correctly #1879

Merged
merged 4 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
Te-Z marked this conversation as resolved.
Show resolved Hide resolved

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);
hieutbui marked this conversation as resolved.
Show resolved Hide resolved
}

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
Loading