diff --git a/FlexibleContactsSort/ContactsSortingConfig.cs b/FlexibleContactsSort/ContactsSortingConfig.cs index 520049c..8f5799c 100644 --- a/FlexibleContactsSort/ContactsSortingConfig.cs +++ b/FlexibleContactsSort/ContactsSortingConfig.cs @@ -12,30 +12,44 @@ namespace FlexibleContactsSort { internal sealed class ContactsSortingConfig : ConfigSection { + private static readonly ConfigKeyQuantity _cooldownTimeFormat = new(new UnitConfiguration("s", "0", " ", ["m", "s"]), null, 0, int.MaxValue); + private readonly DefiningConfigKey _alphabeticPriorityKey = new("AlphabeticPriority", "Priority of the contact's name. Set 0 to ignore; negative to invert.", () => 1); private readonly DefiningConfigKey _headlessPriorityKey = new("HeadlessPriority", "Priority of the contact being an active headless host. Set 0 to ignore; negative to invert.", () => 100_000); + private readonly DefiningConfigKey _hideOffline = new("HideOffline", "Hide offline contacts completely.", () => false); private readonly DefiningConfigKey _incomingContactRequestPriorityKey = new("IncomingContactRequestPriority", "Priority of the contact being an incoming request. Set 0 to ignore; negative to invert.", () => -1_000_000); private readonly DefiningConfigKey _joinablePriorityKey = new("JoinablePriority", "Priority of the contact being in a session you can join. Set 0 to ignore; negative to invert.", () => -10_000); + + private readonly DefiningConfigKey _keepPinnedOffline = new("KeepPinnedOffline", "Do not hide pinned contacts, even if they're offline.", () => true); + + private readonly DefiningConfigKey _offlineCooldown = new("OfflineCooldown", "Delay before a contact that has just gone offline is counted as such. Set to 0 to disable.", () => 120) + { + _cooldownTimeFormat + }; + private readonly DefiningConfigKey _onlineStatusPriorityKey = new("OnlineStatusPriority", "Priority of the contact's online status. Set 0 to ignore; negative to invert.", () => 1_000); private readonly DefiningConfigKey _outgoingContactRequestPriorityKey = new("OutgoingContactRequestPriority", "Priority of the contact being an outgoing request. Set 0 to ignore; negative to invert.", () => 1_000_000); - private readonly DefiningConfigKey> _pinnedContactsKey = new("PinnedContacts", "List of Contacts to always keep at the top.", () => new(), internalAccessOnly: true); + private readonly DefiningConfigKey> _pinnedContactsKey = new("PinnedContacts", "List of Contacts to always keep at the top.", () => [], internalAccessOnly: true); private readonly DefiningConfigKey _readMessageCooldownKey = new("ReadMessageCooldown", "Delay before a contact with freshly-read messages is counted as such. Set 0 to disable.", () => 120) { - new ConfigKeyQuantity(new UnitConfiguration("s", "0", " ", new[] { "m", "s" }), null, 0, int.MaxValue ) + _cooldownTimeFormat }; public int AlphabeticPriority => _alphabeticPriorityKey.GetValue(); public override string Description => "Contains options for how to sort the Contacts list."; public int HeadlessPriority => _headlessPriorityKey.GetValue(); + public bool HideOffline => _hideOffline; public override string Id => "ContactsSorting"; public int IncomingContactRequestPriority => _incomingContactRequestPriorityKey.GetValue(); public int JoinablePriority => _joinablePriorityKey.GetValue(); + public bool KeepPinnedOffline => _keepPinnedOffline; public override string Name => "Contact Sorting"; + public int OfflineCooldown => _offlineCooldown; public int OnlineStatusPriority => _onlineStatusPriorityKey.GetValue(); public int OutgoingContactRequestPriority => _outgoingContactRequestPriorityKey.GetValue(); public HashSet PinnedContacts => _pinnedContactsKey.GetValue()!; public int ReadMessageCooldown => _readMessageCooldownKey.GetValue(); - public override Version Version { get; } = new(1, 1, 0); + public override Version Version { get; } = new(1, 2, 0); } } \ No newline at end of file diff --git a/FlexibleContactsSort/FlexibleContactSorting.cs b/FlexibleContactsSort/FlexibleContactSorting.cs index 153c9b4..f61d504 100644 --- a/FlexibleContactsSort/FlexibleContactSorting.cs +++ b/FlexibleContactsSort/FlexibleContactSorting.cs @@ -2,7 +2,6 @@ using FrooxEngine; using FrooxEngine.UIX; using HarmonyLib; -using MonkeyLoader.Patching; using MonkeyLoader.Resonite; using SkyFrost.Base; using System; @@ -16,9 +15,11 @@ namespace FlexibleContactsSort [HarmonyPatchCategory(nameof(FlexibleContactSorting))] internal sealed class FlexibleContactSorting : ConfiguredResoniteMonkey { - private static readonly Dictionary _contactIds = new(); + private static readonly ConditionalWeakTable _activityTrackersByContact = []; + private static readonly Dictionary _contactIds = []; - private static readonly ConditionalWeakTable _contactReadMessageTrackers = new(); + private static readonly LocaleString _pinString = Mod.GetLocaleString("Pin"); + private static readonly LocaleString _unpinString = Mod.GetLocaleString("Unpin"); public override bool CanBeDisabled => true; @@ -73,6 +74,9 @@ private static int CalculateOrderScore((ContactData, bool) contactSortInfo) private static int Compare(Slot slot1, Slot slot2) { + if (slot1.ActiveSelf ^ slot2.ActiveSelf) + return slot1.ActiveSelf ? -1 : 1; + var contactItem1 = slot1.GetComponent(); var contactItem2 = slot2.GetComponent(); @@ -94,7 +98,7 @@ private static int GetOnlineStatusOrder(ContactData contactData) private static bool HasUnreadMessages(ContactItem contactItem) { - var readMessageTracker = _contactReadMessageTrackers.GetOrCreateValue(contactItem); + var readMessageTracker = _activityTrackersByContact.GetOrCreateValue(contactItem); if (contactItem.HasMessages) { @@ -114,7 +118,7 @@ private static bool IsIncomingRequest(Contact contact) => contact.ContactStatus == ContactStatus.Requested; private static bool IsInJoinableSession(ContactData contactData) - => contactData?.CurrentSessionInfo is SessionInfo session + => contactData?.CurrentSessionInfo is not null && !IsHeadlessHost(contactData); private static bool IsOfflineContact(ContactData contactData) @@ -130,9 +134,36 @@ private static bool IsOutgoingRequest(Contact contact) [HarmonyPatch(nameof(ContactsDialog.OnCommonUpdate))] private static void OnCommonUpdatePostfix(ContactsDialog __instance, bool __state) { + if (!__state || !LagFreeContactsLoading.AllowSorting) + return; + + if (string.IsNullOrWhiteSpace(__instance._searchBar.Target.Text.Content)) + { + if (ConfigSection.HideOffline) + { + foreach (var contactSlot in __instance._listRoot.Target.Children) + { + var contactItem = contactSlot.GetComponent(); + + if (contactItem?.Data is null) + continue; + + var activityTracker = _activityTrackersByContact.GetOrCreateValue(contactItem); + var isOffline = activityTracker.IsOffline = IsOfflineContact(contactItem.Data); + + contactSlot.ActiveSelf = !isOffline || activityTracker.SecondsSinceOffline < ConfigSection.OfflineCooldown + || (ConfigSection.KeepPinnedOffline && ConfigSection.PinnedContacts.Contains(contactItem.Data.Contact.ContactUserId)); + } + } + else + { + foreach (var contactSlot in __instance._listRoot.Target.Children) + contactSlot.ActiveSelf = true; + } + } + // Sort only if Resonite would have sorted (but we prevented it) - if (__state && LagFreeContactsLoading.AllowSorting) - __instance._listRoot.Target.SortChildren(Compare); + __instance._listRoot.Target.SortChildren(Compare); } [HarmonyPrefix] @@ -151,29 +182,32 @@ private static void OnCommonUpdatePrefix(ContactsDialog __instance, out bool __s } [HarmonyPostfix] - [HarmonyPatch("UpdateSelectedContact")] + [HarmonyPatch(nameof(ContactsDialog.UpdateSelectedContactUI))] private static void UpdateSelectedContactPostfix(ContactsDialog __instance, UIBuilder ___actionsUi) { if (__instance.SelectedContact is null || __instance.SelectedContactId == __instance.Cloud.Platform.AppUserId || __instance.SelectedContact.IsSelfContact) return; - var pinButton = ___actionsUi.Button((ConfigSection.PinnedContacts.Contains(__instance.SelectedContactId) ? "FlexibleContactsSort.Unpin" : "FlexibleContactsSort.Pin").AsLocaleKey()); + var pinButton = ___actionsUi.Button(ConfigSection.PinnedContacts.Contains(__instance.SelectedContactId) ? _unpinString : _pinString); pinButton.LocalPressed += (button, data) => { if (ConfigSection.PinnedContacts.Add(__instance.SelectedContactId)) { - ((Text)pinButton.LabelTextField.Parent).LocaleContent = "FlexibleContactsSort.Unpin".AsLocaleKey(); + ((Text)pinButton.LabelTextField.Parent).LocaleContent = _unpinString; return; } ConfigSection.PinnedContacts.Remove(__instance.SelectedContactId); - ((Text)pinButton.LabelTextField.Parent).LocaleContent = "FlexibleContactsSort.Pin".AsLocaleKey(); + ((Text)pinButton.LabelTextField.Parent).LocaleContent = _pinString; }; } - private sealed class ReadMessageTracker + private sealed class ActivityTracker { private bool _hasMessages; + private bool _isOffline = true; + + private DateTime _offlineTime = DateTime.MinValue; private DateTime _readTime = DateTime.MinValue; public bool HasMessages @@ -188,6 +222,20 @@ public bool HasMessages } } + public bool IsOffline + { + get => _isOffline; + set + { + if (_isOffline && !value) + _offlineTime = DateTime.UtcNow; + + _isOffline = value; + } + } + + public double SecondsSinceOffline => (DateTime.UtcNow - _offlineTime).TotalSeconds; + public double SecondsSinceRead => (DateTime.UtcNow - _readTime).TotalSeconds; } } diff --git a/FlexibleContactsSort/FlexibleContactsSort.csproj b/FlexibleContactsSort/FlexibleContactsSort.csproj index c6cbbc5..acb8cb1 100644 --- a/FlexibleContactsSort/FlexibleContactsSort.csproj +++ b/FlexibleContactsSort/FlexibleContactsSort.csproj @@ -10,7 +10,7 @@ FlexibleContactSorting Flexible Contact Sorting Banane9 - 0.7.0-beta + 0.8.0-beta This MonkeyLoader mod for Resonite allows sorting contacts flexibly and to your liking, including pinning your favorites to the top. It also adds other Quality of Life features to the Contacts Page, such as a clear button for the search, an extra color for your outgoing conctact requests, capacity display to contacts' sessions, and contacts loading without lag. README.md LGPL-3.0-or-later diff --git a/FlexibleContactsSort/LagFreeContactsLoading.cs b/FlexibleContactsSort/LagFreeContactsLoading.cs index 72da1f4..bf3e11d 100644 --- a/FlexibleContactsSort/LagFreeContactsLoading.cs +++ b/FlexibleContactsSort/LagFreeContactsLoading.cs @@ -21,9 +21,8 @@ internal sealed class LagFreeContactsLoading : ResoniteMonkey true; - internal static bool AllowSorting { get; private set; } = true; - protected override IEnumerable GetFeaturePatches() => Enumerable.Empty(); + internal static bool AllowSorting { get; private set; } = true; private static void AddContactItems(ContactsDialog contactsDialog) => contactsDialog.StartTask(async () => diff --git a/FlexibleContactsSort/Locale/de.json b/FlexibleContactsSort/Locale/de.json index 56dcf1e..126d494 100644 --- a/FlexibleContactsSort/Locale/de.json +++ b/FlexibleContactsSort/Locale/de.json @@ -2,7 +2,7 @@ "localeCode": "de", "authors": [ "Banane9" ], "messages": { - "FlexibleContactsSort.Pin": "Kontakt anheften", - "FlexibleContactsSort.Unpin": "Kontakt lösen" + "FlexibleContactSorting.Pin": "Kontakt anheften", + "FlexibleContactSorting.Unpin": "Kontakt lösen" } } \ No newline at end of file diff --git a/FlexibleContactsSort/Locale/en.json b/FlexibleContactsSort/Locale/en.json index be9f8a3..8032737 100644 --- a/FlexibleContactsSort/Locale/en.json +++ b/FlexibleContactsSort/Locale/en.json @@ -2,7 +2,7 @@ "localeCode": "en", "authors": [ "Banane9" ], "messages": { - "FlexibleContactsSort.Pin": "Pin Contact", - "FlexibleContactsSort.Unpin": "Unpin Contact" + "FlexibleContactSorting.Pin": "Pin Contact", + "FlexibleContactSorting.Unpin": "Unpin Contact" } } \ No newline at end of file diff --git a/FlexibleContactsSort/Locale/es.json b/FlexibleContactsSort/Locale/es.json index 9b61a2f..163ab06 100644 --- a/FlexibleContactsSort/Locale/es.json +++ b/FlexibleContactsSort/Locale/es.json @@ -2,7 +2,7 @@ "localeCode": "es", "authors": [ "Raptoranim" ], "messages": { - "FlexibleContactsSort.Pin": "Anclar Contacto", - "FlexibleContactsSort.Unpin": "Desanclar Contacto" + "FlexibleContactSorting.Pin": "Anclar Contacto", + "FlexibleContactSorting.Unpin": "Desanclar Contacto" } } \ No newline at end of file diff --git a/FlexibleContactsSort/Locale/fr.json b/FlexibleContactsSort/Locale/fr.json index a49ad7b..6b9658e 100644 --- a/FlexibleContactsSort/Locale/fr.json +++ b/FlexibleContactsSort/Locale/fr.json @@ -2,7 +2,7 @@ "localeCode": "fr", "authors": [ "j4" ], "messages": { - "FlexibleContactsSort.Pin": "Épingler le contact", - "FlexibleContactsSort.Unpin": "Désépingler le contact" + "FlexibleContactSortinging.Pin": "Épingler le contact", + "FlexibleContactSorting.Unpin": "Désépingler le contact" } } \ No newline at end of file diff --git a/FlexibleContactsSort/Locale/jp.json b/FlexibleContactsSort/Locale/jp.json index 944d259..20a85fd 100644 --- a/FlexibleContactsSort/Locale/jp.json +++ b/FlexibleContactsSort/Locale/jp.json @@ -2,7 +2,7 @@ "localeCode": "jp", "authors": [ "Aesc" ], "messages": { - "FlexibleContactsSort.Pin": "ピン留めする", - "FlexibleContactsSort.Unpin": "ピン留めを外す" + "FlexibleContactSorting.Pin": "ピン留めする", + "FlexibleContactSorting.Unpin": "ピン留めを外す" } } \ No newline at end of file diff --git a/FlexibleContactsSort/Locale/ko.json b/FlexibleContactsSort/Locale/ko.json index 4a50848..b70e6ac 100644 --- a/FlexibleContactsSort/Locale/ko.json +++ b/FlexibleContactsSort/Locale/ko.json @@ -2,7 +2,7 @@ "localeCode": "ko", "authors": [ "Sinduy" ], "messages": { - "FlexibleContactsSort.Pin": "연락처 고정", - "FlexibleContactsSort.Unpin": "연락처 고정해제" + "FlexibleContactSorting.Pin": "연락처 고정", + "FlexibleContactSorting.Unpin": "연락처 고정해제" } } \ No newline at end of file diff --git a/FlexibleContactsSort/Locale/nl.json b/FlexibleContactsSort/Locale/nl.json index 4d77f19..51d734f 100644 --- a/FlexibleContactsSort/Locale/nl.json +++ b/FlexibleContactsSort/Locale/nl.json @@ -2,7 +2,7 @@ "localeCode": "nl", "authors": [ "zahndy" ], "messages": { - "FlexibleContactsSort.Pin": "Contact Vastpinnen", - "FlexibleContactsSort.Unpin": "Contact Losmaken" + "FlexibleContactSorting.Pin": "Contact Vastpinnen", + "FlexibleContactSorting.Unpin": "Contact Losmaken" } } \ No newline at end of file diff --git a/FlexibleContactsSort/Locale/pt.json b/FlexibleContactsSort/Locale/pt.json index 04138ad..782861c 100644 --- a/FlexibleContactsSort/Locale/pt.json +++ b/FlexibleContactsSort/Locale/pt.json @@ -2,7 +2,7 @@ "localeCode": "pt", "authors": [ "LucasRo7" ], "messages": { - "FlexibleContactsSort.Pin": "Fixar Contato", - "FlexibleContactsSort.Unpin": "Soltar Contato" + "FlexibleContactSorting.Pin": "Fixar Contato", + "FlexibleContactSorting.Unpin": "Soltar Contato" } } \ No newline at end of file