Fix user session status not showing
This commit is contained in:
parent
56ed403d79
commit
bace94b6d2
9 changed files with 130 additions and 100 deletions
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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==";
|
||||
}
|
|
@ -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:
|
||||
|
|
28
lib/models/hub_events.dart
Normal file
28
lib/models/hub_events.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
),
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in a new issue