Fix user session status not showing

This commit is contained in:
Nutcake 2023-10-03 18:20:02 +02:00
parent 56ed403d79
commit bace94b6d2
9 changed files with 130 additions and 100 deletions

View file

@ -1,51 +1,23 @@
import 'dart:async';
import 'package:contacts_plus_plus/apis/session_api.dart';
import 'package:contacts_plus_plus/apis/contact_api.dart';
import 'package:contacts_plus_plus/apis/message_api.dart';
import 'package:contacts_plus_plus/apis/user_api.dart';
import 'package:contacts_plus_plus/clients/api_client.dart';
import 'package:contacts_plus_plus/clients/notification_client.dart';
import 'package:contacts_plus_plus/crypto_helper.dart';
import 'package:contacts_plus_plus/hub_manager.dart';
import 'package:contacts_plus_plus/models/hub_events.dart';
import 'package:contacts_plus_plus/models/message.dart';
import 'package:contacts_plus_plus/models/session.dart';
import 'package:contacts_plus_plus/models/users/friend.dart';
import 'package:contacts_plus_plus/models/users/user_status.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:logging/logging.dart';
import 'package:contacts_plus_plus/apis/contact_api.dart';
import 'package:contacts_plus_plus/apis/message_api.dart';
import 'package:contacts_plus_plus/apis/user_api.dart';
import 'package:contacts_plus_plus/clients/notification_client.dart';
import 'package:contacts_plus_plus/models/users/friend.dart';
import 'package:contacts_plus_plus/clients/api_client.dart';
import 'package:contacts_plus_plus/models/message.dart';
import 'package:package_info_plus/package_info_plus.dart';
enum EventType {
undefined,
invocation,
streamItem,
completion,
streamInvocation,
cancelInvocation,
ping,
close;
}
enum EventTarget {
unknown,
messageSent,
receiveMessage,
messagesRead,
receiveSessionUpdate,
removeSession,
receiveStatusUpdate;
factory EventTarget.parse(String? text) {
if (text == null) return EventTarget.unknown;
return EventTarget.values.firstWhere(
(element) => element.name.toLowerCase() == text.toLowerCase(),
orElse: () => EventTarget.unknown,
);
}
}
class MessagingClient extends ChangeNotifier {
static const Duration _autoRefreshDuration = Duration(seconds: 10);
@ -77,11 +49,6 @@ class MessagingClient extends ChangeNotifier {
debugPrint("mClient created: $hashCode");
Hive.openBox(_messageBoxKey).then((box) async {
await box.delete(_lastUpdateKey);
final activeSessions = await SessionApi.getSessions(apiClient);
for (final session in activeSessions) {
final idHash = CryptoHelper.idHash(session.id + _userStatus.hashSalt);
_sessionMap[idHash] = session;
}
_setupHub();
});
}
@ -113,8 +80,6 @@ class MessagingClient extends ChangeNotifier {
MessageCache _createUserMessageCache(String userId) => MessageCache(apiClient: _apiClient, userId: userId);
Session? getSessionInfo(String idHash) => _sessionMap[idHash];
Future<void> refreshFriendsListWithErrorHandler() async {
try {
await refreshFriendsList();
@ -155,9 +120,10 @@ class MessagingClient extends ChangeNotifier {
Future<void> setUserStatus(UserStatus status) async {
final pkginfo = await PackageInfo.fromPlatform();
_userStatus = status.copyWith(
_userStatus = _userStatus.copyWith(
appVersion: "${pkginfo.version} of ${pkginfo.appName}",
lastStatusChange: DateTime.now(),
onlineStatus: status.onlineStatus,
);
_hubManager.send(
@ -165,14 +131,16 @@ class MessagingClient extends ChangeNotifier {
arguments: [
_userStatus.toMap(),
{
"group": 0,
"targetIds": [],
"group": 1,
"targetIds": null,
}
],
);
final self = getAsFriend(_apiClient.userId);
await _updateContact(self!.copyWith(userStatus: _userStatus));
if (self != null) {
await _updateContact(self.copyWith(userStatus: _userStatus));
}
notifyListeners();
}
@ -284,7 +252,7 @@ class MessagingClient extends ChangeNotifier {
_hubManager.setHandler(EventTarget.receiveSessionUpdate, _onReceiveSessionUpdate);
await _hubManager.start();
setUserStatus(userStatus);
await setUserStatus(userStatus);
_hubManager.send(
"InitializeStatus",
responseHandler: (Map data) async {
@ -302,6 +270,10 @@ class MessagingClient extends ChangeNotifier {
);
}
Map<String, Session> createSessionMap(String salt) {
return _sessionMap.map((key, value) => MapEntry(CryptoHelper.idHash(value.id + salt), value));
}
void _onMessageSent(List args) {
final msg = args[0];
final message = Message.fromMap(msg, withState: MessageState.sent);
@ -338,9 +310,11 @@ class MessagingClient extends ChangeNotifier {
void _onReceiveStatusUpdate(List args) {
for (final statusUpdate in args) {
final status = UserStatus.fromMap(statusUpdate);
var friend = getAsFriend(statusUpdate["userId"]);
friend = friend?.copyWith(userStatus: status);
var status = UserStatus.fromMap(statusUpdate);
final sessionMap = createSessionMap(status.hashSalt);
status = status.copyWith(
sessionData: status.sessions.map((e) => sessionMap[e.sessionHash] ?? Session.none()).toList());
final friend = getAsFriend(statusUpdate["userId"])?.copyWith(userStatus: status);
if (friend != null) {
_updateContact(friend);
}
@ -351,8 +325,7 @@ class MessagingClient extends ChangeNotifier {
void _onReceiveSessionUpdate(List args) {
for (final sessionUpdate in args) {
final session = Session.fromMap(sessionUpdate);
final idHash = CryptoHelper.idHash(session.id + _userStatus.hashSalt);
_sessionMap[idHash] = session;
_sessionMap[session.id] = session;
}
notifyListeners();
}

View file

@ -5,5 +5,5 @@ class Config {
static const int messageCacheValiditySeconds = 90;
static const String latestCompatHash = "jnnkdwkBqGv5+jlf1u/k7A==";
static const String latestCompatHash = "YPDxN4N9fu7ZgV+Nr/AHQw==";
}

View file

@ -2,8 +2,8 @@ import 'dart:convert';
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:contacts_plus_plus/clients/messaging_client.dart';
import 'package:contacts_plus_plus/config.dart';
import 'package:contacts_plus_plus/models/hub_events.dart';
import 'package:logging/logging.dart';
import 'package:uuid/uuid.dart';
@ -81,6 +81,7 @@ class HubManager {
case EventType.completion:
final handler = _responseHandlers[body["invocationId"]];
handler?.call(body["result"] ?? {});
_logger.info("Received completion event: $rawType: $body");
break;
case EventType.cancelInvocation:
case EventType.undefined:

View file

@ -0,0 +1,28 @@
enum EventType {
undefined,
invocation,
streamItem,
completion,
streamInvocation,
cancelInvocation,
ping,
close;
}
enum EventTarget {
unknown,
messageSent,
receiveMessage,
messagesRead,
receiveSessionUpdate,
removeSession,
receiveStatusUpdate;
factory EventTarget.parse(String? text) {
if (text == null) return EventTarget.unknown;
return EventTarget.values.firstWhere(
(element) => element.name.toLowerCase() == text.toLowerCase(),
orElse: () => EventTarget.unknown,
);
}
}

View file

@ -101,6 +101,7 @@ enum SessionAccessLevel {
private,
contacts,
contactsPlus,
registeredUsers,
anyone;
static const _readableNamesMap = {
@ -108,6 +109,7 @@ enum SessionAccessLevel {
SessionAccessLevel.private: "Private",
SessionAccessLevel.contacts: "Contacts",
SessionAccessLevel.contactsPlus: "Contacts+",
SessionAccessLevel.registeredUsers: "Registered users",
SessionAccessLevel.anyone: "Anyone",
};
@ -119,7 +121,7 @@ enum SessionAccessLevel {
}
String toReadableString() {
return SessionAccessLevel._readableNamesMap[this] ?? "Unknown";
return SessionAccessLevel._readableNamesMap[this] ?? SessionAccessLevel.unknown.toReadableString();
}
}

View file

@ -1,58 +1,81 @@
import 'dart:convert';
import 'package:contacts_plus_plus/crypto_helper.dart';
import 'package:contacts_plus_plus/models/session.dart';
import 'package:contacts_plus_plus/models/session_metadata.dart';
import 'package:contacts_plus_plus/models/users/online_status.dart';
import 'package:crypto/crypto.dart';
import 'package:uuid/uuid.dart';
enum UserSessionType
{
unknown,
graphicalClient,
chatClient,
headless,
not;
factory UserSessionType.fromString(String? text) {
return UserSessionType.values.firstWhere((element) => element.name.toLowerCase() == text?.toLowerCase(),
orElse: () => UserSessionType.unknown,
);
}
}
class UserStatus {
final OnlineStatus onlineStatus;
final DateTime lastStatusChange;
final int currentSessionAccessLevel;
final bool currentSessionHidden;
final bool currentHosting;
final DateTime lastPresenceTimestamp;
final String userSessionId;
final int currentSessionIndex;
final List<SessionMetadata> sessions;
final String appVersion;
final String outputDevice;
final bool isMobile;
final bool isPresent;
final String compatibilityHash;
final String hashSalt;
final UserSessionType sessionType;
final List<Session> decodedSessions;
const UserStatus({
required this.onlineStatus,
required this.lastStatusChange,
required this.lastPresenceTimestamp,
required this.userSessionId,
required this.currentSessionIndex,
required this.currentSessionAccessLevel,
required this.currentSessionHidden,
required this.currentHosting,
required this.sessions,
required this.appVersion,
required this.outputDevice,
required this.isMobile,
required this.isPresent,
required this.compatibilityHash,
required this.hashSalt,
required this.sessionType,
this.decodedSessions = const []
});
factory UserStatus.initial() => UserStatus.empty().copyWith(
factory UserStatus.initial() =>
UserStatus.empty().copyWith(
onlineStatus: OnlineStatus.online,
hashSalt: CryptoHelper.cryptoToken(),
outputDevice: "Mobile",
userSessionId: const Uuid().v4().toString(),
sessionType: UserSessionType.chatClient,
);
factory UserStatus.empty() => UserStatus(
factory UserStatus.empty() =>
UserStatus(
onlineStatus: OnlineStatus.offline,
lastStatusChange: DateTime.now(),
currentSessionAccessLevel: 0,
currentSessionHidden: false,
currentHosting: false,
lastPresenceTimestamp: DateTime.now(),
userSessionId: "",
currentSessionIndex: -1,
sessions: [],
appVersion: "",
outputDevice: "Unknown",
isMobile: false,
isPresent: false,
compatibilityHash: "",
hashSalt: "",
sessionType: UserSessionType.unknown
);
factory UserStatus.fromMap(Map map) {
@ -60,10 +83,10 @@ class UserStatus {
final status = OnlineStatus.fromString(statusString);
return UserStatus(
onlineStatus: status,
lastStatusChange: DateTime.parse(map["lastStatusChange"]),
currentSessionAccessLevel: map["currentSessionAccessLevel"] ?? 0,
currentSessionHidden: map["currentSessionHidden"] ?? false,
currentHosting: map["currentHosting"] ?? false,
lastStatusChange: DateTime.tryParse(map["lastStatusChange"] ?? "") ?? DateTime.now(),
lastPresenceTimestamp: DateTime.tryParse(map["lastPresenceTimestamp"] ?? "") ?? DateTime.now(),
userSessionId: map["userSessionId"] ?? "",
isPresent: map["isPresent"] ?? false,
currentSessionIndex: map["currentSessionIndex"] ?? -1,
sessions: (map["sessions"] as List? ?? []).map((e) => SessionMetadata.fromMap(e)).toList(),
appVersion: map["appVersion"] ?? "",
@ -71,6 +94,7 @@ class UserStatus {
isMobile: map["isMobile"] ?? false,
compatibilityHash: map["compatabilityHash"] ?? "",
hashSalt: map["hashSalt"] ?? "",
sessionType: UserSessionType.fromString(map["sessionType"])
);
}
@ -78,17 +102,17 @@ class UserStatus {
return {
"onlineStatus": onlineStatus.index,
"lastStatusChange": lastStatusChange.toIso8601String(),
"currentSessionAccessLevel": currentSessionAccessLevel,
"currentSessionHidden": currentSessionHidden,
"currentHosting": currentHosting,
"isPresent": isPresent,
"lastPresenceTimestamp": lastPresenceTimestamp.toIso8601String(),
"userSessionId": userSessionId,
"currentSessionIndex": currentSessionIndex,
"sessions": shallow
? []
: sessions
.map(
(e) => e.toMap(),
)
.toList(),
.map(
(e) => e.toMap(),
)
.toList(),
"appVersion": appVersion,
"outputDevice": outputDevice,
"isMobile": isMobile,
@ -99,9 +123,9 @@ class UserStatus {
UserStatus copyWith({
OnlineStatus? onlineStatus,
DateTime? lastStatusChange,
int? currentSessionAccessLevel,
bool? currentSessionHidden,
bool? currentHosting,
DateTime? lastPresenceTimestamp,
bool? isPresent,
String? userSessionId,
int? currentSessionIndex,
List<SessionMetadata>? sessions,
String? appVersion,
@ -109,13 +133,15 @@ class UserStatus {
bool? isMobile,
String? compatibilityHash,
String? hashSalt,
UserSessionType? sessionType,
List<Session>? sessionData,
}) =>
UserStatus(
onlineStatus: onlineStatus ?? this.onlineStatus,
lastStatusChange: lastStatusChange ?? this.lastStatusChange,
currentSessionAccessLevel: currentSessionAccessLevel ?? this.currentSessionAccessLevel,
currentSessionHidden: currentSessionHidden ?? this.currentSessionHidden,
currentHosting: currentHosting ?? this.currentHosting,
lastPresenceTimestamp: lastPresenceTimestamp ?? this.lastPresenceTimestamp,
isPresent: isPresent ?? this.isPresent,
userSessionId: userSessionId ?? this.userSessionId,
currentSessionIndex: currentSessionIndex ?? this.currentSessionIndex,
sessions: sessions ?? this.sessions,
appVersion: appVersion ?? this.appVersion,
@ -123,5 +149,7 @@ class UserStatus {
isMobile: isMobile ?? this.isMobile,
compatibilityHash: compatibilityHash ?? this.compatibilityHash,
hashSalt: hashSalt ?? this.hashSalt,
sessionType: sessionType ?? this.sessionType,
decodedSessions: sessionData ?? this.decodedSessions,
);
}

View file

@ -1,9 +1,7 @@
import 'dart:math';
import 'package:contacts_plus_plus/auxiliary.dart';
import 'package:contacts_plus_plus/clients/messaging_client.dart';
import 'package:contacts_plus_plus/models/users/friend.dart';
import 'package:contacts_plus_plus/models/message.dart';
import 'package:contacts_plus_plus/models/users/friend.dart';
import 'package:contacts_plus_plus/widgets/formatted_text.dart';
import 'package:contacts_plus_plus/widgets/friends/friend_online_status_indicator.dart';
import 'package:contacts_plus_plus/widgets/generic_avatar.dart';
@ -24,8 +22,9 @@ class FriendListTile extends StatelessWidget {
final imageUri = Aux.resdbToHttp(friend.userProfile.iconUrl);
final theme = Theme.of(context);
final mClient = Provider.of<MessagingClient>(context, listen: false);
final currentSessionMetadata = friend.userStatus.sessions.elementAtOrNull(max(0, friend.userStatus.currentSessionIndex));
final currentSession = mClient.getSessionInfo(currentSessionMetadata?.sessionHash ?? "");
final currentSession = friend.userStatus.currentSessionIndex == -1
? null
: friend.userStatus.decodedSessions.elementAtOrNull(friend.userStatus.currentSessionIndex);
return ListTile(
leading: GenericAvatar(
imageUri: imageUri,
@ -59,7 +58,7 @@ class FriendListTile extends StatelessWidget {
width: 4,
),
Text(toBeginningOfSentenceCase(friend.userStatus.onlineStatus.name) ?? "Unknown"),
if (currentSession != null) ...[
if (currentSession != null && !currentSession.isNone) ...[
const Text(" in "),
Expanded(
child: FormattedText(

View file

@ -1,3 +1,4 @@
import 'package:collection/collection.dart';
import 'package:contacts_plus_plus/clients/audio_cache_client.dart';
import 'package:contacts_plus_plus/clients/messaging_client.dart';
import 'package:contacts_plus_plus/models/users/friend.dart';
@ -56,7 +57,7 @@ class _MessagesListState extends State<MessagesList> with SingleTickerProviderSt
return Consumer<MessagingClient>(builder: (context, mClient, _) {
final friend = mClient.selectedFriend ?? Friend.empty();
final cache = mClient.getUserMessageCache(friend.id);
final sessions = friend.userStatus.sessions;
final sessions = friend.userStatus.decodedSessions.whereNot((element) => element.isNone).toList();
return Scaffold(
appBar: AppBar(
title: Row(
@ -121,9 +122,7 @@ class _MessagesListState extends State<MessagesList> with SingleTickerProviderSt
scrollDirection: Axis.horizontal,
itemCount: sessions.length,
itemBuilder: (context, index) {
final currentSessionMetadata = sessions[index];
final currentSession = mClient.getSessionInfo(currentSessionMetadata.sessionHash);
if (currentSession == null) return null;
final currentSession = sessions[index];
return SessionTile(session: currentSession);
},
),

View file

@ -181,10 +181,10 @@ packages:
dependency: transitive
description:
name: ffi
sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
url: "https://pub.dev"
source: hosted
version: "2.0.2"
version: "2.1.0"
file:
dependency: transitive
description: