731 lines
32 KiB
C#

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<Room> RoomList = [];
private List<UserInformationDto> OnlineUsers = [];
private List<UserInformationDto> Contacts = [];
private bool FirstMinimize = false;
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<UserInformationDto> 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<UserInformationDto> 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<Form>().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);
}
}
}
}