using QtCNETAPI.Dtos.User; using QtCNETAPI.Services.ApiService; using QtCNETAPI.Services.GatewayService; using QtCNETAPI.Events; using QtCNETAPI.Models; using qtc_net_client_2.Forms; using qtc_net_client_2.Services; using qtc_net_client_2.ClientModel; namespace qtc_net_client_2 { public partial class Main : Form { private IApiService _apiService; private IGatewayService _gatewayService; private Config _config; private AudioService AudioService = new(); private List RoomList = []; private List OnlineUsers = []; private List Contacts = []; private bool FirstMinimize = true; public Main(IApiService apiService, IGatewayService gatewayService, Config config) { _apiService = apiService; _gatewayService = gatewayService; _config = config; InitializeComponent(); } private async void frmMain_Load(object sender, EventArgs e) { // start request notif blink task Thread blinkThread = new Thread(async () => await StartRequestNotifBlankLoop(lblRequestNotif)); blinkThread.Start(); // check if client is already logged in if (_apiService.CurrentUser == null) { // not logged in, load the login form Login frmLogin = new Login(_apiService); var result = frmLogin.ShowDialog(); if (result == DialogResult.OK) await OnSuccessfulLogin(); } } private async void llblSignIn_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { // just reshow the login dialog lol Login frmLogin = new Login(_apiService); var result = frmLogin.ShowDialog(); if (result == DialogResult.OK) await OnSuccessfulLogin(); } private async void lbRooms_DoubleClick(object sender, EventArgs e) { if (lbRooms.SelectedItems.Count > 0) { string? selectedRoom = (string?)lbRooms.SelectedItems[lbRooms.SelectedItems.Count - 1]; if (selectedRoom != null) { if (selectedRoom == "Lobby") { // join lobby if (!_gatewayService.InLobby) await _gatewayService.JoinLobbyAsync(); Chat frmChat = new Chat(_gatewayService, _apiService); frmChat.Show(); return; } // join the room Room? room = RoomList.FirstOrDefault(e => e.Name == selectedRoom); if (room != null) { if (_gatewayService.CurrentRoom != room) await _gatewayService.JoinRoomAsync(room); Chat frmChat = new Chat(_gatewayService, _apiService); frmChat.Show(); } } } } private void pbUserPfp_Click(object sender, EventArgs e) { var args = (MouseEventArgs)e; if (args.Button == MouseButtons.Right) return; Thread t = new Thread(new ThreadStart(async () => { using (OpenFileDialog openFileDialog = new OpenFileDialog()) { openFileDialog.InitialDirectory = Environment.CurrentDirectory; openFileDialog.Title = "Select New Profile Picture"; openFileDialog.Filter = "Image Files (*.png, *.jpg)|*.png;*.jpg"; if (openFileDialog.ShowDialog() == DialogResult.OK) { // upload to server with api endpoint var res = await _apiService.UpdateUserProfilePic(openFileDialog.FileName); if (res.Success) { // update profile pic in ui var pfpRes = await _apiService.GetUserProfilePic(_apiService.CurrentUser.Id); if (pfpRes.Success && pfpRes.Data != null) { using (var ms = new MemoryStream(pfpRes.Data)) { pbUserPfp.Image = Image.FromStream(ms); ms.Dispose(); } } } } } })); t.SetApartmentState(ApartmentState.STA); t.Start(); } private void llblSignOut_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { if (MessageBox.Show("Are You Sure You Want To Sign Out?\nThis Deletes Your session.token, Requiring You To Sign In Again", "are you sure...?", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes) { File.Delete("./session.token"); Environment.Exit(0); } } private async void lbOnlineUsers_DoubleClick(object sender, EventArgs e) { if (lbOnlineUsers.SelectedItems.Count > 0) { string? selectedUser = (string?)lbOnlineUsers.SelectedItems[lbOnlineUsers.SelectedItems.Count - 1]; if (selectedUser != null) { // get user info and open profile dialog var user = OnlineUsers.FirstOrDefault(e => e.Username == selectedUser); var res = await _apiService.GetUserInformationAsync(user!.Id); var pfpRes = await _apiService.GetUserProfilePic(user!.Id); if (res.Data != null && res.Success) { Profile frmProfile = new Profile(res.Data, pfpRes, _apiService, _gatewayService); frmProfile.ShowDialog(); } } } } private void llblEditProfile_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { ProfileEdit frmProfileEdit = new ProfileEdit(_apiService); var dialogResult = frmProfileEdit.ShowDialog(); if (dialogResult == DialogResult.OK) { MessageBox.Show("If you updated your username, hit the refresh button to see it update on your lists.\nThe top username will not update until you restart your client."); } } private async void lvContacts_DoubleClick(object sender, EventArgs e) { if (lvContacts.SelectedItems.Count > 0) { string? selectedUser = (string?)lvContacts.SelectedItems[lvContacts.SelectedItems.Count - 1].Text; if (selectedUser != null) { // split from [ if it exists if (selectedUser.Contains('[')) selectedUser = selectedUser.Split('[', options: StringSplitOptions.TrimEntries)[0]; // get user info and open profile dialog var user = Contacts.FirstOrDefault(e => e.Username == selectedUser); var res = await _apiService.GetUserInformationAsync(user!.Id); var pfpRes = await _apiService.GetUserProfilePic(user!.Id); if (res.Data != null && res.Success) { Profile frmProfile = new Profile(res.Data, pfpRes, _apiService, _gatewayService); frmProfile.ShowDialog(); } } } } private async void btnRefresh_Click(object sender, EventArgs e) { // refresh all await RefreshContactsList(); await RefreshRoomsList(); await RefreshOnlineUsersList(); } private void frmMain_Resize(object sender, EventArgs e) { if (WindowState == FormWindowState.Minimized && _config.MinimizeToTray) { Hide(); niMain.Visible = true; if (FirstMinimize) { niMain.ShowBalloonTip(10, "I'm over here!", "QtC.NET Mimimizes To Tray By Default. To Change This Behaviour, Refer To config.json", ToolTipIcon.Info); FirstMinimize = false; } } } private void niMain_DoubleClick(object sender, EventArgs e) { Show(); WindowState = FormWindowState.Normal; niMain.Visible = false; } private async void frmMain_FormClosed(object sender, FormClosedEventArgs e) { DialogResult = DialogResult.OK; // ensure the gateway stops the connection and disposes properly await _gatewayService.StopAsync(); await _gatewayService.DisposeAsync(); } private async void ctxmChangeStatus_ItemClicked(object sender, ToolStripItemClickedEventArgs e) { if (e.ClickedItem != null) { UserStatus userStatus = UserStatus.Unknown; switch (e.ClickedItem.Name) { case "onlineToolStripMenuItem": userStatus = UserStatus.Online; onlineToolStripMenuItem.Checked = true; awayToolStripMenuItem.Checked = false; doNotDisturbToolStripMenuItem.Checked = false; invisibleToolStripMenuItem.Checked = false; break; case "awayToolStripMenuItem": userStatus = UserStatus.Away; onlineToolStripMenuItem.Checked = false; awayToolStripMenuItem.Checked = true; doNotDisturbToolStripMenuItem.Checked = false; invisibleToolStripMenuItem.Checked = false; break; case "doNotDisturbToolStripMenuItem": userStatus = UserStatus.DoNotDisturb; onlineToolStripMenuItem.Checked = false; awayToolStripMenuItem.Checked = false; doNotDisturbToolStripMenuItem.Checked = true; invisibleToolStripMenuItem.Checked = false; break; case "invisibleToolStripMenuItem": userStatus = UserStatus.Offline; onlineToolStripMenuItem.Checked = false; awayToolStripMenuItem.Checked = false; doNotDisturbToolStripMenuItem.Checked = false; invisibleToolStripMenuItem.Checked = true; break; } // set it await _gatewayService.UpdateStatus((int)userStatus); } } private void btnAddRoom_Click(object sender, EventArgs e) { CreateRoom createRoom = new CreateRoom(_apiService); createRoom.ShowDialog(); } private async void llblClaimSpin_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { TokenJackpotSpinner tokenJackpotSpinner = new TokenJackpotSpinner(); var frmResult = tokenJackpotSpinner.ShowDialog(); if (frmResult == DialogResult.OK && tokenJackpotSpinner.TokensWon > 0) { // claim var result = await _apiService.AddCurrencyToCurrentUser(tokenJackpotSpinner.TokensWon, true); if (result.Success) { lblCurrencyAmount.Text = (_apiService.CurrentUser.CurrencyAmount + tokenJackpotSpinner.TokensWon).ToString(); llblClaimSpin.Visible = false; } else MessageBox.Show("We Were Unable To Claim Your Prize At This Time. Please Try Again Later.", "Uh Oh.", MessageBoxButtons.OK, MessageBoxIcon.Error); } } private async Task OnSuccessfulLogin() { // double check if (_apiService.CurrentUser != null && _apiService.SessionToken != null) { // start gateway connection await _gatewayService.StartAsync(); // subscribe to gateway events _gatewayService.OnServerReconnecting += _gatewayService_OnServerReconnecting; _gatewayService.OnServerReconnected += _gatewayService_OnServerReconnected; _gatewayService.OnServerDisconnect += _gatewayService_OnServerDisconnect; _gatewayService.OnClientFunctionReceived += _gatewayService_OnClientFunctionReceived; _gatewayService.OnDirectMessageReceived += _gatewayService_OnDirectMessageReceived; _gatewayService.OnServerConfigReceived += _gatewayService_OnServerConfigReceived; if (_gatewayService.HubConnection != null && _gatewayService.HubConnection.State == Microsoft.AspNetCore.SignalR.Client.HubConnectionState.Connected) { // we are fully logged in, get current user profile pic and set up ui llblSignIn.Visible = false; lblWelcome.Text = $"Welcome, {_apiService.CurrentUser.Username}"; lblCurrencyAmount.Visible = true; pbCurrencyIcon.Visible = true; lblCurrencyAmount.Text = _apiService.CurrentUser.CurrencyAmount.ToString(); llblSignOut.Visible = true; llblEditProfile.Visible = true; tbcMain.Enabled = true; var pfpRes = await _apiService.GetUserProfilePic(_apiService.CurrentUser.Id); if (pfpRes.Success && pfpRes.Data != null) { using (var ms = new MemoryStream(pfpRes.Data)) { pbUserPfp.Image = Image.FromStream(ms); ms.Dispose(); } } await RefreshContactsList(); await RefreshRoomsList(); await RefreshOnlineUsersList(); // TODO - figure out server side why online status is invisible on login _apiService.CurrentUser.Status = 1; // set status context menu checked UserStatus cuStatus = (UserStatus)_apiService.CurrentUser.Status; switch (cuStatus) { case UserStatus.Online: onlineToolStripMenuItem.Checked = true; awayToolStripMenuItem.Checked = false; doNotDisturbToolStripMenuItem.Checked = false; invisibleToolStripMenuItem.Checked = false; break; case UserStatus.Away: onlineToolStripMenuItem.Checked = false; awayToolStripMenuItem.Checked = true; doNotDisturbToolStripMenuItem.Checked = false; invisibleToolStripMenuItem.Checked = false; break; case UserStatus.DoNotDisturb: onlineToolStripMenuItem.Checked = false; awayToolStripMenuItem.Checked = false; doNotDisturbToolStripMenuItem.Checked = true; invisibleToolStripMenuItem.Checked = false; break; case UserStatus.Offline: onlineToolStripMenuItem.Checked = false; awayToolStripMenuItem.Checked = false; doNotDisturbToolStripMenuItem.Checked = false; invisibleToolStripMenuItem.Checked = true; break; } if (_apiService.CurrentUser.Role == "Admin") btnAddRoom.Enabled = true; else btnAddRoom.Enabled = false; var current = DateTime.UtcNow; if (current > _apiService.CurrentUser.LastCurrencySpin.ToUniversalTime() || _apiService.CurrentUser.LastCurrencySpin == new DateTime()) llblClaimSpin.Visible = true; else llblClaimSpin.Visible = false; if(_config.StartMinimized) WindowState = FormWindowState.Minimized; } } } private async Task RefreshOnlineUsersList() { if (InvokeRequired) { await Invoke(async delegate () { lbOnlineUsers.Items.Clear(); var usersOnlineRes = await _apiService.GetOnlineUsersAsync(); if (usersOnlineRes.Success && usersOnlineRes.Data != null) { foreach (var user in usersOnlineRes.Data) { lbOnlineUsers.Items.Add(user.Username); } OnlineUsers = usersOnlineRes.Data; } }); return; } lbOnlineUsers.Items.Clear(); var usersOnlineRes = await _apiService.GetOnlineUsersAsync(); if (usersOnlineRes.Success && usersOnlineRes.Data != null) { lbOnlineUsers.Items.Clear(); foreach (var user in usersOnlineRes.Data) { lbOnlineUsers.Items.Add(user.Username); } OnlineUsers = usersOnlineRes.Data; } } private async Task RefreshRoomsList() { if (InvokeRequired) { await Invoke(async delegate () { lbRooms.Items.Clear(); var roomsRes = await _apiService.GetAllRoomsAsync(); if (roomsRes.Success && roomsRes.Data != null) { foreach (var room in roomsRes.Data) { lbRooms.Items.Add(room.Name); } RoomList = roomsRes.Data; } // always add lobby room to rooms list lbRooms.Items.Add("Lobby"); }); return; } lbRooms.Items.Clear(); var roomsRes = await _apiService.GetAllRoomsAsync(); if (roomsRes.Success && roomsRes.Data != null) { lbRooms.Items.Clear(); foreach (var room in roomsRes.Data) { lbRooms.Items.Add(room.Name); } RoomList = roomsRes.Data; } // always add lobby room to rooms list lbRooms.Items.Add("Lobby"); } private async Task RefreshContactsList() { if (InvokeRequired) { await Invoke(async delegate () { lvContacts.Items.Clear(); Contacts.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; 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); if (user.Data != null) { Contacts.Add(user.Data); if (contact.OwnerId == _apiService.CurrentUser!.Id) { switch (contact.OwnerStatus) { case Contact.ContactStatus.AwaitingApprovalFromOther: var lvi = lvContacts.Items.Add($"{user.Data.Username} [Request Sent]"); await AddProfilePicToList(user.Data.Id); lvi.ImageKey = user.Data.Id; break; case Contact.ContactStatus.Accepted: var lvi2 = lvContacts.Items.Add($"{user.Data.Username}"); await AddProfilePicToList(user.Data.Id); lvi2.ImageKey = user.Data.Id; break; } } else if (contact.UserId == _apiService.CurrentUser!.Id) { switch (contact.UserStatus) { case Contact.ContactStatus.AwaitingApprovalFromSelf: var lvi = lvContacts.Items.Add($"{user.Data.Username} [Contact Request]"); await AddProfilePicToList(user.Data.Id); lvi.ImageKey = user.Data.Id; AudioService.PlaySoundEffect("sndContactRequest"); break; case Contact.ContactStatus.Accepted: var lvi2 = lvContacts.Items.Add($"{user.Data.Username}"); await AddProfilePicToList(user.Data.Id); lvi2.ImageKey = user.Data.Id; break; } } } } } }); return; } lvContacts.Items.Clear(); Contacts.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; 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); if (user.Data != null) { Contacts.Add(user.Data); if (contact.OwnerId == _apiService.CurrentUser!.Id) { switch (contact.OwnerStatus) { case Contact.ContactStatus.AwaitingApprovalFromOther: var lvi = lvContacts.Items.Add($"{user.Data.Username} [Request Sent]"); await AddProfilePicToList(user.Data.Id); lvi.ImageKey = user.Data.Id; break; case Contact.ContactStatus.Accepted: var lvi2 = lvContacts.Items.Add($"{user.Data.Username}"); await AddProfilePicToList(user.Data.Id); lvi2.ImageKey = user.Data.Id; break; } } else if (contact.UserId == _apiService.CurrentUser!.Id) { switch (contact.UserStatus) { case Contact.ContactStatus.AwaitingApprovalFromSelf: var lvi = lvContacts.Items.Add($"{user.Data.Username} [Contact Request]"); await AddProfilePicToList(user.Data.Id); lvi.ImageKey = user.Data.Id; AudioService.PlaySoundEffect("sndContactRequest"); break; case Contact.ContactStatus.Accepted: var lvi2 = lvContacts.Items.Add($"{user.Data.Username}"); await AddProfilePicToList(user.Data.Id); lvi2.ImageKey = user.Data.Id; break; } } } } } } private async Task StartRequestNotifBlankLoop(Label label) { while (true) { if (InvokeRequired && label.IsHandleCreated) { await Invoke(async delegate () { label.ForeColor = Color.Red; await Task.Delay(500); label.ForeColor = Color.Blue; await Task.Delay(500); }); } else if (label.IsHandleCreated) { label.ForeColor = Color.Red; await Task.Delay(500); label.ForeColor = Color.Blue; await Task.Delay(500); } } } private async Task AddProfilePicToList(string userId) { var result = await _apiService.GetUserProfilePic(userId); if (result != null && result.Success && result.Data != null) { using (var ms = new MemoryStream(result.Data)) { ilProfilePics.Images.Add(userId, Image.FromStream(ms)); ms.Dispose(); } } } private async void _gatewayService_OnServerDisconnect(object? sender, EventArgs e) { if (DialogResult == DialogResult.OK) return; var args = (ServerConnectionClosedEventArgs)e; string? error = string.Empty; if (args.Error != null) error = args.Error.Message; // disconnect can sometimes be caused by an expired JWT token, attempt connection restart await _gatewayService.StopAsync(); await _gatewayService.StartAsync(); if (_gatewayService.HubConnection != null && _gatewayService.HubConnection.State == Microsoft.AspNetCore.SignalR.Client.HubConnectionState.Connected) { BeginInvoke(delegate () { Enabled = true; }); return; } ConnectionClosed frmConnectionClosed = new ConnectionClosed(error); var result = frmConnectionClosed.ShowDialog(); if (result == DialogResult.OK) { // tell the gateway service to attempt reconnection Reconnect: if (_gatewayService.HubConnection != null) { try { await _gatewayService.StopAsync(); await _gatewayService.StartAsync(); if (_gatewayService.HubConnection.State == Microsoft.AspNetCore.SignalR.Client.HubConnectionState.Connected) BeginInvoke(delegate () { Enabled = true; }); } catch (HttpRequestException ex) { // reshow the dialog frmConnectionClosed.Reason = ex.Message; var result1 = frmConnectionClosed.ShowDialog(); if (result1 == DialogResult.OK) goto Reconnect; else Environment.Exit(0); } } } else Environment.Exit(0); } private void _gatewayService_OnServerReconnecting(object? sender, EventArgs e) => BeginInvoke(delegate () { Enabled = false; }); private void _gatewayService_OnServerReconnected(object? sender, EventArgs e) => BeginInvoke(delegate () { Enabled = true; }); private async void _gatewayService_OnClientFunctionReceived(object? sender, EventArgs e) { var args = (ClientFunctionEventArgs)e; switch (args.Function) { case "rul": await RefreshOnlineUsersList(); break; case "rr": await RefreshRoomsList(); break; case "rcl": await RefreshContactsList(); break; } } private async void _gatewayService_OnServerConfigReceived(object? sender, EventArgs e) { var args = (ServerConfigEventArgs)e; if (args.ServerConfig != null) { if (args.ServerConfig.IsDown) { MessageBox.Show($"Sorry, This Server Is Currently Down.\nMessage: {args.ServerConfig.IsDownMessage}\n\nPlease Try Again Later"); if (_gatewayService.HubConnection != null && _gatewayService.HubConnection.State == Microsoft.AspNetCore.SignalR.Client.HubConnectionState.Connected) { await _gatewayService.StopAsync(); await _gatewayService.DisposeAsync(); } Environment.Exit(0); } BeginInvoke(delegate () { Text = $"QtC.NET Client = Connected To {args.ServerConfig.Name}"; }); } } private void _gatewayService_OnDirectMessageReceived(object? sender, EventArgs e) { var args = (DirectMessageEventArgs)e; DirectMessage? existingForm = (DirectMessage?)Application.OpenForms.Cast
().FirstOrDefault(e => e.Name == "DirectMessage"); if (existingForm != null && existingForm.User.Id == args.User.Id) { // we want to just add to its text box existingForm.Messages.Add($"[{args.User.Username}] {args.Message.Content}\n"); } else { // start a new form DirectMessage frmDirectMessage = new DirectMessage(_gatewayService, _apiService, args.User, args.Message); Task.Run(frmDirectMessage.ShowDialog); } } } }