Skip to content

Commit

Permalink
TW-578: improve sync room status
Browse files Browse the repository at this point in the history
  • Loading branch information
Julian KOUNE committed Oct 6, 2023
1 parent 2a16ca7 commit 6e7bbd8
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 92 deletions.
151 changes: 101 additions & 50 deletions lib/pages/chat/chat_app_bar_title.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:fluffychat/pages/chat/chat_app_bar_title_style.dart';
import 'package:fluffychat/utils/common_helper.dart';
import 'package:fluffychat/utils/room_status_extension.dart';
import 'package:fluffychat/utils/string_extension.dart';
import 'package:flutter/material.dart';
Expand All @@ -10,14 +11,15 @@ import 'package:fluffychat/pages/user_bottom_sheet/user_bottom_sheet.dart';
import 'package:fluffychat/utils/adaptive_bottom_sheet.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;
final Room? room;
final List<Event> selectedEvents;
final bool isArchived;
final TextEditingController sendController;
final Stream<ConnectivityResult> getStreamInstance;
final Stream<ConnectivityResult> connectivityResultStream;
final VoidCallback onPushDetails;
final String? roomName;

Expand All @@ -29,7 +31,7 @@ class ChatAppBarTitle extends StatelessWidget {
required this.selectedEvents,
required this.isArchived,
required this.sendController,
required this.getStreamInstance,
required this.connectivityResultStream,
required this.onPushDetails,
}) : super(key: key);

Expand Down Expand Up @@ -98,70 +100,119 @@ class ChatAppBarTitle extends StatelessWidget {
overflow: TextOverflow.ellipsis,
style: ChatAppBarTitleStyle.appBarTitleStyle(context),
),
_buildStatusContent(context, room!),
_ChatAppBarStatusContent(
connectivityResultStream: connectivityResultStream,
room: room!,
),
],
),
),
],
),
);
}
}

StreamBuilder<ConnectivityResult> _buildStatusContent(
BuildContext context,
Room room,
) {
final TextStyle? statusTextStyle =
ChatAppBarTitleStyle.statusTextStyle(context);
return StreamBuilder<ConnectivityResult>(
stream: getStreamInstance,
class _ChatAppBarStatusContent extends StatelessWidget {
const _ChatAppBarStatusContent({
required this.connectivityResultStream,
required this.room,
});

final Stream<ConnectivityResult> connectivityResultStream;
final Room room;

@override
Widget build(BuildContext context) {
return StreamBuilder<List>(
stream: CombineLatestStream.list(
[connectivityResultStream, room.directChatPresenceStream],
),
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data == ConnectivityResult.none) {
return Text(
L10n.of(context)!.noConnection,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: statusTextStyle,
);
final connectivityResult = tryCast<ConnectivityResult>(
snapshot.data?[0],
fallback: ConnectivityResult.none,
);
final directChatPresence =
tryCast<CachedPresence>(snapshot.data?[1], fallback: null);

if (snapshot.hasData && connectivityResult == ConnectivityResult.none) {
return _ChatAppBarTitleText(text: L10n.of(context)!.noConnection);
}
final typingText = room.getLocalizedTypingText(context);
if (typingText.isEmpty) {
return Text(
room.getLocalizedStatus(context).capitalize(context),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: statusTextStyle,
return _ChatAppBarTitleText(
text: room
.getLocalizedStatus(context, presence: directChatPresence)
.capitalize(context),
);
} else {
return IntrinsicWidth(
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Expanded(
child: Text(
typingText,
maxLines: 1,
overflow: TextOverflow.clip,
style: statusTextStyle,
),
),
SizedBox(
width: 32,
height: 16,
child: Transform.translate(
offset: const Offset(0, -2),
child: LottieBuilder.asset(
'assets/typing-indicator.zip',
fit: BoxFit.fitWidth,
width: 32,
),
),
),
],
),
);
return _ChatAppBarTitleTyping(typingText: typingText);
}
},
);
}
}

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

final String text;

@override
Widget build(BuildContext context) {
final TextStyle? statusTextStyle =
ChatAppBarTitleStyle.statusTextStyle(context);

return Text(
text,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: statusTextStyle,
);
}
}

class _ChatAppBarTitleTyping extends StatelessWidget {
const _ChatAppBarTitleTyping({
required this.typingText,
});

final String typingText;

@override
Widget build(BuildContext context) {
final TextStyle? statusTextStyle =
ChatAppBarTitleStyle.statusTextStyle(context);

return IntrinsicWidth(
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Expanded(
child: Text(
typingText,
maxLines: 1,
overflow: TextOverflow.clip,
style: statusTextStyle,
),
),
SizedBox(
width: 32,
height: 16,
child: Transform.translate(
offset: const Offset(0, -2),
child: LottieBuilder.asset(
'assets/typing-indicator.zip',
fit: BoxFit.fitWidth,
width: 32,
),
),
),
],
),
);
}
}
2 changes: 1 addition & 1 deletion lib/pages/chat/chat_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ class ChatView extends StatelessWidget {
room: controller.room,
isArchived: controller.isArchived,
sendController: controller.sendController,
getStreamInstance: controller
connectivityResultStream: controller
.networkConnectionService
.getStreamInstance(),
actions: _appBarActions(context),
Expand Down
7 changes: 7 additions & 0 deletions lib/utils/common_helper.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
T? tryCast<T>(dynamic value, {T? fallback}) {
if (value != null && value is T) {
return value;
}

return fallback;
}
100 changes: 59 additions & 41 deletions lib/utils/room_status_extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,50 +10,19 @@ extension RoomStatusExtension on Room {
CachedPresence? get directChatPresence =>
client.presences[directChatMatrixID];

String getLocalizedStatus(BuildContext context) {
if (isDirectChat) {
final directChatPresence = this.directChatPresence;
if (directChatPresence != null) {
if (directChatPresence.currentlyActive == true) {
return L10n.of(context)!.onlineStatus;
}
if (directChatPresence.lastActiveTimestamp == null) {
return L10n.of(context)!.onlineLongTimeAgo;
}
final time = directChatPresence.lastActiveTimestamp!;
Stream<CachedPresence> get directChatPresenceStream =>
client.onPresenceChanged.stream;

if (DateTime.now().isBefore(time.add(const Duration(hours: 1)))) {
return L10n.of(context)!
.onlineMinAgo(DateTime.now().difference(time).inMinutes);
} else if (DateTime.now()
.isBefore(time.add(const Duration(hours: 24)))) {
final timeOffline = DateTime.now().difference(time);
return L10n.of(context)!.onlineHourAgo(
timeOffline.inHours,
timeOffline.inMinutes - (timeOffline.inHours * 60),
);
} else if (DateTime.now().isBefore(time.add(const Duration(days: 7)))) {
final timeOffline = DateTime.now().difference(time);
return L10n.of(context)!.onlineDayAgo(timeOffline.inDays);
} else if (DateTime.now()
.isBefore(time.add(const Duration(days: 30)))) {
final timeOffline = DateTime.now().difference(time);
return L10n.of(context)!
.onlineWeekAgo((timeOffline.inDays / 7).truncate());
} else if (DateTime.now()
.isBefore(time.add(const Duration(days: 365)))) {
final timeOffline = DateTime.now().difference(time);
return L10n.of(context)!
.onlineMonthAgo((timeOffline.inDays / 30).truncate());
}
}
return L10n.of(context)!.onlineLongTimeAgo;
}
String getLocalizedStatus(BuildContext context, {CachedPresence? presence}) {
if (isDirectChat && directChatMatrixID != null) {
final directChatPresence = presence?.userid == directChatMatrixID
? presence
: this.directChatPresence;

final totalMembers =
(summary.mInvitedMemberCount ?? 0) + (summary.mJoinedMemberCount ?? 0);
return _getLocalizedStatusDirectChat(directChatPresence, context);
}

return L10n.of(context)!.membersCount(totalMembers.toString());
return _getLocalizedStatusGroupChat(context);
}

String getLocalizedTypingText(BuildContext context) {
Expand Down Expand Up @@ -120,4 +89,53 @@ extension RoomStatusExtension on Room {
}
return lastReceipts.toList();
}

String _getLocalizedStatusGroupChat(BuildContext context) {
final totalMembers =
(summary.mInvitedMemberCount ?? 0) + (summary.mJoinedMemberCount ?? 0);

return L10n.of(context)!.membersCount(totalMembers.toString());
}

String _getLocalizedStatusDirectChat(
CachedPresence? directChatPresence,
BuildContext context,
) {
if (directChatPresence != null) {
if (directChatPresence.presence == PresenceType.online) {
return L10n.of(context)!.onlineStatus;
}
final time = directChatPresence.lastActiveTimestamp;

if (time != null) {
if (DateTime.now().isBefore(time.add(const Duration(hours: 1)))) {
return L10n.of(context)!
.onlineMinAgo(DateTime.now().difference(time).inMinutes);
} else if (DateTime.now()
.isBefore(time.add(const Duration(hours: 24)))) {
final timeOffline = DateTime.now().difference(time);
return L10n.of(context)!.onlineHourAgo(
timeOffline.inHours,
timeOffline.inMinutes - (timeOffline.inHours * 60),
);
} else if (DateTime.now().isBefore(time.add(const Duration(days: 7)))) {
final timeOffline = DateTime.now().difference(time);
return L10n.of(context)!.onlineDayAgo(timeOffline.inDays);
} else if (DateTime.now()
.isBefore(time.add(const Duration(days: 30)))) {
final timeOffline = DateTime.now().difference(time);
return L10n.of(context)!
.onlineWeekAgo((timeOffline.inDays / 7).truncate());
} else if (DateTime.now()
.isBefore(time.add(const Duration(days: 365)))) {
final timeOffline = DateTime.now().difference(time);
return L10n.of(context)!
.onlineMonthAgo((timeOffline.inDays / 30).truncate());
} else {
return L10n.of(context)!.onlineLongTimeAgo;
}
}
}
return L10n.of(context)!.offline;
}
}
1 change: 1 addition & 0 deletions lib/widgets/matrix.dart
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,7 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
state != AppLifecycleState.paused;
client.backgroundSync = foreground;
client.syncPresence = foreground ? null : PresenceType.unavailable;
client.sync(setPresence: client.syncPresence);
client.requestHistoryOnLimitedTimeline = !foreground;
backgroundPush?.clearAllNotifications();
}
Expand Down

0 comments on commit 6e7bbd8

Please sign in to comment.