Add preliminary hash based session handling
This commit is contained in:
parent
c1da0da897
commit
56ed403d79
8 changed files with 148 additions and 170 deletions
|
@ -1,6 +1,8 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'package:contacts_plus_plus/apis/session_api.dart';
|
||||||
|
import 'package:contacts_plus_plus/crypto_helper.dart';
|
||||||
import 'package:contacts_plus_plus/hub_manager.dart';
|
import 'package:contacts_plus_plus/hub_manager.dart';
|
||||||
import 'package:contacts_plus_plus/models/users/online_status.dart';
|
import 'package:contacts_plus_plus/models/session.dart';
|
||||||
import 'package:contacts_plus_plus/models/users/user_status.dart';
|
import 'package:contacts_plus_plus/models/users/user_status.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
@ -58,13 +60,14 @@ class MessagingClient extends ChangeNotifier {
|
||||||
final Logger _logger = Logger("Messaging");
|
final Logger _logger = Logger("Messaging");
|
||||||
final NotificationClient _notificationClient;
|
final NotificationClient _notificationClient;
|
||||||
final HubManager _hubManager = HubManager();
|
final HubManager _hubManager = HubManager();
|
||||||
|
final Map<String, Session> _sessionMap = {};
|
||||||
Friend? selectedFriend;
|
Friend? selectedFriend;
|
||||||
|
|
||||||
Timer? _notifyOnlineTimer;
|
Timer? _notifyOnlineTimer;
|
||||||
Timer? _autoRefresh;
|
Timer? _autoRefresh;
|
||||||
Timer? _unreadSafeguard;
|
Timer? _unreadSafeguard;
|
||||||
String? _initStatus;
|
String? _initStatus;
|
||||||
UserStatus _userStatus = UserStatus.empty().copyWith(onlineStatus: OnlineStatus.online);
|
UserStatus _userStatus = UserStatus.initial();
|
||||||
|
|
||||||
UserStatus get userStatus => _userStatus;
|
UserStatus get userStatus => _userStatus;
|
||||||
|
|
||||||
|
@ -73,9 +76,14 @@ class MessagingClient extends ChangeNotifier {
|
||||||
_notificationClient = notificationClient {
|
_notificationClient = notificationClient {
|
||||||
debugPrint("mClient created: $hashCode");
|
debugPrint("mClient created: $hashCode");
|
||||||
Hive.openBox(_messageBoxKey).then((box) async {
|
Hive.openBox(_messageBoxKey).then((box) async {
|
||||||
box.delete(_lastUpdateKey);
|
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();
|
||||||
});
|
});
|
||||||
_setupHub();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -105,6 +113,8 @@ class MessagingClient extends ChangeNotifier {
|
||||||
|
|
||||||
MessageCache _createUserMessageCache(String userId) => MessageCache(apiClient: _apiClient, userId: userId);
|
MessageCache _createUserMessageCache(String userId) => MessageCache(apiClient: _apiClient, userId: userId);
|
||||||
|
|
||||||
|
Session? getSessionInfo(String idHash) => _sessionMap[idHash];
|
||||||
|
|
||||||
Future<void> refreshFriendsListWithErrorHandler() async {
|
Future<void> refreshFriendsListWithErrorHandler() async {
|
||||||
try {
|
try {
|
||||||
await refreshFriendsList();
|
await refreshFriendsList();
|
||||||
|
@ -147,17 +157,19 @@ class MessagingClient extends ChangeNotifier {
|
||||||
|
|
||||||
_userStatus = status.copyWith(
|
_userStatus = status.copyWith(
|
||||||
appVersion: "${pkginfo.version} of ${pkginfo.appName}",
|
appVersion: "${pkginfo.version} of ${pkginfo.appName}",
|
||||||
isMobile: true,
|
|
||||||
lastStatusChange: DateTime.now(),
|
lastStatusChange: DateTime.now(),
|
||||||
);
|
);
|
||||||
|
|
||||||
_hubManager.send("BroadcastStatus", arguments: [
|
_hubManager.send(
|
||||||
_userStatus.toMap(),
|
"BroadcastStatus",
|
||||||
{
|
arguments: [
|
||||||
"group": 0,
|
_userStatus.toMap(),
|
||||||
"targetIds": [],
|
{
|
||||||
}
|
"group": 0,
|
||||||
]);
|
"targetIds": [],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
final self = getAsFriend(_apiClient.userId);
|
final self = getAsFriend(_apiClient.userId);
|
||||||
await _updateContact(self!.copyWith(userStatus: _userStatus));
|
await _updateContact(self!.copyWith(userStatus: _userStatus));
|
||||||
|
@ -272,6 +284,7 @@ class MessagingClient extends ChangeNotifier {
|
||||||
_hubManager.setHandler(EventTarget.receiveSessionUpdate, _onReceiveSessionUpdate);
|
_hubManager.setHandler(EventTarget.receiveSessionUpdate, _onReceiveSessionUpdate);
|
||||||
|
|
||||||
await _hubManager.start();
|
await _hubManager.start();
|
||||||
|
setUserStatus(userStatus);
|
||||||
_hubManager.send(
|
_hubManager.send(
|
||||||
"InitializeStatus",
|
"InitializeStatus",
|
||||||
responseHandler: (Map data) async {
|
responseHandler: (Map data) async {
|
||||||
|
@ -335,5 +348,12 @@ class MessagingClient extends ChangeNotifier {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onReceiveSessionUpdate(List args) {}
|
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;
|
||||||
|
}
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
14
lib/crypto_helper.dart
Normal file
14
lib/crypto_helper.dart
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:crypto/crypto.dart';
|
||||||
|
|
||||||
|
class CryptoHelper {
|
||||||
|
static final Random _random = Random.secure();
|
||||||
|
|
||||||
|
static List<int> randomBytes(int length) => List<int>.generate(length, (i) => _random.nextInt(256));
|
||||||
|
|
||||||
|
static String cryptoToken([int length = 128]) => base64UrlEncode(randomBytes(length)).replaceAll("/", "_");
|
||||||
|
|
||||||
|
static String idHash(String id) => sha256.convert(utf8.encode(id)).toString().replaceAll("-", "").toUpperCase();
|
||||||
|
}
|
|
@ -1,4 +1,7 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:contacts_plus_plus/string_formatter.dart';
|
import 'package:contacts_plus_plus/string_formatter.dart';
|
||||||
|
import 'package:crypto/crypto.dart';
|
||||||
|
|
||||||
class Session {
|
class Session {
|
||||||
final String id;
|
final String id;
|
||||||
|
@ -96,15 +99,15 @@ class Session {
|
||||||
enum SessionAccessLevel {
|
enum SessionAccessLevel {
|
||||||
unknown,
|
unknown,
|
||||||
private,
|
private,
|
||||||
friends,
|
contacts,
|
||||||
friendsOfFriends,
|
contactsPlus,
|
||||||
anyone;
|
anyone;
|
||||||
|
|
||||||
static const _readableNamesMap = {
|
static const _readableNamesMap = {
|
||||||
SessionAccessLevel.unknown: "Unknown",
|
SessionAccessLevel.unknown: "Unknown",
|
||||||
SessionAccessLevel.private: "Private",
|
SessionAccessLevel.private: "Private",
|
||||||
SessionAccessLevel.friends: "Contacts",
|
SessionAccessLevel.contacts: "Contacts",
|
||||||
SessionAccessLevel.friendsOfFriends: "Contacts+",
|
SessionAccessLevel.contactsPlus: "Contacts+",
|
||||||
SessionAccessLevel.anyone: "Anyone",
|
SessionAccessLevel.anyone: "Anyone",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
38
lib/models/session_metadata.dart
Normal file
38
lib/models/session_metadata.dart
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import 'package:contacts_plus_plus/models/session.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
class SessionMetadata {
|
||||||
|
final String sessionHash;
|
||||||
|
final SessionAccessLevel accessLevel;
|
||||||
|
final bool sessionHidden;
|
||||||
|
final bool? isHost;
|
||||||
|
final String? broadcastKey;
|
||||||
|
|
||||||
|
SessionMetadata({
|
||||||
|
required this.sessionHash,
|
||||||
|
required this.accessLevel,
|
||||||
|
required this.sessionHidden,
|
||||||
|
required this.isHost,
|
||||||
|
required this.broadcastKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory SessionMetadata.fromMap(Map map) {
|
||||||
|
return SessionMetadata(
|
||||||
|
sessionHash: map["sessionHash"],
|
||||||
|
accessLevel: SessionAccessLevel.fromName(map["accessLevel"]),
|
||||||
|
sessionHidden: map["sessionHidden"],
|
||||||
|
isHost: map["ishost"],
|
||||||
|
broadcastKey: map["broadcastKey"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map toMap() {
|
||||||
|
return {
|
||||||
|
"sessionHash": sessionHash,
|
||||||
|
"accessLevel": toBeginningOfSentenceCase(accessLevel.name),
|
||||||
|
"sessionHidden": sessionHidden,
|
||||||
|
"isHost": isHost,
|
||||||
|
"broadcastKey": broadcastKey,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,9 @@
|
||||||
import 'package:contacts_plus_plus/models/session.dart';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:contacts_plus_plus/crypto_helper.dart';
|
||||||
|
import 'package:contacts_plus_plus/models/session_metadata.dart';
|
||||||
import 'package:contacts_plus_plus/models/users/online_status.dart';
|
import 'package:contacts_plus_plus/models/users/online_status.dart';
|
||||||
|
import 'package:crypto/crypto.dart';
|
||||||
|
|
||||||
class UserStatus {
|
class UserStatus {
|
||||||
final OnlineStatus onlineStatus;
|
final OnlineStatus onlineStatus;
|
||||||
|
@ -7,56 +11,67 @@ class UserStatus {
|
||||||
final int currentSessionAccessLevel;
|
final int currentSessionAccessLevel;
|
||||||
final bool currentSessionHidden;
|
final bool currentSessionHidden;
|
||||||
final bool currentHosting;
|
final bool currentHosting;
|
||||||
final Session currentSession;
|
final int currentSessionIndex;
|
||||||
final List<Session> activeSessions;
|
final List<SessionMetadata> sessions;
|
||||||
final String appVersion;
|
final String appVersion;
|
||||||
final String outputDevice;
|
final String outputDevice;
|
||||||
final bool isMobile;
|
final bool isMobile;
|
||||||
final String compatibilityHash;
|
final String compatibilityHash;
|
||||||
|
final String hashSalt;
|
||||||
|
|
||||||
const UserStatus({
|
const UserStatus({
|
||||||
required this.onlineStatus,
|
required this.onlineStatus,
|
||||||
required this.lastStatusChange,
|
required this.lastStatusChange,
|
||||||
required this.currentSession,
|
required this.currentSessionIndex,
|
||||||
required this.currentSessionAccessLevel,
|
required this.currentSessionAccessLevel,
|
||||||
required this.currentSessionHidden,
|
required this.currentSessionHidden,
|
||||||
required this.currentHosting,
|
required this.currentHosting,
|
||||||
required this.activeSessions,
|
required this.sessions,
|
||||||
required this.appVersion,
|
required this.appVersion,
|
||||||
required this.outputDevice,
|
required this.outputDevice,
|
||||||
required this.isMobile,
|
required this.isMobile,
|
||||||
required this.compatibilityHash,
|
required this.compatibilityHash,
|
||||||
|
required this.hashSalt,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
factory UserStatus.initial() => UserStatus.empty().copyWith(
|
||||||
|
onlineStatus: OnlineStatus.online,
|
||||||
|
hashSalt: CryptoHelper.cryptoToken(),
|
||||||
|
outputDevice: "Mobile",
|
||||||
|
);
|
||||||
|
|
||||||
factory UserStatus.empty() => UserStatus(
|
factory UserStatus.empty() => UserStatus(
|
||||||
onlineStatus: OnlineStatus.offline,
|
onlineStatus: OnlineStatus.offline,
|
||||||
lastStatusChange: DateTime.now(),
|
lastStatusChange: DateTime.now(),
|
||||||
currentSessionAccessLevel: 0,
|
currentSessionAccessLevel: 0,
|
||||||
currentSessionHidden: false,
|
currentSessionHidden: false,
|
||||||
currentHosting: false,
|
currentHosting: false,
|
||||||
currentSession: Session.none(),
|
currentSessionIndex: -1,
|
||||||
activeSessions: [],
|
sessions: [],
|
||||||
appVersion: "",
|
appVersion: "",
|
||||||
outputDevice: "Unknown",
|
outputDevice: "Unknown",
|
||||||
isMobile: false,
|
isMobile: false,
|
||||||
compatibilityHash: "",
|
compatibilityHash: "",
|
||||||
|
hashSalt: "",
|
||||||
);
|
);
|
||||||
|
|
||||||
factory UserStatus.fromMap(Map map) {
|
factory UserStatus.fromMap(Map map) {
|
||||||
final statusString = map["onlineStatus"].toString();
|
final statusString = map["onlineStatus"].toString();
|
||||||
final status = OnlineStatus.fromString(statusString);
|
final status = OnlineStatus.fromString(statusString);
|
||||||
return UserStatus(
|
return UserStatus(
|
||||||
onlineStatus: status,
|
onlineStatus: status,
|
||||||
lastStatusChange: DateTime.parse(map["lastStatusChange"]),
|
lastStatusChange: DateTime.parse(map["lastStatusChange"]),
|
||||||
currentSessionAccessLevel: map["currentSessionAccessLevel"] ?? 0,
|
currentSessionAccessLevel: map["currentSessionAccessLevel"] ?? 0,
|
||||||
currentSessionHidden: map["currentSessionHidden"] ?? false,
|
currentSessionHidden: map["currentSessionHidden"] ?? false,
|
||||||
currentHosting: map["currentHosting"] ?? false,
|
currentHosting: map["currentHosting"] ?? false,
|
||||||
currentSession: Session.fromMap(map["currentSession"]),
|
currentSessionIndex: map["currentSessionIndex"] ?? -1,
|
||||||
activeSessions: (map["activeSessions"] as List? ?? []).map((e) => Session.fromMap(e)).toList(),
|
sessions: (map["sessions"] as List? ?? []).map((e) => SessionMetadata.fromMap(e)).toList(),
|
||||||
appVersion: map["appVersion"] ?? "",
|
appVersion: map["appVersion"] ?? "",
|
||||||
outputDevice: map["outputDevice"] ?? "Unknown",
|
outputDevice: map["outputDevice"] ?? "Unknown",
|
||||||
isMobile: map["isMobile"] ?? false,
|
isMobile: map["isMobile"] ?? false,
|
||||||
compatibilityHash: map["compatabilityHash"] ?? "");
|
compatibilityHash: map["compatabilityHash"] ?? "",
|
||||||
|
hashSalt: map["hashSalt"] ?? "",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Map toMap({bool shallow = false}) {
|
Map toMap({bool shallow = false}) {
|
||||||
|
@ -66,10 +81,10 @@ class UserStatus {
|
||||||
"currentSessionAccessLevel": currentSessionAccessLevel,
|
"currentSessionAccessLevel": currentSessionAccessLevel,
|
||||||
"currentSessionHidden": currentSessionHidden,
|
"currentSessionHidden": currentSessionHidden,
|
||||||
"currentHosting": currentHosting,
|
"currentHosting": currentHosting,
|
||||||
"currentSession": currentSession.isNone || shallow ? null : currentSession.toMap(),
|
"currentSessionIndex": currentSessionIndex,
|
||||||
"activeSessions": shallow
|
"sessions": shallow
|
||||||
? []
|
? []
|
||||||
: activeSessions
|
: sessions
|
||||||
.map(
|
.map(
|
||||||
(e) => e.toMap(),
|
(e) => e.toMap(),
|
||||||
)
|
)
|
||||||
|
@ -87,12 +102,13 @@ class UserStatus {
|
||||||
int? currentSessionAccessLevel,
|
int? currentSessionAccessLevel,
|
||||||
bool? currentSessionHidden,
|
bool? currentSessionHidden,
|
||||||
bool? currentHosting,
|
bool? currentHosting,
|
||||||
Session? currentSession,
|
int? currentSessionIndex,
|
||||||
List<Session>? activeSessions,
|
List<SessionMetadata>? sessions,
|
||||||
String? appVersion,
|
String? appVersion,
|
||||||
String? outputDevice,
|
String? outputDevice,
|
||||||
bool? isMobile,
|
bool? isMobile,
|
||||||
String? compatibilityHash,
|
String? compatibilityHash,
|
||||||
|
String? hashSalt,
|
||||||
}) =>
|
}) =>
|
||||||
UserStatus(
|
UserStatus(
|
||||||
onlineStatus: onlineStatus ?? this.onlineStatus,
|
onlineStatus: onlineStatus ?? this.onlineStatus,
|
||||||
|
@ -100,11 +116,12 @@ class UserStatus {
|
||||||
currentSessionAccessLevel: currentSessionAccessLevel ?? this.currentSessionAccessLevel,
|
currentSessionAccessLevel: currentSessionAccessLevel ?? this.currentSessionAccessLevel,
|
||||||
currentSessionHidden: currentSessionHidden ?? this.currentSessionHidden,
|
currentSessionHidden: currentSessionHidden ?? this.currentSessionHidden,
|
||||||
currentHosting: currentHosting ?? this.currentHosting,
|
currentHosting: currentHosting ?? this.currentHosting,
|
||||||
currentSession: currentSession ?? this.currentSession,
|
currentSessionIndex: currentSessionIndex ?? this.currentSessionIndex,
|
||||||
activeSessions: activeSessions ?? this.activeSessions,
|
sessions: sessions ?? this.sessions,
|
||||||
appVersion: appVersion ?? this.appVersion,
|
appVersion: appVersion ?? this.appVersion,
|
||||||
outputDevice: outputDevice ?? this.outputDevice,
|
outputDevice: outputDevice ?? this.outputDevice,
|
||||||
isMobile: isMobile ?? this.isMobile,
|
isMobile: isMobile ?? this.isMobile,
|
||||||
compatibilityHash: compatibilityHash ?? this.compatibilityHash,
|
compatibilityHash: compatibilityHash ?? this.compatibilityHash,
|
||||||
|
hashSalt: hashSalt ?? this.hashSalt,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:contacts_plus_plus/auxiliary.dart';
|
import 'package:contacts_plus_plus/auxiliary.dart';
|
||||||
import 'package:contacts_plus_plus/clients/messaging_client.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/users/friend.dart';
|
||||||
|
@ -21,6 +23,9 @@ class FriendListTile extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final imageUri = Aux.resdbToHttp(friend.userProfile.iconUrl);
|
final imageUri = Aux.resdbToHttp(friend.userProfile.iconUrl);
|
||||||
final theme = Theme.of(context);
|
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 ?? "");
|
||||||
return ListTile(
|
return ListTile(
|
||||||
leading: GenericAvatar(
|
leading: GenericAvatar(
|
||||||
imageUri: imageUri,
|
imageUri: imageUri,
|
||||||
|
@ -54,11 +59,11 @@ class FriendListTile extends StatelessWidget {
|
||||||
width: 4,
|
width: 4,
|
||||||
),
|
),
|
||||||
Text(toBeginningOfSentenceCase(friend.userStatus.onlineStatus.name) ?? "Unknown"),
|
Text(toBeginningOfSentenceCase(friend.userStatus.onlineStatus.name) ?? "Unknown"),
|
||||||
if (!friend.userStatus.currentSession.isNone) ...[
|
if (currentSession != null) ...[
|
||||||
const Text(" in "),
|
const Text(" in "),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: FormattedText(
|
child: FormattedText(
|
||||||
friend.userStatus.currentSession.formattedName,
|
currentSession.formattedName,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
))
|
))
|
||||||
|
@ -67,7 +72,6 @@ class FriendListTile extends StatelessWidget {
|
||||||
),
|
),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
onTap?.call();
|
onTap?.call();
|
||||||
final mClient = Provider.of<MessagingClient>(context, listen: false);
|
|
||||||
mClient.loadUserMessageCache(friend.id);
|
mClient.loadUserMessageCache(friend.id);
|
||||||
final unreads = mClient.getUnreadsForFriend(friend);
|
final unreads = mClient.getUnreadsForFriend(friend);
|
||||||
if (unreads.isNotEmpty) {
|
if (unreads.isNotEmpty) {
|
||||||
|
|
|
@ -6,7 +6,6 @@ import 'package:contacts_plus_plus/widgets/friends/friend_online_status_indicato
|
||||||
import 'package:contacts_plus_plus/widgets/messages/message_input_bar.dart';
|
import 'package:contacts_plus_plus/widgets/messages/message_input_bar.dart';
|
||||||
import 'package:contacts_plus_plus/widgets/messages/messages_session_header.dart';
|
import 'package:contacts_plus_plus/widgets/messages/messages_session_header.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'message_bubble.dart';
|
import 'message_bubble.dart';
|
||||||
|
@ -57,7 +56,7 @@ class _MessagesListState extends State<MessagesList> with SingleTickerProviderSt
|
||||||
return Consumer<MessagingClient>(builder: (context, mClient, _) {
|
return Consumer<MessagingClient>(builder: (context, mClient, _) {
|
||||||
final friend = mClient.selectedFriend ?? Friend.empty();
|
final friend = mClient.selectedFriend ?? Friend.empty();
|
||||||
final cache = mClient.getUserMessageCache(friend.id);
|
final cache = mClient.getUserMessageCache(friend.id);
|
||||||
final sessions = friend.userStatus.activeSessions;
|
final sessions = friend.userStatus.sessions;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Row(
|
title: Row(
|
||||||
|
@ -121,7 +120,12 @@ class _MessagesListState extends State<MessagesList> with SingleTickerProviderSt
|
||||||
controller: _sessionListScrollController,
|
controller: _sessionListScrollController,
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
itemCount: sessions.length,
|
itemCount: sessions.length,
|
||||||
itemBuilder: (context, index) => SessionTile(session: sessions[index]),
|
itemBuilder: (context, index) {
|
||||||
|
final currentSessionMetadata = sessions[index];
|
||||||
|
final currentSession = mClient.getSessionInfo(currentSessionMetadata.sessionHash);
|
||||||
|
if (currentSession == null) return null;
|
||||||
|
return SessionTile(session: currentSession);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
AnimatedOpacity(
|
AnimatedOpacity(
|
||||||
opacity: _shevronOpacity,
|
opacity: _shevronOpacity,
|
||||||
|
|
|
@ -6,128 +6,6 @@ import 'package:contacts_plus_plus/widgets/generic_avatar.dart';
|
||||||
import 'package:contacts_plus_plus/widgets/sessions/session_view.dart';
|
import 'package:contacts_plus_plus/widgets/sessions/session_view.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class SessionPopup extends StatelessWidget {
|
|
||||||
const SessionPopup({required this.session, super.key});
|
|
||||||
|
|
||||||
final Session session;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final ScrollController userListScrollController = ScrollController();
|
|
||||||
final thumbnailUri = Aux.resdbToHttp(session.thumbnailUrl);
|
|
||||||
return Dialog(
|
|
||||||
insetPadding: const EdgeInsets.all(32),
|
|
||||||
child: Container(
|
|
||||||
constraints: const BoxConstraints(maxHeight: 400),
|
|
||||||
padding: const EdgeInsets.all(24),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: ListView(
|
|
||||||
children: [
|
|
||||||
FormattedText(session.formattedName, style: Theme.of(context).textTheme.titleMedium),
|
|
||||||
session.formattedDescription.isEmpty
|
|
||||||
? const Text("No description")
|
|
||||||
: FormattedText(session.formattedDescription,
|
|
||||||
style: Theme.of(context).textTheme.labelMedium),
|
|
||||||
Text(
|
|
||||||
"Tags: ${session.tags.isEmpty ? "None" : session.tags.join(", ")}",
|
|
||||||
style: Theme.of(context).textTheme.labelMedium,
|
|
||||||
softWrap: true,
|
|
||||||
),
|
|
||||||
Text("Access: ${session.accessLevel.toReadableString()}",
|
|
||||||
style: Theme.of(context).textTheme.labelMedium),
|
|
||||||
Text("Users: ${session.sessionUsers.length}", style: Theme.of(context).textTheme.labelMedium),
|
|
||||||
Text("Maximum users: ${session.maxUsers}", style: Theme.of(context).textTheme.labelMedium),
|
|
||||||
Text("Headless: ${session.headlessHost ? "Yes" : "No"}",
|
|
||||||
style: Theme.of(context).textTheme.labelMedium),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (session.sessionUsers.isNotEmpty)
|
|
||||||
Expanded(
|
|
||||||
child: Scrollbar(
|
|
||||||
trackVisibility: true,
|
|
||||||
controller: userListScrollController,
|
|
||||||
thumbVisibility: true,
|
|
||||||
child: ListView.builder(
|
|
||||||
controller: userListScrollController,
|
|
||||||
shrinkWrap: true,
|
|
||||||
itemCount: session.sessionUsers.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final user = session.sessionUsers[index];
|
|
||||||
return ListTile(
|
|
||||||
dense: true,
|
|
||||||
title: Text(
|
|
||||||
user.username,
|
|
||||||
textAlign: TextAlign.end,
|
|
||||||
),
|
|
||||||
subtitle: Text(
|
|
||||||
user.isPresent ? "Active" : "Inactive",
|
|
||||||
textAlign: TextAlign.end,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
else
|
|
||||||
const Expanded(
|
|
||||||
child: Center(
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Icon(Icons.person_remove_alt_1_rounded),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.all(16.0),
|
|
||||||
child: Text(
|
|
||||||
"No one is currently playing.",
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Center(
|
|
||||||
child: CachedNetworkImage(
|
|
||||||
imageUrl: thumbnailUri,
|
|
||||||
placeholder: (context, url) {
|
|
||||||
return const CircularProgressIndicator();
|
|
||||||
},
|
|
||||||
errorWidget: (context, error, what) => const Column(
|
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(Icons.no_photography),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.all(16.0),
|
|
||||||
child: Text("Failed to load Image"),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SessionTile extends StatelessWidget {
|
class SessionTile extends StatelessWidget {
|
||||||
const SessionTile({required this.session, super.key});
|
const SessionTile({required this.session, super.key});
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue