diff --git a/qtc-net-client-2/Forms/Main.Designer.cs b/qtc-net-client-2/Forms/Main.Designer.cs index 86ce0c7..8c48b82 100644 --- a/qtc-net-client-2/Forms/Main.Designer.cs +++ b/qtc-net-client-2/Forms/Main.Designer.cs @@ -113,7 +113,6 @@ tbcMain.SelectedIndex = 0; tbcMain.Size = new Size(352, 499); tbcMain.TabIndex = 0; - tbcMain.SelectedIndexChanged += tbcMain_SelectedIndexChanged; // // tbpContacts // diff --git a/qtc-net-client-2/Forms/Main.cs b/qtc-net-client-2/Forms/Main.cs index d68051b..071e6ef 100644 --- a/qtc-net-client-2/Forms/Main.cs +++ b/qtc-net-client-2/Forms/Main.cs @@ -1,7 +1,9 @@ +using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.VisualBasic.ApplicationServices; using qtc_net_client_2.ClientModel; using qtc_net_client_2.Controls; using qtc_net_client_2.Forms; +using qtc_net_client_2.Properties; using qtc_net_client_2.Services; using QtCNETAPI.Dtos.User; using QtCNETAPI.Events; @@ -20,7 +22,7 @@ namespace qtc_net_client_2 private IGatewayService _gatewayService; private Config _config; - private ServerConfig _serverConfig; + private ServerConfig? _serverConfig; private AudioService AudioService = new(); private ImageFactory _imgFactory = new(); private CredentialService _credService = new(); @@ -365,27 +367,67 @@ namespace qtc_net_client_2 donationWindow.Show(); } - private async void tbcMain_SelectedIndexChanged(object sender, EventArgs e) + private async Task RefreshStore() { - if (tbcMain.SelectedIndex == 4) + // get store items + var storeItems = await _apiService.GetStoreItems(); + if (storeItems != null && storeItems.Success && storeItems.Data != null) { - // get store items - var storeItems = await _apiService.GetStoreItems(); - if (storeItems != null && storeItems.Success && storeItems.Data != null) - { - if (lvStoreItems.Items.Count == storeItems.Data.Count) return; + if (lvStoreItems.Items.Count == storeItems.Data.Count) return; - ilStoreThumbnails.Images.Clear(); - foreach (var item in storeItems.Data) - { - await GetAndAddStoreThumbnail(item); - var lvitem = lvStoreItems.Items.Add(new ListViewItem { Text = item.Name, Name = item.Id.ToString() }); - lvitem.ImageKey = item.Id.ToString(); - } + if (!IsHandleCreated || IsDisposed) + return; + + if (InvokeRequired) + { + Invoke(() => ApplyStoreItems(storeItems.Data)); + } + else + { + ApplyStoreItems(storeItems.Data); } } } + private async void ApplyStoreItems(IEnumerable storeItems) + { + ilStoreThumbnails.Images.Clear(); + lvStoreItems.Items.Clear(); + + var items = await BuildListViewItemsAsync(storeItems); + + lvStoreItems.BeginUpdate(); + try + { + lvStoreItems.Items.AddRange([.. items]); + } + finally + { + lvStoreItems.EndUpdate(); + } + } + + private async Task> BuildListViewItemsAsync(IEnumerable storeItems) + { + var items = new List(); + + foreach (var item in storeItems) + { + await GetAndAddStoreThumbnail(item); + + var lvItem = new ListViewItem + { + Text = item.Name, + Name = item.Id.ToString(), + ImageKey = item.Id.ToString() + }; + + items.Add(lvItem); + } + + return items; + } + private async void lvStoreItems_DoubleClick(object sender, EventArgs e) { if (lvStoreItems.SelectedItems.Count > 0) @@ -621,6 +663,8 @@ namespace qtc_net_client_2 llblEditProfile.Visible = true; tbcMain.Enabled = true; + await RefreshStore(); + var pfpRes = await _apiService.GetUserProfilePic(_apiService.CurrentUser.Id); var cosmeticRes = await GetCosmeticImage(_apiService.CurrentUser.ActiveProfileCosmetic); @@ -647,12 +691,6 @@ namespace qtc_net_client_2 pbUserPfp.Image = _imgFactory.CreateProfileImage(null, null, cosmetic); } - if (lvUserDirectory.Items.Count <= 0) - await RefreshUsers(); // prevent edge case where the refresh event never gets sent to connecting client - - await RefreshContactsList(); - await RefreshRoomsList(); - // set status context menu checked // TODO - figure out more efficient way to do this UserStatus cuStatus = (UserStatus)_apiService.CurrentUser.Status; @@ -733,153 +771,200 @@ namespace qtc_net_client_2 } } + private readonly SemaphoreSlim _roomsRefreshLock = new(1, 1); private async Task RefreshRoomsList() { LoggingService.LogString("Refreshing Rooms List..."); if (IsHandleCreated && !IsDisposed) { - await Invoke(async delegate () + if (!await _roomsRefreshLock.WaitAsync(0)) + return; + + try { - flpRooms.Controls.Clear(); - - // always add lobby room to rooms list - var lobbyCtrl = new RoomEntryControl - { - RoomName = "Lobby", - HideUserCount = true, - BackColor = flpRooms.BackColor - }; - - lobbyCtrl.Margin = new Padding(0, 4, 0, 4); - lobbyCtrl.Width = flpRooms.ClientSize.Width - flpRooms.Padding.Horizontal; - lobbyCtrl.OnRoomDoubleClicked += OnRoomDoubleClicked; - - flpRooms.Controls.Add(lobbyCtrl); - var roomsRes = await _apiService.GetAllRoomsAsync(); - if (roomsRes.Success && roomsRes.Data != null) + + if (!roomsRes.Success || roomsRes.Data == null) + return; + + roomsRes.Data.Add(new Room { - foreach (var room in roomsRes.Data) - { - // create room entry - var ctrl = new RoomEntryControl - { - RoomName = room.Name, - RoomUserCount = room.UserCount, // placeholder - BackColor = flpRooms.BackColor - }; + Id = "LOBBY", + Name = "Lobby" + }); - ctrl.Margin = new Padding(0, 4, 0, 4); - ctrl.Width = flpRooms.ClientSize.Width - flpRooms.Padding.Horizontal; - ctrl.OnRoomDoubleClicked += OnRoomDoubleClicked; + var uniqueRooms = roomsRes.Data + .GroupBy(r => r.Id) + .Select(g => g.First()) + .ToList(); - flpRooms.Controls.Add(ctrl); - } - RoomList = roomsRes.Data; - if (System.Diagnostics.Debugger.IsAttached || _config.EnableDebugLogs) - LoggingService.LogModel(roomsRes.Data); + if (InvokeRequired) + { + Invoke(() => ApplyRooms(uniqueRooms)); } - }); + else + { + ApplyRooms(uniqueRooms); + } + } + finally + { + _roomsRefreshLock.Release(); + } } } + private void ApplyRooms(List newRooms) + { + RoomList.Clear(); + RoomList.AddRange(newRooms.DistinctBy(r => r.Id)); + + var roomsSnap = RoomList.ToList(); + + flpRooms.SuspendLayout(); + flpRooms.Controls.Clear(); + + foreach (var room in roomsSnap) + { + var ctrl = new RoomEntryControl + { + RoomName = room.Name, + RoomUserCount = room.UserCount, + }; + + if(room.Id == "LOBBY") + ctrl.HideUserCount = true; // TODO - probably make this work with the lobby room (might need backend work) + + ctrl.Margin = new Padding(0, 4, 0, 4); + ctrl.Width = flpRooms.ClientSize.Width - flpRooms.Padding.Horizontal; + ctrl.OnRoomDoubleClicked += OnRoomDoubleClicked; + + flpRooms.Controls.Add(ctrl); + } + + flpRooms.ResumeLayout(true); + } + + private readonly SemaphoreSlim _contactsRefreshLock = new(1, 1); private async Task RefreshContactsList() { - LoggingService.LogString("Refreshing Contacts List..."); + if (!await _contactsRefreshLock.WaitAsync(0)) + return; // already refreshing, skip - if (IsHandleCreated && !IsDisposed) + try { - await Invoke(async delegate () - { - Contacts.Clear(); - flpContacts.Controls.Clear(); - lblRequestNotif.Visible = false; - var contactsRes = await _apiService.GetCurrentUserContacts(); - if (contactsRes.Success && contactsRes.Data != null) - { - if (contactsRes.Data.Where(e => e.UserId == _apiService.CurrentUser!.Id && e.UserStatus == Contact.ContactStatus.AwaitingApprovalFromSelf).Count() >= 1) - lblRequestNotif.Visible = true; - else - lblRequestNotif.Visible = false; + var contactsRes = await _apiService.GetCurrentUserContacts(); + if (!contactsRes.Success || contactsRes.Data == null) + return; - foreach (var contact in contactsRes.Data) - { - ServiceResponse user = null!; - if (contact.OwnerId == _apiService.CurrentUser!.Id) - user = await _apiService.GetUserInformationAsync(contact.UserId); - else if (contact.UserId == _apiService.CurrentUser!.Id) - user = await _apiService.GetUserInformationAsync(contact.OwnerId); - - var ctrl = new ContactEntryControl(); - - if (user.Data != null) - { - Contacts.Add(contact); - - switch(user.Data.Status) - { - case 0: - ctrl.StatusColor = Color.Gray; - break; - case 1: - ctrl.StatusColor = Color.LightGreen; - break; - case 2: - ctrl.StatusColor = Color.Gold; - break; - case 3: - ctrl.StatusColor = Color.Red; - break; - } - - if (contact.OwnerId == _apiService.CurrentUser!.Id) - { - switch (contact.OwnerStatus) - { - case Contact.ContactStatus.AwaitingApprovalFromOther: - ctrl.Username = $"{user.Data.Username} [Request Sent]"; - await AddProfilePicToList(user.Data.Id); - ctrl.Avatar = ilProfilePics.Images[user.Data.Id] ?? ilProfilePics.Images[0]; - break; - case Contact.ContactStatus.Accepted: - ctrl.Username = user.Data.Username; - await AddProfilePicToList(user.Data.Id); - ctrl.Avatar = ilProfilePics.Images[user.Data.Id] ?? ilProfilePics.Images[0]; - break; - } - } - else if (contact.UserId == _apiService.CurrentUser!.Id) - { - switch (contact.UserStatus) - { - case Contact.ContactStatus.AwaitingApprovalFromSelf: - ctrl.Username = $"{user.Data.Username} [Contact Request]"; - await AddProfilePicToList(user.Data.Id); - ctrl.Avatar = ilProfilePics.Images[user.Data.Id] ?? ilProfilePics.Images[0]; - AudioService.PlaySoundEffect("sndContactRequest"); - break; - case Contact.ContactStatus.Accepted: - ctrl.Username = user.Data.Username; - await AddProfilePicToList(user.Data.Id); - ctrl.Avatar = ilProfilePics.Images[user.Data.Id] ?? ilProfilePics.Images[0]; - break; - } - } - - // add the control to the flow panel - ctrl.Margin = new Padding(0, 4, 0, 4); - ctrl.Width = flpContacts.ClientSize.Width - flpContacts.Padding.Horizontal; - ctrl.ContactDoubleClicked += Ctrl_ContactDoubleClicked; - flpContacts.Controls.Add(ctrl); - } - } - - if (System.Diagnostics.Debugger.IsAttached || _config.EnableDebugLogs) - LoggingService.LogModel(contactsRes.Data); - } - }); + if (InvokeRequired) + Invoke(() => ApplyContacts(contactsRes.Data)); + else + ApplyContacts(contactsRes.Data); } + finally + { + _contactsRefreshLock.Release(); + } + } + + private async void ApplyContacts(List newContacts) + { + lblRequestNotif.Visible = + newContacts.Any(e => + e.UserId == _apiService.CurrentUser!.Id && + e.UserStatus == Contact.ContactStatus.AwaitingApprovalFromSelf); + + Contacts.Clear(); + Contacts.AddRange(newContacts.DistinctBy(c => c.Id)); + + flpContacts.SuspendLayout(); + flpContacts.Controls.Clear(); + + var contactsSnap = Contacts.ToList(); + + foreach (var contact in contactsSnap) + { + var ctrl = await BuildContactControl(contact); + if (ctrl != null) + flpContacts.Controls.Add(ctrl); + } + + flpContacts.ResumeLayout(true); + } + + private async Task BuildContactControl(Contact contact) + { + ServiceResponse user = null!; + if (contact.OwnerId == _apiService.CurrentUser!.Id) + user = await _apiService.GetUserInformationAsync(contact.UserId); + else if (contact.UserId == _apiService.CurrentUser!.Id) + user = await _apiService.GetUserInformationAsync(contact.OwnerId); + + var ctrl = new ContactEntryControl(); + + if (user.Data != null) + { + switch (user.Data.Status) + { + case 0: + ctrl.StatusColor = Color.Gray; + break; + case 1: + ctrl.StatusColor = Color.LightGreen; + break; + case 2: + ctrl.StatusColor = Color.Gold; + break; + case 3: + ctrl.StatusColor = Color.Red; + break; + } + + if (contact.OwnerId == _apiService.CurrentUser!.Id) + { + switch (contact.OwnerStatus) + { + case Contact.ContactStatus.AwaitingApprovalFromOther: + ctrl.Username = $"{user.Data.Username} [Request Sent]"; + await AddProfilePicToList(user.Data.Id); + ctrl.Avatar = ilProfilePics.Images[user.Data.Id] ?? ilProfilePics.Images[0]; + break; + case Contact.ContactStatus.Accepted: + ctrl.Username = user.Data.Username; + await AddProfilePicToList(user.Data.Id); + ctrl.Avatar = ilProfilePics.Images[user.Data.Id] ?? ilProfilePics.Images[0]; + break; + } + } + else if (contact.UserId == _apiService.CurrentUser!.Id) + { + switch (contact.UserStatus) + { + case Contact.ContactStatus.AwaitingApprovalFromSelf: + ctrl.Username = $"{user.Data.Username} [Contact Request]"; + await AddProfilePicToList(user.Data.Id); + ctrl.Avatar = ilProfilePics.Images[user.Data.Id] ?? ilProfilePics.Images[0]; + AudioService.PlaySoundEffect("sndContactRequest"); + break; + case Contact.ContactStatus.Accepted: + ctrl.Username = user.Data.Username; + await AddProfilePicToList(user.Data.Id); + ctrl.Avatar = ilProfilePics.Images[user.Data.Id] ?? ilProfilePics.Images[0]; + break; + } + } + + // add the control to the flow panel + ctrl.Margin = new Padding(0, 4, 0, 4); + ctrl.Width = flpContacts.ClientSize.Width - flpContacts.Padding.Horizontal; + ctrl.ContactDoubleClicked += Ctrl_ContactDoubleClicked; + + // return the control + return ctrl; + } + else return default; } private async void Ctrl_ContactDoubleClicked(object? sender, EventArgs e) diff --git a/qtc-net-client-2/Properties/Resources.Designer.cs b/qtc-net-client-2/Properties/Resources.Designer.cs index a21c108..6b5a14f 100644 --- a/qtc-net-client-2/Properties/Resources.Designer.cs +++ b/qtc-net-client-2/Properties/Resources.Designer.cs @@ -81,7 +81,7 @@ namespace qtc_net_client_2.Properties { } /// - /// Looks up a localized string similar to 6.5.5. + /// Looks up a localized string similar to 6.5.6. /// internal static string AssemblyVersion { get { diff --git a/qtc-net-client-2/Properties/Resources.resx b/qtc-net-client-2/Properties/Resources.resx index 895a7b8..3c4b1bd 100644 --- a/qtc-net-client-2/Properties/Resources.resx +++ b/qtc-net-client-2/Properties/Resources.resx @@ -173,7 +173,7 @@ ..\Icons\MessageIcon.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - 6.5.5 + 6.5.6 ..\Resources\cobalt_sittingatputer.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a diff --git a/qtc-net-client-2/Services/UpdateService.cs b/qtc-net-client-2/Services/UpdateService.cs index 04a023b..6081fe0 100644 --- a/qtc-net-client-2/Services/UpdateService.cs +++ b/qtc-net-client-2/Services/UpdateService.cs @@ -19,15 +19,24 @@ namespace qtc_net_client_2.Services if (Debugger.IsAttached) return; // get client update info - HttpClient client = new(); - client.BaseAddress = new Uri("https://qtcclient.alanmoon.net"); - + HttpClient client = new() + { + BaseAddress = new Uri("https://qtcclient.alanmoon.net") + }; + try { ClientUpdateInfo? updateInfo = await client.GetFromJsonAsync("clientinfo.json"); if (updateInfo != null) { - if (updateInfo.Version != Resources.AssemblyVersion) + var serverVersion = Version.Parse(updateInfo.Version); + var clientVersion = Version.Parse(Resources.AssemblyVersion); + + int relativeComparison = clientVersion.CompareTo(serverVersion); + + if (relativeComparison > 0) return; // probably a version being developed or tested without a debugger, so don't do anything + + if (relativeComparison < 0) { // inform the user an update is available if (!updateInfo.IsUpdateMandatory)