Add project files.

This commit is contained in:
Alan Moon 2025-12-07 17:20:35 -08:00
parent 695afb8449
commit d351ed86be
87 changed files with 7176 additions and 0 deletions

View File

@ -0,0 +1,8 @@
namespace QtCNETAPI.Dtos.Room
{
public class RoomDto
{
public string Name { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; } = new DateTime();
}
}

View File

@ -0,0 +1,10 @@
namespace QtCNETAPI.Dtos.User
{
public class UserDto
{
public string Username { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public DateTime DateOfBirth { get; set; } = new DateTime();
}
}

View File

@ -0,0 +1,17 @@
namespace QtCNETAPI.Dtos.User
{
public class UserInformationDto
{
public string Id { get; set; } = string.Empty;
public string Username { get; set; } = string.Empty;
public string ProfilePicture { get; set; } = string.Empty;
public string Bio { get; set; } = string.Empty;
public string Role { get; set; } = string.Empty;
public DateTime DateOfBirth { get; set; } = new DateTime();
public DateTime CreatedAt { get; set; } = new DateTime();
public DateTime LastLogin { get; set; } = new DateTime();
public int Status { get; set; } = 0;
public int CurrencyAmount { get; set; } = 0;
public int ProfileCosmeticId { get; set; } = 0;
}
}

View File

@ -0,0 +1,9 @@
namespace QtCNETAPI.Dtos.User
{
public class UserLoginDto
{
public string Email { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public bool RememberMe { get; set; } = false;
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace QtCNETAPI.Dtos.User
{
public class UserPasswordResetDto
{
public string Token { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
}
}

View File

@ -0,0 +1,8 @@
namespace QtCNETAPI.Dtos.User
{
public class UserRefreshLoginDto
{
public string Email { get; set; } = string.Empty;
public string RefreshToken { get; set; } = string.Empty;
}
}

View File

@ -0,0 +1,8 @@
namespace QtCNETAPI.Dtos.User
{
public class UserStatusDto
{
public string Id { get; set; } = string.Empty;
public int Status { get; set; } = 0;
}
}

View File

@ -0,0 +1,8 @@
namespace QtCNETAPI.Dtos.User
{
public class UserStockActionResultDto
{
public int StockAmount { get; set; }
public int CurrencyAmount { get; set; }
}
}

View File

@ -0,0 +1,11 @@
namespace QtCNETAPI.Dtos.User
{
public class UserUpdateInformationDto
{
public string Id { get; set; } = string.Empty;
public string Username { get; set; } = string.Empty;
public string Bio { get; set; } = string.Empty;
public DateTime DateOfBirth { get; set; } = new DateTime();
public int ProfileCosmeticId { get; set; } = 0;
}
}

View File

@ -0,0 +1,13 @@
namespace QtCNETAPI.Enums
{
public enum GameStatus
{
WaitingForPlayer,
SelectingSymbol,
Ongoing,
P1Win,
P2Win,
NoWin,
PlayerDisconnected
}
}

View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace QtCNETAPI.Enums
{
public enum NumberGuessResult
{
Higher,
Lower,
Correct,
Incorrect
}
}

View File

@ -0,0 +1,8 @@
namespace QtCNETAPI.Enums
{
public enum StoreItemType
{
ProfileCosmetic = 1,
ClientCosmetic = 2
}
}

View File

@ -0,0 +1,9 @@
namespace QtCNETAPI.Enums
{
public enum TicTacToeSymbol
{
X,
O,
Blank
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace QtCNETAPI.Events
{
public class ClientFunctionEventArgs : EventArgs
{
public required string Function { get; set; }
}
}

View File

@ -0,0 +1,16 @@
using QtCNETAPI.Dtos.User;
using QtCNETAPI.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace QtCNETAPI.Events
{
public class DirectMessageEventArgs : EventArgs
{
public required Message Message { get; set; }
public required UserInformationDto User { get; set; }
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace QtCNETAPI.Events
{
public class GuestUserJoinEventArgs : EventArgs
{
public required string Username { get; set; }
}
}

View File

@ -0,0 +1,14 @@
using QtCNETAPI.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace QtCNETAPI.Events
{
public class RoomListEventArgs : EventArgs
{
public required List<User> UserList { get; set; }
}
}

View File

@ -0,0 +1,14 @@
using QtCNETAPI.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace QtCNETAPI.Events
{
public class ServerConfigEventArgs : EventArgs
{
public required ServerConfig ServerConfig { get; set; }
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace QtCNETAPI.Events
{
public class ServerConnectionClosedEventArgs : EventArgs
{
public Exception? Error { get; set; }
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace QtCNETAPI.Events
{
public class ServerConnectionReconnectingEventArgs : EventArgs
{
public Exception? Error { get; set; }
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace QtCNETAPI.Events
{
public class ServerMessageEventArgs : EventArgs
{
public required string Message { get; set; }
}
}

View File

@ -0,0 +1,16 @@
namespace QtCNETAPI.Models
{
public class Contact
{
public string Id { get; set; } = string.Empty;
public string OwnerId { get; set; } = string.Empty;
public string UserId { get; set; } = string.Empty;
public ContactStatus OwnerStatus { get; set; } = ContactStatus.AwaitingApprovalFromOther;
public ContactStatus UserStatus { get; set; } = ContactStatus.AwaitingApprovalFromSelf;
public virtual User? Owner { get; }
public virtual User? User { get; }
public enum ContactStatus { AwaitingApprovalFromOther = 0, AwaitingApprovalFromSelf = 1, Accepted = 2 }
}
}

View File

@ -0,0 +1,10 @@
namespace QtCNETAPI.Models
{
public class Message
{
public string Id { get; set; } = string.Empty;
public string AuthorId { get; set; } = string.Empty;
public string Content { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; } = new DateTime();
}
}

View File

@ -0,0 +1,14 @@
using System.ComponentModel.DataAnnotations;
namespace QtCNETAPI.Models
{
public class OwnedStoreItem
{
[Key]
public int Id { get; set; }
public string UserId { get; set; } = string.Empty;
public int StoreItemId { get; set; }
public virtual User? User { get; }
}
}

View File

@ -0,0 +1,12 @@
namespace QtCNETAPI.Models
{
public class RefreshToken
{
public string ID { get; set; } = string.Empty;
public string UserID { get; set; } = string.Empty;
public string Token { get; set; } = string.Empty;
public DateTime Expires { get; set; }
public virtual User User { get; } = null!;
}
}

11
QtCNETAPI/Models/Room.cs Normal file
View File

@ -0,0 +1,11 @@
namespace QtCNETAPI.Models
{
public class Room
{
public string Id { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string CreatorId { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; } = new DateTime();
public int UserCount { get; set; } = 0;
}
}

View File

@ -0,0 +1,11 @@
namespace QtCNETAPI.Models
{
public class ServerConfig
{
public string? Name { get; set; }
public string? Description { get; set; }
public string? AdminUserId { get; set; }
public bool IsDown { get; set; }
public string? IsDownMessage { get; set; }
}
}

View File

@ -0,0 +1,9 @@
namespace QtCNETAPI.Models
{
public class ServiceResponse<T>
{
public T? Data { get; set; }
public bool Success { get; set; } = false;
public string Message { get; set; } = string.Empty;
}
}

28
QtCNETAPI/Models/User.cs Normal file
View File

@ -0,0 +1,28 @@
namespace QtCNETAPI.Models
{
public class User
{
public string Id { get; set; } = string.Empty;
public string Username { get; set; } = string.Empty;
public string ProfilePicture { get; set; } = string.Empty;
public string Bio { get; set; } = string.Empty;
public string Role { get; set; } = string.Empty;
public string PasswordHash { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public bool IsEmailVerified { get; set; } = false;
public DateTime DateOfBirth { get; set; }
public DateTime CreatedAt { get; set; }
public int Status { get; set; } = 0;
public int CurrencyAmount { get; set; } = 0;
public int StockAmount { get; set; } = 0;
public DateTime LastCurrencySpin { get; set; }
public int ActiveProfileCosmetic { get; set; } = 0;
public string CurrentRoomId { get; set; } = string.Empty;
public DateTime LastLogin { get; set; }
public virtual IEnumerable<RefreshToken>? RefreshTokens { get; }
public virtual IEnumerable<Contact>? ContactsMade { get; }
public virtual IEnumerable<Contact>? ContactsList { get; }
public virtual IEnumerable<OwnedStoreItem>? OwnedStoreItems { get; }
}
}

View File

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Meziantou.Framework.Win32.CredentialManager" Version="1.7.6" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.16" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.9" />
<PackageReference Include="RestSharp" Version="112.1.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.14.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,16 @@
using QtCNETAPI.Enums;
using QtCNETAPI.Models;
namespace QtCNETAPI.Schema
{
public class GameRoom
{
public string Id { get; set; } = string.Empty;
public GameStatus Status { get; set; }
public TicTacToeBoard Board { get; set; } = new();
public User? Player1 { get; set; }
public TicTacToeSymbol P1Symbol { get; set; } = TicTacToeSymbol.Blank;
public User? Player2 { get; set; }
public TicTacToeSymbol P2Symbol { get; set; } = TicTacToeSymbol.Blank;
}
}

View File

@ -0,0 +1,23 @@
using QtCNETAPI.Enums;
using System.Text.Json.Serialization;
namespace QtCNETAPI.Schema
{
public class StoreItem
{
[JsonPropertyName("Id")]
public int Id { get; set; }
[JsonPropertyName("Type")]
public StoreItemType Type { get; set; }
[JsonPropertyName("Price")]
public int Price { get; set; }
[JsonPropertyName("Name")]
public string Name { get; set; } = string.Empty;
[JsonPropertyName("Description")]
public string Description { get; set; } = string.Empty;
[JsonPropertyName("AssetUrl")]
public string AssetUrl { get; set; } = string.Empty;
[JsonPropertyName("ThumbnailUrl")]
public string ThumbnailUrl { get; set; } = string.Empty;
}
}

View File

@ -0,0 +1,17 @@
using QtCNETAPI.Enums;
namespace QtCNETAPI.Schema
{
public class TicTacToeBoard
{
public TicTacToeSymbol Square1 { get; set; } = TicTacToeSymbol.Blank;
public TicTacToeSymbol Square2 { get; set; } = TicTacToeSymbol.Blank;
public TicTacToeSymbol Square3 { get; set; } = TicTacToeSymbol.Blank;
public TicTacToeSymbol Square4 { get; set; } = TicTacToeSymbol.Blank;
public TicTacToeSymbol Square5 { get; set; } = TicTacToeSymbol.Blank;
public TicTacToeSymbol Square6 { get; set; } = TicTacToeSymbol.Blank;
public TicTacToeSymbol Square7 { get; set; } = TicTacToeSymbol.Blank;
public TicTacToeSymbol Square8 { get; set; } = TicTacToeSymbol.Blank;
public TicTacToeSymbol Square9 { get; set; } = TicTacToeSymbol.Blank;
}
}

View File

@ -0,0 +1,10 @@
using QtCNETAPI.Models;
namespace QtCNETAPI.Schema
{
public class TicTacToeMove
{
public User User { get; set; } = new();
public int Point { get; set; }
}
}

View File

@ -0,0 +1,956 @@
using QtCNETAPI.Dtos.Room;
using QtCNETAPI.Dtos.User;
using QtCNETAPI.Enums;
using QtCNETAPI.Models;
using QtCNETAPI.Schema;
using RestSharp;
using System.IdentityModel.Tokens.Jwt;
using System.Text.Json;
namespace QtCNETAPI.Services.ApiService
{
public class ApiService : IApiService
{
private User? user;
private readonly RestClient _client;
internal string? sessionToken;
public event EventHandler? OnCurrentUserUpdate;
public string? SessionToken
{
get { return sessionToken; }
set { sessionToken = value; }
}
public string? ApiUri { get; private set; }
public User? CurrentUser
{
get { return user; }
private set { user = value; }
}
private readonly LoggingService _loggingService;
private readonly CredentialService _credentialService;
public ApiService(string apiUri, LoggingService loggingService, CredentialService credentialService)
{
ApiUri = apiUri;
_client = new RestClient(ApiUri);
_loggingService = loggingService;
_credentialService = credentialService;
}
public async Task<ServiceResponse<string>> PingServerAsync()
{
var serviceResponse = new ServiceResponse<string>();
try
{
var request = new RestRequest("general/ping");
var response = await _client.GetAsync<string>(request);
if (response != null)
{
if (response != "Pong!") serviceResponse.Success = false;
else serviceResponse.Success = true;
}
else serviceResponse.Success = false;
} catch (HttpRequestException ex)
{
serviceResponse.Success = false;
serviceResponse.Message = ex.Message;
}
return serviceResponse;
}
public async Task<ServiceResponse<List<UserInformationDto>>> GetAllUsersAsync()
{
await RefreshSessionIfInvalid();
var serviceResponse = new ServiceResponse<List<UserInformationDto>>();
if (SessionToken == null) throw new NullReferenceException("Function Was Called Before A Session Was Made.");
var request = new RestRequest("users/all")
.AddHeader("Authorization", $"Bearer {SessionToken}");
var response = await _client.GetAsync<ServiceResponse<List<UserInformationDto>>>(request);
if (response != null || response!.Data != null)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
}
else
{
serviceResponse.Success = false;
serviceResponse.Message = "API didn't respond with users.";
}
return serviceResponse;
}
public async Task<ServiceResponse<List<UserInformationDto>>> GetOnlineUsersAsync()
{
await RefreshSessionIfInvalid();
var serviceResponse = new ServiceResponse<List<UserInformationDto>>();
if (SessionToken == null) throw new NullReferenceException("Function Was Called Before A Session Was Made.");
var request = new RestRequest("users/users-online")
.AddHeader("Authorization", $"Bearer {SessionToken}");
var response = await _client.GetAsync<ServiceResponse<List<UserInformationDto>>>(request);
if (response != null || response!.Data != null)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
} else
{
serviceResponse.Success = false;
serviceResponse.Message = "API didn't respond with online users.";
}
return serviceResponse;
}
public async Task<ServiceResponse<UserInformationDto>> GetUserInformationAsync(string id)
{
await RefreshSessionIfInvalid();
var serviceResponse = new ServiceResponse<UserInformationDto>();
if (SessionToken == null) throw new NullReferenceException("Function Was Called Before A Session Was Made.");
var request = new RestRequest($"users/user-info")
.AddHeader("Authorization", $"Bearer {SessionToken}")
.AddQueryParameter("id", id);
var response = await _client.GetAsync<ServiceResponse<UserInformationDto>>(request);
if(response != null || response!.Data != null)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
} else
{
serviceResponse.Success = false;
serviceResponse.Message = "API did not return user information.";
}
return serviceResponse;
}
public async Task<ServiceResponse<UserInformationDto>> UpdateUserInformationAsync(UserUpdateInformationDto request)
{
await RefreshSessionIfInvalid();
var serviceResponse = new ServiceResponse<UserInformationDto>();
var restRequest = new RestRequest("users/update")
.AddJsonBody(request)
.AddHeader("Authorization", $"Bearer {SessionToken}");
var response = await _client.PutAsync<ServiceResponse<UserInformationDto>>(restRequest);
if(response != null || response!.Data != null)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
// anything that changes the user should tell the api service to set it again
await SetCurrentUser();
OnCurrentUserUpdate?.Invoke(this, EventArgs.Empty);
} else
{
serviceResponse.Success = false;
serviceResponse.Message = "API never responded.";
}
return serviceResponse;
}
public async Task<ServiceResponse<string>> UpdateUserProfilePic(string filePath)
{
var serviceResponse = new ServiceResponse<string>();
try
{
var restRequest = new RestRequest($"users/upload-profile-pic")
.AddQueryParameter("userId", CurrentUser!.Id)
.AddHeader("Authorization", $"Bearer {SessionToken}")
.AddFile("file", filePath);
var response = await _client.PostAsync<ServiceResponse<string>>(restRequest);
if (response != null && response.Success != false)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
// anything that changes the user should tell the api service to set it again
await SetCurrentUser();
OnCurrentUserUpdate?.Invoke(this, EventArgs.Empty);
}
else
{
serviceResponse.Success = false;
serviceResponse.Data = "Upload Failed.";
}
} catch (JsonException)
{
serviceResponse.Success = false;
serviceResponse.Message = "Profile Pictures Can Only Be Less Then 3 MB In Size.";
} catch (HttpRequestException ex)
{
serviceResponse.Success = false;
serviceResponse.Data = ex.Message;
}
return serviceResponse;
}
public async Task<ServiceResponse<byte[]>> GetUserProfilePic(string userId)
{
var serviceResponse = new ServiceResponse<byte[]>();
try
{
var restRequest = new RestRequest($"users/profile-pic/{userId}")
.AddHeader("Authorization", $"Bearer {SessionToken}");
var response = await _client.GetAsync(restRequest);
if (response != null)
{
serviceResponse.Success = true;
serviceResponse.Data = response.RawBytes;
}
else
{
serviceResponse.Success = false;
serviceResponse.Message = "No Profile Picture Received.";
}
return serviceResponse;
} catch (HttpRequestException ex)
{
serviceResponse.Success = false;
serviceResponse.Message = ex.Message;
return serviceResponse;
}
}
public async Task<ServiceResponse<string>> LoginAsync(UserLoginDto userLoginDto)
{
var serviceResponse = new ServiceResponse<string>();
try
{
if (string.IsNullOrWhiteSpace(userLoginDto.Email) || string.IsNullOrWhiteSpace(userLoginDto.Password))
{
serviceResponse.Success = false;
serviceResponse.Message = "Email or Password cannot be null.";
}
var request = new RestRequest("auth/login")
.AddJsonBody(userLoginDto);
var response = await _client.PostAsync<ServiceResponse<string>>(request);
if (response != null)
{
if (response.Data != null && response.Success)
{
SessionToken = response.Data;
var user = await SetCurrentUser();
serviceResponse.Success = true;
if (response.Message != null) serviceResponse.Message = response.Message;
serviceResponse.Data = response.Message;
}
else
{
serviceResponse.Success = false;
serviceResponse.Message = response.Message;
}
}
} catch (Exception ex)
{
serviceResponse.Success = false;
serviceResponse.Message = ex.Message;
}
return serviceResponse;
}
public async Task<ServiceResponse<bool>> ResendVerificationEmail(string email)
{
var serviceResponse = new ServiceResponse<bool>();
var restRequest = new RestRequest($"auth/resend-verification-email")
.AddQueryParameter("email", email);
var response = await _client.PostAsync<ServiceResponse<bool>>(restRequest);
if (response != null)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
}
else
{
serviceResponse.Success = false;
serviceResponse.Message = "API never responded.";
}
return serviceResponse;
}
public async Task<ServiceResponse<bool>> SendPasswordResetEmail(string email)
{
var serviceResponse = new ServiceResponse<bool>();
var restRequest = new RestRequest($"auth/request-password-reset")
.AddQueryParameter("email", email);
var response = await _client.PostAsync<ServiceResponse<bool>>(restRequest);
if (response != null)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
}
else
{
serviceResponse.Success = false;
serviceResponse.Message = "API never responded.";
}
return serviceResponse;
}
public async Task<ServiceResponse<bool>> ResetPassword(UserPasswordResetDto request)
{
var serviceResponse = new ServiceResponse<bool>();
var restRequest = new RestRequest($"auth/reset-password")
.AddJsonBody(request);
var response = await _client.PostAsync<ServiceResponse<bool>>(restRequest);
if (response != null)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
}
else
{
serviceResponse.Success = false;
serviceResponse.Message = "API never responded.";
}
return serviceResponse;
}
public async Task<User> SetCurrentUser()
{
var userRequest = new RestRequest("users/user-authorized")
.AddHeader("Authorization", $"Bearer {SessionToken}");
var userResponse = await _client.GetAsync<ServiceResponse<User>>(userRequest);
if (userResponse != null && userResponse.Success && userResponse.Data != null)
{
user = userResponse.Data;
_loggingService.LogString($"Current User's Status Is {userResponse.Data.Status}");
return userResponse.Data;
} else
{
throw new NullReferenceException("Current User could not be set.");
}
}
public async Task<ServiceResponse<User>> RefreshLogin(string refreshToken)
{
var serviceResponse = new ServiceResponse<User>();
try
{
if (string.IsNullOrEmpty(refreshToken))
{
serviceResponse.Success = false;
serviceResponse.Message = "No Refresh Token Specified.";
}
var request = new RestRequest("auth/refresh")
.AddQueryParameter("token", refreshToken);
var response = await _client.PostAsync<ServiceResponse<string>>(request);
if (response != null && response.Success != false)
{
SessionToken = response.Data;
if (user == null)
{
var user = await SetCurrentUser();
serviceResponse.Success = true;
serviceResponse.Data = user;
}
serviceResponse.Success = true;
serviceResponse.Data = CurrentUser;
}
else
{
serviceResponse.Success = false;
serviceResponse.Message = "API didn't respond with a session token.";
}
} catch (Exception ex)
{
serviceResponse.Success = false;
serviceResponse.Message = ex.Message;
}
return serviceResponse;
}
public async Task<ServiceResponse<string>> RefreshSessionIfInvalid()
{
var tokenHandler = new JwtSecurityTokenHandler();
var refToken = _credentialService.GetAccessToken(); // fuck CA1416, if this is being ran on linux it should just crash (theoretically)
if (refToken == null)
{
// treat as session expired
return new ServiceResponse<string> { Success = false, Message = "Refresh Token Not Found. Session Expired." };
}
JwtSecurityToken token = tokenHandler.ReadJwtToken(SessionToken);
if(DateTime.Compare(DateTime.UtcNow, token.ValidTo) > 0)
{
var result = await RefreshLogin(refToken);
if (result == null || result.Success == false)
{
return new ServiceResponse<string> { Success = false, Message = "Session Expired." }; // logging in again should overwrite old token
} else return new ServiceResponse<string> { Success = true, Data = refToken };
} else return new ServiceResponse<string> { Success = true, Data = refToken };
}
public async Task<ServiceResponse<User>> RegisterAsync(UserDto userDto)
{
var serviceResponse = new ServiceResponse<User>();
if (string.IsNullOrEmpty(userDto.Username) || string.IsNullOrEmpty(userDto.Password) || string.IsNullOrEmpty(userDto.Email))
{
serviceResponse.Success = false;
serviceResponse.Message = "Incomplete UserDto.";
}
var request = new RestRequest("auth/register")
.AddJsonBody(userDto);
var response = await _client.PostAsync<ServiceResponse<User>>(request);
if (response != null || response!.Success == true)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
} else
{
serviceResponse.Success = false;
serviceResponse.Message = "API never responded with created user.";
}
return serviceResponse;
}
public async Task<ServiceResponse<Room>> CreateRoomAsync(RoomDto request)
{
await RefreshSessionIfInvalid();
var serviceResponse = new ServiceResponse<Room>();
if (SessionToken == null) throw new NullReferenceException("Function Was Called Before A Session Was Made.");
var restRequest = new RestRequest($"rooms/create-room?userId={user!.Id}")
.AddJsonBody(request)
.AddHeader("Authorization", $"Bearer {SessionToken}");
var response = await _client.PostAsync<ServiceResponse<Room>>(restRequest);
if(response != null || response!.Data != null)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
} else
{
serviceResponse.Success = false;
serviceResponse.Message = "API never responded with created room.";
}
return serviceResponse;
}
public async Task<ServiceResponse<List<Room>>> GetAllRoomsAsync()
{
await RefreshSessionIfInvalid();
var serviceResponse = new ServiceResponse<List<Room>>();
if(SessionToken == null) throw new NullReferenceException("Function Was Called Before A Session Was Made.");
var request = new RestRequest("rooms/get-all-rooms")
.AddHeader("Authorization", $"Bearer {SessionToken}");
var response = await _client.GetAsync<ServiceResponse<List<Room>>>(request);
if (response != null || response!.Data != null)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
}
else
{
serviceResponse.Success = false;
serviceResponse.Message = "API never responded with rooms.";
}
return serviceResponse;
}
public async Task<ServiceResponse<Room>> DeleteRoomAsync(string roomId)
{
await RefreshSessionIfInvalid();
var serviceResponse = new ServiceResponse<Room>();
if(SessionToken == null) throw new NullReferenceException("Function Was Called Before A Session Was Made.");
var request = new RestRequest($"rooms/delete-room?roomId={roomId}")
.AddHeader("Authorization", $"Bearer {SessionToken}");
var response = await _client.DeleteAsync<ServiceResponse<Room>>(request);
if (response != null || response!.Data != null)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
} else
{
serviceResponse.Success = false;
serviceResponse.Message = "API never responded.";
}
return serviceResponse;
}
public async Task<ServiceResponse<List<Contact>>> GetCurrentUserContacts()
{
await RefreshSessionIfInvalid();
var serviceResponse = new ServiceResponse<List<Contact>>();
if (SessionToken == null) throw new NullReferenceException("Function Was Called Before A Session Was Made.");
var request = new RestRequest("contacts/get-user-contacts")
.AddHeader("Authorization", $"Bearer {SessionToken}")
.AddJsonBody(CurrentUser!);
var response = await _client.GetAsync<ServiceResponse<List<Contact>>>(request);
if (response == null)
{
serviceResponse.Success = false;
serviceResponse.Message = "API didn't respond.";
return serviceResponse;
}
if (response.Message == "User Has No Contacts.")
{
serviceResponse.Success = true;
serviceResponse.Message = "No Contacts Found.";
}
if (response.Success == true)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
}
return serviceResponse;
}
public async Task<ServiceResponse<Contact>> AddContactToCurrentUser(string userId)
{
await RefreshSessionIfInvalid();
var serviceResponse = new ServiceResponse<Contact>();
if (SessionToken == null) throw new NullReferenceException("Function Was Called Before A Session Was Made.");
var restRequest = new RestRequest("contacts/add-contact")
.AddHeader("Authorization", $"Bearer {SessionToken}")
.AddQueryParameter("ownerId", CurrentUser!.Id)
.AddQueryParameter("userId", userId);
var response = await _client.PostAsync<ServiceResponse<Contact>>(restRequest);
if (response == null) { serviceResponse.Success = false; serviceResponse.Message = "API did not respond."; return serviceResponse; }
if (response.Success)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
}
return serviceResponse;
}
public async Task<ServiceResponse<bool>> AcceptContactRequest(string userId)
{
await RefreshSessionIfInvalid();
var serviceResponse = new ServiceResponse<bool>();
if (SessionToken == null) throw new NullReferenceException("Function Was Called Before A Session Was Made.");
var restRequest = new RestRequest("contacts/approve-contact")
.AddHeader("Authorization", $"Bearer {SessionToken}")
.AddQueryParameter("ownerId", CurrentUser!.Id)
.AddQueryParameter("userId", userId);
var response = await _client.PostAsync<ServiceResponse<bool>>(restRequest);
if (response == null) { serviceResponse.Success = false; serviceResponse.Message = "API did not respond."; return serviceResponse; }
if (response.Success)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
}
return serviceResponse;
}
public async Task<ServiceResponse<Contact>> RemoveContactFromCurrentUser(string userId)
{
await RefreshSessionIfInvalid();
var serviceResponse = new ServiceResponse<Contact>();
if (SessionToken == null) throw new NullReferenceException("Function Was Called Before A Session Was Made.");
var restRequest = new RestRequest("contacts/remove-contact")
.AddHeader("Authorization", $"Bearer {SessionToken}")
.AddQueryParameter("userId", userId);
var response = await _client.DeleteAsync<ServiceResponse<Contact>>(restRequest);
if (response == null) { serviceResponse.Success = false; serviceResponse.Message = "API did not respond."; return serviceResponse; }
if (response.Success)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
}
return serviceResponse;
}
public async Task<ServiceResponse<int>> AddCurrencyToCurrentUser(int amount, bool isSpinClaim)
{
await RefreshSessionIfInvalid();
var serviceResponse = new ServiceResponse<int>();
if (SessionToken == null) throw new NullReferenceException("Function Was Called Before A Session Was Made.");
var restRequest = new RestRequest("users/update-user-currency")
.AddHeader("Authorization", $"Bearer {SessionToken}")
.AddQueryParameter("amount", amount)
.AddQueryParameter("isSpinClaim", isSpinClaim);
var response = await _client.PostAsync<ServiceResponse<int>>(restRequest);
if (response == null) { serviceResponse.Success = false; serviceResponse.Message = "API did not respond."; return serviceResponse; }
if (response.Success)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
// anything that changes the user should tell the api service to set it again
await SetCurrentUser();
OnCurrentUserUpdate?.Invoke(this, EventArgs.Empty);
}
return serviceResponse;
}
public async Task<ServiceResponse<int>> GetCurrentStockPrice()
{
await RefreshSessionIfInvalid();
var serviceResponse = new ServiceResponse<int>();
if (SessionToken == null) throw new NullReferenceException("Function Was Called Before A Session Was Made.");
var restRequest = new RestRequest("games/stock-market/current-price")
.AddHeader("Authorization", $"Bearer {SessionToken}");
var response = await _client.GetAsync<ServiceResponse<int>>(restRequest);
if (response == null) { serviceResponse.Success = false; serviceResponse.Message = "API did not respond."; return serviceResponse; }
if (response.Success)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
}
return serviceResponse;
}
public async Task<ServiceResponse<UserStockActionResultDto>> BuyStock(int amount)
{
await RefreshSessionIfInvalid();
var serviceResponse = new ServiceResponse<UserStockActionResultDto>();
if (SessionToken == null) throw new NullReferenceException("Function Was Called Before A Session Was Made.");
var restRequest = new RestRequest("games/stock-market/buy-stock")
.AddHeader("Authorization", $"Bearer {SessionToken}")
.AddQueryParameter("amount", amount);
var response = await _client.PostAsync<ServiceResponse<UserStockActionResultDto>>(restRequest);
if (response == null) { serviceResponse.Success = false; serviceResponse.Message = "API did not respond."; return serviceResponse; }
if (response.Success)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
// anything that changes the user should tell the api service to set it again
await SetCurrentUser();
OnCurrentUserUpdate?.Invoke(this, EventArgs.Empty);
}
return serviceResponse;
}
public async Task<ServiceResponse<UserStockActionResultDto>> SellStock(int amount)
{
await RefreshSessionIfInvalid();
var serviceResponse = new ServiceResponse<UserStockActionResultDto>();
if (SessionToken == null) throw new NullReferenceException("Function Was Called Before A Session Was Made.");
var restRequest = new RestRequest("games/stock-market/sell-stock")
.AddHeader("Authorization", $"Bearer {SessionToken}")
.AddQueryParameter("amount", amount);
var response = await _client.PostAsync<ServiceResponse<UserStockActionResultDto>>(restRequest);
if (response == null) { serviceResponse.Success = false; serviceResponse.Message = "API did not respond."; return serviceResponse; }
if (response.Success)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
// anything that changes the user should tell the api service to set it again
await SetCurrentUser();
OnCurrentUserUpdate?.Invoke(this, EventArgs.Empty);
}
return serviceResponse;
}
public async Task<ServiceResponse<int>> GetRandomNumber()
{
await RefreshSessionIfInvalid();
var serviceResponse = new ServiceResponse<int>();
if (SessionToken == null) throw new NullReferenceException("Function Was Called Before A Session Was Made.");
var restRequest = new RestRequest("games/number-guess/get-number")
.AddHeader("Authorization", $"Bearer {SessionToken}");
var response = await _client.GetAsync<ServiceResponse<int>>(restRequest);
if (response == null) { serviceResponse.Success = false; serviceResponse.Message = "API did not respond."; return serviceResponse; }
if (response.Success)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
}
return serviceResponse;
}
public async Task<ServiceResponse<NumberGuessResult>> GuessRandomNumber(int original, int guess)
{
await RefreshSessionIfInvalid();
var serviceResponse = new ServiceResponse<NumberGuessResult>();
if (SessionToken == null) throw new NullReferenceException("Function Was Called Before A Session Was Made.");
var restRequest = new RestRequest("games/number-guess/guess-number")
.AddHeader("Authorization", $"Bearer {SessionToken}")
.AddQueryParameter("original", original)
.AddQueryParameter("guess", guess);
var response = await _client.GetAsync<ServiceResponse<NumberGuessResult>>(restRequest);
if (response == null) { serviceResponse.Success = false; serviceResponse.Message = "API did not respond."; return serviceResponse; }
if (response.Success)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
}
return serviceResponse;
}
public async Task<ServiceResponse<List<StoreItem>>> GetStoreItems()
{
await RefreshSessionIfInvalid();
var serviceResponse = new ServiceResponse<List<StoreItem>>();
if (SessionToken == null) throw new NullReferenceException("Function Was Called Before A Session Was Made.");
var restRequest = new RestRequest("store/all-items")
.AddHeader("Authorization", $"Bearer {SessionToken}");
var response = await _client.GetAsync<ServiceResponse<List<StoreItem>>>(restRequest);
if (response == null) { serviceResponse.Success = false; serviceResponse.Message = "API did not respond."; return serviceResponse; }
if (response.Success)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
}
return serviceResponse;
}
public async Task<ServiceResponse<StoreItem>> GetStoreItem(int id)
{
await RefreshSessionIfInvalid();
var serviceResponse = new ServiceResponse<StoreItem>();
if (SessionToken == null) throw new NullReferenceException("Function Was Called Before A Session Was Made.");
var restRequest = new RestRequest("store/item")
.AddHeader("Authorization", $"Bearer {SessionToken}")
.AddQueryParameter("id", id);
var response = await _client.GetAsync<ServiceResponse<StoreItem>>(restRequest);
if (response == null) { serviceResponse.Success = false; serviceResponse.Message = "API did not respond."; return serviceResponse; }
if (response.Success)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
}
return serviceResponse;
}
public async Task<ServiceResponse<bool>> BuyStoreItem(int id)
{
await RefreshSessionIfInvalid();
var serviceResponse = new ServiceResponse<bool>();
if (SessionToken == null) throw new NullReferenceException("Function Was Called Before A Session Was Made.");
var restRequest = new RestRequest("store/buy-item")
.AddHeader("Authorization", $"Bearer {SessionToken}")
.AddQueryParameter("id", id);
var response = await _client.PostAsync<ServiceResponse<bool>>(restRequest);
if (response == null) { serviceResponse.Success = false; serviceResponse.Message = "API did not respond."; return serviceResponse; }
if (response.Success)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
// anything that changes the user should tell the api service to set it again
await SetCurrentUser();
OnCurrentUserUpdate?.Invoke(this, EventArgs.Empty);
}
return serviceResponse;
}
public async Task<ServiceResponse<List<OwnedStoreItem>>> GetOwnedStoreItems()
{
await RefreshSessionIfInvalid();
var serviceResponse = new ServiceResponse<List<OwnedStoreItem>>();
if (SessionToken == null) throw new NullReferenceException("Function Was Called Before A Session Was Made.");
var restRequest = new RestRequest("store/bought-items")
.AddHeader("Authorization", $"Bearer {SessionToken}");
var response = await _client.GetAsync<ServiceResponse<List<OwnedStoreItem>>>(restRequest);
if (response == null) { serviceResponse.Success = false; serviceResponse.Message = "API did not respond."; return serviceResponse; }
if (response.Success)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
}
return serviceResponse;
}
public async Task<ServiceResponse<OwnedStoreItem>> GetOwnedStoreItem(int id)
{
await RefreshSessionIfInvalid();
var serviceResponse = new ServiceResponse<OwnedStoreItem>();
if (SessionToken == null) throw new NullReferenceException("Function Was Called Before A Session Was Made.");
var restRequest = new RestRequest("store/bought-item")
.AddHeader("Authorization", $"Bearer {SessionToken}")
.AddQueryParameter("id", id);
var response = await _client.GetAsync<ServiceResponse<OwnedStoreItem>>(restRequest);
if (response == null) { serviceResponse.Success = false; serviceResponse.Message = "API did not respond."; return serviceResponse; }
if (response.Success)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
}
return serviceResponse;
}
public async Task<ServiceResponse<User>> DeleteUserById(string id)
{
await RefreshSessionIfInvalid();
var serviceResponse = new ServiceResponse<User>();
if (SessionToken == null) throw new NullReferenceException("Function Was Called Before A Session Was Made.");
var restRequest = new RestRequest("users/delete-user")
.AddHeader("Authorization", $"Bearer {SessionToken}")
.AddQueryParameter("id", id);
var response = await _client.DeleteAsync<ServiceResponse<User>>(restRequest);
if (response == null) { serviceResponse.Success = false; serviceResponse.Message = "API did not respond."; return serviceResponse; }
if (response.Success)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
}
return serviceResponse;
}
}
}

View File

@ -0,0 +1,58 @@
using Microsoft.AspNetCore.Mvc;
using QtCNETAPI.Dtos.User;
using QtCNETAPI.Dtos.Room;
using QtCNETAPI.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using QtCNETAPI.Enums;
using QtCNETAPI.Schema;
namespace QtCNETAPI.Services.ApiService
{
public interface IApiService
{
public string? SessionToken { get; set; }
public string? ApiUri { get; }
public User? CurrentUser { get; }
public event EventHandler? OnCurrentUserUpdate;
public Task<ServiceResponse<string>> PingServerAsync();
public Task<ServiceResponse<List<UserInformationDto>>> GetOnlineUsersAsync();
public Task<ServiceResponse<List<UserInformationDto>>> GetAllUsersAsync();
public Task<ServiceResponse<User>> DeleteUserById(string id);
public Task<ServiceResponse<string>> LoginAsync(UserLoginDto userLoginDto);
public Task<ServiceResponse<bool>> ResendVerificationEmail(string email);
public Task<ServiceResponse<bool>> SendPasswordResetEmail(string email);
public Task<ServiceResponse<bool>> ResetPassword(UserPasswordResetDto request);
public Task<ServiceResponse<User>> RefreshLogin(string refreshToken);
public Task<ServiceResponse<string>> RefreshSessionIfInvalid();
public Task<User> SetCurrentUser();
public Task<ServiceResponse<User>> RegisterAsync(UserDto userDto);
public Task<ServiceResponse<UserInformationDto>> GetUserInformationAsync(string id);
public Task<ServiceResponse<UserInformationDto>> UpdateUserInformationAsync(UserUpdateInformationDto request);
public Task<ServiceResponse<string>> UpdateUserProfilePic(string filePath);
public Task<ServiceResponse<byte[]>> GetUserProfilePic(string userId);
public Task<ServiceResponse<Room>> CreateRoomAsync(RoomDto request);
public Task<ServiceResponse<Room>> DeleteRoomAsync(string roomId);
public Task<ServiceResponse<List<Room>>> GetAllRoomsAsync();
public Task<ServiceResponse<List<Contact>>> GetCurrentUserContacts();
public Task<ServiceResponse<Contact>> AddContactToCurrentUser(string userId);
public Task<ServiceResponse<bool>> AcceptContactRequest(string userId);
public Task<ServiceResponse<Contact>> RemoveContactFromCurrentUser(string userId);
public Task<ServiceResponse<int>> AddCurrencyToCurrentUser(int amount, bool isSpinClaim);
public Task<ServiceResponse<int>> GetCurrentStockPrice();
public Task<ServiceResponse<UserStockActionResultDto>> BuyStock(int amount);
public Task<ServiceResponse<UserStockActionResultDto>> SellStock(int amount);
public Task<ServiceResponse<int>> GetRandomNumber();
public Task<ServiceResponse<NumberGuessResult>> GuessRandomNumber(int original, int guess);
public Task<ServiceResponse<List<StoreItem>>> GetStoreItems();
public Task<ServiceResponse<StoreItem>> GetStoreItem(int id);
public Task<ServiceResponse<bool>> BuyStoreItem(int id);
public Task<ServiceResponse<List<OwnedStoreItem>>> GetOwnedStoreItems();
public Task<ServiceResponse<OwnedStoreItem>> GetOwnedStoreItem(int id);
}
}

View File

@ -0,0 +1,42 @@
using Meziantou.Framework.Win32;
namespace QtCNETAPI.Services
{
public class CredentialService()
{
/*
* NOTE *
This does not work on other platforms such as Linux or macOS.
I will probably recode the legacy way of doing this for those other platforms.
*/
public void SaveAccessToken(string username, string accessToken)
{
string applicationName = "QtC.NET";
if (System.Diagnostics.Debugger.IsAttached) applicationName = "QtC.NET.Development";
CredentialManager.WriteCredential(applicationName, username, accessToken, $"Access Token For User {username} On QtC.NET", CredentialPersistence.LocalMachine);
}
public void DeleteAccessToken()
{
string applicationName = "QtC.NET";
if (System.Diagnostics.Debugger.IsAttached) applicationName = "QtC.NET.Development";
CredentialManager.DeleteCredential(applicationName);
}
public string? GetAccessToken()
{
string applicationName = "QtC.NET";
if (System.Diagnostics.Debugger.IsAttached) applicationName = "QtC.NET.Development";
var credential = CredentialManager.ReadCredential(applicationName);
if (credential == null) return null;
return credential.Password;
}
}
}

View File

@ -0,0 +1,196 @@
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.Logging;
using QtCNETAPI.Dtos.User;
using QtCNETAPI.Events;
using QtCNETAPI.Models;
using QtCNETAPI.Services.ApiService;
namespace QtCNETAPI.Services.GatewayService
{
public class GatewayService : IGatewayService, IAsyncDisposable
{
public Room? CurrentRoom { get; private set; }
public HubConnection? HubConnection { get; private set; }
public string? GWUri { get; private set; }
public event EventHandler? OnRoomMessageReceived;
public event EventHandler? OnRoomUserListReceived;
public event EventHandler? OnGuestUserJoin;
public event EventHandler? OnRefreshUserListsReceived;
public event EventHandler? OnRefreshRoomListReceived;
public event EventHandler? OnRoomDeleted;
public event EventHandler? OnRefreshContactsListReceived;
public event EventHandler? OnClientFunctionReceived;
public event EventHandler? OnDirectMessageReceived;
public event EventHandler? OnServerConfigReceived;
public event EventHandler? OnServerDisconnect;
public event EventHandler? OnServerReconnecting;
public event EventHandler? OnServerReconnected;
public event EventHandler? OnUserForceLogout;
private IApiService _apiService;
private LoggingService _loggingService;
public GatewayService(string gwUri, IApiService apiService, LoggingService loggingService)
{
GWUri = gwUri;
_apiService = apiService;
_loggingService = loggingService;
}
public async Task StartAsync()
{
// build connection
var gwConBuilder = new HubConnectionBuilder()
.WithAutomaticReconnect()
.ConfigureLogging((builder) =>
{
builder.AddProvider(new LoggingServiceProvider(_loggingService));
if (System.Diagnostics.Debugger.IsAttached) builder.SetMinimumLevel(LogLevel.Debug);
else builder.SetMinimumLevel(LogLevel.Error);
})
.WithUrl(GWUri!, options =>
{
options.AccessTokenProvider = async () =>
{
// this should hopefully refresh the session every time the gateway connection is used to prevent connection aborts
await _apiService.RefreshSessionIfInvalid();
return _apiService.SessionToken;
};
})
.WithStatefulReconnect();
HubConnection = gwConBuilder.Build();
// register events
HubConnection.On<string>("RoomMessage", (serverMessage) => OnRoomMessageReceived?.Invoke(this, new ServerMessageEventArgs { Message = serverMessage }));
HubConnection.On<string>("cf", (function) => OnClientFunctionReceived?.Invoke(this, new ClientFunctionEventArgs { Function = function }));
HubConnection.On<Message, UserInformationDto>("ReceiveDirectMessage", (message, user) => OnDirectMessageReceived?.Invoke(this, new DirectMessageEventArgs { Message = message, User = user }));
HubConnection.On("RefreshUserLists", () => OnRefreshUserListsReceived?.Invoke(this, EventArgs.Empty));
HubConnection.On("RefreshRoomList", () => OnRefreshRoomListReceived?.Invoke(this, EventArgs.Empty));
HubConnection.On("RefreshContactsList", () => OnRefreshContactsListReceived?.Invoke(this, EventArgs.Empty));
HubConnection.On<ServerConfig>("ReceiveServerConfig", (serverConfig) => OnServerConfigReceived?.Invoke(this, new ServerConfigEventArgs { ServerConfig = serverConfig }));
HubConnection.On<List<User>>("RoomUserList", (userList) => OnRoomUserListReceived?.Invoke(this, new RoomListEventArgs { UserList = userList }));
HubConnection.On<string>("GuestJoin", (username) => OnGuestUserJoin?.Invoke(this, new GuestUserJoinEventArgs { Username = username }));
HubConnection.On("RoomDeleted", () => OnRoomDeleted?.Invoke(this, EventArgs.Empty));
HubConnection.On("ForceSignOut", () => OnUserForceLogout?.Invoke(this, EventArgs.Empty));
HubConnection.Closed += HubConnection_Closed;
HubConnection.Reconnecting += HubConnection_Reconnecting;
HubConnection.Reconnected += HubConnection_Reconnected;
// start connection
try
{
await HubConnection.StartAsync();
}
catch (HttpRequestException ex)
{
_loggingService.LogString($"Unable To Connect To SignalR.\n{ex.Message}\n{ex.StackTrace}");
return;
}
// ensure current user is up to date (particularly status)
await _apiService.SetCurrentUser();
}
public async Task StopAsync()
{
if (HubConnection != null && HubConnection.State == HubConnectionState.Connected)
{
await HubConnection.StopAsync();
}
}
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (HubConnection != null && HubConnection.State == HubConnectionState.Disconnected)
{
await HubConnection.DisposeAsync();
HubConnection = null;
CurrentRoom = null;
}
}
public async Task DisposeAsync()
{
if (HubConnection != null && HubConnection.State == HubConnectionState.Disconnected)
{
await HubConnection.DisposeAsync();
HubConnection = null;
CurrentRoom = null;
}
}
public async Task JoinRoomAsync(Room? room = null)
{
if (HubConnection == null || HubConnection.State != HubConnectionState.Connected) throw new InvalidOperationException("Function was called before connection was made.");
// assume user is trying to join lobby if room is null (does not have db entry)
room ??= new Room
{
Id = "LOBBY",
Name = "Lobby"
};
if (CurrentRoom != null) await HubConnection.SendAsync("LeaveRoom", _apiService.CurrentUser, CurrentRoom);
await HubConnection.SendAsync("JoinRoom", _apiService.CurrentUser, room);
CurrentRoom = room;
}
public async Task LeaveRoomAsync()
{
if (HubConnection == null || HubConnection.State != HubConnectionState.Connected) throw new InvalidOperationException("Function was called before connection was made.");
if (CurrentRoom != null) await HubConnection.SendAsync("LeaveRoom", _apiService.CurrentUser, CurrentRoom);
CurrentRoom = null;
}
public async Task PostMessageAsync(Message message)
{
if (HubConnection == null || HubConnection.State != HubConnectionState.Connected) throw new InvalidOperationException("Function was called before connection was made.");
await HubConnection.SendAsync("SendMessage", _apiService.CurrentUser, message, CurrentRoom);
}
public async Task SendDirectMessageAsync(UserInformationDto user, Message message)
{
await _apiService.RefreshSessionIfInvalid();
if (HubConnection == null || HubConnection.State != HubConnectionState.Connected) throw new InvalidOperationException("Function was called before connection was made.");
await HubConnection.SendAsync("SendDirectMessage", _apiService.CurrentUser, user, message);
}
public async Task UpdateStatus(int status)
{
if (HubConnection == null || HubConnection.State != HubConnectionState.Connected) throw new InvalidOperationException("Function was called before connection was made.");
await HubConnection.SendAsync("UpdateStatus", _apiService.CurrentUser, status);
// anything that changes the user should tell the api service to set it again
await _apiService.SetCurrentUser();
}
private Task HubConnection_Closed(Exception? arg)
{
OnServerDisconnect?.Invoke(this, new ServerConnectionClosedEventArgs { Error = arg });
return Task.CompletedTask;
}
private Task HubConnection_Reconnecting(Exception? arg)
{
OnServerReconnecting?.Invoke(this, new ServerConnectionReconnectingEventArgs { Error = arg });
return Task.CompletedTask;
}
private Task HubConnection_Reconnected(string? arg)
{
OnServerReconnected?.Invoke(this, EventArgs.Empty);
return Task.CompletedTask;
}
}
}

View File

@ -0,0 +1,161 @@
using Microsoft.AspNetCore.SignalR.Client;
using QtCNETAPI.Dtos.User;
using QtCNETAPI.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace QtCNETAPI.Services.GatewayService
{
public interface IGatewayService
{
// VARIABLES
/// <summary>
/// The Current Room The Current User Is In
/// </summary>
public Room? CurrentRoom { get; }
/// <summary>
/// The Current Connection To The Gateway
/// </summary>
public HubConnection? HubConnection { get; }
/// <summary>
/// The URI in which the client will try to use for SignalR requests.
/// </summary>
public string? GWUri { get; }
// FUNCTIONS
/// <summary>
/// The Function Used To Connect To The Gateway Server Asynchronously
/// </summary>
/// <returns></returns>
public Task StartAsync();
/// <summary>
/// Stops The Connection To The Gateway Server
/// </summary>
/// <returns></returns>
public Task StopAsync();
/// <summary>
/// Disposes Of The Gateway Connection And Clears Other Variables
/// </summary>
/// <returns></returns>
public Task DisposeAsync();
/// <summary>
/// Joins The Current User To A Room On The Server
/// </summary>
/// <param name="room">Room To Join</param>
/// <returns></returns>
/// <exception cref="InvalidOperationException">Thrown if the function is called before the connection is established.</exception>
public Task JoinRoomAsync(Room? room = null);
/// <summary>
/// Leaves The Current Room The Current User Is In
/// </summary>
/// <returns></returns>
public Task LeaveRoomAsync();
/// <summary>
/// Posts A Message To Whatever Room The User Is In
/// </summary>
/// <param name="message">Message To Post</param>
/// <returns></returns>
/// <exception cref="InvalidOperationException">Thrown if the function is called before the connection is established.</exception>
public Task PostMessageAsync(Message message);
/// <summary>
/// Sends A Direct Message To The Specified User
/// </summary>
/// <param name="user">The User You Wish To DM</param>
/// <param name="currentUser">Yourself</param>
/// <param name="message"></param>
/// <returns></returns>
public Task SendDirectMessageAsync(UserInformationDto user, Message message);
/// <summary>
/// Updates The Status For The Current User
/// </summary>
/// <param name="status">The Status You Want To Set On The Current User</param>
/// <returns></returns>
public Task UpdateStatus(int status);
// EVENTS
/// <summary>
/// When A Room Message Is Received, This Event Fires
/// </summary>
public event EventHandler OnRoomMessageReceived;
/// <summary>
/// Fires When The User List For A Room Is Received
/// </summary>
public event EventHandler OnRoomUserListReceived;
/// <summary>
/// Fires When The Room The User Is In Gets Deleted By An Admin
/// </summary>
public event EventHandler OnRoomDeleted;
/// <summary>
/// Fires When A Guest User Joins Your Room
/// </summary>
public event EventHandler OnGuestUserJoin;
/// <summary>
/// When A Client Function/Event Is Received, This Event Fires
/// </summary>
public event EventHandler OnClientFunctionReceived;
/// <summary>
/// When The Client Received A DM, This Event Fires
/// </summary>
public event EventHandler OnDirectMessageReceived;
/// <summary>
/// Fires When The Client Receives The Request To Refresh Its User List
/// </summary>
public event EventHandler OnRefreshUserListsReceived;
/// <summary>
/// Fires When The Client Receives The Request To Refresh Its Room List
/// </summary>
public event EventHandler OnRefreshRoomListReceived;
/// <summary>
/// Fires When The Client Receives The Request To Refresh Its Contacts List
/// </summary>
public event EventHandler OnRefreshContactsListReceived;
/// <summary>
/// When The Server Config Is Received, This Event Fires
/// </summary>
public event EventHandler OnServerConfigReceived;
/// <summary>
/// When The Connection To The Gateway Is Lost, This Event Fires
/// </summary>
public event EventHandler OnServerDisconnect;
/// <summary>
/// When The Connection Attempts To Reconnect, This Event Fires
/// </summary>
public event EventHandler OnServerReconnecting;
/// <summary>
/// When the Connection Reconnects, This Event Fires
/// </summary>
public event EventHandler OnServerReconnected;
/// <summary>
/// Fires When The Current User Is Signed Out By The Server
/// </summary>
public event EventHandler OnUserForceLogout;
}
}

View File

@ -0,0 +1,93 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Abstractions;
namespace QtCNETAPI.Services
{
public class LoggingService : IDisposable, ILogger
{
private DateTime LogDate { get; set; }
private string LogFilePath { get; set; }
private StreamWriter LogFile { get; set; }
public LoggingService()
{
LogDate = DateTime.Now;
LogFilePath = $"./Logs/QtCClientLog_{LogDate:ddMMyyy-hhmm}.log";
// create log file
if (!Directory.Exists("./Logs")) Directory.CreateDirectory("./Logs");
LogFile = new StreamWriter(File.Create(LogFilePath));
Debug.WriteLine($"Log File Created At {LogFilePath}");
}
public void LogString(string message)
{
try
{
Debug.WriteLine($"({DateTime.Now.ToLocalTime():hh:mm}) {message}");
LogFile.WriteLine($"({DateTime.Now.ToLocalTime():hh:mm}) {message}");
} catch (ObjectDisposedException)
{
}
}
public void LogModel<T>(T model)
{
try
{
// serialize the model as json
string modelSerialized = JsonSerializer.Serialize(model, options: new JsonSerializerOptions { WriteIndented = true });
// log it
Debug.WriteLine($"({DateTime.Now.ToLocalTime():hh:mm}) {modelSerialized}");
LogFile.WriteLine($"({DateTime.Now.ToLocalTime():hh:mm}) {modelSerialized}");
} catch (ObjectDisposedException)
{
}
}
public void Dispose()
{
LogFile.WriteLine("--- END OF LOG ---");
LogFile.Close();
LogFile.Dispose();
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
{
try
{
// format message
string message = $"({DateTime.Now.ToLocalTime():hh:mm}) [{logLevel}] {formatter(state, exception)}";
// log it
Debug.WriteLine(message);
LogFile.WriteLine(message);
} catch (ObjectDisposedException)
{
}
}
public bool IsEnabled(LogLevel logLevel) => true;
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => default!;
}
public class LoggingServiceProvider(LoggingService? loggingService = null) : ILoggerProvider
{
public ILogger CreateLogger(string categoryName)
{
if (loggingService != null) return loggingService;
else return new LoggingService();
}
public void Dispose() { }
}
}

4
qtcnet-client.slnx Normal file
View File

@ -0,0 +1,4 @@
<Solution>
<Project Path="qtcnet-client/qtcnet-client.csproj" />
<Project Path="QtCNETAPI/QtCNETAPI.csproj" />
</Solution>

View File

@ -0,0 +1,62 @@
namespace qtcnet_client.Controls
{
partial class BrandingControl
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
pictureBox1 = new PictureBox();
((System.ComponentModel.ISupportInitialize)pictureBox1).BeginInit();
SuspendLayout();
//
// pictureBox1
//
pictureBox1.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
pictureBox1.Image = Properties.Resources.QtCNETIcon;
pictureBox1.Location = new Point(0, 0);
pictureBox1.Name = "pictureBox1";
pictureBox1.Size = new Size(122, 110);
pictureBox1.SizeMode = PictureBoxSizeMode.Zoom;
pictureBox1.TabIndex = 0;
pictureBox1.TabStop = false;
//
// BrandingControl
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
BackColor = Color.Transparent;
Controls.Add(pictureBox1);
Name = "BrandingControl";
Size = new Size(122, 110);
((System.ComponentModel.ISupportInitialize)pictureBox1).EndInit();
ResumeLayout(false);
}
#endregion
private PictureBox pictureBox1;
}
}

View File

@ -0,0 +1,18 @@
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.Controls
{
public partial class BrandingControl : UserControl
{
public BrandingControl()
{
InitializeComponent();
}
}
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -0,0 +1,96 @@
namespace qtcnet_client.Controls
{
partial class ContactControl
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
pbProfilePic = new PictureBox();
lblUsername = new Label();
lblStatus = new Label();
((System.ComponentModel.ISupportInitialize)pbProfilePic).BeginInit();
SuspendLayout();
//
// pbProfilePic
//
pbProfilePic.Image = Properties.Resources.DefaultPfp;
pbProfilePic.Location = new Point(3, 3);
pbProfilePic.Name = "pbProfilePic";
pbProfilePic.Size = new Size(37, 35);
pbProfilePic.SizeMode = PictureBoxSizeMode.Zoom;
pbProfilePic.TabIndex = 0;
pbProfilePic.TabStop = false;
//
// lblUsername
//
lblUsername.AutoSize = true;
lblUsername.Font = new Font("Segoe UI", 8F, FontStyle.Bold, GraphicsUnit.Point, 0);
lblUsername.ForeColor = Color.Black;
lblUsername.Location = new Point(44, 8);
lblUsername.Name = "lblUsername";
lblUsername.Size = new Size(59, 13);
lblUsername.TabIndex = 1;
lblUsername.Text = "Username";
lblUsername.DoubleClick += lblUsername_DoubleClick;
lblUsername.MouseLeave += lblUsername_MouseLeave;
lblUsername.MouseHover += lblUsername_MouseHover;
//
// lblStatus
//
lblStatus.AutoSize = true;
lblStatus.Font = new Font("Segoe UI Semibold", 7F, FontStyle.Bold | FontStyle.Italic, GraphicsUnit.Point, 0);
lblStatus.ForeColor = SystemColors.AppWorkspace;
lblStatus.Location = new Point(46, 21);
lblStatus.Name = "lblStatus";
lblStatus.Size = new Size(40, 12);
lblStatus.TabIndex = 2;
lblStatus.Text = "\"Status\"";
//
// ContactControl
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
BackColor = Color.Transparent;
Controls.Add(lblUsername);
Controls.Add(lblStatus);
Controls.Add(pbProfilePic);
DoubleBuffered = true;
Name = "ContactControl";
Size = new Size(150, 43);
Load += ContactControl_Load;
Paint += ContactControl_Paint;
((System.ComponentModel.ISupportInitialize)pbProfilePic).EndInit();
ResumeLayout(false);
PerformLayout();
}
#endregion
private PictureBox pbProfilePic;
private Label lblUsername;
private Label lblStatus;
}
}

View File

@ -0,0 +1,94 @@
using qtcnet_client.Properties;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Text;
using System.Windows.Forms;
namespace qtcnet_client.Controls
{
public partial class ContactControl : UserControl
{
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public string UserId { get; set; } = string.Empty; // only for distinction
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string Username { get; set; } = "Username";
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string TextStatus { get; set; } = "Status";
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public int Status { get; set; } = 0;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Image ProfilePic { get; set; } = Resources.DefaultPfp;
public event EventHandler? OnContactDoubleClicked;
bool _isHovering = false;
public ContactControl()
{
InitializeComponent();
}
private void ContactControl_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
Rectangle bgBounds = ClientRectangle;
Color statusColor = Color.Transparent;
switch (Status)
{
case 0:
statusColor = Color.LightGray;
break;
case 1:
statusColor = Color.LawnGreen;
break;
case 2:
statusColor = Color.Gold;
break;
case 3:
statusColor = Color.Red;
break;
}
// draw background based on contacts status
using LinearGradientBrush lgb = new(bgBounds, statusColor, BackColor, LinearGradientMode.Horizontal);
g.FillRectangle(lgb, bgBounds);
}
private void ContactControl_Load(object sender, EventArgs e)
{
lblUsername.Text = Username;
lblStatus.Text = $"'{TextStatus}'";
pbProfilePic.Image = ProfilePic;
if (Status == 0)
{
lblStatus.Visible = false;
lblUsername.Location = new(44, 15);
}
}
private void lblUsername_MouseHover(object sender, EventArgs e)
{
if (!_isHovering)
{
lblUsername.ForeColor = Color.White;
_isHovering = true;
}
}
private void lblUsername_MouseLeave(object sender, EventArgs e)
{
if (_isHovering)
{
lblUsername.ForeColor = Color.Black;
_isHovering = false;
}
}
private void lblUsername_DoubleClick(object sender, EventArgs e) => OnContactDoubleClicked?.Invoke(this, EventArgs.Empty);
}
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -0,0 +1,133 @@
namespace qtcnet_client.Controls
{
partial class CurrentProfileControl
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
pbCurrentProfilePic = new PictureBox();
llblSignOut = new LinkLabel();
linkLabel1 = new LinkLabel();
pictureBox1 = new PictureBox();
lblUsername = new Label();
lblCurrencyAmount = new Label();
((System.ComponentModel.ISupportInitialize)pbCurrentProfilePic).BeginInit();
((System.ComponentModel.ISupportInitialize)pictureBox1).BeginInit();
SuspendLayout();
//
// pbCurrentProfilePic
//
pbCurrentProfilePic.Image = Properties.Resources.DefaultPfp;
pbCurrentProfilePic.Location = new Point(3, 3);
pbCurrentProfilePic.Name = "pbCurrentProfilePic";
pbCurrentProfilePic.Size = new Size(69, 67);
pbCurrentProfilePic.SizeMode = PictureBoxSizeMode.Zoom;
pbCurrentProfilePic.TabIndex = 0;
pbCurrentProfilePic.TabStop = false;
//
// llblSignOut
//
llblSignOut.AutoSize = true;
llblSignOut.Location = new Point(75, 24);
llblSignOut.Name = "llblSignOut";
llblSignOut.Size = new Size(55, 15);
llblSignOut.TabIndex = 2;
llblSignOut.TabStop = true;
llblSignOut.Text = "Not You?";
//
// linkLabel1
//
linkLabel1.AutoSize = true;
linkLabel1.Location = new Point(130, 24);
linkLabel1.Name = "linkLabel1";
linkLabel1.Size = new Size(64, 15);
linkLabel1.TabIndex = 3;
linkLabel1.TabStop = true;
linkLabel1.Text = "Edit Profile";
//
// pictureBox1
//
pictureBox1.Image = Properties.Resources.CurrencyIcon;
pictureBox1.Location = new Point(79, 49);
pictureBox1.Name = "pictureBox1";
pictureBox1.Size = new Size(14, 14);
pictureBox1.SizeMode = PictureBoxSizeMode.AutoSize;
pictureBox1.TabIndex = 4;
pictureBox1.TabStop = false;
//
// lblUsername
//
lblUsername.AutoSize = true;
lblUsername.Font = new Font("Segoe UI", 9F, FontStyle.Bold | FontStyle.Italic);
lblUsername.ForeColor = Color.Black;
lblUsername.Location = new Point(76, 9);
lblUsername.Name = "lblUsername";
lblUsername.Size = new Size(94, 15);
lblUsername.TabIndex = 1;
lblUsername.Text = "Welcome, User!";
//
// lblCurrencyAmount
//
lblCurrencyAmount.AutoSize = true;
lblCurrencyAmount.Font = new Font("Segoe UI", 9F, FontStyle.Bold);
lblCurrencyAmount.ForeColor = Color.Black;
lblCurrencyAmount.Location = new Point(94, 49);
lblCurrencyAmount.Name = "lblCurrencyAmount";
lblCurrencyAmount.Size = new Size(35, 15);
lblCurrencyAmount.TabIndex = 5;
lblCurrencyAmount.Text = "9999";
//
// CurrentProfileControl
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
AutoSize = true;
BackColor = Color.Transparent;
Controls.Add(lblCurrencyAmount);
Controls.Add(pictureBox1);
Controls.Add(linkLabel1);
Controls.Add(llblSignOut);
Controls.Add(lblUsername);
Controls.Add(pbCurrentProfilePic);
Name = "CurrentProfileControl";
Size = new Size(197, 73);
Load += CurrentProfileControl_Load;
((System.ComponentModel.ISupportInitialize)pbCurrentProfilePic).EndInit();
((System.ComponentModel.ISupportInitialize)pictureBox1).EndInit();
ResumeLayout(false);
PerformLayout();
}
#endregion
private PictureBox pbCurrentProfilePic;
private LinkLabel llblSignOut;
private LinkLabel linkLabel1;
private PictureBox pictureBox1;
private Label lblUsername;
private Label lblCurrencyAmount;
}
}

View File

@ -0,0 +1,33 @@
using qtcnet_client.Properties;
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.Controls
{
public partial class CurrentProfileControl : UserControl
{
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string Username { get; set; } = "Username";
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public int CurrencyCount { get; set; } = 0;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Image ProfileImage { get; set; } = Resources.DefaultPfp;
public CurrentProfileControl()
{
InitializeComponent();
}
private void CurrentProfileControl_Load(object sender, EventArgs e)
{
lblUsername.Text = $"Welcome, {Username}!";
lblCurrencyAmount.Text = CurrencyCount.ToString();
pbCurrentProfilePic.Image = ProfileImage;
}
}
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -0,0 +1,158 @@
namespace qtcnet_client.Controls
{
partial class LoginControl
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
pbDefaultPfp = new Krypton.Toolkit.KryptonPictureBox();
txtEmail = new Krypton.Toolkit.KryptonTextBox();
lblEmail = new Label();
lblPassword = new Label();
cbRememberMe = new Krypton.Toolkit.KryptonCheckBox();
llblForgotPassword = new Krypton.Toolkit.KryptonLinkLabel();
llblRegister = new Krypton.Toolkit.KryptonLinkLabel();
btnLogin = new Krypton.Toolkit.KryptonButton();
txtPassword = new Krypton.Toolkit.KryptonTextBox();
((System.ComponentModel.ISupportInitialize)pbDefaultPfp).BeginInit();
SuspendLayout();
//
// pbDefaultPfp
//
pbDefaultPfp.Image = Properties.Resources.DefaultPfp;
pbDefaultPfp.Location = new Point(11, 7);
pbDefaultPfp.Name = "pbDefaultPfp";
pbDefaultPfp.Size = new Size(128, 128);
pbDefaultPfp.SizeMode = PictureBoxSizeMode.AutoSize;
pbDefaultPfp.TabIndex = 0;
pbDefaultPfp.TabStop = false;
//
// txtEmail
//
txtEmail.Location = new Point(187, 23);
txtEmail.Name = "txtEmail";
txtEmail.Size = new Size(375, 23);
txtEmail.TabIndex = 1;
//
// lblEmail
//
lblEmail.AutoSize = true;
lblEmail.ForeColor = Color.White;
lblEmail.Location = new Point(145, 27);
lblEmail.Name = "lblEmail";
lblEmail.Size = new Size(36, 15);
lblEmail.TabIndex = 2;
lblEmail.Text = "Email";
//
// lblPassword
//
lblPassword.AutoSize = true;
lblPassword.ForeColor = Color.White;
lblPassword.Location = new Point(145, 57);
lblPassword.Name = "lblPassword";
lblPassword.Size = new Size(57, 15);
lblPassword.TabIndex = 4;
lblPassword.Text = "Password";
//
// cbRememberMe
//
cbRememberMe.Location = new Point(208, 81);
cbRememberMe.Name = "cbRememberMe";
cbRememberMe.PaletteMode = Krypton.Toolkit.PaletteMode.Office2007Silver;
cbRememberMe.Size = new Size(171, 20);
cbRememberMe.TabIndex = 3;
cbRememberMe.Values.Text = "Remember Me For 30 Days";
//
// llblForgotPassword
//
llblForgotPassword.Location = new Point(450, 81);
llblForgotPassword.Name = "llblForgotPassword";
llblForgotPassword.Size = new Size(112, 20);
llblForgotPassword.TabIndex = 4;
llblForgotPassword.Values.Text = "Forgot Passsword?";
//
// llblRegister
//
llblRegister.Location = new Point(448, 97);
llblRegister.Name = "llblRegister";
llblRegister.Size = new Size(118, 20);
llblRegister.TabIndex = 5;
llblRegister.Values.Text = "New Here? Register";
llblRegister.LinkClicked += llblRegister_LinkClicked;
//
// btnLogin
//
btnLogin.Location = new Point(484, 119);
btnLogin.Name = "btnLogin";
btnLogin.Size = new Size(90, 25);
btnLogin.TabIndex = 6;
btnLogin.Values.DropDownArrowColor = Color.Empty;
btnLogin.Values.Text = "Login";
btnLogin.Click += btnLogin_Click;
//
// txtPassword
//
txtPassword.Location = new Point(208, 52);
txtPassword.Name = "txtPassword";
txtPassword.PasswordChar = '●';
txtPassword.Size = new Size(354, 23);
txtPassword.TabIndex = 2;
txtPassword.UseSystemPasswordChar = true;
//
// LoginControl
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
BackColor = Color.Transparent;
Controls.Add(txtPassword);
Controls.Add(btnLogin);
Controls.Add(llblRegister);
Controls.Add(llblForgotPassword);
Controls.Add(cbRememberMe);
Controls.Add(lblPassword);
Controls.Add(lblEmail);
Controls.Add(txtEmail);
Controls.Add(pbDefaultPfp);
Name = "LoginControl";
Size = new Size(575, 146);
((System.ComponentModel.ISupportInitialize)pbDefaultPfp).EndInit();
ResumeLayout(false);
PerformLayout();
}
#endregion
private Krypton.Toolkit.KryptonPictureBox pbDefaultPfp;
private Krypton.Toolkit.KryptonTextBox txtEmail;
private Label lblEmail;
private Label lblPassword;
private Krypton.Toolkit.KryptonCheckBox cbRememberMe;
private Krypton.Toolkit.KryptonLinkLabel llblForgotPassword;
private Krypton.Toolkit.KryptonLinkLabel llblRegister;
private Krypton.Toolkit.KryptonButton btnLogin;
private Krypton.Toolkit.KryptonTextBox txtPassword;
}
}

View File

@ -0,0 +1,77 @@
using Krypton.Toolkit;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Reflection.Metadata.Ecma335;
using System.Text;
using System.Windows.Forms;
namespace qtcnet_client.Controls
{
public partial class LoginControl : UserControl
{
public string Email { get; private set; } = string.Empty;
public string Password { get; private set; } = string.Empty;
public bool RememberMe { get; private set; } = false;
public event EventHandler? OnSuccessfulLogin;
public event EventHandler? OnRegisterPressed;
public LoginControl()
{
InitializeComponent();
}
private void btnLogin_Click(object sender, EventArgs e)
{
ToggleControls(false);
bool formComplete = !string.IsNullOrWhiteSpace(txtEmail.Text) || !string.IsNullOrWhiteSpace(txtPassword.Text);
if (formComplete)
{
Email = txtEmail.Text;
Password = txtPassword.Text;
RememberMe = cbRememberMe.Checked;
OnSuccessfulLogin?.Invoke(this, EventArgs.Empty);
}
else
{
KryptonMessageBox.Show("A Required Field Is Missing. Please Complete The Form.", "Oops.");
ToggleControls(true);
}
}
private void llblRegister_LinkClicked(object sender, EventArgs e)
{
ToggleControls(false);
OnRegisterPressed?.Invoke(this, EventArgs.Empty);
}
public void ToggleControls(bool toggle)
{
SuspendLayout();
txtEmail.Enabled = toggle;
txtPassword.Enabled = toggle;
cbRememberMe.Enabled = toggle;
llblForgotPassword.Enabled = toggle;
llblRegister.Enabled = toggle;
btnLogin.Enabled = toggle;
ResumeLayout();
}
public void ClearControls()
{
SuspendLayout();
txtEmail.Clear();
txtPassword.Clear();
cbRememberMe.Checked = false;
ResumeLayout();
}
}
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -0,0 +1,284 @@
namespace qtcnet_client.Controls
{
partial class MainTabControl
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainTabControl));
ListViewItem listViewItem1 = new ListViewItem("Stock Market", "StockMarket");
ListViewItem listViewItem2 = new ListViewItem("Guess The Number", "NumberGuessGame");
ListViewItem listViewItem3 = new ListViewItem("Tic-Tac-Toe (Multiplayer)", "TicTacToe");
tcMain = new TabControl();
tpContacts = new TabPage();
tlpContactsList = new TableLayoutPanel();
tpRooms = new TabPage();
tlpRoomsList = new TableLayoutPanel();
tpUsers = new TabPage();
lvUserList = new ListView();
ilStatusIcons = new ImageList(components);
tpGames = new TabPage();
lvGameList = new ListView();
ilGameIcons = new ImageList(components);
tpStore = new TabPage();
listView1 = new ListView();
ilTabIcons = new ImageList(components);
ilStoreIcons = new ImageList(components);
tcMain.SuspendLayout();
tpContacts.SuspendLayout();
tpRooms.SuspendLayout();
tpUsers.SuspendLayout();
tpGames.SuspendLayout();
tpStore.SuspendLayout();
SuspendLayout();
//
// tcMain
//
tcMain.Controls.Add(tpContacts);
tcMain.Controls.Add(tpRooms);
tcMain.Controls.Add(tpUsers);
tcMain.Controls.Add(tpGames);
tcMain.Controls.Add(tpStore);
tcMain.Font = new Font("Segoe UI", 9F, FontStyle.Bold, GraphicsUnit.Point, 0);
tcMain.ImageList = ilTabIcons;
tcMain.Location = new Point(0, 0);
tcMain.Multiline = true;
tcMain.Name = "tcMain";
tcMain.SelectedIndex = 0;
tcMain.Size = new Size(380, 577);
tcMain.TabIndex = 0;
//
// tpContacts
//
tpContacts.BorderStyle = BorderStyle.Fixed3D;
tpContacts.Controls.Add(tlpContactsList);
tpContacts.Font = new Font("Segoe UI", 9F, FontStyle.Regular, GraphicsUnit.Point, 0);
tpContacts.ImageKey = "Contacts";
tpContacts.Location = new Point(4, 24);
tpContacts.Name = "tpContacts";
tpContacts.Padding = new Padding(3);
tpContacts.RightToLeft = RightToLeft.No;
tpContacts.Size = new Size(372, 549);
tpContacts.TabIndex = 1;
tpContacts.Text = "Contacts";
tpContacts.UseVisualStyleBackColor = true;
//
// tlpContactsList
//
tlpContactsList.ColumnCount = 1;
tlpContactsList.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
tlpContactsList.Dock = DockStyle.Fill;
tlpContactsList.Location = new Point(3, 3);
tlpContactsList.Name = "tlpContactsList";
tlpContactsList.Padding = new Padding(0, 5, 0, 5);
tlpContactsList.RowCount = 1;
tlpContactsList.RowStyles.Add(new RowStyle());
tlpContactsList.Size = new Size(362, 539);
tlpContactsList.TabIndex = 0;
//
// tpRooms
//
tpRooms.BorderStyle = BorderStyle.Fixed3D;
tpRooms.Controls.Add(tlpRoomsList);
tpRooms.Font = new Font("Segoe UI", 9F, FontStyle.Regular, GraphicsUnit.Point, 0);
tpRooms.ImageKey = "Rooms";
tpRooms.Location = new Point(4, 24);
tpRooms.Name = "tpRooms";
tpRooms.Padding = new Padding(3);
tpRooms.RightToLeft = RightToLeft.No;
tpRooms.Size = new Size(372, 549);
tpRooms.TabIndex = 7;
tpRooms.Text = "Rooms";
tpRooms.UseVisualStyleBackColor = true;
//
// tlpRoomsList
//
tlpRoomsList.ColumnCount = 1;
tlpRoomsList.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
tlpRoomsList.Dock = DockStyle.Fill;
tlpRoomsList.Location = new Point(3, 3);
tlpRoomsList.Name = "tlpRoomsList";
tlpRoomsList.Padding = new Padding(0, 3, 0, 3);
tlpRoomsList.RowCount = 1;
tlpRoomsList.RowStyles.Add(new RowStyle());
tlpRoomsList.Size = new Size(362, 539);
tlpRoomsList.TabIndex = 1;
//
// tpUsers
//
tpUsers.BorderStyle = BorderStyle.Fixed3D;
tpUsers.Controls.Add(lvUserList);
tpUsers.Font = new Font("Segoe UI", 9F, FontStyle.Regular, GraphicsUnit.Point, 0);
tpUsers.ImageKey = "Users";
tpUsers.Location = new Point(4, 24);
tpUsers.Name = "tpUsers";
tpUsers.Size = new Size(372, 549);
tpUsers.TabIndex = 3;
tpUsers.Text = "Users";
tpUsers.UseVisualStyleBackColor = true;
//
// lvUserList
//
lvUserList.BorderStyle = BorderStyle.FixedSingle;
lvUserList.Location = new Point(3, 3);
lvUserList.MultiSelect = false;
lvUserList.Name = "lvUserList";
lvUserList.Size = new Size(364, 541);
lvUserList.SmallImageList = ilStatusIcons;
lvUserList.TabIndex = 0;
lvUserList.UseCompatibleStateImageBehavior = false;
lvUserList.View = View.List;
//
// ilStatusIcons
//
ilStatusIcons.ColorDepth = ColorDepth.Depth32Bit;
ilStatusIcons.ImageStream = (ImageListStreamer)resources.GetObject("ilStatusIcons.ImageStream");
ilStatusIcons.TransparentColor = Color.Transparent;
ilStatusIcons.Images.SetKeyName(0, "Offline");
ilStatusIcons.Images.SetKeyName(1, "Online");
ilStatusIcons.Images.SetKeyName(2, "Away");
ilStatusIcons.Images.SetKeyName(3, "DoNotDisturb");
//
// tpGames
//
tpGames.BorderStyle = BorderStyle.Fixed3D;
tpGames.Controls.Add(lvGameList);
tpGames.Font = new Font("Segoe UI", 9F, FontStyle.Regular, GraphicsUnit.Point, 0);
tpGames.ImageKey = "Games";
tpGames.Location = new Point(4, 24);
tpGames.Name = "tpGames";
tpGames.Padding = new Padding(3);
tpGames.Size = new Size(372, 549);
tpGames.TabIndex = 5;
tpGames.Text = "Games";
tpGames.UseVisualStyleBackColor = true;
//
// lvGameList
//
lvGameList.Dock = DockStyle.Fill;
lvGameList.Font = new Font("Segoe UI", 9F, FontStyle.Bold, GraphicsUnit.Point, 0);
listViewItem1.ToolTipText = "Stock Market";
listViewItem2.ToolTipText = "Guess The Number";
listViewItem3.ToolTipText = "Tic-Tac-Toe (Multiplayer)";
lvGameList.Items.AddRange(new ListViewItem[] { listViewItem1, listViewItem2, listViewItem3 });
lvGameList.LargeImageList = ilGameIcons;
lvGameList.Location = new Point(3, 3);
lvGameList.MultiSelect = false;
lvGameList.Name = "lvGameList";
lvGameList.Size = new Size(362, 539);
lvGameList.SmallImageList = ilGameIcons;
lvGameList.TabIndex = 0;
lvGameList.UseCompatibleStateImageBehavior = false;
//
// ilGameIcons
//
ilGameIcons.ColorDepth = ColorDepth.Depth32Bit;
ilGameIcons.ImageStream = (ImageListStreamer)resources.GetObject("ilGameIcons.ImageStream");
ilGameIcons.TransparentColor = Color.Transparent;
ilGameIcons.Images.SetKeyName(0, "StockMarket");
ilGameIcons.Images.SetKeyName(1, "NumberGuessGame");
ilGameIcons.Images.SetKeyName(2, "TicTacToe");
//
// tpStore
//
tpStore.BorderStyle = BorderStyle.Fixed3D;
tpStore.Controls.Add(listView1);
tpStore.Font = new Font("Segoe UI", 9F, FontStyle.Regular, GraphicsUnit.Point, 0);
tpStore.ImageKey = "Store";
tpStore.Location = new Point(4, 24);
tpStore.Name = "tpStore";
tpStore.Padding = new Padding(3);
tpStore.Size = new Size(372, 549);
tpStore.TabIndex = 8;
tpStore.Text = "Store";
tpStore.UseVisualStyleBackColor = true;
//
// listView1
//
listView1.Dock = DockStyle.Fill;
listView1.Location = new Point(3, 3);
listView1.MultiSelect = false;
listView1.Name = "listView1";
listView1.Size = new Size(362, 539);
listView1.TabIndex = 0;
listView1.UseCompatibleStateImageBehavior = false;
//
// ilTabIcons
//
ilTabIcons.ColorDepth = ColorDepth.Depth32Bit;
ilTabIcons.ImageStream = (ImageListStreamer)resources.GetObject("ilTabIcons.ImageStream");
ilTabIcons.TransparentColor = Color.Transparent;
ilTabIcons.Images.SetKeyName(0, "Contacts");
ilTabIcons.Images.SetKeyName(1, "Rooms");
ilTabIcons.Images.SetKeyName(2, "Users");
ilTabIcons.Images.SetKeyName(3, "Games");
ilTabIcons.Images.SetKeyName(4, "Store");
//
// ilStoreIcons
//
ilStoreIcons.ColorDepth = ColorDepth.Depth32Bit;
ilStoreIcons.ImageSize = new Size(32, 32);
ilStoreIcons.TransparentColor = Color.Transparent;
//
// MainTabControl
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
AutoSize = true;
BackColor = Color.Transparent;
Controls.Add(tcMain);
DoubleBuffered = true;
Name = "MainTabControl";
Size = new Size(383, 580);
tcMain.ResumeLayout(false);
tpContacts.ResumeLayout(false);
tpRooms.ResumeLayout(false);
tpUsers.ResumeLayout(false);
tpGames.ResumeLayout(false);
tpStore.ResumeLayout(false);
ResumeLayout(false);
}
#endregion
private TabControl tcMain;
private TabPage tpContacts;
private TabPage tpUsers;
private ListView lvUserList;
private TabPage tpGames;
private ListView lvGameList;
private ImageList ilTabIcons;
private ImageList ilStatusIcons;
private TableLayoutPanel tlpContactsList;
private TabPage tpStore;
private ListView listView1;
private TabPage tpRooms;
private TableLayoutPanel tlpRoomsList;
private ImageList ilGameIcons;
private ImageList ilStoreIcons;
}
}

View File

@ -0,0 +1,68 @@
using Krypton.Toolkit;
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.Controls
{
public partial class MainTabControl : UserControl
{
public event EventHandler? OnContactControlDoubleClicked;
public event EventHandler? OnRoomControlDoubleClicked;
public MainTabControl()
{
InitializeComponent();
}
public void AddContacts(List<ContactControl> contactControls)
{
tlpContactsList.SuspendLayout();
foreach (ContactControl contactControl in contactControls)
{
contactControl.Width = tlpContactsList.ClientSize.Width;
contactControl.OnContactDoubleClicked += ContactControl_OnContactDoubleClicked;
}
tlpContactsList.Controls.Clear();
tlpContactsList.Controls.AddRange([.. contactControls.DistinctBy(ctrl => ctrl.UserId)]);
tlpContactsList.ResumeLayout(true);
}
private void ContactControl_OnContactDoubleClicked(object? sender, EventArgs e) => OnContactControlDoubleClicked?.Invoke(sender, e);
public void AddRooms(List<RoomControl> roomControls)
{
tlpRoomsList.SuspendLayout();
foreach (RoomControl roomControl in roomControls)
{
roomControl.Width = tlpRoomsList.ClientSize.Width;
roomControl.OnRoomDoubleClicked += RoomControl_OnRoomDoubleClicked;
}
tlpRoomsList.Controls.Clear();
tlpRoomsList.Controls.AddRange([.. roomControls.DistinctBy(ctrl => ctrl.RoomId)]);
tlpRoomsList.ResumeLayout(true);
}
private void RoomControl_OnRoomDoubleClicked(object? sender, EventArgs e) => OnRoomControlDoubleClicked?.Invoke(sender, e);
public void AddUsers(List<ListViewItem> users)
{
lvUserList.SuspendLayout();
lvUserList.Items.Clear();
lvUserList.Items.AddRange([.. users.DistinctBy(lvi => lvi.Tag)]); // Tag = UserId here
lvUserList.ResumeLayout(true);
}
}
}

View File

@ -0,0 +1,515 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="ilStatusIcons.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>121, 15</value>
</metadata>
<data name="ilStatusIcons.ImageStream" mimetype="application/x-microsoft.net.object.binary.base64">
<value>
AAEAAAD/////AQAAAAAAAAAMAgAAAEZTeXN0ZW0uV2luZG93cy5Gb3JtcywgQ3VsdHVyZT1uZXV0cmFs
LCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5BQEAAAAmU3lzdGVtLldpbmRvd3MuRm9ybXMu
SW1hZ2VMaXN0U3RyZWFtZXIBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAAAhQAAAJNU0Z0AUkBTAIBAQQB
AAGoAQABqAEAARABAAEQAQAE/wEhAQAI/wFCAU0BNgcAATYDAAEoAwABQAMAASADAAEBAQABIAYAASD/
AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AC4AAwYBBwM0AVQDUQGiA14B0gNaAekDYAHoA10B
0QNQAZ8DMQFNAwUBBhgAAwYBBwM0AVQDUQGiA14B0gNaAekDYAHoA10B0QNQAZ8DMQFNAwUBBhgAAwYB
BwM0AVQDUQGiA14B0gNaAekDYAHoA10B0QNQAZ8DMQFNAwUBBhgAAwYBBwM0AVQDUQGiA14B0gNaAekD
YAHoA10B0QNQAZ8DMQFNAwUBBhQAAyABLQNUAasDWwHkA08B9QMkAfsDOQH+AzkB/gMkAfsDUwH0A2IB
4QNRAaEDHgEqEAADIAEtA1QBqwNbAeQBSAFaAUgB9QEhAVUBIQH7ARUBVQEVAf4BFQFVARUB/gEhAVUB
IQH7A1MB9ANiAeEDUQGhAx4BKhAAAyABLQNUAasDWwHkAUgCWgH1ASECVQH7ARUCVQH+ARUCVQH+ASEC
VQH7A1MB9ANiAeEDUQGhAx4BKhAAAyABLQNUAasDWwHkAkgBWgH1AiEBVQH7AhUBVQH+AhUBVQH+AiEB
VQH7A1MB9ANiAeEDUQGhAx4BKgwAAxsBJQNYAb0DWgHyAz0B/gMwAf8DOQH/AzwB/wM2Af8DKgH/AyQB
/wNAAf0DWwHwA1YBsgMaASMIAAMbASUDWAG9A1oB8gEVAV0BFQH+AQABVwEAAf8BAAFnAQAB/wEAAWwB
AAH/AQABYQEAAf8BAAFMAQAB/wEAAUABAAH/ASoBQAEqAf0BVwFeAVcB8ANWAbIBGQEaARkBIwgAAxsB
JQNYAb0DWgHyARUCXQH+AQACVwH/AQACZwH/AQACbAH/AQACYQH/AQACTAH/AQACQAH/ASoCQAH9AVcC
XgHwA1YBsgEZAhoBIwgAAxsBJQNYAb0DWgHyAhUBXQH+AgABVwH/AgABZwH/AgABbAH/AgABYQH/AgAB
TAH/AgABQAH/AioBQAH9AlcBXgHwA1YBsgIZARoBIwQAAwMBBANSAaUDYAHzA0kB/wNVAf8DZQH/A3EB
/wN1Af8DcQH/A2QB/wNMAf8DMQH/AzkB/gNfAe4DUAGaAwMBBAMDAQQBUgFTAVIBpQFTAW8BUwHzAQAB
ggEAAf8BAAGZAQAB/wEAAbYBAAH/AQABzAEAAf8BAAHTAQAB/wEAAcsBAAH/AQABswEAAf8BAAGIAQAB
/wEAAVcBAAH/ARUBVQEVAf4BXAFiAVwB7gNQAZoDAwEEAwMBBAFSAlMBpQFTAm8B8wEAAoIB/wEAApkB
/wEAArYB/wEAAswB/wEAAtMB/wEAAssB/wEAArMB/wEAAogB/wEAAlcB/wEVAlUB/gFcAmIB7gNQAZoD
AwEEAwMBBAJSAVMBpQJTAW8B8wIAAYIB/wIAAZkB/wIAAbYB/wIAAcwB/wIAAdMB/wIAAcsB/wIAAbMB
/wIAAYgB/wIAAVcB/wIVAVUB/gJcAWIB7gNQAZoDAwEEAy0BRANgAegDeAH+A24B/wN7Af8DhQH/A4oB
/wOMAf8DigH/A4UB/wN2Af8DVwH/AzIB/wNAAf0DXgHdAyoBPwMtAUQBYAFpAWAB6AEVAYoBFQH+AQAB
xgEAAf8BAAHcAQAB/wEAAe4BAAH/AQAB+AEAAf8BAAH7AQAB/wEAAfkBAAH/AQAB7wEAAf8BAAHUAQAB
/wEAAZwBAAH/AQABWgEAAf8BKgFAASoB/QNeAd0DKgE/Ay0BRAFgAmkB6AEVAooB/gEAAsYB/wEAAtwB
/wEAAu4B/wEAAvgB/wEAAvsB/wEAAvkB/wEAAu8B/wEAAtQB/wEAApwB/wEAAloB/wEqAkAB/QNeAd0D
KgE/Ay0BRAJgAWkB6AIVAYoB/gIAAcYB/wIAAdwB/wIAAe4B/wIAAfgB/wIAAfsB/wIAAfkB/wIAAe8B
/wIAAdQB/wIAAZwB/wIAAVoB/wIqAUAB/QNeAd0DKgE/A04BlQN3AfgDfwH/A4UB/wOKAf8DjQH/A44B
/wOOAf8DjgH/A40B/wOJAf8DdwH/A00B/wMlAf8DWgHyA0oBiwNOAZUBPwGKAT8B+AEAAeUBAAH/AQAB
7wEAAf8BAAH4AQAB/wEAAf0BAAH/AQAB/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB/gEAAf8BAAH2AQAB
/wEAAdUBAAH/AQABiwEAAf8BAAFBAQAB/wNaAfIDSgGLA04BlQE/AooB+AEAAuUB/wEAAu8B/wEAAvgB
/wEAAv0B/wEAA/8BAAP/AQAD/wEAAv4B/wEAAvYB/wEAAtUB/wEAAosB/wEAAkEB/wNaAfIDSgGLA04B
lQI/AYoB+AIAAeUB/wIAAe8B/wIAAfgB/wIAAf0B/wIAAv8CAAL/AgAC/wIAAf4B/wIAAfYB/wIAAdUB
/wIAAYsB/wIAAUEB/wNaAfIDSgGLA18B0wN+AfwDkwH/A44B/wONAf8DjgH/A44B/wOOAf8DjgH/A44B
/wONAf8DhQH/A2cB/wM0Af8DQQH5A1oBxAFbAV8BWwHTASsBtgErAfwBDgH7AQ4B/wEDAf0BAwH/AQAB
/gEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB/QEAAf8BAAHvAQAB
/wEAAbkBAAH/AQABXQEAAf8BPgFBAT4B+QNaAcQBWwJfAdMBKwK2AfwBDgL7Af8BAwL9Af8BAAL+Af8B
AAP/AQAD/wEAA/8BAAP/AQAD/wEAAv0B/wEAAu8B/wEAArkB/wEAAl0B/wE+AkEB+QNaAcQCWwFfAdMC
KwG2AfwCDgH7Af8CAwH9Af8CAAH+Af8CAAL/AgAC/wIAAv8CAAL/AgAC/wIAAf0B/wIAAe8B/wIAAbkB
/wIAAV0B/wI+AUEB+QNaAcQDbgH1A4AB/gOfAf8DkwH/A48B/wOOAf8DjgH/A44B/wOOAf8DjgH/A44B
/wOLAf8DdwH/A0gB/wNAAf0DYgHhAU0BfwFNAfUBOQHVATkB/gEnAf8BJwH/AQsB/wELAf8BAQH/AQEB
/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB/wEAAfkBAAH/AQAB
1gEAAf8BAAGBAQAB/wEqAUABKgH9A2IB4QFNAn8B9QE5AtUB/gEnA/8BCwP/AQED/wEAA/8BAAP/AQAD
/wEAA/8BAAP/AQAD/wEAAvkB/wEAAtYB/wEAAoEB/wEqAkAB/QNiAeECTQF/AfUCOQHVAf4CJwL/AgsC
/wIBAv8CAAL/AgAC/wIAAv8CAAL/AgAC/wIAAv8CAAH5Af8CAAHWAf8CAAGBAf8CKgFAAf0DYgHhA3IB
9gOEAf4DqwH/A5kB/wOQAf8DjgH/A44B/wOOAf8DjgH/A44B/wOOAf8DjQH/A38B/wNVAf8DQAH9A14B
4gFIAX8BSAH2AVwB1QFcAf4BQgH/AUIB/wEZAf8BGQH/AQQB/wEEAf8BAAH/AQAB/wEAAf8BAAH/AQAB
/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB/wEAAf8BAAH9AQAB/wEAAeQBAAH/AQABmAEAAf8BKgFAASoB
/QNeAeIBSAJ/AfYBXALVAf4BQgP/ARkD/wEEA/8BAAP/AQAD/wEAA/8BAAP/AQAD/wEAA/8BAAL9Af8B
AALkAf8BAAKYAf8BKgJAAf0DXgHiAkgBfwH2AlwB1QH+AkIC/wIZAv8CBAL/AgAC/wIAAv8CAAL/AgAC
/wIAAv8CAAL/AgAB/QH/AgAB5AH/AgABmAH/AioBQAH9A14B4gNhAdYDgwH8A7gB/wOjAf8DkwH/A44B
/wOOAf8DjgH/A44B/wOOAf8DjgH/A40B/wOCAf8DXAH/A00B+gNaAccBXAFhAVwB1gFkAboBZAH8AV8B
/wFfAf8BLwH/AS8B/wEMAf8BDAH/AQEB/wEBAf8BAAH/AQAB/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB
/wEAAf8BAAH/AQAB/gEAAf8BAAHqAQAB/wEAAaUBAAH/ASkBTQEpAfoDWgHHAVwCYQHWAWQCugH8AV8D
/wEvA/8BDAP/AQED/wEAA/8BAAP/AQAD/wEAA/8BAAP/AQAC/gH/AQAC6gH/AQACpQH/ASkCTQH6A1oB
xwJcAWEB1gJkAboB/AJfAv8CLwL/AgwC/wIBAv8CAAL/AgAC/wIAAv8CAAL/AgAC/wIAAf4B/wIAAeoB
/wIAAaUB/wIpAU0B+gNaAccDUAGaA4sB+QPFAf8DsgH/A5wB/wORAf8DjgH/A44B/wOOAf8DjgH/A48B
/wOOAf8DgwH/A2AB/wNaAfIDTAGQA1ABmgFqAZkBagH5AXwB/wF8Af8BUQH/AVEB/wEfAf8BHwH/AQcB
/wEHAf8BAQH/AQEB/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB/wECAf8BAgH/AQIB/gECAf8BAAHrAQAB
/wEAAa0BAAH/AVoBawFaAfIDTAGQA1ABmgFqApkB+QF8A/8BUQP/AR8D/wEHA/8BAQP/AQAD/wEAA/8B
AAP/AQID/wECAv4B/wEAAusB/wEAAq0B/wFaAmsB8gNMAZADUAGaAmoBmQH5AnwC/wJRAv8CHwL/AgcC
/wIBAv8CAAL/AgAC/wIAAv8CAgL/AgIB/gH/AgAB6wH/AgABrQH/AloBawHyA0wBkAMvAUkDbAHrA6YB
/gPGAf8DrgH/A5wB/wOTAf8DkAH/A48B/wOQAf8DkwH/A5MB/wOFAf8DUQH9A2AB4AMtAUUDLwFJA2wB
6wGAAdUBgAH+AX8B/wF/Af8BSQH/AUkB/wEfAf8BHwH/AQwB/wEMAf8BBQH/AQUB/wEDAf8BAwH/AQUB
/wEFAf8BCgH/AQoB/wEKAf4BCgH/AQEB7QEBAf8BKgG2ASoB/QFgAWYBYAHgAy0BRQMvAUkDbAHrAYAC
1QH+AX8D/wFJA/8BHwP/AQwD/wEFA/8BAwP/AQUD/wEKA/8BCgL+Af8BAQLtAf8BKgK2Af0BYAJmAeAD
LQFFAy8BSQNsAesCgAHVAf4CfwL/AkkC/wIfAv8CDAL/AgUC/wIDAv8CBQL/AgoC/wIKAf4B/wIBAe0B
/wIqAbYB/QJgAWYB4AMtAUUDAwEEA1YBrgN2AfUD2QH/A8sB/wO3Af8DpwH/A50B/wOaAf8DnAH/A58B
/wObAf8DiQH/A2gB8ANSAaMDAwEEAwMBBANWAa4BbgF/AW4B9QGoAf8BqAH/AYkB/wGJAf8BXAH/AVwB
/wE3Af8BNwH/ASIB/wEiAf8BGwH/ARsB/wEfAf8BHwH/ASYB/wEmAf8BHQH/AR0B/wEFAfMBBQH/AVcB
aQFXAfADUgGjAwMBBAMDAQQDVgGuAW4CfwH1AagD/wGJA/8BXAP/ATcD/wEiA/8BGwP/AR8D/wEmA/8B
HQP/AQUC8wH/AVcCaQHwA1IBowMDAQQDAwEEA1YBrgJuAX8B9QKoAv8CiQL/AlwC/wI3Av8CIgL/AhsC
/wIfAv8CJgL/Ah0C/wIFAfMB/wJXAWkB8ANSAaMDAwEEBAADHAEnA10BxwN6AfYDtQH+A9cB/wPMAf8D
wgH/A7sB/wO3Af8DsQH/A4AB/gNoAfQDWQG8AxsBJggAAxwBJwNdAccBdQF/AXUB9gGNAdUBjQH+AaUB
/wGlAf8BiwH/AYsB/wF0Af8BdAH/AWYB/wFmAf8BXAH/AVwB/wFOAf8BTgH/AUUB1QFFAf4BUwF6AVMB
9AFXAVkBVwG8AxsBJggAAxwBJwNdAccBdQJ/AfYBjQLVAf4BpQP/AYsD/wF0A/8BZgP/AVwD/wFOA/8B
RQLVAf4BUwJ6AfQBVwJZAbwDGwEmCAADHAEnA10BxwJ1AX8B9gKNAdUB/gKlAv8CiwL/AnQC/wJmAv8C
XAL/Ak4C/wJFAdUB/gJTAXoB9AJXAVkBvAMbASYMAAMhATADWQG2A2oB7gObAfoDvgH9A9QB/wPMAf8D
vgH9A4cB+QNsAesDVQGsAx8BLBAAAyEBMANZAbYBaAFsAWgB7gGBAaUBgQH6Aa4BwAGuAf0BnwH/AZ8B
/wGMAf8BjAH/AWEBwAFhAf0BaAGZAWgB+QFhAWwBYQHrA1UBrAMfASwQAAMhATADWQG2AWgCbAHuAYEC
pQH6Aa4CwAH9AZ8D/wGMA/8BYQLAAf0BaAKZAfkBYQJsAesDVQGsAx8BLBAAAyEBMANZAbYCaAFsAe4C
gQGlAfoCrgHAAf0CnwL/AowC/wJhAcAB/QJoAZkB+QJhAWwB6wNVAawDHwEsFAADBgEHAzYBWANVAawD
ZgHlA6kB/AOWAfsDZQHiA1MBpwMzAVEDBgEHGAADBgEHAzYBWANVAawDZgHlAX4BugF+AfwBcwGqAXMB
+wNlAeIDUwGnAzMBUQMGAQcYAAMGAQcDNgFYA1UBrANmAeUBfgK6AfwBcwKqAfsDZQHiA1MBpwMzAVED
BgEHGAADBgEHAzYBWANVAawDZgHlAn4BugH8AnMBqgH7A2UB4gNTAacDMwFRAwYBBwwAAUIBTQE+BwAB
PgMAASgDAAFAAwABIAMAAQEBAAEBBgABARYAA/+BAAHgAQcB4AEHAeABBwHgAQcBwAEDAcABAwHAAQMB
wAEDAYABAQGAAQEBgAEBAYABAVAAAYABAQGAAQEBgAEBAYABAQHAAQMBwAEDAcABAwHAAQMB4AEHAeAB
BwHgAQcB4AEHCw==
</value>
</data>
<metadata name="ilGameIcons.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>241, 15</value>
</metadata>
<data name="ilGameIcons.ImageStream" mimetype="application/x-microsoft.net.object.binary.base64">
<value>
AAEAAAD/////AQAAAAAAAAAMAgAAAEZTeXN0ZW0uV2luZG93cy5Gb3JtcywgQ3VsdHVyZT1uZXV0cmFs
LCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5BQEAAAAmU3lzdGVtLldpbmRvd3MuRm9ybXMu
SW1hZ2VMaXN0U3RyZWFtZXIBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAAiicAAAJNU0Z0AUkBTAIBAQMB
AAEYAQABGAEAASABAAEgAQAE/wEhAQAI/wFCAU0BNgcAATYDAAEoAwABgAMAASADAAEBAQABIAYAAUB6
AAIxASwB5wEIAQoBAAH/AyoBQAwAAyoBQAM2AVcCQAE/AWwCQAE/AWwCQAE/AWwCQAE/AWwCQAE/AWwC
QAE/AWwCQAE/AWwCQAE/AWwCQAE/AWwCQAE/AWwCQAE/AWwCQAE/AWwCQAE/AWwCQAE/AWwCQAE/AWwC
QAE/AWwCOwE6AWIDMwFRAxgBIVQAA1UBogNhAcAgAANXAaYDXwG88AADIQEwAkYBRQGAFAABFQEfAQQB
/wENARUBAAH/AQgBCgEAAf8BCAEKAQAB/wwAAUYBRQFDAXoBYQFWAU0BpwGMAWUBRAHPAYwBZQFEAc8B
jAFlAUQBzwGMAWUBRAHPAYwBZQFEAc8BjAFlAUQBzwGMAWUBRAHPAYwBZQFEAc8BjAFlAUQBzwGMAWUB
RAHPAYwBZQFEAc8BjAFlAUQBzwGMAWUBRAHPAYwBZQFEAc8BjAFlAUQBzwGMAWcBRQHOAXIBXAFKAbsB
WQFSAUwBmwMqAUBUAANoAdADdQHuIAADagHUA3MB6eQAAkwBSgG/AQgBCgEAAf8BCAEKAQAB/wEIAQoB
AAH/AQgBCgEAAf8BCAEKAQAB/wEIAQoBAAH/AQgBCgEAAf8CTAFKAb8CTAFKAb8BQwF0AQcB/wFHAXcB
DAH/ASUBRwEAAf8BDgEZAQAB/wwAAU8BSwFJAYoBdAFfAUwBvQG+AW8BKAHqAb4BbwEoAeoBvgFvASgB
6gG+AW8BKAHqAb4BbwEoAeoBvgFvASgB6gG+AW8BKAHqAb4BbwEoAeoBvgFvASgB6gG+AW8BKAHqAb4B
bwEoAeoBvgFvASgB6gG+AW8BKAHqAb4BbwEoAeoBvgFvASgB6gG7AW8BKAHpAZIBZwFAAdMBZwFZAU0B
rwEvAi4BSFQAA2gB0AN1Ae4gAANqAdQDcwHp4AABCAEKAQAB/wEQAR4BAAH/ASEBQQEAAf8BJwFLAQAB
/wEmAUoBAAH/ASEBQQEAAf8BEAEeAQAB/wEIAQoBAAH/AQgBCgEAAf8BCAEKAQAB/wEpAU4BAAH/AVoB
lgEQAf8BWgGcARAB/wFzAbYBJgH/ARgBLgEAAf8MAAFMAUoBRwGGAW8BXQFNAbcBrwFtATIB4wGvAW0B
MgHjAa8BbQEyAeMBrwFtATIB4wG2AW8BLgHmAckBcQEiAe0B2gF1ARcB9AHwAXcBBgH8Af8BeAEAAv8B
eAEAAf8B3gFxARQB9QG2AW8BLgHmAa8BbQEyAeMBrwFtATIB4wGvAW0BMgHjAa8BawEzAeIBiQFkAUQB
zQFjAVcBTQGqAi4BLQFGVAADaAHQA3UB7iAAA2oB1ANzAencAAEIAQoBAAH/AT0BcQECAf8BTAGGAQcB
/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEQAf8BTAF/AQ8B/wE7AWcBCAH/ASEB
NAEIAf8BUwGOAQsB/wFaAZwBEAH/AXIBswEnAf8BUwGBARsB/xAAAysBQQE3AjYBWQFBAUABPwFuAUEB
QAE/AW4BQQFAAT8BbgFBAUABPwFuAUcBRQFEAXsBWwFTAUsBnwF9AWEBSQHFAcoBbwEhAe4B/wF4AQAC
/wF4AQAB/wGDAWQBSAHJAUcBRgFEAXwBQQFAAT8BbgFBAUABPwFuAUEBQAE/AW4BQQFAAT8BbQE8AjsB
YwMzAVIDGQEiVAADaAHQA3UB7iAAA2oB1ANzAenYAAEVASkBAAH/AUoBhAEEAf8BUgGMAQgB/wFSAYwB
CAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEQAf8BUgGMARAB/wFSAYwBEAH/AVoBlAEQAf8B
VgGQAQwB/wFaAZoBEAH/AVoBnAEQAf8BcgGpATAB/wEIAQoBAAH/AQgBCgEAAf8MAAMMARADEQEWAxUB
HAMVARwDFQEcAxUBHAMhATABPwE+AT0BaQFeAVUBTAGlAbUBbgEwAeUB/wF4AQAC/wF4AQAB/wFjAVcB
TQGqAyIBMQMVARwDFQEcAxUBHAMUARsDEwEZAxABFQMHAQlUAANoAdADdQHuIAADagHUA3MB6dQAARYB
JwECAf8BSQGAAQcB/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQsB
/wFSAYwBEAH/AVIBjAEQAf8BWAGSARAB/wFaAZQBEAH/AVoBmwEQAf8BWgGcARAB/wFcAZ4BEgH/ATsB
bQEDAf8BCAEKAQAB/wEIAQoBAAH/AQgBCgEAAf8IAAMFBAYECAEKAwgBCgMIAQoDCAEKAxcBHwI5ATgB
XQFZAVIBTAGeAa8BbQEyAeMB/wF4AQAC/wF4AQAB/wFeAVUBTAGjAxgBIQMIAQoDCAEKAwgBCgMIAQoD
BwEJAwYBBwMCAQNUAANoAdADdQHuIAADagHUA3MB6dAAAT0BQgE0Ad8BRwGAAQMB/wFSAYwBCAH/AVIB
jAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBEAH/AVIBjAEQAf8BUgGMARAB
/wFaAZQBEAH/AVoBlAEQAf8BWgGcARAB/wFaAZwBEAH/AVoBnAEQAf8BWgGcARAB/wFaAZgBEAH/ARYB
KgEAAf8BCAEKAQAB/wEIAQoBAAH/HAADEgEXATYCNQFWAVgBUgFMAZoBqwFtATQB4QH/AXgBAAL/AXgB
AAH/AVsBUwFLAZ8DEgEYcAADaAHQA3UB7iAAA2oB1ANzAenQAAEsAVABAAH/AUoBjAEAAf8BUgGMAQgB
/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEOAf8BVQGSARAB/wFaAZwBEAH/AVoB
nAEQAf8BWgGUARAB/wFaAZwBEAH/AVoBnAEQAf8BWgGcARAB/wFaAZwBEAH/AVoBnAEQAf8BWgGUARgB
/wFYAZIBFgH/ARABHgEAAf8BCAEKAQAB/xwAAxIBFwE2AjUBVgFYAVIBTAGaAasBbQE0AeEB/wF4AQAC
/wF4AQAB/wFbAVMBSwGfAxIBGHAAA2gB0AN1Ae4gAANqAdQDcwHp1AACSgFJAY8BMwFeAQAB/wFSAYwB
CAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBEAH/AX8BwAEzAf8BXwGeARcB/wEgAT8BAAH/AQgBCgEAAf8B
UgGMARAB/wFaAZwBEAH/AVoBnAEQAf8BWgGcARAB/wFaAZwBEAH/AVoBlQEXAf8BWgGUARgB/wFaAZQB
GAH/AVoBlAEYAf8BCAEKAQAB/wEIAQoBAAH/GAADEgEXATYCNQFWAVgBUgFMAZoBqwFtATQB4QH/AXgB
AAL/AXgBAAH/AVsBUwFLAZ8DEgEYFAAEAQMDAQQDBgQHAQkDBwEJAwQBBQQCBAEUAAMRARYDIQEvAyEB
LwMhAS8DIQEvAyEBLwMhAS8DIQEvAyEBLwMhAS8DagHZA3YB8QMhAS8DIQEvAyEBLwMhAS8DIQEvAyEB
LwMhAS8DIQEvA20B3AN1Ae0DIQEvAyEBLwMhAS8DIQEvAyEBLwMhAS8DIQEvAyEBLwMhAS8DDQERuAAB
QgF7AQAB/wFSAYwBCAH/AXsBxgEpAf8DMwFQBAADDAEQATkBawEAAf8BWgGcARAB/wFaAZwBEAH/AV0B
lwETAf8BVgGUAQwB/wFaAZwBEAH/AVoBlAEYAf8BWgGUARgB/wFjAZwBGAH/AWMBpQEXAf8BKQFQAQAB
/wEIAQoBAAH/GAADEgEXATYCNQFWAVgBUgFMAZoBqwFtATQB4QH/AXgBAAL/AXgBAAH/AVsBUwFLAZ8D
EgEYFAAEAgMJAQwDDwEUAxMBGgMSARgDCgENAwQBBQQBFAADcwHoA38B/wN/Af8DfwH/A38B/wN/Af8D
fwH/A38B/wN/Af8DfwH/A38B/wN/Af8DfwH/A38B/wN/Af8DfwH/A38B/wN/Af8DfwH/A38B/wN/Af8D
fwH/A38B/wN/Af8DfwH/A38B/wN/Af8DfwH/A38B/wN/Af8DfwH/A2gBz7wAAQgBCgEAAf8MAAETASMB
AAH/AVoBlAEQAf8BWgGcARAB/wGUAdYBSgH/AQ0BEQECAf8BMQFaAQAB/wFaAZQBEgH/AVoBlAEYAf8B
WgGUARgB/wFjAZwBGAH/AWMBpQEXAf8BawGlASEB/wEQAR4BAAH/GAADEgEXATYCNQFWAVgBUgFMAZoB
qwFtATQB4QH/AXgBAAL/AXgBAAH/AVsBUwFLAZ8DEgEYFAADBQEGAx0BKQMwAUoCOwE9AWUCOwE8AWQD
KAE8AhUBFgEdAwYBCBQAAzwBZANNAY8DTQGPA00BjwNNAY8DTQGPA00BjwNNAY8DTQGPA00BjwNyAeoD
fAH4A00BjwNNAY8DTQGPA00BjwNNAY8DTQGPA00BjwNNAY8DdQHsA3YB9QNNAY8DTQGPA00BjwNNAY8D
TQGPA00BjwNNAY8DTQGPA00BjwM1AVaoAAI4ATQB3wEIAQoBAAH/AQgBCgEAAf8BCAEKAQAB/wEIAQoB
AAH/AQgBCgEAAf8BCAEKAQAB/wMzAVADOgFgAUsBhQEHAf8BWgGUARAB/wFaAZwBEAH/ATkBawEAAf8D
CQEMARABHgEAAf8BVAGOARIB/wFaAZQBGAH/AWMBnAEYAf8BYwGcARgB/wFjAaUBFwH/AXMBtAEpAf8B
EAEeAQAB/xgAAxIBFwE2AjUBVgFYAVIBTAGaAasBbQE0AeEB/wF4AQAC/wF4AQAB/wFbAVMBSwGfAxIB
GBQAAwkBDAMzAVACSgFSAZECSAGDAckCRgGFAcoCQwFGAXoDKQE9AwwBEDwAA2gB0AN1Ae4gAANqAdQD
cwHpzAABCAEKAQAB/wEIAQoBAAH/AQgBCgEAAf8BCAEKAQAB/wEIAQoBAAH/AQgBCgEAAf8BCAEKAQAB
/wEIAQoBAAH/AQgBCgEAAf8BMwFfAQIB/wFaAZQBEAH/AVoBnAEQAf8BrQHnAWMB/wEQAR4BAAH/AQgB
CgEAAf8BFAEmAQAB/wFUAY4BEgH/AWIBmwEYAf8BYwGcARgB/wFjAaUBFwH/AWMBpQEXAf8BYwGcASEB
/wEYAS4BAAH/GAADEgEXATYCNQFWAVgBUgFMAZoBqwFtATQB4QH/AXgBAAL/AXgBAAH/AVsBUwFLAZ8D
EgEYFAADCQELAy4DRwFLAYQCSQF8AcMCRQGGAcsCRwFMAYUDLgFHAw8BEzwAA2gB0AN1Ae4gAANqAdQD
cwHpyAABCAEKAQAB/wErAVIBAAH/AUEBcgEFAf8BSgGEAQYB/wFKAYQBBgH/AUoBhAEGAf8BQgFzAQYB
/wEpAVABAAH/ARABHgEAAf8BHwE6AQAB/wFVAY8BCwH/AVoBlQEQAf8BWgGcARAB/wHWAf8BjAH/ARAB
HgEAAf8BGAEuAQAB/wFCAXgBAwH/AVoBlAEYAf8BYgGbARgB/wFjAZwBGAH/AWMBpQEXAf8BYwGlARcB
/wFzAbQBKQH/ARABHgEAAf8YAAMSARcBNgI1AVYBWAFSAUwBmgGrAW0BNAHhAf8BeAEAAv8BeAEAAf8B
WwFTAUsBnwMSARgUAAMGAQgDJAE0AjwBPQFmAk0BYQGnAksBcAG4AkYBTAGDAzEBTQMPARQ8AANoAdAD
dQHuIAADagHUA3MB6cQAASgBTQEAAf8BTgGIAQcB/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwB
CAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBEAH/AVIBjAEQAf8BUgGMARAB/wFaAZQBEAH/AVoBnAEQAf8B
YwGlARcB/wFCAXsBAAH/AUoBhAEGAf8BUgGMAQgB/wFaAZgBFAH/AVoBlAEYAf8BYwGcARgB/wFjAaUB
FwH/AWMBpQEXAf8BYwGlARcB/wGUAckBTQH/BAADEAEVAxoBJAMhAS8DEQEWBAIEAQMSARcBNgI1AVYB
WAFSAUwBmgGrAW0BNAHhAf8BeAEAAv8BeAEAAf8BWwFTAUsBnwMSARgUAAMDAQQDEwEZAyUBNgI7ATwB
ZAJBAUMBcgMzAVEDIQEvAwkBDDwAA2gB0AN1Ae4gAANqAdQDcwHpwAABKQFOAQIB/wFSAYwBCAH/AVIB
jAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEQAf8BUgGMARAB
/wFSAYwBEAH/AVoBlAEQAf8BWgGVARAB/wFaAZwBEAH/AVoBnAEQAf8BWgGcARAB/wFaAZwBEAH/AVoB
lAEYAf8BWgGUARgB/wFjAZwBGAH/AWMBnAEYAf8BYwGlARcB/wFjAaUBFwH/AWMBpQEXAf8BZQGgASEB
/wQAAx4BKwIxATABTAE8AjsBYwMiATEDBwEJAwQBBQMSARgDNgFXAVgBUgFMAZoBqwFtATQB4QH/AXgB
AAL/AXgBAAH/AVsBUwFLAZ8DEgEYHAADBwEJAxwBJwMjATICGQEaASMDDwEUAwQBBTwAA2gB0AN1Ae4g
AANqAdQDcwHpvAACRgFFAYABTgGIAQcB/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIB
jAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEQAf8BUgGMARAB/wFSAYwBEAH/AVoBlAEQAf8BWgGXARAB
/wFaAZwBEAH/AVoBnAEQAf8BWgGcARAB/wFaAZwBEAH/AVoBlAEYAf8BWgGUARgB/wFjAZwBGAH/AWMB
oQEYAf8BYwGlARcB/wFjAaUBFwH/AXgBugEsAf8BCAEKAQAB/wQAAyQBNAFHAUYBRAF8AW8BXQFNAbcB
VQFQAUoBlAFBAUABPwFtAygBOwMfASwDOQFeAVgBUgFMAZoBqwFtATQB4QH/AXgBAAL/AXgBAAH/AVsB
UwFLAZ8DEgEYHAADEgEXAjwBPQFmAkkBTgGJAkABQQFvAy4BRwMOARI8AANoAdADdQHuIAADagHUA3MB
6bwAATUBXgEHAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEIAf8B
UgGMAQgB/wFSAYwBCAH/AVIBjAEQAf8BUgGMARAB/wFaAZQBEAH/AVoBlAEQAf8BWgGbARAB/wFaAZwB
EAH/AVoBnAEQAf8BWgGcARAB/wFaAZYBFgH/AVoBlAEYAf8BXQGXARgB/wFjAZwBGAH/AWMBpQEXAf8B
YwGlARcB/wFwAbMBIAH/ASsBUAEEAf8IAAMnAToBXQFUAUwBogHtAXIBCQH7AboBbgEsAecBfgFiAUgB
xAFEAUMBQgF1AzEBTQFBAUABPwFuAVsBUwFLAZ8BrwFtATIB4wH/AXgBAAL/AXgBAAH/AVsBUwFLAZ8D
EgEYHAADGQEiAksBVQGVAkQBiQHNAkwBagGyAkMBRgF6Ax0BKQMGAQgEAgQBMAADaAHQA3UB7iAAA2oB
1ANzAem8AAFKAYQBBgH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFnAakB
FwH/AVIBjAEIAf8BSgGEAQYB/wFKAYQBBgH/AVIBjAEQAf8BWgGUARAB/wFaAZcBEAH/AVoBnAEQAf8B
WgGcARAB/wFaAZwBEAH/AVoBnAEQAf8BWgGUARgB/wFaAZQBGAH/AV8BmAEYAf8BYwGeARgB/wFjAaUB
FwH/AWsBqQEdAf8BKQFQAQAB/wwAAyIBMQFWAVABSwGYAdcBcgEaAfMBzQFwAR0B8AG2AW8BLgHmAXoB
XwFJAcIBYwFXAU0BqgFhAVYBTQGnAXIBXgFKAbwBwgFyASkB6wH/AXgBAAL/AXgBAAH/AVsBUwFLAZ8D
EgEYHAADFAEbAkMBRQF3AkwBbQG2AkQBiQHNAk0BawGzAj8BQAFsAyYBOAMMAQ8DAgEDCAADAgEDAw0B
EQMNAREDDQERAw0BEQMNAREDDQERAw0BEQMNAREDDQERA2cB0wN1Ae8DDQERAw0BEQMNAREDDQERAw0B
EQMNAREDDQERAw0BEQNqAdcDcwHqAw0BEQMNAREDDQERAw0BEQMNAREDDQERAw0BEQMNAREDDQERAwQB
BZQAAUoBhAEGAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFpAaQBHgH/ASwBUAEDAf8B
CAEKAQAB/wE3AWEBBwH/AVIBjAEOAf8BUgGMARAB/wF/AcEBMgH/AU8BXgFBAdMBWAFhAUoBvwEvAVkB
AAH/AUQBegEFAf8BUAGKAQgB/wFaAZwBDgH/AVoBnAEQAf8BWgGcARAB/wFPAYYBEAH/AUQBdAEMAf8B
TwFSAUoBvxAAAhoBGQEjAUcBRQFEAXsBhQFkAUYBygGeAWkBOwHaAbsBcAEqAegBygFxASAB7wHFAXEB
JwHsAZgBaQE9AdYBmAFoAT0B2AHaAXUBFwH0Af8BeAEAAv8BeAEAAf8BWwFTAUsBnwMSARgcAAMPARMD
NAFTAkoBUwGSAkQBjAHPAkEBkwHUAk0BYgGoAkEBQwFyAyYBOAMUARsDBgEIAwIBAwNhAcIDfwH/A38B
/wN/Af8DfwH/A38B/wN/Af8DfwH/A38B/wN/Af8DfwH/A38B/wN/Af8DfwH/A38B/wN/Af8DfwH/A38B
/wN/Af8DfwH/A38B/wN/Af8DfwH/A38B/wN/Af8DfwH/A38B/wN/Af8DfwH/A38B/wN/Af8DbgHclAAB
SgGMAQAB/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBCAH/AWcBnQEnAf8BCAEKAQAB/wEKAQ8B
AAH/AUIBdwEDAf8BUgGMARAB/wFaAZwBEAH/ASUBQQEFAf8EAAM6AWABCAEKAQAB/wEIAQoBAAH/BAAC
RgFFAYACRgFFAYACRgFFAYAcAAMKAQ0DIgExAzQBVAFGAUQBQwF4AVsBUwFLAZ8BjAFlAUQBzwHJAXIB
IgHtAcUBcQEnAewBzQFwAR0B8AHtAXIBCQH7Af8BeAEAAv8BeAEAAf8BWwFTAUsBnwMSARgcAAMGAQgD
GAEhAzEBTQJKAVIBkQJMAXQBvQJCAY8B0QJJAXwBwwJMAV0BogI9AT4BZwIZARoBIwMMAQ8DQAFvA1oB
rQNaAa0DWgGtA1oBrQNaAa0DWgGtA1oBrQNaAa0DWgGtA3YB8AN9AfoDWgGtA1oBrQNaAa0DWgGtA1oB
rQNaAa0DWgGtA1oBrQN2AfEDfAH4A1oBrQNaAa0DWgGtA1oBrQNaAa0DWgGtA1oBrQNaAa0DWgGtA0cB
gZQAATsBbQECAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFTAY4BEAH/AQ8BGwEAAf8B
IwFDAQAB/wFSAYwBEAH/AVIBjAEQAf8BewG9ATAB/wEIAQoBAAH/AQgBCgEAAf8BCAEKAQAB/wEIAQoB
AAH/AQgBCgEAAf8BCAEKAQAB/wEIAQoBAAH/KAADAwEEAwkBCwMiATEDOAFbAVYBUAFLAZcBhAFkAUcB
yAG1AW4BMAHlAeQBcgEUAfcB9gF1AQYB/QH/AXgBAAL/AXgBAAH/AVsBUwFLAZ8DEgEYHAAEAQMCAQMD
FQEcAjUBNgFWAkkBUQGPAkcBhAHIAjQBpgHfAjIBrwHjAkwBXgGlAzQBUwMcAScoAANoAdADdQHuIAAD
agHUA3MB6bwAAQgBCgEAAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBCAH/AU4B
ggEPAf8BSgGEAQYB/wFSAYwBEAH/AWMBpQEXAf8BCAEKAQAB/wEIAQoBAAH/ARMBIwEAAf8BQgFzAQYB
/wEgAT8BAAH/AQgBCgEAAf8BCAEKAQAB/wEIAQoBAAH/AQgBCgEAAf8BCAEKAQAB/yAABAEDAwEEAwwB
EAMYASADKQE9AT8CPgFqAW0BWwFLArUBbgEwAeUB5AFyARQB9wH/AXgBAAL/AXgBAAH/AVsBUwFLAZ8D
EgEYIAAEAQMHAQkDFAEbAyoBQAJCAUQBdgJNAWcBrwIoAbsB6QJCAY4B0AJLAVsBnwMzAVEoAANoAdAD
dQHuIAADagHUA3MB6cAAAUIBcwEGAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwB
CAH/AVIBjAEQAf8BUgGMARAB/wFKAYQBBgH/ARABHgEAAf8BMQFaAQAB/wFNAYQBDQH/AVoBnAEQAf8B
WgGcARAB/wFSAYwBCAH/ARABHgEAAf8BCAEKAQAB/wEIAQoBAAH/AkYBRQGALAADAgEDAwsBDgMgAS4B
RAFDAUIBdgFqAVoBTgGxAaoBbQE1AeAB3wF1ARQB9gHnAXIBDwH5AVkBUwFMAZwDEgEXDAAEAQMMARAD
FwEfAx0BKAMdASgDDAEPCAADDwEUAiYBJwE5AkYBTAGDAjQBqwHhAiwBugHnAkABkAHSAj8BQQFuKAAD
aAHQA3UB7iAAA2oB1ANzAenEAAFJAYkBAAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEIAf8B
UgGMARAB/wFSAYwBEAH/AVIBjAEQAf8BUgGMARAB/wFaAZQBEAH/AVoBnAEQAf8BWgGcARAB/wFaAZwB
EAH/AVoBnAEQAf8BWgGUARgB/wFCAXMBBgH/AQoBDgEAAf8wAAQBAwMBBAMMAQ8DHgEqATsCOgFhAXEB
XQFLAbkBsAFrAS8B5AHKAW8BIQHuAVYBUAFLAZcDEgEXDAADAgEDAyEBLwI2ATcBWQJCAUQBdQJBAUMB
cwMeASsIAAMEAQUDDAEPAjgBOQFcAkUBjAHOAicBxQHsAiABygHvAkUBSQF/KAADaAHQA3UB7iAAA2oB
1ANzAenIAAE6AWsBAAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEQAf8BUgGMARAB/wFaAZQB
EAH/AVoBlAEQAf8BWgGcARAB/wFaAZwBEAH/AVoBnAEQAf8BWgGcARAB/wFaAZQBGAH/AVoBlAEYAf8B
YwGlARcB/wEZASwBAwH/OAADAgEDAwgBCgMgAS0BQgFBAUABcQFSAU0BSgGRAVgBUgFMAZoCOwE6AWID
DAEPDAADAwEEAysBQQJDAUYBegJNAWEBpwJNAWUBrQM3AVoDGAEgAwkBCwMGAQcDDQERAjkBOgFfAkIB
jwHRAiABygHvAhoB1wHzAkUBSQGBKAADaAHQA3UB7iAAA2oB1ANzAenMAAEsAVUBAAH/AVIBjAEIAf8B
UgGMAQgB/wFSAYwBEAH/AVIBjAEQAf8BWgGUARAB/wFaAZQBEAH/AVoBnAEQAf8BWgGcARAB/wFaAZwB
EAH/AVoBnAEQAf8BWgGUARgB/wFaAZQBGAH/ASEBQQEAAf9AAAQCAwcBCQMQBBUBHAIWARUBHQMPARMD
AgEDDAADAwEEAzABSwJJAVEBjwJGAYUBygI4AaUB3gJKAVUBlAM0AVMDFQEcAw0BEQMeASsCQwFFAXcC
NAGmAd8CHQHUAfECKAG7AekCRAFHAXsoAANoAdADdQHuIAADagHUA3MB6cgAARIBIgEAAf8BTAGGAQYB
/wFSAYwBCAH/AVoBnAEQAf8BUAGKAQgB/wFSAYwBEAH/AVoBlAEQAf8BWgGcARAB/wFaAZwBEAH/AVoB
nAEQAf8BWgGcARAB/wFaAZwBEQH/AWkBqwEfAf8BCAEKAQAB/2wAAwIBAwMhAS8COgE7AWECTQFlAa0C
OwGeAdoCSgF3Ab8CSwFbAZ8CQwFGAXoCQQFDAXICRwFMAYUCTAFqAbICIgHJAe0COgGgAdsCTgFqAbED
OAFbKAADaAHQA3UB7iAAA2oB1ANzAenIAAEQAR4BAAH/AUgBfQEHAf8BWQGTARMB/wFVAYgBGAH/CAAB
IQFBAQAB/wFCAXsBAAH/AVIBjAEIAf8BVQGRAQgB/wE5AWYBBAH/ASoBMwEgAe94AAMGAQgDFwEfAkQB
RwF7AkcBfwHGAi8BsAHkAhcB2gH0AiEBygHuAiEBygHuAh0B1AHxAhQB3wH2AgYB8AH8AksBdgG+Aj4B
PwFrAyQBNCgAA2gB0AN1Ae4gAANqAdQDcwHp0AABHgE6AQAB/5wABAIDCQEMAyoDPwFBAW4CSgFVAZYC
TQFrAbMCSQF5AcECRgGAAccCRgGAAccCSwFxAbkCTQFeAaQCQAFCAXEDJQE2AxMBGSgAA2gB0AN1Ae4g
AANqAdQDcwHp/wB5AAQBAwsBDgMmATgDOQFeAkUBSAF9AkkBTwGLAkkBTgGJAj4BPwFrAyoBPwMXAR8D
AgEDLAADVQGiA2EBvyAAA1cBpgNfAbuoAAFCAU0BPgcAAT4DAAEoAwABgAMAASADAAEBAQABAQYAAQIW
AAP/AQAD/wHHAgABBwL/Ac8B8wH/BAAC/wHPAYcCAAEHAv8BzwHzAf8EAAH/Af4BAAEHAgABBwL/Ac8B
8wH/BAAB/wH8AQABBwIAAQcC/wHPAfMB/wQAAf8B+AEAAQ8CAAEHAv8BzwHzAf8EAAH/AfABAAEHAgAB
BwL/Ac8B8wH/BAAB/wHgAQABAwIAAQcC/wHPAfMB/wQAAf8BwAEAAQEB/AEDA/8BzwHzAf8EAAH/AcAB
AAEBAfwBAwP/Ac8B8wH/BAAB/wHgAgAB/AEDAeABHwgAAf8B/AEgAQAB/AEDAeABHwgAAf8B/gHgAQAB
/AEDAeABHwgAAf8BwAIAAfwBAwHgAR8B/wHPAfMB/wQAAf8BgAIAAfwBAwHgAR8B/wHPAfMB/wQAAf8D
AAH8AQMB4AEfAf8BzwHzAf8EAAH+AgABAQEAAQMB4AEfAf8BzwHzAf8EAAH8AgABAQEAAQMB+AEfAf8B
zwHzAf8EAAH4AgABAQEAAQMB+AEfAf8BzwHzAf8EAAH4AgABAwEAAQMB+AEDAf8BzwHzAf8EAAH4AgAB
BwEAAQMB+AEDCAAB+AIAAQ8BAAEDAfgJAAH4AQABRAF/AQABAwH4CQAB+AEAAQEB/wGAAQMB+AEAAf8B
zwHzAf8EAAH4AgABfwGAAQMB/AEAAf8BzwHzAf8EAAH8AgABfwHwAQMBgQGAAf8BzwHzAf8EAAH+AgAB
/wHwAQMBgQGAAf8BzwHzAf8EAAH/AgAB/wH8AQMBgAEAAf8BzwHzAf8EAAH/AYABAQH/Af4BAwGAAQAB
/wHPAfMB/wQAAf8BAAEDA/8BgAEAAf8BzwHzAf8EAAH/AQwBDwP/AcABAAH/Ac8B8wH/BAAB/wHfBP8B
wAEAAf8BzwHzAf8EAAb/AfABAQH/Ac8B8wH/BAAL
</value>
</data>
<metadata name="ilTabIcons.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<data name="ilTabIcons.ImageStream" mimetype="application/x-microsoft.net.object.binary.base64">
<value>
AAEAAAD/////AQAAAAAAAAAMAgAAAEZTeXN0ZW0uV2luZG93cy5Gb3JtcywgQ3VsdHVyZT1uZXV0cmFs
LCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5BQEAAAAmU3lzdGVtLldpbmRvd3MuRm9ybXMu
SW1hZ2VMaXN0U3RyZWFtZXIBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAAHBkAAAJNU0Z0AUkBTAIBAQUB
AAGwAQABsAEAARABAAEQAQAE/wEhAQAI/wFCAU0BNgcAATYDAAEoAwABQAMAASADAAEBAQABIAYAASAa
AAM3AVoDWAG4A2MB3wJjAV0B3wFiAl0B3wNdAd8DXQHfAWECXQHfA2MB3wNjAd8DVQGsAzABS8wAAzsB
YgNdAcUBcwJoAfQBnwFlATEB/wGXAVMBFwH/AZYBSwEJAf8BkwFGAQEB/wGMAUMBAwH/AX0BQAELAf8B
awFAARoB/wFuAVABNgH/A2gB8ANaAbcDNAFUxAADNQFVA10BxwFxAW4BWgH1AbgBZQEbAf8BuQFYAQIB
/wHJAV8BAAH/AdgBZQEAAf8B3AFnAQAB/wHWAWQBAAH/AcMBXAEAAf8BogFMAQAB/wF8ATsBAwH/AW4B
RgEjAf8DaAHwA1oBtwMwAUrAAANcAcQBkAF8AVwB+AHUAXEBGAH/AdcBZQEAAf8B5QFsAQAB/wHyAXIB
AAH/AfoBdQEAAf8B/AF2AQAB/wH6AXYBAAH/AfMBcgEAAf8B4gFrAQAB/wG9AVkBAAH/AYcBQAEAAf8B
cQFIASMB/wNoAfADVgGrwAAB/gHdAcEB/wHtAYABIAH/Ae0BcQECAf8B8wFzAQAB/wH6AXYBAAH/Af4B
eAEAAv8BewEIAv8BiAEnAv8BogFTAf8B/gGBARcB/wH8AXgBBAH/AewBbwEAAf8BwQFbAQAB/wGGAUEB
AwH/AXgBVgE2Af8DYwHfwAAB/wGyAW8B/wH9AYABEQH/AfwBdwEBAf8B/QF3AQAC/wF4AQAC/wF/AQ8C
/wGSATsC/wGzAYMC/wHqAeAC/wGQAT0C/wF7AQoB/wH8AXcBAAH/AeUBbAEAAf8BsQFUAQAB/wGEAUsB
GgH/A2MB38AAAf8BmgFCAv8BgwEVAf8B/gF6AQQC/wF4AQAC/wF4AQAC/wGVAT8C/wHKAa4C/wHaAcYC
/wHtAeUC/wGWAUkC/wF8AQ0B/wH+AXgBAAH/AfQBcwEAAf8B0AFiAQAB/wGaAU4BCgH/AWMCXQHfwAAB
/wGVATcC/wGJAR8C/wF9AQgC/wF4AQAC/wF4AQAC/wGoAVwC/wHgAc0C/wGhAWYC/wHYAcUC/wG5AZQC
/wGHASMC/wF4AQAB/wH7AXYBAAH/AeIBagEAAf8BrwFUAQMB/wFjAl0B38AAAf8BoAFJAv8BkgExAv8B
gQERAv8BeQEDAv8BeAEAAv8BqAFcAv8B4AHNAv8BoQFmAv8B2AHFAv8BwAGfAv8BiQEnAv8BeAEAAf8B
/gF4AQAB/wHsAW8BAAH/Ab8BWgECAf8BYwJdAd/AAAH/AbMBbwL/AZ4BSAL/AYgBHgL/AXwBBwL/AXgB
AAL/AZgBQwL/Ac4BtAL/AdcBwAL/AeoB4AL/AZ0BVwL/AX4BEQL/AXgBAAL/AXgBAAH/AfABcQEAAf8B
ywFkAQkB/wFjAWEBXQHfwAAB/wHMAZ8C/wGsAWMC/wGTATMC/wGBAREC/wF5AQIC/wGCARYC/wGaAUsC
/wGuAXgC/wGlAVcC/wGBARcC/wF5AQQC/wF4AQAC/wF4AQAB/wHwAXEBAAH/AdUBcQEXAf8CYwFdAd/A
AAH/AekB1QL/AbwBgQL/AaQBVAL/AY4BKgL/AX8BDQL/AXkBAgL/AXsBCQL/AYIBHQL/AXgBAAL/AXgB
AAL/AXgBAQL/AXoBBAL/AXoBAwH/AfMBdAEDAf8B4gGEATIB/wNjAd/AAANeAdIBlQGMAYMB+QH/AbsB
fQL/AaMBUgL/AZABLAL/AYIBEwL/AX0BCAL/AXoBBAL/AXkBAgL/AXoBBAL/AX0BCQL/AYABDwL/AX8B
DQH/AfgBhgEgAf8BeAFtAWgB9ANYAbjAAAM8AWYDYwHVAZUBhwF8AfgB/wG+AYUC/wGqAV8C/wGZAT4C
/wGNAScC/wGGARoC/wGDARUC/wGFARkC/wGKASIC/wGNASgC/wGTATMB/wF8AW4BaAH1A10BxQM2AVnE
AANCAXIDYwHVAZUBjgGIAfkB/wHJAZkC/wG8AX8C/wGuAWYC/wGkAVMC/wGfAUoC/wGfAUsC/wGjAVEC
/wGnAVgB/wGVAX4BfAH4A10BxwM7AWLMAAM8AWUDXgHSAf8B7wHgAv8B3AG8Av8BzQGfAv8BwQGKAv8B
uwF/Av8BvwGGAv8BzQGhAv8B6QHWAf8DXAHEAzUBVcgAA2cB7wJnAVkB7wFnAV0BWQHvAWcBWwFZAe8B
ZwFbAVkB7wFnAlkB7wFnAWQBWQHvA2cB7wNnAe8DZwHvA2cB7wNnAe8DZwHvA2cB7wNnAe8DZwHvOAAD
MwFRA3kB9QMHAQkDKgE/A0UBfANZAbsDYwHfA2gB9AOAAf4DgQH/A4EB/wOBAf8DgAH+A2gB9ANjAd8D
WgG6A0QBegMnAToIAAM3AVoDWAG4A2MB3wJjAV0B3wFiAl0B3wNdAd8DXQHfAWECXQHfA2MB3wNjAd8D
VQGsAzABSwgAA/gB/wG5AZUBPAH/AYMBfQFuAf8BhAF9AWwB/wGqAYQBJwH/AawBewEAAf8BzAG8AZQB
/wN+Af8DfgH/A34B/wN+Af8DfgH/A34B/wN+Af8DfgH/A44B/zgAAxIBGAM/AW0DQwF1A10BzAN8AfgD
gQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wN8AfgDVAGoBAADOwFiA10B
xQFzAmgB9AGfAWUBMQH/AZcBUwEXAf8BlgFLAQkB/wGTAUYBAQH/AYwBQwEDAf8BfQFAAQsB/wFrAUAB
GgH/AW4BUAE2Af8DaAHwA1oBtwM0AVQEAAT/AZcBiwFtAf8CgQGAAf8BggGBAYAB/wGYAYgBYAH/AcoB
kAEAAf8B3QHMAZ8B/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A5MB/zQAA18B0wM9AWcE
AANqAe0DfQH6A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB
/wNVAa8DNQFVA10BxwFxAW4BWgH1AbgBZQEbAf8BuQFYAQIB/wHJAV8BAAH/AdgBZQEAAf8B3AFnAQAB
/wHWAWQBAAH/AcMBXAEAAf8BogFMAQAB/wF8ATsBAwH/AW4BRgEjAf8DaAHwA1oBtwMwAUoE/wGGAYQB
fQH/A4EB/wOBAf8BhwGDAXoB/wHPAZQBAAH/Ad4BzAGfAf8D4AH/A+AB/wPgAf8D4AH/A+AB/wPgAf8D
4AH/A+AB/wO8Af80AANaAcIDNAFTBAADYwHfA24B9QOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8D
gQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DVQGvA1wBxAGQAXwBXAH4AdQBcQEYAf8B1wFlAQAB/wHlAWwB
AAH/AfIBcgEAAf8B+gF1AQAB/wH8AXYBAAH/AfoBdgEAAf8B8wFyAQAB/wHiAWsBAAH/Ab0BWQEAAf8B
hwFAAQAB/wFxAUgBIwH/A2gB8ANWAasE/wGLAYYBegH/A4EB/wOBAf8BjgGGAXEB/wHPAZQBAAH/Ad4B
zAGfIf8DygH/EAADDQERAz8BbANTAacBXAJZAb4BWAJWAbMBSAJHAYMDIQEwBAADcwHzAzoBYAgAAzYB
WANbAcADbgH1A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DaAH0A1IB
pAH+Ad0BwQH/Ae0BgAEgAf8B7QFxAQIB/wHzAXMBAAH/AfoBdgEAAf8B/gF4AQAC/wF7AQgC/wGIAScC
/wGiAVMB/wH+AYEBFwH/AfwBeAEEAf8B7AFvAQAB/wHBAVsBAAH/AYYBQQEDAf8BeAFWATYB/wNjAd8E
/wGsAZYBYAH/AYMBggF/Af8BhQGCAX0B/wGzAZMBRAH/Ac8BlAEAAf8B3gHMAZ8B/wOwAf8DsAH/A7AB
/wOwAf8DsAH/A7AB/wOwAf8DsAH/A6gB/wgAAxoBJANSAaABZwFjAUgB9gGiAXMBAAH/Aa4BfAEAAf8B
sAF9AQAB/wGoAXgBAAH/AZUBagEAAf8BgAFqARYB/gFcAlkBxgNXAbUDFgEeCAADAgEDAxoBIwM4AVwD
VAGoA2IB1wNwAfEDgAH+A4EB/wOBAf8DgQH/A4EB/QNoAfADYQHUA1MBpQM2AVkDGAEgAf8BsgFvAf8B
/QGAAREB/wH8AXcBAQH/Af0BdwEAAv8BeAEAAv8BfwEPAv8BkgE7Av8BswGDAv8B6gHgAv8BkAE9Av8B
ewEKAf8B/AF3AQAB/wHlAWwBAAH/AbEBVAEAAf8BhAFLARoB/wNjAd8E/wHZAaoBNwH/Ab4BmAE4Af8B
wAGYATYB/wHcAaIBFAH/Ac8BlAEAAf8B3gHMAZ8B/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8D
gQH/A5MB/wQAAyABLQJjAVoB6QG/AYgBAAH/Ac0BlQEKAf8BsAGIAScB/wFzAWQBPwH/AU0BSwFHAf8B
TgFLAUIB/wFmAVcBMQH/AZoBdAEXAf8BpAF2AQMB/wFwAU8BAAH/A0MBdgQBRAAB/wGaAUIC/wGDARUB
/wH+AXoBBAL/AXgBAAL/AXgBAAL/AZUBPwL/AcoBrgL/AdoBxgL/Ae0B5QL/AZYBSQL/AXwBDQH/Af4B
eAEAAf8B9AFzAQAB/wHQAWIBAAH/AZoBTgEKAf8BYwJdAd8E/wHhAa4BMQH/Ab0BlwE7Af8BwAGYATUB
/wHjAaUBCgH/Ac8BlAEAAf8B3gHMAZ8B/wPAAf8DwAH/A8AB/wPAAf8DwAH/A8AB/wPAAf8DwAH/A68B
/wQAAmMBWgHpAdkBmgEAAf8B2gGjARwB/wKOAYwB/wOKAf8DlwH/A5sB/wORAf8DdAH/A0gB/wFDAUIB
PwH/AbUBgwEHAf8BegFXAQAB/wM2AVgMAAMCAQMDCAEKAyEBLwMxAU4DPQFoA0MBdgNEAXoDQwF1Az0B
ZwMxAU0DIAEuAwcBCQQCBAAB/wGVATcC/wGJAR8C/wF9AQgC/wF4AQAC/wF4AQAC/wGoAVwC/wHgAc0C
/wGhAWYC/wHYAcUC/wG5AZQC/wGHASMC/wF4AQAB/wH7AXYBAAH/AeIBagEAAf8BrwFUAQMB/wFjAl0B
3wT/AbsBnQFTAf8BiAGEAXkB/wGMAYUBdAH/AcQBmQEwAf8BzwGUAQAB/wHeAcwBnyH/A8oB/wNDAXYB
6QGnAQIB/wHpAasBEgH/AdABygG7Af8DrAH/A10B/wNMAf8DSwH/A0QB/wMPAf8DswH/A2YB/wFUAUwB
OgH/Aa4BfQEEAf8DXQHMCAADEwEaAzkBXQNZAbwDZAHbA2oB7QNjAfYDXwH7A4EB/QNfAfsDYwH2A2UB
7ANjAdoDWgG6AzgBXAMTARoB/wGgAUkC/wGSATEC/wGBAREC/wF5AQMC/wF4AQAC/wGoAVwC/wHgAc0C
/wGhAWYC/wHYAcUC/wHAAZ8C/wGJAScC/wF4AQAB/wH+AXgBAAH/AewBbwEAAf8BvwFaAQIB/wFjAl0B
3wT/AZQBigFzAf8DgQH/A4EB/wGaAYoBYwH/Ac8BlAEAAf8B3gHMAZ8B/wPQAf8D0AH/A9AB/wPQAf8D
0AH/A9AB/wPQAf8D0AH/A7UB/wJqAWEB5gHtAa0BEAH/AfQB0AF2Af8D+gH/A/oB/wN9Af8DfwH/A4AB
/wOAAf8DfgH/A4QB/wO7Af8DagH/AagBgAEcAf8BaQFjAUgB9gQAAxoBJANWAa4DaAH0A4EB/wOBAf8D
gQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DawHyA1IBoQH/AbMBbwL/AZ4BSAL/AYgB
HgL/AXwBBwL/AXgBAAL/AZgBQwL/Ac4BtAL/AdcBwAL/AeoB4AL/AZ0BVwL/AX4BEQL/AXgBAAL/AXgB
AAH/AfABcQEAAf8BywFkAQkB/wFjAWEBXQHfBP8BhgGDAX4B/wOBAf8DgQH/AYYBgwF7Af8BzwGUAQAB
/wHeAcwBnwH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DkwH/AWoBZwFiAe4B7wG0ASEB
/wH3AdwBlwn/A5EB/wOIAf8DhwH/A4cB/wOBAf8DVwH/A+YB/wOjAf8BtgGRATYB/wGFAWoBQAH5BAAD
VwGyA2UB5wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8D
VQGvAf8BzAGfAv8BrAFjAv8BkwEzAv8BgQERAv8BeQECAv8BggEWAv8BmgFLAv8BrgF4Av8BpQFXAv8B
gQEXAv8BeQEEAv8BeAEAAv8BeAEAAf8B8AFxAQAB/wHVAXEBFwH/AmMBXQHfBP8BiwGGAXkB/wOBAf8D
gQH/AY4BhgFxAf8BzwGUAQAB/wHeAcwBnwH/A6EB/wOhAf8DoQH/A6EB/wOhAf8DoQH/A6EB/wOhAf8D
oQH/A0sBjQHwAb4BPwH/AfQBzQFsIf8D+wH/A9AB/wHXAacBMQH/AmEBXQHRBAADbwHzA18B+wOBAf8D
gQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DVQGvAf8B6QHVAv8B
vAGBAv8BpAFUAv8BjgEqAv8BfwENAv8BeQECAv8BewEJAv8BggEdAv8BeAEAAv8BeAEAAv8BeAEBAv8B
egEEAv8BegEDAf8B8wF0AQMB/wHiAYQBMgH/A2MB3wT/AacBlAFnAf8BgwGCAX8B/wGFAYMBfgH/AbAB
lAFOAf8B0wGXAQIB/wHgAc4BnwH/A+cB/wPnAf8D5wH/A+cB/wPnAf8D5wH/A+cB/wPnAf8DwAH/AwcB
CQFqAWgBYgHuAfEBvAE7Af8B+gHqAcIB/wPcAf8DdwH/A2gB/wNoAf8DaAH/AzIJ/wHyAd0BqQH/AeoB
qQEIAf8DPgFqBAADZAHbA2gB9AOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8D
gQH/A4EB/wOBAf8DVQGvA14B0gGVAYwBgwH5Af8BuwF9Av8BowFSAv8BkAEsAv8BggETAv8BfQEIAv8B
egEEAv8BeQECAv8BegEEAv8BfQEJAv8BgAEPAv8BfwENAf8B+AGGASAB/wF4AW0BaAH0A1gBuAT/AdcB
sQFSAf8BmgGPAXQB/wGgAZIBbQH/AeABrwE3Af8B5wGpARAB/wHrAdUBoAH/A4EB/wOBAf8DgQH/A4EB
/wOBAf8DgQH/A4EB/wOBAf8DkwH/BAADMwFRAWoCaAHwAfMBxgFYAf8B+gHnAbgW/wH+AfsB/wH5AeIB
qgH/Ae8BuAEtAf8DTgGWBAIEAAM8AWQDXwHJA30B+gOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8D
gQH/A4EB/wOBAf8DgQH/A30B+gNVAaoDPAFmA2MB1QGVAYcBfAH4Af8BvgGFAv8BqgFfAv8BmQE+Av8B
jQEnAv8BhgEaAv8BgwEVAv8BhQEZAv8BigEiAv8BjQEoAv8BkwEzAf8BfAFuAWgB9QNdAcUDNgFZBP8B
9AHNAWwB/wH0AcsBZgH/AfQBywFlAf8B9AHLAWUB/wHxAcEBSQH/AfkB4wGsAf8DiQH/A4kB/wOJAf8D
iQH/A4kB/wOJAf8DiQH/A4kB/wOaAf8IAAMiATEDXwHJAaABigFnAfoB8wHKAWUB/wH5AeEBpgH/AfsB
7QHMAf8B+wHsAcgB/wH4Ad0BmwH/AcYBmAFnAf4CZQFeAeIDPQFoBAEIAAMGAQgDMQFMA1ABmwNlAewD
fQH6A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wN9AfoDagHtA1ABmwMvAUkEAANCAXIDYwHVAZUB
jgGIAfkB/wHJAZkC/wG8AX8C/wGuAWYC/wGkAVMC/wGfAUoC/wGfAUsC/wGjAVEC/wGnAVgB/wGVAX4B
fAH4A10BxwM7AWIEAED/EAADDwETA0cBggNkAdsBtAGeAW8B/ANnAeoDVAGoAygBOxwAAwUBBgMSARcD
OgFgA1EBnwNfAdMDZwHvA2MB9gNiAe4DXgHSA1EBngM5AV8DEQEWAwUBBgwAAzwBZQNeAdIB/wHvAeAC
/wHcAbwC/wHNAZ8C/wHBAYoC/wG7AX8C/wG/AYYC/wHNAaEC/wHpAdYB/wNcAcQDNQFVCAABQgFNAT4H
AAE+AwABKAMAAUADAAEgAwABAQEAAQEGAAEBFgAD/wEAAcABAwYAAYABAWYAAYABAQYAAcABAwgAAf8B
/AIAAcABAwIAAf8B/AIAAYABAQIAAf8B+QYAAf8B+QYAAfABEwYAAcABAwYAAYABAQL/BAABgAEBAcAB
AQUAAQEBgAYAAQEHAAEBBwABAQcAAQEGAAGAAQEGAAHAAQMCAAGAAQECAAHwAR8BwAEBAcABAws=
</value>
</data>
<metadata name="ilStoreIcons.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>360, 15</value>
</metadata>
</root>

View File

@ -0,0 +1,92 @@
namespace qtcnet_client.Controls
{
partial class MessageControl
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
pbProfileImage = new PictureBox();
lblUsername = new Label();
rtxtMessage = new RichTextBox();
((System.ComponentModel.ISupportInitialize)pbProfileImage).BeginInit();
SuspendLayout();
//
// pbProfileImage
//
pbProfileImage.Image = Properties.Resources.DefaultPfp;
pbProfileImage.Location = new Point(4, 5);
pbProfileImage.Name = "pbProfileImage";
pbProfileImage.Size = new Size(40, 38);
pbProfileImage.SizeMode = PictureBoxSizeMode.Zoom;
pbProfileImage.TabIndex = 0;
pbProfileImage.TabStop = false;
//
// lblUsername
//
lblUsername.AutoSize = true;
lblUsername.Font = new Font("Segoe UI", 9F, FontStyle.Bold);
lblUsername.Location = new Point(46, 5);
lblUsername.Name = "lblUsername";
lblUsername.Size = new Size(64, 15);
lblUsername.TabIndex = 1;
lblUsername.Text = "Username";
//
// rtxtMessage
//
rtxtMessage.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
rtxtMessage.BackColor = Color.White;
rtxtMessage.BorderStyle = BorderStyle.None;
rtxtMessage.Location = new Point(50, 23);
rtxtMessage.Name = "rtxtMessage";
rtxtMessage.ReadOnly = true;
rtxtMessage.Size = new Size(98, 22);
rtxtMessage.TabIndex = 2;
rtxtMessage.Text = "";
rtxtMessage.ContentsResized += rtxtMessage_ContentsResized;
//
// MessageControl
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
BackColor = Color.Transparent;
Controls.Add(rtxtMessage);
Controls.Add(lblUsername);
Controls.Add(pbProfileImage);
Name = "MessageControl";
Size = new Size(154, 48);
Load += MessageControl_Load;
((System.ComponentModel.ISupportInitialize)pbProfileImage).EndInit();
ResumeLayout(false);
PerformLayout();
}
#endregion
private PictureBox pbProfileImage;
private Label lblUsername;
private RichTextBox rtxtMessage;
}
}

View File

@ -0,0 +1,37 @@
using qtcnet_client.Properties;
using System.ComponentModel;
namespace qtcnet_client.Controls
{
public partial class MessageControl : UserControl
{
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string Username { get; set; } = "Username";
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string Message { get; set; } = "Message";
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Image ProfileImage { get; set; } = Resources.DefaultPfp;
public MessageControl()
{
InitializeComponent();
}
private void MessageControl_Load(object sender, EventArgs e)
{
lblUsername.Text = Username;
rtxtMessage.Text = Message;
pbProfileImage.Image = ProfileImage;
}
private void rtxtMessage_ContentsResized(object sender, ContentsResizedEventArgs e)
{
rtxtMessage.Height = e.NewRectangle.Height + 4;
int bottom =
rtxtMessage.Bottom +
Padding.Bottom;
Height = Math.Max(bottom, pbProfileImage.Bottom + 4);
}
}
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -0,0 +1,245 @@
namespace qtcnet_client.Controls
{
partial class RegisterControl
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
pbDefaultPfp = new Krypton.Toolkit.KryptonPictureBox();
txtPassword = new Krypton.Toolkit.KryptonTextBox();
lblPassword = new Label();
lblEmail = new Label();
txtEmail = new Krypton.Toolkit.KryptonTextBox();
txtConfirmPassword = new Krypton.Toolkit.KryptonTextBox();
lblConfirmPassword = new Label();
lblConfirmEmail = new Label();
txtConfirmEmail = new Krypton.Toolkit.KryptonTextBox();
cbTOSAgree = new Krypton.Toolkit.KryptonCheckBox();
btnRegister = new Krypton.Toolkit.KryptonButton();
llblReshowLogin = new LinkLabel();
label3 = new Label();
txtUsername = new Krypton.Toolkit.KryptonTextBox();
dtpDateOfBirth = new Krypton.Toolkit.KryptonDateTimePicker();
lblDateOfBirth = new Label();
((System.ComponentModel.ISupportInitialize)pbDefaultPfp).BeginInit();
SuspendLayout();
//
// pbDefaultPfp
//
pbDefaultPfp.Image = Properties.Resources.DefaultPfp;
pbDefaultPfp.Location = new Point(11, 15);
pbDefaultPfp.Name = "pbDefaultPfp";
pbDefaultPfp.Size = new Size(128, 128);
pbDefaultPfp.SizeMode = PictureBoxSizeMode.AutoSize;
pbDefaultPfp.TabIndex = 1;
pbDefaultPfp.TabStop = false;
//
// txtPassword
//
txtPassword.Location = new Point(208, 76);
txtPassword.Name = "txtPassword";
txtPassword.PasswordChar = '●';
txtPassword.Size = new Size(354, 23);
txtPassword.TabIndex = 3;
txtPassword.UseSystemPasswordChar = true;
//
// lblPassword
//
lblPassword.AutoSize = true;
lblPassword.ForeColor = Color.White;
lblPassword.Location = new Point(145, 81);
lblPassword.Name = "lblPassword";
lblPassword.Size = new Size(57, 15);
lblPassword.TabIndex = 8;
lblPassword.Text = "Password";
//
// lblEmail
//
lblEmail.AutoSize = true;
lblEmail.ForeColor = Color.White;
lblEmail.Location = new Point(145, 51);
lblEmail.Name = "lblEmail";
lblEmail.Size = new Size(36, 15);
lblEmail.TabIndex = 7;
lblEmail.Text = "Email";
//
// txtEmail
//
txtEmail.Location = new Point(187, 47);
txtEmail.Name = "txtEmail";
txtEmail.Size = new Size(375, 23);
txtEmail.TabIndex = 2;
//
// txtConfirmPassword
//
txtConfirmPassword.Location = new Point(255, 134);
txtConfirmPassword.Name = "txtConfirmPassword";
txtConfirmPassword.PasswordChar = '●';
txtConfirmPassword.Size = new Size(307, 23);
txtConfirmPassword.TabIndex = 5;
txtConfirmPassword.UseSystemPasswordChar = true;
//
// lblConfirmPassword
//
lblConfirmPassword.AutoSize = true;
lblConfirmPassword.ForeColor = Color.White;
lblConfirmPassword.Location = new Point(145, 138);
lblConfirmPassword.Name = "lblConfirmPassword";
lblConfirmPassword.Size = new Size(104, 15);
lblConfirmPassword.TabIndex = 12;
lblConfirmPassword.Text = "Confirm Password";
//
// lblConfirmEmail
//
lblConfirmEmail.AutoSize = true;
lblConfirmEmail.ForeColor = Color.White;
lblConfirmEmail.Location = new Point(145, 109);
lblConfirmEmail.Name = "lblConfirmEmail";
lblConfirmEmail.Size = new Size(83, 15);
lblConfirmEmail.TabIndex = 11;
lblConfirmEmail.Text = "Confirm Email";
//
// txtConfirmEmail
//
txtConfirmEmail.Location = new Point(234, 105);
txtConfirmEmail.Name = "txtConfirmEmail";
txtConfirmEmail.Size = new Size(328, 23);
txtConfirmEmail.TabIndex = 4;
//
// cbTOSAgree
//
cbTOSAgree.Location = new Point(255, 190);
cbTOSAgree.Name = "cbTOSAgree";
cbTOSAgree.PaletteMode = Krypton.Toolkit.PaletteMode.Office2007Silver;
cbTOSAgree.Size = new Size(354, 20);
cbTOSAgree.TabIndex = 7;
cbTOSAgree.Values.Text = "By Checking Me, You Agree To This Servers Terms Of Service";
//
// btnRegister
//
btnRegister.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
btnRegister.Location = new Point(502, 231);
btnRegister.Name = "btnRegister";
btnRegister.Size = new Size(90, 25);
btnRegister.TabIndex = 9;
btnRegister.Values.DropDownArrowColor = Color.Empty;
btnRegister.Values.Text = "Register";
btnRegister.Click += btnRegister_Click;
//
// llblReshowLogin
//
llblReshowLogin.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
llblReshowLogin.AutoSize = true;
llblReshowLogin.Location = new Point(499, 210);
llblReshowLogin.Name = "llblReshowLogin";
llblReshowLogin.Size = new Size(95, 15);
llblReshowLogin.TabIndex = 8;
llblReshowLogin.TabStop = true;
llblReshowLogin.Text = "Meant To Login?";
llblReshowLogin.LinkClicked += llblReshowLogin_LinkClicked;
//
// label3
//
label3.AutoSize = true;
label3.ForeColor = Color.White;
label3.Location = new Point(145, 22);
label3.Name = "label3";
label3.Size = new Size(60, 15);
label3.TabIndex = 17;
label3.Text = "Username";
//
// txtUsername
//
txtUsername.Location = new Point(211, 18);
txtUsername.Name = "txtUsername";
txtUsername.Size = new Size(351, 23);
txtUsername.TabIndex = 1;
//
// dtpDateOfBirth
//
dtpDateOfBirth.Location = new Point(226, 163);
dtpDateOfBirth.Name = "dtpDateOfBirth";
dtpDateOfBirth.Size = new Size(336, 21);
dtpDateOfBirth.TabIndex = 6;
//
// lblDateOfBirth
//
lblDateOfBirth.AutoSize = true;
lblDateOfBirth.ForeColor = Color.White;
lblDateOfBirth.Location = new Point(145, 165);
lblDateOfBirth.Name = "lblDateOfBirth";
lblDateOfBirth.Size = new Size(75, 15);
lblDateOfBirth.TabIndex = 19;
lblDateOfBirth.Text = "Date Of Birth";
//
// RegisterControl
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
BackColor = Color.Transparent;
Controls.Add(lblDateOfBirth);
Controls.Add(dtpDateOfBirth);
Controls.Add(label3);
Controls.Add(txtUsername);
Controls.Add(llblReshowLogin);
Controls.Add(btnRegister);
Controls.Add(cbTOSAgree);
Controls.Add(txtConfirmPassword);
Controls.Add(lblConfirmPassword);
Controls.Add(lblConfirmEmail);
Controls.Add(txtConfirmEmail);
Controls.Add(txtPassword);
Controls.Add(lblPassword);
Controls.Add(lblEmail);
Controls.Add(txtEmail);
Controls.Add(pbDefaultPfp);
Name = "RegisterControl";
Size = new Size(597, 260);
((System.ComponentModel.ISupportInitialize)pbDefaultPfp).EndInit();
ResumeLayout(false);
PerformLayout();
}
#endregion
private Krypton.Toolkit.KryptonPictureBox pbDefaultPfp;
private Krypton.Toolkit.KryptonTextBox txtPassword;
private Label lblPassword;
private Label lblEmail;
private Krypton.Toolkit.KryptonTextBox txtEmail;
private Krypton.Toolkit.KryptonTextBox txtConfirmPassword;
private Label lblConfirmPassword;
private Label lblConfirmEmail;
private Krypton.Toolkit.KryptonTextBox txtConfirmEmail;
private Krypton.Toolkit.KryptonCheckBox cbTOSAgree;
private Krypton.Toolkit.KryptonButton btnRegister;
private LinkLabel llblReshowLogin;
private Label label3;
private Krypton.Toolkit.KryptonTextBox txtUsername;
private Krypton.Toolkit.KryptonDateTimePicker dtpDateOfBirth;
private Label lblDateOfBirth;
}
}

View File

@ -0,0 +1,74 @@
using Krypton.Toolkit;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Formats.Cbor;
using System.Text;
using System.Windows.Forms;
namespace qtcnet_client.Controls
{
public partial class RegisterControl : UserControl
{
public string Username { get; private set; } = string.Empty;
public string Email { get; private set; } = string.Empty;
public string Password { get; private set; } = string.Empty;
public DateTime DateOfBirth { get; private set; } = DateTime.MinValue;
public event EventHandler? OnReshowLoginPressed;
public event EventHandler? OnRegister;
public RegisterControl()
{
InitializeComponent();
}
private void llblReshowLogin_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
ToggleControls(false);
OnReshowLoginPressed?.Invoke(this, EventArgs.Empty);
}
private void btnRegister_Click(object sender, EventArgs e)
{
if(ValidateForm())
{
ToggleControls(false);
Username = txtUsername.Text;
Email = txtEmail.Text;
Password = txtPassword.Text;
DateOfBirth = dtpDateOfBirth.Value;
OnRegister?.Invoke(this, EventArgs.Empty);
}
else
{
KryptonMessageBox.Show("A Required Field Is Missing. Please Complete The Form.", "Oops.");
ToggleControls(true);
}
}
public void ToggleControls(bool toggle)
{
txtUsername.Enabled = toggle;
txtEmail.Enabled = toggle;
txtPassword.Enabled = toggle;
txtConfirmEmail.Enabled = toggle;
txtConfirmPassword.Enabled = toggle;
cbTOSAgree.Enabled = toggle;
dtpDateOfBirth.Enabled = toggle;
llblReshowLogin.Enabled = toggle;
btnRegister.Enabled = toggle;
}
private bool ValidateForm()
{
return !string.IsNullOrEmpty(txtEmail.Text) &&
!string.IsNullOrEmpty(txtPassword.Text) &&
(txtConfirmEmail.Text == txtEmail.Text) &&
(txtConfirmPassword.Text == txtPassword.Text) &&
cbTOSAgree.Checked;
}
}
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -0,0 +1,97 @@
namespace qtcnet_client.Controls
{
partial class RoomControl
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
pbIcon = new PictureBox();
lblRoomName = new Label();
lblRoomUserCount = new Label();
((System.ComponentModel.ISupportInitialize)pbIcon).BeginInit();
SuspendLayout();
//
// pbIcon
//
pbIcon.Image = Properties.Resources.RoomsChatIcon;
pbIcon.Location = new Point(3, 3);
pbIcon.Name = "pbIcon";
pbIcon.Size = new Size(40, 35);
pbIcon.SizeMode = PictureBoxSizeMode.Zoom;
pbIcon.TabIndex = 0;
pbIcon.TabStop = false;
//
// lblRoomName
//
lblRoomName.AutoSize = true;
lblRoomName.Font = new Font("Segoe UI", 8F, FontStyle.Bold, GraphicsUnit.Point, 0);
lblRoomName.Location = new Point(49, 14);
lblRoomName.Name = "lblRoomName";
lblRoomName.Size = new Size(38, 13);
lblRoomName.TabIndex = 1;
lblRoomName.Text = "Room";
lblRoomName.DoubleClick += lblRoomName_DoubleClick;
lblRoomName.MouseLeave += lblRoomName_MouseLeave;
lblRoomName.MouseHover += lblRoomName_MouseHover;
//
// lblRoomUserCount
//
lblRoomUserCount.AutoSize = true;
lblRoomUserCount.Dock = DockStyle.Right;
lblRoomUserCount.Font = new Font("Segoe UI Semibold", 9F, FontStyle.Bold | FontStyle.Italic, GraphicsUnit.Point, 0);
lblRoomUserCount.ForeColor = Color.Silver;
lblRoomUserCount.Location = new Point(154, 0);
lblRoomUserCount.Name = "lblRoomUserCount";
lblRoomUserCount.Padding = new Padding(0, 15, 0, 0);
lblRoomUserCount.Size = new Size(14, 30);
lblRoomUserCount.TabIndex = 2;
lblRoomUserCount.Text = "0";
lblRoomUserCount.TextAlign = ContentAlignment.MiddleCenter;
//
// RoomControl
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
BackColor = Color.Transparent;
Controls.Add(lblRoomUserCount);
Controls.Add(lblRoomName);
Controls.Add(pbIcon);
DoubleBuffered = true;
Name = "RoomControl";
Size = new Size(168, 42);
Load += RoomControl_Load;
((System.ComponentModel.ISupportInitialize)pbIcon).EndInit();
ResumeLayout(false);
PerformLayout();
}
#endregion
private PictureBox pbIcon;
private Label lblRoomName;
private Label lblRoomUserCount;
}
}

View File

@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace qtcnet_client.Controls
{
public partial class RoomControl : UserControl
{
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public string RoomId { get; set; } = string.Empty;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string RoomName { get; set; } = "Room";
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public int UserCount { get; set; } = 0;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public bool IsUserCountVisible { get; set; } = true;
public event EventHandler? OnRoomDoubleClicked;
bool _isHovering = false;
public RoomControl()
{
InitializeComponent();
AutoSize = false;
Height = 59;
}
private void RoomControl_Load(object sender, EventArgs e)
{
lblRoomName.Text = RoomName;
lblRoomUserCount.Text = UserCount.ToString();
lblRoomUserCount.Visible = IsUserCountVisible;
}
private void lblRoomName_MouseHover(object sender, EventArgs e)
{
if (!_isHovering)
{
lblRoomName.ForeColor = Color.White;
_isHovering = true;
}
}
private void lblRoomName_MouseLeave(object sender, EventArgs e)
{
if (_isHovering)
{
lblRoomName.ForeColor = Color.Black;
_isHovering = false;
}
}
private void lblRoomName_DoubleClick(object sender, EventArgs e) => OnRoomDoubleClicked?.Invoke(this, EventArgs.Empty);
}
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -0,0 +1,149 @@
namespace qtcnet_client.Forms
{
partial class ChatRoomForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ChatRoomForm));
lblUsersOnline = new Label();
rtxtChatbox = new RichTextBox();
btnSend = new Button();
lblRoomName = new Label();
lvUsers = new ListView();
flpMessages = new FlowLayoutPanel();
ilStatusIcons = new ImageList(components);
SuspendLayout();
//
// lblUsersOnline
//
lblUsersOnline.AutoSize = true;
lblUsersOnline.Font = new Font("Segoe UI", 10F, FontStyle.Bold);
lblUsersOnline.ForeColor = Color.White;
lblUsersOnline.Location = new Point(12, 47);
lblUsersOnline.Name = "lblUsersOnline";
lblUsersOnline.Size = new Size(92, 19);
lblUsersOnline.TabIndex = 1;
lblUsersOnline.Text = "Users Online";
//
// rtxtChatbox
//
rtxtChatbox.Location = new Point(169, 318);
rtxtChatbox.Name = "rtxtChatbox";
rtxtChatbox.Size = new Size(508, 66);
rtxtChatbox.TabIndex = 3;
rtxtChatbox.Text = "";
rtxtChatbox.KeyDown += rtxtChatbox_KeyDown;
//
// btnSend
//
btnSend.BackgroundImage = Properties.Resources.SendIcon;
btnSend.BackgroundImageLayout = ImageLayout.Zoom;
btnSend.FlatAppearance.BorderSize = 0;
btnSend.FlatStyle = FlatStyle.Flat;
btnSend.Location = new Point(683, 329);
btnSend.Name = "btnSend";
btnSend.Size = new Size(75, 44);
btnSend.TabIndex = 4;
btnSend.UseVisualStyleBackColor = true;
btnSend.Click += btnSend_Click;
//
// lblRoomName
//
lblRoomName.AutoSize = true;
lblRoomName.Font = new Font("Segoe UI", 20F, FontStyle.Bold | FontStyle.Italic);
lblRoomName.ForeColor = Color.White;
lblRoomName.Location = new Point(6, 4);
lblRoomName.Name = "lblRoomName";
lblRoomName.Size = new Size(91, 37);
lblRoomName.TabIndex = 5;
lblRoomName.Text = "Room";
//
// lvUsers
//
lvUsers.Location = new Point(12, 69);
lvUsers.Name = "lvUsers";
lvUsers.Size = new Size(151, 315);
lvUsers.SmallImageList = ilStatusIcons;
lvUsers.TabIndex = 6;
lvUsers.UseCompatibleStateImageBehavior = false;
lvUsers.View = View.List;
//
// flpMessages
//
flpMessages.AutoScroll = true;
flpMessages.BackColor = Color.White;
flpMessages.FlowDirection = FlowDirection.TopDown;
flpMessages.Location = new Point(169, 69);
flpMessages.Name = "flpMessages";
flpMessages.Size = new Size(589, 243);
flpMessages.TabIndex = 7;
flpMessages.WrapContents = false;
//
// ilStatusIcons
//
ilStatusIcons.ColorDepth = ColorDepth.Depth32Bit;
ilStatusIcons.ImageStream = (ImageListStreamer)resources.GetObject("ilStatusIcons.ImageStream");
ilStatusIcons.TransparentColor = Color.Transparent;
ilStatusIcons.Images.SetKeyName(0, "Offline");
ilStatusIcons.Images.SetKeyName(1, "Online");
ilStatusIcons.Images.SetKeyName(2, "Away");
ilStatusIcons.Images.SetKeyName(3, "DoNotDisturb");
//
// ChatRoomForm
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
BackColor = Color.DodgerBlue;
ClientSize = new Size(770, 396);
Controls.Add(flpMessages);
Controls.Add(lvUsers);
Controls.Add(lblRoomName);
Controls.Add(btnSend);
Controls.Add(rtxtChatbox);
Controls.Add(lblUsersOnline);
FormBorderStyle = FormBorderStyle.FixedSingle;
MaximizeBox = false;
Name = "ChatRoomForm";
StartPosition = FormStartPosition.CenterScreen;
Text = "QtC.NET Chat Room";
FormClosed += ChatRoomForm_FormClosed;
Load += ChatRoomForm_Load;
ResumeLayout(false);
PerformLayout();
}
#endregion
private Label lblUsersOnline;
private RichTextBox rtxtChatbox;
private Button btnSend;
private Label lblRoomName;
private ListView lvUsers;
private FlowLayoutPanel flpMessages;
private ImageList ilStatusIcons;
}
}

View File

@ -0,0 +1,105 @@
using qtcnet_client.Controls;
using QtCNETAPI.Dtos.User;
using System.ComponentModel;
namespace qtcnet_client.Forms
{
public partial class ChatRoomForm : Form
{
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public string RoomId { get; set; } = string.Empty;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string RoomName { get; set; } = "Room";
public string SentMessage { get; private set; } = string.Empty;
public event EventHandler? OnSend;
public event EventHandler? OnClose;
public ChatRoomForm()
{
InitializeComponent();
}
private void ChatRoomForm_Load(object sender, EventArgs e)
{
Text = $"QtC.NET Chat Room - {RoomName}";
lblRoomName.Text = RoomName;
}
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 btnSend_Click(object sender, EventArgs e)
{
if (ValidateChatBox())
{
SentMessage = rtxtChatbox.Text;
OnSend?.Invoke(this, EventArgs.Empty);
rtxtChatbox.Clear();
}
}
private void ChatRoomForm_FormClosed(object sender, FormClosedEventArgs e)
{
OnClose?.Invoke(this, EventArgs.Empty);
Close();
}
public void AddUsersToRoomList(List<UserInformationDto> users)
{
lvUsers.SuspendLayout();
lvUsers.Items.Clear();
List<ListViewItem> lvis = [];
foreach (UserInformationDto user in users)
{
lvis.Add(new ListViewItem
{
Tag = user.Id,
Text = user.Username,
ImageIndex = user.Status
});
}
lvUsers.Items.AddRange([.. lvis.DistinctBy(u => u.Tag)]);
lvUsers.ResumeLayout(true);
}
public void AddUserToRoomList(UserInformationDto user)
{
ListViewItem lvi = new()
{
Tag = user.Id,
Text = user.Username,
ImageIndex = user.Status
};
lvUsers.Items.Add(lvi);
}
public void AddMessage(MessageControl messageCtrl)
{
messageCtrl.Width = flpMessages.ClientSize.Width - 10;
messageCtrl.Margin = new Padding(0, 0, 0, 6);
flpMessages.Controls.Add(messageCtrl);
flpMessages.ScrollControlIntoView(messageCtrl);
}
private bool ValidateChatBox()
{
return !string.IsNullOrWhiteSpace(rtxtChatbox.Text) || !string.IsNullOrEmpty(rtxtChatbox.Text);
}
}
}

View File

@ -0,0 +1,216 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="ilStatusIcons.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>16, 13</value>
</metadata>
<data name="ilStatusIcons.ImageStream" mimetype="application/x-microsoft.net.object.binary.base64">
<value>
AAEAAAD/////AQAAAAAAAAAMAgAAAEZTeXN0ZW0uV2luZG93cy5Gb3JtcywgQ3VsdHVyZT1uZXV0cmFs
LCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5BQEAAAAmU3lzdGVtLldpbmRvd3MuRm9ybXMu
SW1hZ2VMaXN0U3RyZWFtZXIBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAAGhQAAAJNU0Z0AUkBTAIBAQQB
AAGYAQABmAEAARABAAEQAQAE/wEhAQAI/wFCAU0BNgcAATYDAAEoAwABQAMAASADAAEBAQABIAYAASD/
AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AC4AAwYBBwM0AVQDUQGiA14B0gNaAekDYAHoA10B
0QNQAZ8DMQFNAwUBBhgAAwYBBwM0AVQDUQGiA14B0gNaAekDYAHoA10B0QNQAZ8DMQFNAwUBBhgAAwYB
BwM0AVQDUQGiA14B0gNaAekDYAHoA10B0QNQAZ8DMQFNAwUBBhgAAwYBBwM0AVQDUQGiA14B0gNaAekD
YAHoA10B0QNQAZ8DMQFNAwUBBhQAAyABLQNUAasDWwHkA0wB9QMkAfsDNwH+AzcB/gMkAfsDUwH0A2IB
4QNRAaEDHgEqEAADIAEtA1QBqwNbAeQBRgFYAUYB9QEhAVMBIQH7ARMBUwETAf4BEwFTARMB/gEhAVMB
IQH7AU8BUwFPAfQDYgHhA1EBoQMeASoQAAMgAS0DVAGrA1sB5AFGAlgB9QEhAlMB+wETAlMB/gETAlMB
/gEhAlMB+wFPAlMB9ANiAeEDUQGhAx4BKhAAAyABLQNUAasDWwHkAkYBWAH1AiEBUwH7AhMBUwH+AhMB
UwH+AiEBUwH7Ak8BUwH0A2IB4QNRAaEDHgEqDAADGwElA1gBvQNaAfIDOwH+AzAB/wM5Af8DPAH/AzYB
/wMqAf8DJAH/A0AB/QNZAfADVgGyAxoBIwgAAxsBJQNYAb0BVwFaAVcB8gETAVsBEwH+AQABVwEAAf8B
AAFnAQAB/wEAAWwBAAH/AQABYQEAAf8BAAFMAQAB/wEAAUABAAH/ASYBQAEmAf0BVAFeAVQB8ANWAbIB
GQEaARkBIwgAAxsBJQNYAb0BVwJaAfIBEwJbAf4BAAJXAf8BAAJnAf8BAAJsAf8BAAJhAf8BAAJMAf8B
AAJAAf8BJgJAAf0BVAJeAfADVgGyARkCGgEjCAADGwElA1gBvQJXAVoB8gITAVsB/gIAAVcB/wIAAWcB
/wIAAWwB/wIAAWEB/wIAAUwB/wIAAUAB/wImAUAB/QJUAV4B8ANWAbICGQEaASMEAAMDAQQDUgGlA18B
8wNJAf8DVQH/A2UB/wNxAf8DdQH/A3EB/wNkAf8DTAH/AzEB/wM3Af4DXQHuA1ABmgMDAQQDAwEEAVIB
UwFSAaUBUQFvAVEB8wEAAYIBAAH/AQABmQEAAf8BAAG2AQAB/wEAAcwBAAH/AQAB0wEAAf8BAAHLAQAB
/wEAAbMBAAH/AQABiAEAAf8BAAFXAQAB/wETAVMBEwH+AVoBYQFaAe4DUAGaAwMBBAMDAQQBUgJTAaUB
UQJvAfMBAAKCAf8BAAKZAf8BAAK2Af8BAALMAf8BAALTAf8BAALLAf8BAAKzAf8BAAKIAf8BAAJXAf8B
EwJTAf4BWgJhAe4DUAGaAwMBBAMDAQQCUgFTAaUCUQFvAfMCAAGCAf8CAAGZAf8CAAG2Af8CAAHMAf8C
AAHTAf8CAAHLAf8CAAGzAf8CAAGIAf8CAAFXAf8CEwFTAf4CWgFhAe4DUAGaAwMBBAMtAUQDYAHoA3YB
/gNuAf8DewH/A4UB/wOKAf8DjAH/A4oB/wOFAf8DdgH/A1cB/wMyAf8DQAH9A14B3QMqAT8DLQFEAWAB
aQFgAegBEwGOARMB/gEAAcYBAAH/AQAB3AEAAf8BAAHuAQAB/wEAAfgBAAH/AQAB+wEAAf8BAAH5AQAB
/wEAAe8BAAH/AQAB1AEAAf8BAAGcAQAB/wEAAVoBAAH/ASYBQAEmAf0DXgHdAyoBPwMtAUQBYAJpAegB
EwKOAf4BAALGAf8BAALcAf8BAALuAf8BAAL4Af8BAAL7Af8BAAL5Af8BAALvAf8BAALUAf8BAAKcAf8B
AAJaAf8BJgJAAf0DXgHdAyoBPwMtAUQCYAFpAegCEwGOAf4CAAHGAf8CAAHcAf8CAAHuAf8CAAH4Af8C
AAH7Af8CAAH5Af8CAAHvAf8CAAHUAf8CAAGcAf8CAAFaAf8CJgFAAf0DXgHdAyoBPwNOAZUDdwH4A38B
/wOFAf8DigH/A40B/wOOAf8DjgH/A44B/wONAf8DiQH/A3cB/wNNAf8DJQH/A1oB8gNKAYsDTgGVAT0B
kAE9AfgBAAHlAQAB/wEAAe8BAAH/AQAB+AEAAf8BAAH9AQAB/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB
/wEAAf4BAAH/AQAB9gEAAf8BAAHVAQAB/wEAAYsBAAH/AQABQQEAAf8BVwFaAVcB8gNKAYsDTgGVAT0C
kAH4AQAC5QH/AQAC7wH/AQAC+AH/AQAC/QH/AQAD/wEAA/8BAAP/AQAC/gH/AQAC9gH/AQAC1QH/AQAC
iwH/AQACQQH/AVcCWgHyA0oBiwNOAZUCPQGQAfgCAAHlAf8CAAHvAf8CAAH4Af8CAAH9Af8CAAL/AgAC
/wIAAv8CAAH+Af8CAAH2Af8CAAHVAf8CAAGLAf8CAAFBAf8CVwFaAfIDSgGLA18B0wN+AfwDkwH/A44B
/wONAf8DjgH/A44B/wOOAf8DjgH/A44B/wONAf8DhQH/A2cB/wM0Af8DQQH5A1oBxAFbAV8BWwHTASsB
ugErAfwBDgH7AQ4B/wEDAf0BAwH/AQAB/gEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB
/wEAAf8BAAH/AQAB/QEAAf8BAAHvAQAB/wEAAbkBAAH/AQABXQEAAf8BOAFBATgB+QNaAcQBWwJfAdMB
KwK6AfwBDgL7Af8BAwL9Af8BAAL+Af8BAAP/AQAD/wEAA/8BAAP/AQAD/wEAAv0B/wEAAu8B/wEAArkB
/wEAAl0B/wE4AkEB+QNaAcQCWwFfAdMCKwG6AfwCDgH7Af8CAwH9Af8CAAH+Af8CAAL/AgAC/wIAAv8C
AAL/AgAC/wIAAf0B/wIAAe8B/wIAAbkB/wIAAV0B/wI4AUEB+QNaAcQDbgH1A4AB/gOfAf8DkwH/A48B
/wOOAf8DjgH/A44B/wOOAf8DjgH/A44B/wOLAf8DdwH/A0gB/wNAAf0DYgHhAUsBgwFLAfUBNwHZATcB
/gEnAf8BJwH/AQsB/wELAf8BAQH/AQEB/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB
/wEAAf8BAAH/AQAB/wEAAfkBAAH/AQAB1gEAAf8BAAGBAQAB/wEmAUABJgH9A2IB4QFLAoMB9QE3AtkB
/gEnA/8BCwP/AQED/wEAA/8BAAP/AQAD/wEAA/8BAAP/AQAD/wEAAvkB/wEAAtYB/wEAAoEB/wEmAkAB
/QNiAeECSwGDAfUCNwHZAf4CJwL/AgsC/wIBAv8CAAL/AgAC/wIAAv8CAAL/AgAC/wIAAv8CAAH5Af8C
AAHWAf8CAAGBAf8CJgFAAf0DYgHhA3QB9gOIAf4DqwH/A5kB/wOQAf8DjgH/A44B/wOOAf8DjgH/A44B
/wOOAf8DjQH/A38B/wNVAf8DQAH9A14B4gFIAYcBSAH2AVoB2QFaAf4BQgH/AUIB/wEZAf8BGQH/AQQB
/wEEAf8BAAH/AQAB/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB/wEAAf8BAAH9AQAB
/wEAAeQBAAH/AQABmAEAAf8BJgFAASYB/QNeAeIBSAKHAfYBWgLZAf4BQgP/ARkD/wEEA/8BAAP/AQAD
/wEAA/8BAAP/AQAD/wEAA/8BAAL9Af8BAALkAf8BAAKYAf8BJgJAAf0DXgHiAkgBhwH2AloB2QH+AkIC
/wIZAv8CBAL/AgAC/wIAAv8CAAL/AgAC/wIAAv8CAAL/AgAB/QH/AgAB5AH/AgABmAH/AiYBQAH9A14B
4gNhAdYDiQH8A7gB/wOjAf8DkwH/A44B/wOOAf8DjgH/A44B/wOOAf8DjgH/A40B/wOCAf8DXAH/A00B
+gNaAccBXAFhAVwB1gFkAb4BZAH8AV8B/wFfAf8BLwH/AS8B/wEMAf8BDAH/AQEB/wEBAf8BAAH/AQAB
/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB/gEAAf8BAAHqAQAB/wEAAaUBAAH/AScB
TQEnAfoDWgHHAVwCYQHWAWQCvgH8AV8D/wEvA/8BDAP/AQED/wEAA/8BAAP/AQAD/wEAA/8BAAP/AQAC
/gH/AQAC6gH/AQACpQH/AScCTQH6A1oBxwJcAWEB1gJkAb4B/AJfAv8CLwL/AgwC/wIBAv8CAAL/AgAC
/wIAAv8CAAL/AgAC/wIAAf4B/wIAAeoB/wIAAaUB/wInAU0B+gNaAccDUAGaA40B+QPFAf8DsgH/A5wB
/wORAf8DjgH/A44B/wOOAf8DjgH/A48B/wOOAf8DgwH/A2AB/wNaAfIDTAGQA1ABmgFqAaEBagH5AXwB
/wF8Af8BUQH/AVEB/wEfAf8BHwH/AQcB/wEHAf8BAQH/AQEB/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB
/wECAf8BAgH/AQIB/gECAf8BAAHrAQAB/wEAAa0BAAH/AVcBawFXAfIDTAGQA1ABmgFqAqEB+QF8A/8B
UQP/AR8D/wEHA/8BAQP/AQAD/wEAA/8BAAP/AQID/wECAv4B/wEAAusB/wEAAq0B/wFXAmsB8gNMAZAD
UAGaAmoBoQH5AnwC/wJRAv8CHwL/AgcC/wIBAv8CAAL/AgAC/wIAAv8CAgL/AgIB/gH/AgAB6wH/AgAB
rQH/AlcBawHyA0wBkAMvAUkDbAHrA6oB/gPGAf8DrgH/A5wB/wOTAf8DkAH/A48B/wOQAf8DkwH/A5MB
/wOFAf8DUwH9A2AB4AMtAUUDLwFJA2wB6wGAAdkBgAH+AX8B/wF/Af8BSQH/AUkB/wEfAf8BHwH/AQwB
/wEMAf8BBQH/AQUB/wEDAf8BAwH/AQUB/wEFAf8BCgH/AQoB/wEKAf4BCgH/AQEB7QEBAf8BJgG2ASYB
/QFgAWYBYAHgAy0BRQMvAUkDbAHrAYAC2QH+AX8D/wFJA/8BHwP/AQwD/wEFA/8BAwP/AQUD/wEKA/8B
CgL+Af8BAQLtAf8BJgK2Af0BYAJmAeADLQFFAy8BSQNsAesCgAHZAf4CfwL/AkkC/wIfAv8CDAL/AgUC
/wIDAv8CBQL/AgoC/wIKAf4B/wIBAe0B/wImAbYB/QJgAWYB4AMtAUUDAwEEA1YBrgN8AfUD2QH/A8sB
/wO3Af8DpwH/A50B/wOaAf8DnAH/A58B/wObAf8DiQH/A2gB8ANSAaMDAwEEAwMBBANWAa4BbwGDAW8B
9QGoAf8BqAH/AYkB/wGJAf8BXAH/AVwB/wE3Af8BNwH/ASIB/wEiAf8BGwH/ARsB/wEfAf8BHwH/ASYB
/wEmAf8BHQH/AR0B/wEFAfMBBQH/AVQBawFUAfADUgGjAwMBBAMDAQQDVgGuAW8CgwH1AagD/wGJA/8B
XAP/ATcD/wEiA/8BGwP/AR8D/wEmA/8BHQP/AQUC8wH/AVQCawHwA1IBowMDAQQDAwEEA1YBrgJvAYMB
9QKoAv8CiQL/AlwC/wI3Av8CIgL/AhsC/wIfAv8CJgL/Ah0C/wIFAfMB/wJUAWsB8ANSAaMDAwEEBAAD
HAEnA10BxwN9AfYDuQH+A9cB/wPMAf8DwgH/A7sB/wO3Af8DsQH/A4AB/gNpAfQDWQG8AxsBJggAAxwB
JwNdAccBdwGHAXcB9gGRAdkBkQH+AaUB/wGlAf8BiwH/AYsB/wF0Af8BdAH/AWYB/wFmAf8BXAH/AVwB
/wFOAf8BTgH/AUMB2QFDAf4BUgF9AVIB9AFXAVkBVwG8AxsBJggAAxwBJwNdAccBdwKHAfYBkQLZAf4B
pQP/AYsD/wF0A/8BZgP/AVwD/wFOA/8BQwLZAf4BUgJ9AfQBVwJZAbwDGwEmCAADHAEnA10BxwJ3AYcB
9gKRAdkB/gKlAv8CiwL/AnQC/wJmAv8CXAL/Ak4C/wJDAdkB/gJSAX0B9AJXAVkBvAMbASYMAAMhATAD
WQG2A2wB7gOfAfoDvgH9A9QB/wPMAf8DvgH9A4kB+QNsAesDVQGsAx8BLBAAAyEBMANZAbYBagFuAWoB
7gGHAakBhwH6Aa4BxgGuAf0BnwH/AZ8B/wGMAf8BjAH/AWMBxgFjAf0BaAGhAWgB+QFhAWwBYQHrA1UB
rAMfASwQAAMhATADWQG2AWoCbgHuAYcCqQH6Aa4CxgH9AZ8D/wGMA/8BYwLGAf0BaAKhAfkBYQJsAesD
VQGsAx8BLBAAAyEBMANZAbYCagFuAe4ChwGpAfoCrgHGAf0CnwL/AowC/wJjAcYB/QJoAaEB+QJhAWwB
6wNVAawDHwEsFAADBgEHAzYBWANVAawDZgHlA60B/AOYAfsDZQHiA1MBpwMzAVEDBgEHGAADBgEHAzYB
WANVAawDZgHlAX4BvgF+AfwBdwGyAXcB+wNlAeIDUwGnAzMBUQMGAQcYAAMGAQcDNgFYA1UBrANmAeUB
fgK+AfwBdwKyAfsDZQHiA1MBpwMzAVEDBgEHGAADBgEHAzYBWANVAawDZgHlAn4BvgH8AncBsgH7A2UB
4gNTAacDMwFRAwYBBwwAAUIBTQE+BwABPgMAASgDAAFAAwABIAMAAQEBAAEBBgABARYAA/+BAAHgAQcB
4AEHAeABBwHgAQcBwAEDAcABAwHAAQMBwAEDAYABAQGAAQEBgAEBAYABAVAAAYABAQGAAQEBgAEBAYAB
AQHAAQMBwAEDAcABAwHAAQMB4AEHAeABBwHgAQcB4AEHCw==
</value>
</data>
</root>

View File

@ -0,0 +1,68 @@
namespace qtcnet_client.Forms
{
partial class JackpotSpinForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
lblSpinAnim = new Label();
SuspendLayout();
//
// lblSpinAnim
//
lblSpinAnim.Dock = DockStyle.Fill;
lblSpinAnim.Font = new Font("Segoe UI", 15F, FontStyle.Bold);
lblSpinAnim.ForeColor = Color.White;
lblSpinAnim.Location = new Point(0, 0);
lblSpinAnim.Name = "lblSpinAnim";
lblSpinAnim.Size = new Size(150, 52);
lblSpinAnim.TabIndex = 0;
lblSpinAnim.Text = "9999 Q's Won";
lblSpinAnim.TextAlign = ContentAlignment.MiddleCenter;
//
// JackpotSpinForm
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
BackColor = Color.DodgerBlue;
ClientSize = new Size(150, 52);
Controls.Add(lblSpinAnim);
FormBorderStyle = FormBorderStyle.FixedDialog;
MaximizeBox = false;
MinimizeBox = false;
Name = "JackpotSpinForm";
StartPosition = FormStartPosition.CenterScreen;
Text = "Jackpot Spin";
FormClosed += JackpotSpinForm_FormClosed;
Load += JackpotSpinForm_Load;
ResumeLayout(false);
}
#endregion
private Label lblSpinAnim;
}
}

View File

@ -0,0 +1,83 @@
using System;
using System.ComponentModel;
using System.Windows.Forms;
namespace qtcnet_client.Forms
{
public partial class JackpotSpinForm : Form
{
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public int CurrencyWon { get; set; } = 0;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public double AnimationLengthMilliseconds { get; set; } = 1500;
private readonly System.Windows.Forms.Timer _animationTimer;
private readonly System.Windows.Forms.Timer _stopTimer;
private readonly Random _rndInt = new();
private bool _allowClose = true;
public JackpotSpinForm()
{
InitializeComponent();
FormClosing += JackpotSpinForm_FormClosing;
_animationTimer = new()
{
Interval = 20
};
_animationTimer.Tick += AnimationTimer_Tick;
_stopTimer = new();
_stopTimer.Tick += StopTimer_Tick;
}
private void JackpotSpinForm_Load(object sender, EventArgs e)
{
ExecuteAnimation();
}
private void JackpotSpinForm_FormClosing(object? sender, FormClosingEventArgs e)
{
if (!_allowClose)
e.Cancel = true;
}
private void JackpotSpinForm_FormClosed(object sender, FormClosedEventArgs e)
{
DialogResult = DialogResult.OK;
}
private void ExecuteAnimation()
{
DoubleBuffered = true;
_allowClose = false;
// Start spinning numbers
_animationTimer.Start();
// Schedule stop
_stopTimer.Interval = (int)AnimationLengthMilliseconds;
_stopTimer.Start();
}
private void AnimationTimer_Tick(object? sender, EventArgs e)
{
if (!IsHandleCreated && IsDisposed)
return;
lblSpinAnim.Text = $"{_rndInt.Next(999)} Q's Won";
}
private void StopTimer_Tick(object? sender, EventArgs e)
{
_stopTimer.Stop();
_animationTimer.Stop();
// Show final result
lblSpinAnim.Text = $"{CurrencyWon} Q's Won";
_allowClose = true;
}
}
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

52
qtcnet-client/Forms/MainForm.Designer.cs generated Normal file
View File

@ -0,0 +1,52 @@
namespace qtcnet_client
{
partial class MainForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
SuspendLayout();
//
// MainForm
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(403, 672);
DoubleBuffered = true;
FormBorderStyle = FormBorderStyle.FixedSingle;
MaximizeBox = false;
Name = "MainForm";
StartPosition = FormStartPosition.CenterScreen;
Text = "QtC.NET";
Load += MainForm_Load;
Paint += MainForm_Paint;
Resize += MainForm_Resize;
ResumeLayout(false);
}
#endregion
}
}

View File

@ -0,0 +1,586 @@
using Krypton.Toolkit;
using qtcnet_client.Controls;
using qtcnet_client.Forms;
using qtcnet_client.Model;
using QtCNETAPI.Dtos.User;
using QtCNETAPI.Models;
using QtCNETAPI.Services;
using QtCNETAPI.Services.ApiService;
using QtCNETAPI.Services.GatewayService;
using System.Diagnostics;
using System.Drawing.Drawing2D;
namespace qtcnet_client
{
public partial class MainForm : Form
{
private BrandingControl? BrandingControl;
private LoginControl? LoginControl;
private RegisterControl? RegisterControl;
private CurrentProfileControl? CurrentProfileControl;
private MainTabControl? MainTabControl;
private readonly List<ChatRoomForm> OpenChatRoomForms = [];
private readonly List<ProfileForm> OpenProfileForms = [];
private Size LoggedOutSize = new(615, 702);
private Size LoggedInSize = new(419, 715);
private readonly IApiService _apiService;
private readonly IGatewayService _gatewayService;
private readonly LoggingService _loggingService;
private readonly CredentialService _credentialService;
private readonly ClientConfig _config;
public MainForm(IApiService apiService, IGatewayService gatewayService, LoggingService loggingService, CredentialService credentialService, ClientConfig config)
{
_apiService = apiService;
_gatewayService = gatewayService;
_loggingService = loggingService;
_credentialService = credentialService;
_config = config;
InitializeComponent();
}
private void MainForm_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
Rectangle _formRect = ClientRectangle;
if (_formRect.IsEmpty)
_formRect = e.ClipRectangle;
// draw gradient background
using LinearGradientBrush b = new(_formRect, Color.White, Color.DodgerBlue, LinearGradientMode.Vertical);
g.FillRectangle(b, _formRect);
}
private void MainForm_Resize(object sender, EventArgs e) => Invalidate();
private async void MainForm_Load(object sender, EventArgs e)
{
SuspendLayout();
Size = LoggedOutSize;
// add branding control
BrandingControl = new()
{
Location = new(-2, -17),
Anchor = AnchorStyles.Top | AnchorStyles.Left
};
// add login control
LoginControl = new()
{
Location = new(12, 233)
};
LoginControl.OnSuccessfulLogin += LoginControl_OnSuccessfulLogin;
LoginControl.OnRegisterPressed += LoginControl_OnRegisterPressed;
Controls.Add(BrandingControl);
Controls.Add(LoginControl);
ResumeLayout(true);
// ensure api is reachable before letting the user login
LoginControl.ToggleControls(false);
var _pingRes = await Task.Run(PingAPI);
if (_pingRes)
{
// check for existing login in credential service
var _token = _credentialService.GetAccessToken();
if (_token != null)
{
// try it
var _refreshRes = await _apiService.RefreshLogin(_token);
if (_refreshRes.Success)
OnSuccessfulLoginRefresh();
}
else
LoginControl.ToggleControls(true);
}
else
{
KryptonMessageBox.Show("The Server Is Unreachable. Ensure Your Config Is Correct. The Client Will Now Close", "Oops");
Application.Exit();
}
}
private void LoginControl_OnRegisterPressed(object? sender, EventArgs e)
{
if (sender is LoginControl _)
{
// pause ui
SuspendLayout();
// remove and dispose login control
Controls.Remove(LoginControl);
LoginControl?.Dispose();
LoginControl = null;
// create register control
RegisterControl = new()
{
Location = new(1, 233)
};
RegisterControl.OnReshowLoginPressed += RegisterControl_OnReshowLoginPressed;
RegisterControl.OnRegister += RegisterControl_OnRegister;
Controls.Add(RegisterControl);
ResumeLayout(true);
}
}
private void RegisterControl_OnReshowLoginPressed(object? sender, EventArgs e)
{
if (sender is RegisterControl _)
{
// pause ui
SuspendLayout();
// remove and dispose register control
Controls.Remove(RegisterControl);
RegisterControl?.Dispose();
RegisterControl = null;
// add login control
LoginControl = new()
{
Location = new(12, 233)
};
LoginControl.OnSuccessfulLogin += LoginControl_OnSuccessfulLogin;
LoginControl.OnRegisterPressed += LoginControl_OnRegisterPressed;
Controls.Add(LoginControl);
ResumeLayout(true);
}
}
private async void RegisterControl_OnRegister(object? sender, EventArgs e)
{
if (sender is RegisterControl _senderCtrl)
{
// attempt registration
var res = await _apiService.RegisterAsync(new()
{
Username = _senderCtrl.Username,
Email = _senderCtrl.Email,
Password = _senderCtrl.Password,
DateOfBirth = _senderCtrl.DateOfBirth,
});
if (res.Success && res.Data != null)
{
KryptonMessageBox.Show("Registration Success!\n\nNote: Some Servers Require Email Verification, Check Your Email!\n\nYou May Now Login.", "Success!");
// pause ui
SuspendLayout();
// remove and dispose register control
Controls.Remove(RegisterControl);
RegisterControl?.Dispose();
RegisterControl = null;
// add login control
LoginControl = new()
{
Location = new(12, 233)
};
LoginControl.OnSuccessfulLogin += LoginControl_OnSuccessfulLogin;
LoginControl.OnRegisterPressed += LoginControl_OnRegisterPressed;
Controls.Add(LoginControl);
ResumeLayout(true);
}
else
{
KryptonMessageBox.Show($"Sorry, Something Went Wrong Registering You. Please Try Again Later.\n\nServer Response = {res.Message}", "Uh Oh.",
KryptonMessageBoxButtons.OK,
KryptonMessageBoxIcon.Error);
_senderCtrl.ToggleControls(true);
}
}
}
private async void LoginControl_OnSuccessfulLogin(object? sender, EventArgs e)
{
if (sender is LoginControl _senderCtrl)
{
// login using function
var _loginRes = await OnSuccessfulLogin(new() { Email = _senderCtrl.Email, Password = _senderCtrl.Password, RememberMe = _senderCtrl.RememberMe });
if(_loginRes)
{
// pause ui
SuspendLayout();
// remove and dispose login and branding controls
Controls.Remove(LoginControl);
Controls.Remove(BrandingControl);
LoginControl?.Dispose();
LoginControl = null;
BrandingControl?.Dispose();
BrandingControl = null;
// set size to logged in size
Size = LoggedInSize;
// start gateway connection
var _gwRes = await SetupGatewayConnection();
if(_gwRes)
{
// setup current profile control based on current user
CurrentProfileControl = new()
{
Username = _apiService.CurrentUser!.Username,
CurrencyCount = _apiService.CurrentUser.CurrencyAmount,
Location = new(12, 12),
};
// get profile image for the current user
var _currentProfileImageRes = await _apiService.GetUserProfilePic(_apiService.CurrentUser.Id);
if (_currentProfileImageRes.Success && _currentProfileImageRes.Data != null)
{
using MemoryStream ms = new(_currentProfileImageRes.Data);
Image img = Image.FromStream(ms);
CurrentProfileControl.ProfileImage = img;
}
// setup main tab control
MainTabControl = new()
{
Location = new(12, 91)
};
// add controls to current form
Controls.Add(CurrentProfileControl);
Controls.Add(MainTabControl);
// get and set contacts
var _currentUserContacts = await _apiService.GetCurrentUserContacts();
if (_currentUserContacts.Success && _currentUserContacts.Data != null)
await SetupContactsUI(_currentUserContacts.Data);
// get and set user directory
var _currentUserDirectory = await _apiService.GetAllUsersAsync();
if (_currentUserDirectory.Success && _currentUserDirectory.Data != null)
await SetupDirectoryUI(_currentUserDirectory.Data);
ResumeLayout(true);
}
else
{
ResumeLayout(true);
KryptonMessageBox.Show("An Error Occured Trying To Connect To The Gateway. Please Try Again Later.", "Uh Oh.", KryptonMessageBoxButtons.OK, KryptonMessageBoxIcon.Error);
Application.Exit();
}
}
}
}
private void MainTabControl_OnRoomControlDoubleClicked(object? sender, EventArgs e)
{
if (sender is RoomControl _)
{
}
}
private void Chat_OnSend(object? sender, EventArgs e)
{
if (sender is ChatRoomForm _)
{
}
}
private void Chat_OnClose(object? sender, EventArgs e)
{
if (sender is ChatRoomForm chatRoom)
{
OpenChatRoomForms.Remove(chatRoom);
chatRoom.Dispose();
}
}
private void MainTabControl_OnContactControlDoubleClicked(object? sender, EventArgs e)
{
if (sender is ContactControl _)
{
}
}
private void ProfileForm_OnClose(object? sender, EventArgs e)
{
if (sender is ProfileForm profile)
{
OpenProfileForms.Remove(profile);
profile.Dispose();
}
}
private void _apiService_OnCurrentUserUpdate(object? sender, EventArgs e)
{
throw new NotImplementedException();
}
private async Task<bool> PingAPI()
{
// ping it
var res = await _apiService.PingServerAsync();
// return result
return res.Success;
}
private async Task<bool> SetupGatewayConnection()
{
// subscribe to gateway events
//_gatewayService.OnServerReconnecting += _gatewayService_OnServerReconnecting;
//_gatewayService.OnServerReconnected += _gatewayService_OnServerReconnected;
//_gatewayService.OnServerDisconnect += _gatewayService_OnServerDisconnect;
//_gatewayService.OnDirectMessageReceived += _gatewayService_OnDirectMessageReceived;
//_gatewayService.OnRefreshUserListsReceived += _gatewayService_OnRefreshUserListReceived;
//_gatewayService.OnRefreshRoomListReceived += _gatewayService_OnRefreshRoomListReceived;
//_gatewayService.OnRefreshContactsListReceived += _gatewayService_OnRefreshContactsListReceived;
//_gatewayService.OnServerConfigReceived += _gatewayService_OnServerConfigReceived;
//_gatewayService.OnUserForceLogout += _gatewayService_OnUserForceLogout;
// start connection
await _gatewayService.StartAsync();
return _gatewayService.HubConnection != null && _gatewayService.HubConnection.State == Microsoft.AspNetCore.SignalR.Client.HubConnectionState.Connected;
}
private void _gatewayService_OnUserForceLogout(object? sender, EventArgs e)
{
throw new NotImplementedException();
}
private void _gatewayService_OnServerConfigReceived(object? sender, EventArgs e)
{
throw new NotImplementedException();
}
private void _gatewayService_OnRefreshContactsListReceived(object? sender, EventArgs e)
{
throw new NotImplementedException();
}
private void _gatewayService_OnRefreshRoomListReceived(object? sender, EventArgs e)
{
throw new NotImplementedException();
}
private void _gatewayService_OnRefreshUserListReceived(object? sender, EventArgs e)
{
throw new NotImplementedException();
}
private void _gatewayService_OnServerDisconnect(object? sender, EventArgs e)
{
throw new NotImplementedException();
}
private void _gatewayService_OnServerReconnected(object? sender, EventArgs e)
{
throw new NotImplementedException();
}
private void _gatewayService_OnServerReconnecting(object? sender, EventArgs e)
{
throw new NotImplementedException();
}
private void _gatewayService_OnDirectMessageReceived(object? sender, EventArgs e)
{
throw new NotImplementedException();
}
private async Task SetupContactsUI(List<Contact> data)
{
// build ctrl list
List<ContactControl> _contactCtrls = [];
foreach (var contact in data)
{
var ctrl = await BuildContactControl(contact);
if (ctrl != null)
_contactCtrls.Add(ctrl);
}
// add to control
MainTabControl?.AddContacts(_contactCtrls);
}
private async Task SetupDirectoryUI(List<UserInformationDto> data)
{
// build listviewitem list
List<ListViewItem> _userViews = [];
foreach(var user in data)
{
ListViewItem lvi = new()
{
Tag = user.Id,
Text = user.Username,
ImageIndex = user.Status,
};
_userViews.Add(lvi);
}
// add to control
MainTabControl?.AddUsers(_userViews);
}
private async Task<ContactControl?> BuildContactControl(Contact contact)
{
if(_apiService.CurrentUser != null)
{
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)
{
var ctrl = new ContactControl
{
Username = user.Data.Username,
TextStatus = "NOT IMPLEMENTED",
Status = user.Data.Status,
};
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;
}
}
// return the control
return ctrl;
}
}
return default;
}
private async Task<bool> OnSuccessfulLogin(UserLoginDto dto)
{
// attempt login
var res = await _apiService.LoginAsync(dto);
if (res.Success && res.Data != null && _apiService.CurrentUser != null) // "CurrentUser" should not be null on successful login but checking anyways
{
// store refresh token in credential store
_credentialService.SaveAccessToken(_apiService.CurrentUser.Username, res.Message);
// sub to currentuser updates
_apiService.OnCurrentUserUpdate += _apiService_OnCurrentUserUpdate;
return true;
}
else
{
KryptonMessageBox.Show(res.Message, "Login Error", KryptonMessageBoxButtons.OK, KryptonMessageBoxIcon.Error);
return false;
}
}
private async void OnSuccessfulLoginRefresh()
{
if (_apiService.CurrentUser != null)
{
// remove and dispose login and branding controls
Controls.Remove(LoginControl);
Controls.Remove(BrandingControl);
LoginControl?.Dispose();
LoginControl = null;
BrandingControl?.Dispose();
BrandingControl = null;
// set size to logged in size
Size = LoggedInSize;
// start gateway connection
var _gwRes = await SetupGatewayConnection();
if (_gwRes)
{
// setup current profile control based on current user
CurrentProfileControl = new()
{
Username = _apiService.CurrentUser!.Username,
CurrencyCount = _apiService.CurrentUser.CurrencyAmount,
Location = new(12, 12),
};
// get profile image for the current user
var _currentProfileImageRes = await _apiService.GetUserProfilePic(_apiService.CurrentUser.Id);
if (_currentProfileImageRes.Success && _currentProfileImageRes.Data != null)
{
using MemoryStream ms = new(_currentProfileImageRes.Data);
Image img = Image.FromStream(ms);
CurrentProfileControl.ProfileImage = img;
}
// setup main tab control
MainTabControl = new()
{
Location = new(12, 91)
};
// add controls to current form
Controls.Add(CurrentProfileControl);
Controls.Add(MainTabControl);
// get and set contacts
var _currentUserContacts = await _apiService.GetCurrentUserContacts();
if (_currentUserContacts.Success && _currentUserContacts.Data != null)
await SetupContactsUI(_currentUserContacts.Data);
// get and set user directory
var _currentUserDirectory = await _apiService.GetAllUsersAsync();
if (_currentUserDirectory.Success && _currentUserDirectory.Data != null)
await SetupDirectoryUI(_currentUserDirectory.Data);
ResumeLayout(true);
}
}
}
}
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -0,0 +1,166 @@
namespace qtcnet_client.Forms
{
partial class ProfileForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
pbProfileImage = new PictureBox();
lblStatus = new Label();
rtxtBio = new RichTextBox();
tlpActionButtons = new TableLayoutPanel();
tlpUsernameTags = new TableLayoutPanel();
lblUsername = new Label();
tlpTagIcons = new TableLayoutPanel();
((System.ComponentModel.ISupportInitialize)pbProfileImage).BeginInit();
tlpUsernameTags.SuspendLayout();
SuspendLayout();
//
// pbProfileImage
//
pbProfileImage.Image = Properties.Resources.DefaultPfp;
pbProfileImage.Location = new Point(12, 12);
pbProfileImage.Name = "pbProfileImage";
pbProfileImage.Size = new Size(97, 99);
pbProfileImage.SizeMode = PictureBoxSizeMode.Zoom;
pbProfileImage.TabIndex = 0;
pbProfileImage.TabStop = false;
//
// lblStatus
//
lblStatus.AutoEllipsis = true;
lblStatus.Font = new Font("Segoe UI Semibold", 9F, FontStyle.Bold, GraphicsUnit.Point, 0);
lblStatus.ForeColor = Color.White;
lblStatus.Location = new Point(12, 114);
lblStatus.Name = "lblStatus";
lblStatus.Padding = new Padding(0, 0, 0, 15);
lblStatus.Size = new Size(97, 54);
lblStatus.TabIndex = 2;
lblStatus.Text = "Status";
lblStatus.TextAlign = ContentAlignment.MiddleCenter;
//
// rtxtBio
//
rtxtBio.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
rtxtBio.Location = new Point(115, 69);
rtxtBio.Name = "rtxtBio";
rtxtBio.ReadOnly = true;
rtxtBio.Size = new Size(302, 340);
rtxtBio.TabIndex = 4;
rtxtBio.Text = "";
//
// tlpActionButtons
//
tlpActionButtons.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
tlpActionButtons.ColumnCount = 2;
tlpActionButtons.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
tlpActionButtons.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
tlpActionButtons.Location = new Point(296, 415);
tlpActionButtons.Name = "tlpActionButtons";
tlpActionButtons.RowCount = 1;
tlpActionButtons.RowStyles.Add(new RowStyle(SizeType.Percent, 50F));
tlpActionButtons.Size = new Size(121, 42);
tlpActionButtons.TabIndex = 5;
//
// tlpUsernameTags
//
tlpUsernameTags.AutoSize = true;
tlpUsernameTags.ColumnCount = 2;
tlpUsernameTags.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
tlpUsernameTags.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
tlpUsernameTags.Controls.Add(lblUsername, 0, 0);
tlpUsernameTags.Controls.Add(tlpTagIcons, 1, 0);
tlpUsernameTags.Location = new Point(115, 11);
tlpUsernameTags.Name = "tlpUsernameTags";
tlpUsernameTags.RowCount = 1;
tlpUsernameTags.RowStyles.Add(new RowStyle(SizeType.Percent, 50F));
tlpUsernameTags.Size = new Size(304, 55);
tlpUsernameTags.TabIndex = 6;
//
// lblUsername
//
lblUsername.AutoSize = true;
lblUsername.Dock = DockStyle.Left;
lblUsername.Font = new Font("Segoe UI", 20F, FontStyle.Bold);
lblUsername.ForeColor = Color.White;
lblUsername.Location = new Point(3, 0);
lblUsername.Name = "lblUsername";
lblUsername.Size = new Size(146, 55);
lblUsername.TabIndex = 0;
lblUsername.Text = "Username";
lblUsername.TextAlign = ContentAlignment.MiddleLeft;
//
// tlpTagIcons
//
tlpTagIcons.AutoSize = true;
tlpTagIcons.AutoSizeMode = AutoSizeMode.GrowAndShrink;
tlpTagIcons.ColumnCount = 1;
tlpTagIcons.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
tlpTagIcons.GrowStyle = TableLayoutPanelGrowStyle.AddColumns;
tlpTagIcons.Location = new Point(155, 3);
tlpTagIcons.MinimumSize = new Size(145, 49);
tlpTagIcons.Name = "tlpTagIcons";
tlpTagIcons.RowCount = 1;
tlpTagIcons.RowStyles.Add(new RowStyle(SizeType.Percent, 50F));
tlpTagIcons.Size = new Size(145, 49);
tlpTagIcons.TabIndex = 1;
//
// ProfileForm
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
AutoSize = true;
BackColor = Color.DodgerBlue;
ClientSize = new Size(429, 463);
Controls.Add(tlpUsernameTags);
Controls.Add(tlpActionButtons);
Controls.Add(rtxtBio);
Controls.Add(lblStatus);
Controls.Add(pbProfileImage);
MaximizeBox = false;
Name = "ProfileForm";
StartPosition = FormStartPosition.CenterScreen;
Text = "QtC.NET User Profile";
FormClosed += ProfileForm_FormClosed;
Load += ProfileForm_Load;
((System.ComponentModel.ISupportInitialize)pbProfileImage).EndInit();
tlpUsernameTags.ResumeLayout(false);
tlpUsernameTags.PerformLayout();
ResumeLayout(false);
PerformLayout();
}
#endregion
private PictureBox pbProfileImage;
private Label lblStatus;
private RichTextBox rtxtBio;
private TableLayoutPanel tlpActionButtons;
private TableLayoutPanel tlpUsernameTags;
private Label lblUsername;
private TableLayoutPanel tlpTagIcons;
}
}

View File

@ -0,0 +1,52 @@
using qtcnet_client.Properties;
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 ProfileForm : Form
{
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string Username { get; set; } = "Username";
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string Bio { get; set; } = string.Empty;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public int Status { get; set; } = 0;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string TextStatus { get; set; } = string.Empty;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string[] Tags { get; set; } = [];
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public int ContactStatus { get; set; } = 0;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Image ProfileImage { get; set; } = Resources.DefaultPfp;
public event EventHandler? OnClose;
public ProfileForm()
{
InitializeComponent();
}
private void ProfileForm_Load(object sender, EventArgs e)
{
Text = $"QtC.NET User Profile - {Username}";
lblUsername.Text = Username;
lblStatus.Text = TextStatus;
rtxtBio.Text = Bio;
pbProfileImage.Image = ProfileImage;
if (Status == 0) lblStatus.Visible = false;
}
private void ProfileForm_FormClosed(object sender, FormClosedEventArgs e)
{
OnClose?.Invoke(this, EventArgs.Empty);
Close();
}
}
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json.Serialization;
namespace qtcnet_client.Model
{
public class ClientConfig
{
[JsonPropertyName("serverUri")]
public string ServerBaseUri { get; set; } = "https://api.qtchat.net";
[JsonPropertyName("startMinimized")]
public bool StartMinimized { get; set; } = false;
[JsonPropertyName("minimizeToTray")]
public bool MinimizeToTray { get; set; } = false;
[JsonPropertyName("enableDebugLogs")]
public bool EnableDebugLogs { get; set; } = false;
}
}

98
qtcnet-client/Program.cs Normal file
View File

@ -0,0 +1,98 @@
using Krypton.Toolkit;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using qtcnet_client.Model;
using QtCNETAPI.Services;
using QtCNETAPI.Services.ApiService;
using QtCNETAPI.Services.GatewayService;
using System.Text.Json;
namespace qtcnet_client
{
internal static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
// To customize application configuration such as set high DPI settings or default font,
// see https://aka.ms/applicationconfiguration.
ApplicationConfiguration.Initialize();
var host = CreateHostBuilder().Build();
ServiceProvider = host.Services;
Application.Run(ServiceProvider.GetRequiredService<MainForm>());
}
private static JsonSerializerOptions JsonSerializerOptions { get; } = new() { WriteIndented = true };
public static IServiceProvider? ServiceProvider { get; private set; }
static IHostBuilder CreateHostBuilder()
{
return Host.CreateDefaultBuilder()
.ConfigureServices((context, service) =>
{
service.AddTransient<MainForm>();
service.AddSingleton<LoggingService>();
service.AddSingleton<CredentialService>();
service.AddSingleton(provider => GetOrCreateClientConfig());
service.AddSingleton<IApiService>(provider => new ApiService(provider.GetService<ClientConfig>()!.ServerBaseUri + "/api", provider.GetService<LoggingService>()!, provider.GetService<CredentialService>()!));
service.AddSingleton<IGatewayService>(provider => new GatewayService(provider.GetService<ClientConfig>()!.ServerBaseUri + "/chat", provider.GetService<IApiService>()!, provider.GetService<LoggingService>()!));
});
}
static ClientConfig GetOrCreateClientConfig()
{
var _configPath = $"{Application.StartupPath}/config.json";
try
{
if (!File.Exists(_configPath))
{
var _defaultConfig = new ClientConfig();
// create the config using the default config model
string serializedModel = JsonSerializer.Serialize(_defaultConfig, JsonSerializerOptions);
// write it to a new file
File.WriteAllText(_configPath, serializedModel);
// return it
return _defaultConfig;
}
else
{
// deserialize the contents of config.json and return it
ClientConfig? _deserializedConfig = JsonSerializer.Deserialize<ClientConfig>(File.ReadAllText(_configPath));
if (_deserializedConfig != null)
return _deserializedConfig;
}
}
catch (JsonException)
{
}
// if the functions gets here, inform the user their config is incorrect and create a new one if allowed
var _diagResult = KryptonMessageBox.Show($"Your Config Is Incorrect. Would You Like To Create A New One?", "Uh Oh.", KryptonMessageBoxButtons.YesNo);
if (_diagResult == DialogResult.Yes)
{
var _defaultConfig = new ClientConfig();
// create the config using the default config model
string serializedModel = JsonSerializer.Serialize(_defaultConfig, JsonSerializerOptions);
// write it to a new file
File.WriteAllText(_configPath, serializedModel);
// return it
return _defaultConfig;
}
else
{
Application.Exit();
return default!; // application should exit before it can return this lol
}
}
}
}

View File

@ -0,0 +1,113 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace qtcnet_client.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("qtcnet_client.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap CurrencyIcon {
get {
object obj = ResourceManager.GetObject("CurrencyIcon", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap DefaultPfp {
get {
object obj = ResourceManager.GetObject("DefaultPfp", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap QtCNETIcon {
get {
object obj = ResourceManager.GetObject("QtCNETIcon", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap RoomsChatIcon {
get {
object obj = ResourceManager.GetObject("RoomsChatIcon", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap SendIcon {
get {
object obj = ResourceManager.GetObject("SendIcon", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
}
}

View File

@ -0,0 +1,136 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="DefaultPfp" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\DefaultPfp.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="QtCNETIcon" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\QtCNETIcon.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="CurrencyIcon" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\CurrencyIcon.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="RoomsChatIcon" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\RoomsChatIcon.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="SendIcon" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\SendIcon.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
</root>

Binary file not shown.

After

Width:  |  Height:  |  Size: 649 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,37 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0-windows</TargetFramework>
<RootNamespace>qtcnet_client</RootNamespace>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Krypton.Toolkit" Version="100.25.11.328" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\QtCNETAPI\QtCNETAPI.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>