From 3677f145e0bd20dc8e12e7c52b350af0059f5f7b Mon Sep 17 00:00:00 2001 From: Moonbase Date: Sat, 13 Dec 2025 14:33:34 -0800 Subject: [PATCH] Reimplement Direct Messages --- .../Forms/DirectMessageForm.Designer.cs | 127 +++++++++++++++++ qtcnet-client/Forms/DirectMessageForm.cs | 130 ++++++++++++++++++ qtcnet-client/Forms/DirectMessageForm.resx | 120 ++++++++++++++++ qtcnet-client/Forms/MainForm.cs | 102 +++++++++++++- qtcnet-client/Forms/ProfileForm.cs | 4 +- 5 files changed, 479 insertions(+), 4 deletions(-) create mode 100644 qtcnet-client/Forms/DirectMessageForm.Designer.cs create mode 100644 qtcnet-client/Forms/DirectMessageForm.cs create mode 100644 qtcnet-client/Forms/DirectMessageForm.resx diff --git a/qtcnet-client/Forms/DirectMessageForm.Designer.cs b/qtcnet-client/Forms/DirectMessageForm.Designer.cs new file mode 100644 index 0000000..9ec07eb --- /dev/null +++ b/qtcnet-client/Forms/DirectMessageForm.Designer.cs @@ -0,0 +1,127 @@ +namespace qtcnet_client.Forms +{ + partial class DirectMessageForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + flpMessages = new FlowLayoutPanel(); + btnSend = new Button(); + rtxtChatbox = new RichTextBox(); + pbProfileImage = new PictureBox(); + lblUsername = new Label(); + ((System.ComponentModel.ISupportInitialize)pbProfileImage).BeginInit(); + SuspendLayout(); + // + // flpMessages + // + flpMessages.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; + flpMessages.AutoScroll = true; + flpMessages.BackColor = Color.White; + flpMessages.FlowDirection = FlowDirection.TopDown; + flpMessages.Location = new Point(12, 68); + flpMessages.Name = "flpMessages"; + flpMessages.Size = new Size(589, 243); + flpMessages.TabIndex = 10; + flpMessages.WrapContents = false; + // + // btnSend + // + btnSend.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; + btnSend.BackgroundImage = Properties.Resources.SendIcon; + btnSend.BackgroundImageLayout = ImageLayout.Zoom; + btnSend.FlatAppearance.BorderSize = 0; + btnSend.FlatStyle = FlatStyle.Flat; + btnSend.Location = new Point(526, 328); + btnSend.Name = "btnSend"; + btnSend.Size = new Size(75, 44); + btnSend.TabIndex = 9; + btnSend.UseVisualStyleBackColor = true; + btnSend.Click += btnSend_Click; + // + // rtxtChatbox + // + rtxtChatbox.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; + rtxtChatbox.Location = new Point(12, 317); + rtxtChatbox.Name = "rtxtChatbox"; + rtxtChatbox.Size = new Size(508, 66); + rtxtChatbox.TabIndex = 8; + rtxtChatbox.Text = ""; + rtxtChatbox.KeyDown += rtxtChatbox_KeyDown; + // + // pbProfileImage + // + pbProfileImage.Image = Properties.Resources.DefaultPfp; + pbProfileImage.Location = new Point(12, 12); + pbProfileImage.Name = "pbProfileImage"; + pbProfileImage.Size = new Size(52, 50); + pbProfileImage.SizeMode = PictureBoxSizeMode.Zoom; + pbProfileImage.TabIndex = 11; + pbProfileImage.TabStop = false; + // + // lblUsername + // + lblUsername.AutoSize = true; + lblUsername.Font = new Font("Segoe UI", 20F, FontStyle.Bold | FontStyle.Italic); + lblUsername.ForeColor = Color.White; + lblUsername.Location = new Point(64, 17); + lblUsername.Name = "lblUsername"; + lblUsername.Size = new Size(145, 37); + lblUsername.TabIndex = 12; + lblUsername.Text = "Username"; + // + // DirectMessageForm + // + AutoScaleDimensions = new SizeF(7F, 15F); + AutoScaleMode = AutoScaleMode.Font; + BackColor = Color.DodgerBlue; + ClientSize = new Size(612, 395); + Controls.Add(lblUsername); + Controls.Add(pbProfileImage); + Controls.Add(flpMessages); + Controls.Add(btnSend); + Controls.Add(rtxtChatbox); + FormBorderStyle = FormBorderStyle.FixedSingle; + MaximizeBox = false; + Name = "DirectMessageForm"; + StartPosition = FormStartPosition.CenterScreen; + Text = "Direct Message"; + FormClosed += DirectMessageForm_FormClosed; + Load += DirectMessageForm_Load; + ((System.ComponentModel.ISupportInitialize)pbProfileImage).EndInit(); + ResumeLayout(false); + PerformLayout(); + } + + #endregion + + private FlowLayoutPanel flpMessages; + private Button btnSend; + private RichTextBox rtxtChatbox; + private PictureBox pbProfileImage; + private Label lblUsername; + } +} \ No newline at end of file diff --git a/qtcnet-client/Forms/DirectMessageForm.cs b/qtcnet-client/Forms/DirectMessageForm.cs new file mode 100644 index 0000000..c837930 --- /dev/null +++ b/qtcnet-client/Forms/DirectMessageForm.cs @@ -0,0 +1,130 @@ +using qtcnet_client.Controls; +using qtcnet_client.Factories; +using qtcnet_client.Properties; +using QtCNETAPI.Dtos.User; +using QtCNETAPI.Models; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Text; +using System.Windows.Forms; + +namespace qtcnet_client.Forms +{ + public partial class DirectMessageForm : Form + { + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public string UserId { get; set; } = string.Empty; + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] + public string Username { get; set; } = "Username"; + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] + public Image ProfileImage { get; set; } = Resources.DefaultPfp; + + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public Dictionary UserProfileImages { get; set; } = []; + public string SentMessage { get; private set; } = string.Empty; + + public event EventHandler? OnSend; + public event EventHandler? OnClose; + + private readonly ImageFactory _imgFactory; + public DirectMessageForm(ImageFactory imageFactory) + { + _imgFactory = imageFactory; + InitializeComponent(); + } + + private void DirectMessageForm_Load(object sender, EventArgs e) + { + lblUsername.Text = Username; + pbProfileImage.Image = ProfileImage; + } + + private void btnSend_Click(object sender, EventArgs e) + { + if (ValidateChatBox()) + { + SentMessage = rtxtChatbox.Text; + OnSend?.Invoke(this, EventArgs.Empty); + rtxtChatbox.Clear(); + } + } + + private void rtxtChatbox_KeyDown(object sender, KeyEventArgs e) + { + if (e.KeyCode == Keys.Enter) + { + if (ValidateChatBox()) + { + SentMessage = rtxtChatbox.Text; + OnSend?.Invoke(this, EventArgs.Empty); + rtxtChatbox.Clear(); + } + } + } + + private void DirectMessageForm_FormClosed(object sender, FormClosedEventArgs e) => OnClose?.Invoke(this, EventArgs.Empty); + + public async Task LoadProfileImage(MessageControl ctrl, UserInformationDto user) + { + _ = Task.Run(async () => + { + var img = await AddUserProfileImageToDictionary(user); + + if (img == null || ctrl.IsDisposed) + return; + + if (ctrl.InvokeRequired) + ctrl.Invoke(() => + { + ctrl.ProfileImage = img; + ctrl.ReloadProfileImage(); + }); + else + { + ctrl.ProfileImage = img; + ctrl.ReloadProfileImage(); + } + }); + } + + private async Task AddUserProfileImageToDictionary(UserInformationDto user) + { + if (UserProfileImages.TryGetValue(user.Id, out var image)) + return image; + else + { + var img = _imgFactory.GetAndCreateProfileImage(user.Id, -1, user.ProfileCosmeticId); + UserProfileImages.Add(user.Id, img); + return img; + } + } + + public void AddMessage(MessageControl messageCtrl) + { + messageCtrl.Width = flpMessages.ClientSize.Width - 10; + messageCtrl.Margin = new Padding(0, 0, 0, 6); + + if (flpMessages.InvokeRequired) + { + Invoke(() => + { + flpMessages.Controls.Add(messageCtrl); + flpMessages.ScrollControlIntoView(messageCtrl); + }); + } + else + { + flpMessages.Controls.Add(messageCtrl); + flpMessages.ScrollControlIntoView(messageCtrl); + } + } + + private bool ValidateChatBox() + { + return !string.IsNullOrWhiteSpace(rtxtChatbox.Text) || !string.IsNullOrEmpty(rtxtChatbox.Text); + } + } +} diff --git a/qtcnet-client/Forms/DirectMessageForm.resx b/qtcnet-client/Forms/DirectMessageForm.resx new file mode 100644 index 0000000..8b2ff64 --- /dev/null +++ b/qtcnet-client/Forms/DirectMessageForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/qtcnet-client/Forms/MainForm.cs b/qtcnet-client/Forms/MainForm.cs index 5aed5c1..e077b24 100644 --- a/qtcnet-client/Forms/MainForm.cs +++ b/qtcnet-client/Forms/MainForm.cs @@ -25,6 +25,7 @@ namespace qtcnet_client private MainTabControl? MainTabControl; private readonly List OpenChatRoomForms = []; + private readonly List OpenDirectMessagesForms = []; private readonly List OpenProfileForms = []; private readonly List UserContactsList = []; @@ -340,6 +341,7 @@ namespace qtcnet_client _profile.ProfileImage = _imgFactory.GetAndCreateProfileImage(_user.Data.Id, _user.Data.Status, _user.Data.ProfileCosmeticId); _profile.OnClose += ProfileForm_OnClose; + _profile.OnMessageClicked += ProfileForm_OnMessageClicked; OpenProfileForms.Add(_profile); _profile.Show(); @@ -347,6 +349,25 @@ namespace qtcnet_client } } + private void ProfileForm_OnMessageClicked(object? sender, EventArgs e) + { + if(sender is ProfileForm _profileForm) + { + // create dm form + DirectMessageForm _dmForm = new(_imgFactory) + { + UserId = _profileForm.UserId, + Username = _profileForm.Username, + ProfileImage = _imgFactory.GetAndCreateProfileImage(_profileForm.UserId, -1), // TODO - probably rework forms to take in models instead of individual params + }; + _dmForm.OnClose += DirectMessage_OnClose; + _dmForm.OnSend += DirectMessage_OnSend; + + _dmForm.Show(); + OpenDirectMessagesForms.Add(_dmForm); + } + } + private async void MainTabControl_OnUserItemDoubleClicked(object? sender, EventArgs e) { if (sender is MainTabControl _mtCtrl) @@ -526,9 +547,84 @@ namespace qtcnet_client } } - private void _gatewayService_OnDirectMessageReceived(object? sender, EventArgs e) + private async void _gatewayService_OnDirectMessageReceived(object? sender, EventArgs e) { - throw new NotImplementedException(); + if(e is DirectMessageEventArgs _args) + { + // check for existing open form + var _existingForm = OpenDirectMessagesForms.FirstOrDefault(e => e.UserId == _args.User.Id); + if (_existingForm != null) + { + // just create a new message ctrl and add it + MessageControl _newMsg = new() + { + Message = _args.Message.Content, + Username = _args.User.Username, + }; + + _existingForm.AddMessage(_newMsg); + await _existingForm.LoadProfileImage(_newMsg, _args.User); + _audioService.PlaySoundEffect("sndMessage"); + } + else + { + // create a new form + DirectMessageForm _dmForm = new(_imgFactory) + { + UserId = _args.User.Id, + Username = _args.User.Username, + ProfileImage = _imgFactory.GetAndCreateProfileImage(_args.User.Id, -1, _args.User.ProfileCosmeticId), + }; + _dmForm.OnSend += DirectMessage_OnSend; + _dmForm.OnClose += DirectMessage_OnClose; + + // add the new message to it + MessageControl _msg = new() + { + Username = _args.User.Username, + Message = _args.Message.Content, + }; + + _dmForm.AddMessage(_msg); + await _dmForm.LoadProfileImage(_msg, _args.User); + + Task.Run(_dmForm.ShowDialog); // workaround async issues (i suck at coding and dont wanna rework the entire ui again) + OpenDirectMessagesForms.Add(_dmForm); + } + } + } + + private void DirectMessage_OnClose(object? sender, EventArgs e) + { + if (sender is DirectMessageForm _dmForm) + { + OpenDirectMessagesForms.Remove(_dmForm); + _dmForm.Dispose(); + } + } + + private async void DirectMessage_OnSend(object? sender, EventArgs e) + { + if(sender is DirectMessageForm _dmForm) + { + // send message to user + var _userInfo = await _apiService.GetUserInformationAsync(_dmForm.UserId); + if(_userInfo.Success && _userInfo.Data != null) + { + await _gatewayService.SendDirectMessageAsync(_userInfo.Data, new QtCNETAPI.Models.Message { Content = _dmForm.SentMessage }); + + // add own message to form message list + MessageControl _selfMsg = new() + { + Username = _apiService.CurrentUser!.Username, + Message = _dmForm.SentMessage, + }; + + _dmForm.AddMessage(_selfMsg); + await _dmForm.LoadProfileImage(_selfMsg, new UserInformationDto { Id = _apiService.CurrentUser!.Id, ProfileCosmeticId = _apiService.CurrentUser.ActiveProfileCosmetic }); + _audioService.PlaySoundEffect("sndSendClick"); + } + } } private async Task PingAPI() @@ -592,7 +688,7 @@ namespace qtcnet_client _gatewayService.OnServerReconnecting += _gatewayService_OnServerReconnecting; _gatewayService.OnServerReconnected += _gatewayService_OnServerReconnected; _gatewayService.OnServerDisconnect += _gatewayService_OnServerDisconnect; - //_gatewayService.OnDirectMessageReceived += _gatewayService_OnDirectMessageReceived; + _gatewayService.OnDirectMessageReceived += _gatewayService_OnDirectMessageReceived; _gatewayService.OnRoomMessageReceived += _gatewayService_OnRoomMessageReceived; _gatewayService.OnRoomUserListReceived += _gatewayService_OnRoomUserListReceived; _gatewayService.OnRefreshUserListsReceived += _gatewayService_OnRefreshUserListReceived; diff --git a/qtcnet-client/Forms/ProfileForm.cs b/qtcnet-client/Forms/ProfileForm.cs index 7c88d67..6fcd1c3 100644 --- a/qtcnet-client/Forms/ProfileForm.cs +++ b/qtcnet-client/Forms/ProfileForm.cs @@ -33,6 +33,7 @@ namespace qtcnet_client.Forms [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public Image ProfileImage { get; set; } = Resources.DefaultPfp; + public event EventHandler? OnMessageClicked; public event EventHandler? OnClose; private IApiService _apiService; @@ -177,7 +178,8 @@ namespace qtcnet_client.Forms private void BtnAction1_MessageClick(object? sender, EventArgs e) { - throw new NotImplementedException(); + Close(); + OnMessageClicked?.Invoke(this, EventArgs.Empty); } private async void BtnAction2_RemoveContactClick(object? sender, EventArgs e)