Add active session name and headless indicator to friend tile

This commit is contained in:
Nutcake 2023-05-12 19:29:23 +02:00
parent 801881cc07
commit 8a6a033cfd
5 changed files with 81 additions and 26 deletions

View file

@ -16,6 +16,8 @@ class Friend implements Comparable {
required this.friendStatus, required this.latestMessageTime, required this.friendStatus, required this.latestMessageTime,
}); });
bool get isHeadless => userStatus.activeSessions.any((session) => session.headlessHost == true && session.hostUserId == id);
factory Friend.fromMap(Map map) { factory Friend.fromMap(Map map) {
final userStatus = UserStatus.fromMap(map["userStatus"]); final userStatus = UserStatus.fromMap(map["userStatus"]);
return Friend( return Friend(
@ -116,17 +118,21 @@ enum OnlineStatus {
class UserStatus { class UserStatus {
final OnlineStatus onlineStatus; final OnlineStatus onlineStatus;
final DateTime lastStatusChange; final DateTime lastStatusChange;
final Session currentSession;
final List<Session> activeSessions; final List<Session> activeSessions;
final String neosVersion; final String neosVersion;
const UserStatus({required this.onlineStatus, required this.lastStatusChange, required this.activeSessions, const UserStatus(
{required this.onlineStatus, required this.lastStatusChange, required this.currentSession, required this.activeSessions,
required this.neosVersion, required this.neosVersion,
}); });
factory UserStatus.empty() => UserStatus( factory UserStatus.empty() =>
UserStatus(
onlineStatus: OnlineStatus.offline, onlineStatus: OnlineStatus.offline,
lastStatusChange: DateTime.now(), lastStatusChange: DateTime.now(),
activeSessions: [], activeSessions: [],
currentSession: Session.none(),
neosVersion: "", neosVersion: "",
); );
@ -136,26 +142,33 @@ class UserStatus {
return UserStatus( return UserStatus(
onlineStatus: status, onlineStatus: status,
lastStatusChange: DateTime.parse(map["lastStatusChange"]), lastStatusChange: DateTime.parse(map["lastStatusChange"]),
currentSession: Session.fromMap(map["currentSession"]),
activeSessions: (map["activeSessions"] as List? ?? []).map((e) => Session.fromMap(e)).toList(), activeSessions: (map["activeSessions"] as List? ?? []).map((e) => Session.fromMap(e)).toList(),
neosVersion: map["neosVersion"] ?? "", neosVersion: map["neosVersion"] ?? "",
); );
} }
Map toMap({bool shallow=false}) { Map toMap({bool shallow = false}) {
return { return {
"onlineStatus": onlineStatus.index, "onlineStatus": onlineStatus.index,
"lastStatusChange": lastStatusChange.toIso8601String(), "lastStatusChange": lastStatusChange.toIso8601String(),
"currentSession": currentSession.isNone || shallow ? null : currentSession.toMap(),
"activeSessions": shallow ? [] : activeSessions.map((e) => e.toMap(),), "activeSessions": shallow ? [] : activeSessions.map((e) => e.toMap(),),
"neosVersion": neosVersion, "neosVersion": neosVersion,
}; };
} }
UserStatus copyWith({OnlineStatus? onlineStatus, DateTime? lastStatusChange, List<Session>? activeSessions, UserStatus copyWith({
OnlineStatus? onlineStatus,
DateTime? lastStatusChange,
Session? currentSession,
List<Session>? activeSessions,
String? neosVersion String? neosVersion
}) }) =>
=> UserStatus( UserStatus(
onlineStatus: onlineStatus ?? this.onlineStatus, onlineStatus: onlineStatus ?? this.onlineStatus,
lastStatusChange: lastStatusChange ?? this.lastStatusChange, lastStatusChange: lastStatusChange ?? this.lastStatusChange,
currentSession: currentSession ?? this.currentSession,
activeSessions: activeSessions ?? this.activeSessions, activeSessions: activeSessions ?? this.activeSessions,
neosVersion: neosVersion ?? this.neosVersion, neosVersion: neosVersion ?? this.neosVersion,
); );

View file

@ -13,15 +13,38 @@ class Session {
final FormatNode formattedDescription; final FormatNode formattedDescription;
final List<String> tags; final List<String> tags;
final bool headlessHost; final bool headlessHost;
final String hostUserId;
final String hostUsername; final String hostUsername;
final SessionAccessLevel accessLevel; final SessionAccessLevel accessLevel;
Session({required this.id, required this.name, required this.sessionUsers, required this.thumbnail, Session({required this.id, required this.name, required this.sessionUsers, required this.thumbnail,
required this.maxUsers, required this.hasEnded, required this.isValid, required this.description, required this.maxUsers, required this.hasEnded, required this.isValid, required this.description,
required this.tags, required this.headlessHost, required this.hostUsername, required this.accessLevel, required this.tags, required this.headlessHost, required this.hostUserId, required this.hostUsername,
required this.accessLevel,
}) : formattedName = FormatNode.fromText(name), formattedDescription = FormatNode.fromText(description); }) : formattedName = FormatNode.fromText(name), formattedDescription = FormatNode.fromText(description);
factory Session.fromMap(Map map) { factory Session.none() {
return Session(
id: "",
name: "",
sessionUsers: const [],
thumbnail: "",
maxUsers: 0,
hasEnded: true,
isValid: false,
description: "",
tags: const [],
headlessHost: false,
hostUserId: "",
hostUsername: "",
accessLevel: SessionAccessLevel.unknown
);
}
bool get isNone => id.isEmpty && isValid == false;
factory Session.fromMap(Map? map) {
if (map == null) return Session.none();
return Session( return Session(
id: map["sessionId"], id: map["sessionId"],
name: map["name"], name: map["name"],
@ -33,6 +56,7 @@ class Session {
description: map["description"] ?? "", description: map["description"] ?? "",
tags: ((map["tags"] as List?) ?? []).map((e) => e.toString()).toList(), tags: ((map["tags"] as List?) ?? []).map((e) => e.toString()).toList(),
headlessHost: map["headlessHost"] ?? false, headlessHost: map["headlessHost"] ?? false,
hostUserId: map["hostUserId"] ?? "",
hostUsername: map["hostUsername"] ?? "", hostUsername: map["hostUsername"] ?? "",
accessLevel: SessionAccessLevel.fromName(map["accessLevel"]), accessLevel: SessionAccessLevel.fromName(map["accessLevel"]),
); );
@ -50,6 +74,7 @@ class Session {
"description": description, "description": description,
"tags": shallow ? [] : throw UnimplementedError(), "tags": shallow ? [] : throw UnimplementedError(),
"headlessHost": headlessHost, "headlessHost": headlessHost,
"hostUserId": hostUserId,
"hostUsername": hostUsername, "hostUsername": hostUsername,
"accessLevel": accessLevel.name, // This probably wont work, the API usually expects integers. "accessLevel": accessLevel.name, // This probably wont work, the API usually expects integers.
}; };

View file

@ -2,19 +2,19 @@ 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/friend.dart'; import 'package:contacts_plus_plus/models/friend.dart';
import 'package:contacts_plus_plus/models/message.dart'; import 'package:contacts_plus_plus/models/message.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/friends/friend_online_status_indicator.dart';
import 'package:contacts_plus_plus/widgets/generic_avatar.dart'; import 'package:contacts_plus_plus/widgets/generic_avatar.dart';
import 'package:contacts_plus_plus/widgets/messages/messages_list.dart'; import 'package:contacts_plus_plus/widgets/messages/messages_list.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class FriendListTile extends StatelessWidget { class FriendListTile extends StatelessWidget {
const FriendListTile({required this.friend, this.unreads, this.onTap, super.key}); const FriendListTile({required this.friend, required this.unreads, this.onTap, super.key});
final Friend friend; final Friend friend;
final int? unreads; final int unreads;
final Function? onTap; final Function? onTap;
@override @override
@ -23,10 +23,18 @@ class FriendListTile extends StatelessWidget {
final theme = Theme.of(context); final theme = Theme.of(context);
return ListTile( return ListTile(
leading: GenericAvatar(imageUri: imageUri,), leading: GenericAvatar(imageUri: imageUri,),
trailing: unreads != null && unreads != 0 trailing: unreads != 0
? Text("+$unreads", style: theme.textTheme.bodyMedium?.copyWith(color: theme.colorScheme.primary),) ? Text("+$unreads", style: theme.textTheme.bodyMedium?.copyWith(color: theme.colorScheme.primary),)
: null, : null,
title: Text(friend.username), title: Row(
children: [
Text(friend.username),
if (friend.isHeadless) Padding(
padding: const EdgeInsets.only(left: 8),
child: Icon(Icons.dns, size: 12, color: theme.colorScheme.onSecondaryContainer.withAlpha(150),),
),
],
),
subtitle: Row( subtitle: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
@ -34,6 +42,11 @@ class FriendListTile extends StatelessWidget {
FriendOnlineStatusIndicator(userStatus: friend.userStatus), FriendOnlineStatusIndicator(userStatus: friend.userStatus),
const SizedBox(width: 4,), const SizedBox(width: 4,),
Text(toBeginningOfSentenceCase(friend.userStatus.onlineStatus.name) ?? "Unknown"), Text(toBeginningOfSentenceCase(friend.userStatus.onlineStatus.name) ?? "Unknown"),
if (!friend.userStatus.currentSession.isNone)
...[
const Text(" in "),
Expanded(child: FormattedText(friend.userStatus.currentSession.formattedName, overflow: TextOverflow.ellipsis, maxLines: 1,))
]
], ],
), ),
onTap: () async { onTap: () async {

View file

@ -6,6 +6,7 @@ import 'package:contacts_plus_plus/clients/messaging_client.dart';
import 'package:contacts_plus_plus/models/friend.dart'; import 'package:contacts_plus_plus/models/friend.dart';
import 'package:contacts_plus_plus/models/personal_profile.dart'; import 'package:contacts_plus_plus/models/personal_profile.dart';
import 'package:contacts_plus_plus/widgets/default_error_widget.dart'; import 'package:contacts_plus_plus/widgets/default_error_widget.dart';
import 'package:contacts_plus_plus/widgets/formatted_text.dart';
import 'package:contacts_plus_plus/widgets/friends/expanding_input_fab.dart'; import 'package:contacts_plus_plus/widgets/friends/expanding_input_fab.dart';
import 'package:contacts_plus_plus/widgets/friends/friend_list_tile.dart'; import 'package:contacts_plus_plus/widgets/friends/friend_list_tile.dart';
import 'package:contacts_plus_plus/widgets/my_profile_dialog.dart'; import 'package:contacts_plus_plus/widgets/my_profile_dialog.dart';

View file

@ -72,7 +72,10 @@ class _MessagesListState extends State<MessagesList> {
FriendOnlineStatusIndicator(userStatus: widget.friend.userStatus), FriendOnlineStatusIndicator(userStatus: widget.friend.userStatus),
const SizedBox(width: 8,), const SizedBox(width: 8,),
Text(widget.friend.username), Text(widget.friend.username),
if (widget.friend.isHeadless) Padding(
padding: const EdgeInsets.only(left: 12),
child: Icon(Icons.dns, size: 18, color: Theme.of(context).colorScheme.onSecondaryContainer.withAlpha(150),),
),
], ],
), ),
scrolledUnderElevation: 0.0, scrolledUnderElevation: 0.0,