Isovel/contacts sorting (#26)
* feat: update contacts list sorting + correctly handle headless hosts --------- Co-authored-by: Garrett Watson <toast@isota.ch>
This commit is contained in:
parent
8b285203aa
commit
76e32887e4
7 changed files with 183 additions and 78 deletions
|
@ -1,5 +1,10 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:recon/apis/contact_api.dart';
|
import 'package:recon/apis/contact_api.dart';
|
||||||
import 'package:recon/apis/message_api.dart';
|
import 'package:recon/apis/message_api.dart';
|
||||||
import 'package:recon/apis/session_api.dart';
|
import 'package:recon/apis/session_api.dart';
|
||||||
|
@ -15,12 +20,6 @@ import 'package:recon/models/session.dart';
|
||||||
import 'package:recon/models/users/friend.dart';
|
import 'package:recon/models/users/friend.dart';
|
||||||
import 'package:recon/models/users/online_status.dart';
|
import 'package:recon/models/users/online_status.dart';
|
||||||
import 'package:recon/models/users/user_status.dart';
|
import 'package:recon/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:package_info_plus/package_info_plus.dart';
|
|
||||||
|
|
||||||
|
|
||||||
class MessagingClient extends ChangeNotifier {
|
class MessagingClient extends ChangeNotifier {
|
||||||
static const Duration _autoRefreshDuration = Duration(seconds: 10);
|
static const Duration _autoRefreshDuration = Duration(seconds: 10);
|
||||||
|
@ -49,11 +48,13 @@ class MessagingClient extends ChangeNotifier {
|
||||||
|
|
||||||
UserStatus get userStatus => _userStatus;
|
UserStatus get userStatus => _userStatus;
|
||||||
|
|
||||||
MessagingClient({required ApiClient apiClient, required NotificationClient notificationClient, required SettingsClient settingsClient})
|
MessagingClient(
|
||||||
|
{required ApiClient apiClient,
|
||||||
|
required NotificationClient notificationClient,
|
||||||
|
required SettingsClient settingsClient})
|
||||||
: _apiClient = apiClient,
|
: _apiClient = apiClient,
|
||||||
_notificationClient = notificationClient,
|
_notificationClient = notificationClient,
|
||||||
_settingsClient = settingsClient
|
_settingsClient = settingsClient {
|
||||||
{
|
|
||||||
debugPrint("mClient created: $hashCode");
|
debugPrint("mClient created: $hashCode");
|
||||||
Hive.openBox(_messageBoxKey).then((box) async {
|
Hive.openBox(_messageBoxKey).then((box) async {
|
||||||
await box.delete(_lastUpdateKey);
|
await box.delete(_lastUpdateKey);
|
||||||
|
@ -222,14 +223,44 @@ class MessagingClient extends ChangeNotifier {
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate online status value, with 'headless' between 'busy' and 'offline'
|
||||||
|
double getOnlineStatusValue(Friend friend) {
|
||||||
|
// Adjusting values to ensure correct placement of 'headless'
|
||||||
|
if (friend.isHeadless) return 2.5;
|
||||||
|
switch (friend.userStatus.onlineStatus) {
|
||||||
|
case OnlineStatus.online:
|
||||||
|
return 0;
|
||||||
|
case OnlineStatus.away:
|
||||||
|
return 1;
|
||||||
|
case OnlineStatus.busy:
|
||||||
|
return 2;
|
||||||
|
case OnlineStatus.invisible:
|
||||||
|
return 2.5;
|
||||||
|
case OnlineStatus.offline:
|
||||||
|
default:
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _sortFriendsCache() {
|
void _sortFriendsCache() {
|
||||||
_sortedFriendsCache.sort((a, b) {
|
_sortedFriendsCache.sort((a, b) {
|
||||||
var aVal = friendHasUnreads(a) ? -3 : 0;
|
// Check for unreads and sort by latest message time if either has unreads
|
||||||
var bVal = friendHasUnreads(b) ? -3 : 0;
|
bool aHasUnreads = friendHasUnreads(a);
|
||||||
|
bool bHasUnreads = friendHasUnreads(b);
|
||||||
|
if (aHasUnreads || bHasUnreads) {
|
||||||
|
if (aHasUnreads && bHasUnreads) {
|
||||||
|
return -a.latestMessageTime.compareTo(b.latestMessageTime);
|
||||||
|
}
|
||||||
|
|
||||||
aVal -= a.latestMessageTime.compareTo(b.latestMessageTime);
|
return aHasUnreads ? -1 : 1;
|
||||||
aVal += a.userStatus.onlineStatus.compareTo(b.userStatus.onlineStatus) * 2;
|
}
|
||||||
return aVal.compareTo(bVal);
|
|
||||||
|
int onlineStatusComparison = getOnlineStatusValue(a).compareTo(getOnlineStatusValue(b));
|
||||||
|
if (onlineStatusComparison != 0) {
|
||||||
|
return onlineStatusComparison;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -a.latestMessageTime.compareTo(b.latestMessageTime);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,7 +311,8 @@ class MessagingClient extends ChangeNotifier {
|
||||||
await _refreshUnreads();
|
await _refreshUnreads();
|
||||||
_unreadSafeguard = Timer.periodic(_unreadSafeguardDuration, (timer) => _refreshUnreads());
|
_unreadSafeguard = Timer.periodic(_unreadSafeguardDuration, (timer) => _refreshUnreads());
|
||||||
_hubManager.send("RequestStatus", arguments: [null, false]);
|
_hubManager.send("RequestStatus", arguments: [null, false]);
|
||||||
final lastOnline = OnlineStatus.values.elementAtOrNull(_settingsClient.currentSettings.lastOnlineStatus.valueOrDefault);
|
final lastOnline =
|
||||||
|
OnlineStatus.values.elementAtOrNull(_settingsClient.currentSettings.lastOnlineStatus.valueOrDefault);
|
||||||
await setOnlineStatus(lastOnline ?? OnlineStatus.online);
|
await setOnlineStatus(lastOnline ?? OnlineStatus.online);
|
||||||
_statusHeartbeat = Timer.periodic(_statusHeartbeatDuration, (timer) {
|
_statusHeartbeat = Timer.periodic(_statusHeartbeatDuration, (timer) {
|
||||||
setOnlineStatus(_userStatus.onlineStatus);
|
setOnlineStatus(_userStatus.onlineStatus);
|
||||||
|
@ -332,7 +364,9 @@ class MessagingClient extends ChangeNotifier {
|
||||||
var status = UserStatus.fromMap(statusUpdate);
|
var status = UserStatus.fromMap(statusUpdate);
|
||||||
final sessionMap = createSessionMap(status.hashSalt);
|
final sessionMap = createSessionMap(status.hashSalt);
|
||||||
status = status.copyWith(
|
status = status.copyWith(
|
||||||
decodedSessions: status.sessions.map((e) => sessionMap[e.sessionHash] ?? Session.none().copyWith(accessLevel: e.accessLevel)).toList());
|
decodedSessions: status.sessions
|
||||||
|
.map((e) => sessionMap[e.sessionHash] ?? Session.none().copyWith(accessLevel: e.accessLevel))
|
||||||
|
.toList());
|
||||||
final friend = getAsFriend(statusUpdate["userId"])?.copyWith(userStatus: status);
|
final friend = getAsFriend(statusUpdate["userId"])?.copyWith(userStatus: status);
|
||||||
if (friend != null) {
|
if (friend != null) {
|
||||||
_updateContact(friend);
|
_updateContact(friend);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:recon/auxiliary.dart';
|
import 'package:recon/auxiliary.dart';
|
||||||
import 'package:recon/models/users/user_profile.dart';
|
|
||||||
import 'package:recon/models/users/friend_status.dart';
|
import 'package:recon/models/users/friend_status.dart';
|
||||||
import 'package:recon/models/users/online_status.dart';
|
import 'package:recon/models/users/online_status.dart';
|
||||||
|
import 'package:recon/models/users/user_profile.dart';
|
||||||
import 'package:recon/models/users/user_status.dart';
|
import 'package:recon/models/users/user_status.dart';
|
||||||
|
|
||||||
class Friend implements Comparable {
|
class Friend implements Comparable {
|
||||||
|
@ -15,11 +15,26 @@ class Friend implements Comparable {
|
||||||
final FriendStatus contactStatus;
|
final FriendStatus contactStatus;
|
||||||
final DateTime latestMessageTime;
|
final DateTime latestMessageTime;
|
||||||
|
|
||||||
const Friend({required this.id, required this.username, required this.ownerId, required this.userStatus, required this.userProfile,
|
const Friend({
|
||||||
required this.contactStatus, required this.latestMessageTime,
|
required this.id,
|
||||||
|
required this.username,
|
||||||
|
required this.ownerId,
|
||||||
|
required this.userStatus,
|
||||||
|
required this.userProfile,
|
||||||
|
required this.contactStatus,
|
||||||
|
required this.latestMessageTime,
|
||||||
});
|
});
|
||||||
|
|
||||||
bool get isHeadless => userStatus.outputDevice == "Headless";
|
bool get isHeadless => userStatus.sessionType == UserSessionType.headless;
|
||||||
|
|
||||||
|
bool get isBot => userStatus.sessionType == UserSessionType.bot || id == _resoniteBotId;
|
||||||
|
|
||||||
|
bool get isOffline =>
|
||||||
|
(userStatus.onlineStatus == OnlineStatus.offline || userStatus.onlineStatus == OnlineStatus.invisible) &&
|
||||||
|
!isBot &&
|
||||||
|
!isHeadless;
|
||||||
|
|
||||||
|
bool get isOnline => !isOffline;
|
||||||
|
|
||||||
factory Friend.fromMap(Map map) {
|
factory Friend.fromMap(Map map) {
|
||||||
var userStatus = map["userStatus"] == null ? UserStatus.empty() : UserStatus.fromMap(map["userStatus"]);
|
var userStatus = map["userStatus"] == null ? UserStatus.empty() : UserStatus.fromMap(map["userStatus"]);
|
||||||
|
@ -27,12 +42,13 @@ class Friend implements Comparable {
|
||||||
id: map["id"],
|
id: map["id"],
|
||||||
username: map["contactUsername"],
|
username: map["contactUsername"],
|
||||||
ownerId: map["ownerId"] ?? map["id"],
|
ownerId: map["ownerId"] ?? map["id"],
|
||||||
// Neos bot status is always offline but should be displayed as online
|
// Resonite bot status is always offline but should be displayed as online
|
||||||
userStatus: map["id"] == _resoniteBotId ? userStatus.copyWith(onlineStatus: OnlineStatus.online) : userStatus,
|
userStatus: map["id"] == _resoniteBotId ? userStatus.copyWith(onlineStatus: OnlineStatus.online) : userStatus,
|
||||||
userProfile: UserProfile.fromMap(map["profile"] ?? {}),
|
userProfile: UserProfile.fromMap(map["profile"] ?? {}),
|
||||||
contactStatus: FriendStatus.fromString(map["contactStatus"]),
|
contactStatus: FriendStatus.fromString(map["contactStatus"]),
|
||||||
latestMessageTime: map["latestMessageTime"] == null
|
latestMessageTime: map["latestMessageTime"] == null
|
||||||
? DateTime.fromMillisecondsSinceEpoch(0) : DateTime.parse(map["latestMessageTime"]),
|
? DateTime.fromMillisecondsSinceEpoch(0)
|
||||||
|
: DateTime.parse(map["latestMessageTime"]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,15 +65,20 @@ class Friend implements Comparable {
|
||||||
userStatus: UserStatus.empty(),
|
userStatus: UserStatus.empty(),
|
||||||
userProfile: UserProfile.empty(),
|
userProfile: UserProfile.empty(),
|
||||||
contactStatus: FriendStatus.none,
|
contactStatus: FriendStatus.none,
|
||||||
latestMessageTime: DateTimeX.epoch
|
latestMessageTime: DateTimeX.epoch,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get isEmpty => id == _emptyId;
|
bool get isEmpty => id == _emptyId;
|
||||||
|
|
||||||
Friend copyWith({
|
Friend copyWith(
|
||||||
String? id, String? username, String? ownerId, UserStatus? userStatus, UserProfile? userProfile,
|
{String? id,
|
||||||
FriendStatus? contactStatus, DateTime? latestMessageTime}) {
|
String? username,
|
||||||
|
String? ownerId,
|
||||||
|
UserStatus? userStatus,
|
||||||
|
UserProfile? userProfile,
|
||||||
|
FriendStatus? contactStatus,
|
||||||
|
DateTime? latestMessageTime}) {
|
||||||
return Friend(
|
return Friend(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
username: username ?? this.username,
|
username: username ?? this.username,
|
||||||
|
@ -69,7 +90,7 @@ class Friend implements Comparable {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Map toMap({bool shallow=false}) {
|
Map toMap({bool shallow = false}) {
|
||||||
return {
|
return {
|
||||||
"id": id,
|
"id": id,
|
||||||
"contactUsername": username,
|
"contactUsername": username,
|
||||||
|
|
|
@ -15,10 +15,13 @@ enum OnlineStatus {
|
||||||
Colors.green,
|
Colors.green,
|
||||||
];
|
];
|
||||||
|
|
||||||
Color color(BuildContext context) => this == OnlineStatus.offline || this == OnlineStatus.invisible ? Theme.of(context).colorScheme.onSurface : _colors[index];
|
Color color(BuildContext context) => this == OnlineStatus.offline || this == OnlineStatus.invisible
|
||||||
|
? Theme.of(context).colorScheme.onSecondaryContainer.withAlpha(150)
|
||||||
|
: _colors[index];
|
||||||
|
|
||||||
factory OnlineStatus.fromString(String? text) {
|
factory OnlineStatus.fromString(String? text) {
|
||||||
return OnlineStatus.values.firstWhere((element) => element.name.toLowerCase() == text?.toLowerCase(),
|
return OnlineStatus.values.firstWhere(
|
||||||
|
(element) => element.name.toLowerCase() == text?.toLowerCase(),
|
||||||
orElse: () => OnlineStatus.online,
|
orElse: () => OnlineStatus.online,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,13 +54,12 @@ class FriendListTile extends StatelessWidget {
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
FriendOnlineStatusIndicator(userStatus: friend.userStatus),
|
FriendOnlineStatusIndicator(friend: friend),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 4,
|
width: 4,
|
||||||
),
|
),
|
||||||
|
if (!(friend.isOffline || friend.isHeadless)) ...[
|
||||||
Text(toBeginningOfSentenceCase(friend.userStatus.onlineStatus.name) ?? "Unknown"),
|
Text(toBeginningOfSentenceCase(friend.userStatus.onlineStatus.name) ?? "Unknown"),
|
||||||
if (!(friend.userStatus.onlineStatus == OnlineStatus.offline ||
|
|
||||||
friend.userStatus.onlineStatus == OnlineStatus.invisible))
|
|
||||||
if (currentSession != null) ...[
|
if (currentSession != null) ...[
|
||||||
const Text(" in "),
|
const Text(" in "),
|
||||||
if (currentSession.name.isNotEmpty)
|
if (currentSession.name.isNotEmpty)
|
||||||
|
@ -87,6 +86,24 @@ class FriendListTile extends StatelessWidget {
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
] else if (friend.isOffline)
|
||||||
|
Text(
|
||||||
|
"Offline",
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 1,
|
||||||
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
|
color: OnlineStatus.offline.color(context),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Text(
|
||||||
|
"Headless Host",
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 1,
|
||||||
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
|
color: const Color.fromARGB(255, 41, 77, 92),
|
||||||
|
),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
|
|
|
@ -1,27 +1,30 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:recon/models/users/friend.dart';
|
||||||
import 'package:recon/models/users/online_status.dart';
|
import 'package:recon/models/users/online_status.dart';
|
||||||
import 'package:recon/models/users/user_status.dart';
|
import 'package:recon/models/users/user_status.dart';
|
||||||
|
|
||||||
class FriendOnlineStatusIndicator extends StatelessWidget {
|
class FriendOnlineStatusIndicator extends StatelessWidget {
|
||||||
const FriendOnlineStatusIndicator({required this.userStatus, super.key});
|
const FriendOnlineStatusIndicator({required this.friend, super.key});
|
||||||
|
|
||||||
final UserStatus userStatus;
|
final Friend friend;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return userStatus.appVersion.contains("ReCon") && userStatus.onlineStatus != OnlineStatus.offline
|
final UserStatus userStatus = friend.userStatus;
|
||||||
|
final OnlineStatus onlineStatus = userStatus.onlineStatus;
|
||||||
|
return userStatus.appVersion.contains("ReCon") && friend.isOnline
|
||||||
? SizedBox.square(
|
? SizedBox.square(
|
||||||
dimension: 10,
|
dimension: 10,
|
||||||
child: Image.asset(
|
child: Image.asset(
|
||||||
"assets/images/logo-white.png",
|
"assets/images/logo-white.png",
|
||||||
color: userStatus.onlineStatus.color(context),
|
color: onlineStatus.color(context),
|
||||||
filterQuality: FilterQuality.medium,
|
filterQuality: FilterQuality.medium,
|
||||||
isAntiAlias: true,
|
isAntiAlias: true,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: Icon(
|
: Icon(
|
||||||
userStatus.onlineStatus == OnlineStatus.offline ? Icons.circle_outlined : Icons.circle,
|
friend.isOffline ? Icons.circle_outlined : Icons.circle,
|
||||||
color: userStatus.onlineStatus.color(context),
|
color: friend.isHeadless ? const Color.fromARGB(255, 41, 77, 92) : onlineStatus.color(context),
|
||||||
size: 10,
|
size: 10,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,7 @@ class _MessagesListState extends State<MessagesList> with SingleTickerProviderSt
|
||||||
title: Row(
|
title: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
FriendOnlineStatusIndicator(userStatus: friend.userStatus),
|
FriendOnlineStatusIndicator(friend: friend),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 8,
|
width: 8,
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
import 'package:recon/apis/user_api.dart';
|
import 'package:recon/apis/user_api.dart';
|
||||||
import 'package:recon/auxiliary.dart';
|
import 'package:recon/auxiliary.dart';
|
||||||
import 'package:recon/client_holder.dart';
|
import 'package:recon/client_holder.dart';
|
||||||
import 'package:recon/models/personal_profile.dart';
|
import 'package:recon/models/personal_profile.dart';
|
||||||
import 'package:recon/widgets/default_error_widget.dart';
|
import 'package:recon/widgets/default_error_widget.dart';
|
||||||
import 'package:recon/widgets/generic_avatar.dart';
|
import 'package:recon/widgets/generic_avatar.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
|
|
||||||
class MyProfileDialog extends StatefulWidget {
|
class MyProfileDialog extends StatefulWidget {
|
||||||
const MyProfileDialog({super.key});
|
const MyProfileDialog({super.key});
|
||||||
|
@ -19,7 +19,6 @@ class _MyProfileDialogState extends State<MyProfileDialog> {
|
||||||
Future<PersonalProfile>? _personalProfileFuture;
|
Future<PersonalProfile>? _personalProfileFuture;
|
||||||
Future<StorageQuota>? _storageQuotaFuture;
|
Future<StorageQuota>? _storageQuotaFuture;
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeDependencies() async {
|
void didChangeDependencies() async {
|
||||||
super.didChangeDependencies();
|
super.didChangeDependencies();
|
||||||
|
@ -34,9 +33,7 @@ class _MyProfileDialogState extends State<MyProfileDialog> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final tt = Theme
|
final tt = Theme.of(context).textTheme;
|
||||||
.of(context)
|
|
||||||
.textTheme;
|
|
||||||
DateFormat dateFormat = DateFormat.yMd();
|
DateFormat dateFormat = DateFormat.yMd();
|
||||||
return Dialog(
|
return Dialog(
|
||||||
child: FutureBuilder(
|
child: FutureBuilder(
|
||||||
|
@ -58,50 +55,79 @@ class _MyProfileDialogState extends State<MyProfileDialog> {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(profile.username, style: tt.titleLarge),
|
Text(profile.username, style: tt.titleLarge),
|
||||||
Text(profile.email, style: tt.labelMedium?.copyWith(color: Theme.of(context).colorScheme.onSurface.withAlpha(150)),)
|
Text(
|
||||||
|
profile.email,
|
||||||
|
style:
|
||||||
|
tt.labelMedium?.copyWith(color: Theme.of(context).colorScheme.onSurface.withAlpha(150)),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
GenericAvatar(imageUri: Aux.resdbToHttp(profile.userProfile.iconUrl), radius: 24,)
|
GenericAvatar(
|
||||||
|
imageUri: Aux.resdbToHttp(profile.userProfile.iconUrl),
|
||||||
|
radius: 24,
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16,),
|
const SizedBox(
|
||||||
Row(
|
height: 16,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [Text("User ID: ", style: tt.labelLarge,), Text(profile.id)],
|
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text("2FA: ", style: tt.labelLarge,),
|
Text(
|
||||||
|
"User ID: ",
|
||||||
|
style: tt.labelLarge,
|
||||||
|
),
|
||||||
|
Text(profile.id)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"2FA: ",
|
||||||
|
style: tt.labelLarge,
|
||||||
|
),
|
||||||
Text(profile.twoFactor ? "Enabled" : "Disabled")
|
Text(profile.twoFactor ? "Enabled" : "Disabled")
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text("Patreon Supporter: ", style: tt.labelLarge,),
|
Text(
|
||||||
|
"Patreon Supporter: ",
|
||||||
|
style: tt.labelLarge,
|
||||||
|
),
|
||||||
Text(profile.isPatreonSupporter ? "Yes" : "No")
|
Text(profile.isPatreonSupporter ? "Yes" : "No")
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (profile.publicBanExpiration?.isAfter(DateTime.now()) ?? false)
|
if (profile.publicBanExpiration?.isAfter(DateTime.now()) ?? false)
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [Text("Ban Expiration: ", style: tt.labelLarge,),
|
children: [
|
||||||
Text(dateFormat.format(profile.publicBanExpiration!))],
|
Text(
|
||||||
|
"Ban Expiration: ",
|
||||||
|
style: tt.labelLarge,
|
||||||
|
),
|
||||||
|
Text(dateFormat.format(profile.publicBanExpiration!))
|
||||||
|
],
|
||||||
),
|
),
|
||||||
FutureBuilder(
|
FutureBuilder(
|
||||||
future: _storageQuotaFuture,
|
future: _storageQuotaFuture,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
final storage = snapshot.data;
|
final storage = snapshot.data;
|
||||||
return StorageIndicator(usedBytes: storage?.usedBytes ?? 0, maxBytes: storage?.fullQuotaBytes ?? 1,);
|
return StorageIndicator(
|
||||||
}
|
usedBytes: storage?.usedBytes ?? 0,
|
||||||
|
maxBytes: storage?.fullQuotaBytes ?? 1,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12,),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
} else if (snapshot.hasError) {
|
||||||
else if (snapshot.hasError) {
|
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
@ -109,9 +135,7 @@ class _MyProfileDialogState extends State<MyProfileDialog> {
|
||||||
message: snapshot.error.toString(),
|
message: snapshot.error.toString(),
|
||||||
onRetry: () {
|
onRetry: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
_personalProfileFuture = UserApi.getPersonalProfile(ClientHolder
|
_personalProfileFuture = UserApi.getPersonalProfile(ClientHolder.of(context).apiClient);
|
||||||
.of(context)
|
|
||||||
.apiClient);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -142,7 +166,7 @@ class StorageIndicator extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final value = usedBytes/maxBytes;
|
final value = usedBytes / maxBytes;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(top: 16.0),
|
padding: const EdgeInsets.only(top: 16.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
@ -152,10 +176,13 @@ class StorageIndicator extends StatelessWidget {
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text("Storage:", style: Theme.of(context).textTheme.titleMedium),
|
Text("Storage:", style: Theme.of(context).textTheme.titleMedium),
|
||||||
Text("${(usedBytes * 1e-9).toStringAsFixed(2)}/${(maxBytes * 1e-9).toStringAsFixed(2)} GiB"),
|
Text(// Displayed in GiB instead of GB for consistency with Resonite
|
||||||
|
"${(usedBytes * 9.3132257461548e-10).toStringAsFixed(2)}/${(maxBytes * 9.3132257461548e-10).toStringAsFixed(2)} GB"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8,),
|
const SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
ClipRRect(
|
ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
child: LinearProgressIndicator(
|
child: LinearProgressIndicator(
|
||||||
|
|
Loading…
Reference in a new issue