Compare commits

...

44 Commits
5.1 ... master

Author SHA1 Message Date
a95d012d69 Update Packages That Could Be Updated
Rework Missed Refresh Token Code
Minor Version Bump
2025-09-21 14:16:41 -07:00
d1d3af2ec9 Rework Refresh Token Storage Solution To Use Windows Credentials API
Minor Version Bump
2025-09-21 13:49:11 -07:00
4e61a1d7b9 Version Bump 2025-08-03 15:00:37 -07:00
446745d4df Implement CreateProfileImage To Combine Precense Icon, Profile Image, and Cosmetic To Make Final Profile Image 2025-08-03 15:00:09 -07:00
00df7505a7 Move Cosmetic Download To Main Form Thread 2025-08-03 14:27:00 -07:00
b17d391406 Minor Version Bump 2025-07-31 15:21:50 -07:00
2092d2c6d3 Keep Default KA Values 2025-07-31 15:05:03 -07:00
fd743dcb42 Do Not Set Server Timeout (it was the default value anyways) 2025-07-31 15:00:37 -07:00
9214460927 Server Timeout = 30s
Keep-Alive Interval  = 1m
2025-07-31 14:59:13 -07:00
1b30636937 Configure TimeSpans For ServerTimeout And KeepAliveInterval 2025-07-31 10:18:57 -07:00
72d9b99c4c Refresh Session Everytime Gateway Connection Is Used To Prevent The Server From Aborting Requests 2025-07-31 10:06:12 -07:00
c7137e4c7e Version Bump 2025-07-27 13:56:55 -07:00
4ee524778d Merge pull request 'Email Features' (#5) from email-features into master
Reviewed-on: #5
2025-07-27 13:55:57 -07:00
cfcc5ad4c1 Message Box On Register To Inform User Of Email Verification 2025-07-27 13:54:17 -07:00
b81b059177 Initial Implementation Of Email Features 2025-07-27 13:51:44 -07:00
5d553cdf58 Change Default Pfp 2025-07-19 13:54:14 -07:00
8cb59e1b21 Work Around Weird Offline Behaviour 2025-07-19 13:31:54 -07:00
6acb72975e Optimizations To Main Form 2025-07-17 15:33:17 -07:00
098a59f555 Avoid Updating Store Items ListView If Count Is Still Equal (No New Items)
Fix Stock Market Game UI Positioning
2025-07-17 15:27:16 -07:00
e6c842cb19 Remove Uneeded Log Entry 2025-07-17 15:20:53 -07:00
758a5a96dc Add Additional Logging/Diagnostics For Weird Status Behaviours 2025-07-17 14:36:56 -07:00
6bf77aa12e Add enableDebugLogs Config Property 2025-07-15 14:15:31 -07:00
f98e5a490c Implement Extra Logging To Diagnose Status Issues
Better Connection Error Handling
2025-07-15 14:04:46 -07:00
75b61c0a8c Move LoggingService to API library
Add Additional Logging For SignalR
2025-07-14 19:07:54 -07:00
0432fec3cd Finish Initial Implementation Of Admin Menus
Implement Logout Event
Fix Own Direct Messages Not New Lining The Chat
Version Bump
2025-07-13 17:27:16 -07:00
de2a81d485 Initial WIP Implementation Of Admin Menus 2025-07-12 14:23:15 -07:00
fca34d5479 Version Bump 2025-07-12 11:14:40 -07:00
932ee5fe62 Implement OnCurrentUserUpdate Event 2025-07-12 11:14:22 -07:00
34f478213e Version Bump 2025-07-11 12:29:06 -07:00
ffa44ff036 More Profile Image Sizing Fixes 2025-07-11 12:28:22 -07:00
217e3f301a Fix Some Profile Images Not Showing As A Square 2025-07-11 12:15:53 -07:00
28358f44f8 Version Bump 2025-07-11 11:07:10 -07:00
eb9fd6c8cf Fix Order Of Operations In StoreItemDisplay 2025-07-11 11:03:21 -07:00
a325a622a5 Check If User Already Owns The Item Requested 2025-07-11 11:02:10 -07:00
0c76a206f7 Refresh Currency Counter When Buying From Store 2025-07-11 10:58:28 -07:00
502dda6436 Profile Editing - Reworked Cosmetic Selection 2025-07-11 10:43:40 -07:00
868f6fa067 Positioning And Size Fixes 2025-07-11 10:18:37 -07:00
003d01fe4e Implement Store Frontend
Implement Profile Cosmetics
2025-07-10 17:18:54 -07:00
0d93557959 Implement Store API Functions
Fix CurrentUser Not Being Up To Date When Updating User API Side
2025-07-10 10:34:09 -07:00
9fecd1fb74 Version Bump (i keep forgetting) 2025-07-07 12:57:15 -07:00
074b68c698 Implement Donation Button 2025-07-07 12:50:17 -07:00
922528c61c Fix Auto-Updater Not Working If .bak File Exists 2025-07-06 15:36:15 -07:00
846a477587 Add Event For When A Guest User Joins 2025-07-06 10:38:22 -07:00
ad5345512a Rework Contacts List Refreshing 2025-07-06 10:22:29 -07:00
55 changed files with 3101 additions and 788 deletions

View File

@ -11,5 +11,6 @@
public DateTime CreatedAt { 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,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

@ -6,5 +6,6 @@
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,8 @@
namespace QtCNETAPI.Enums
{
public enum StoreItemType
{
ProfileCosmetic = 1,
ClientCosmetic = 2
}
}

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 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

@ -15,9 +15,11 @@
public int CurrencyAmount { get; set; } = 0;
public int StockAmount { get; set; } = 0;
public DateTime LastCurrencySpin { get; set; }
public int ActiveProfileCosmetic { get; set; } = 0;
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

@ -7,10 +7,11 @@
</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.5" />
<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.10.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.14.0" />
</ItemGroup>
</Project>

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

@ -2,9 +2,9 @@
using QtCNETAPI.Dtos.User;
using QtCNETAPI.Enums;
using QtCNETAPI.Models;
using QtCNETAPI.Schema;
using RestSharp;
using System.IdentityModel.Tokens.Jwt;
using System.Resources;
using System.Text.Json;
namespace QtCNETAPI.Services.ApiService
@ -13,10 +13,15 @@ namespace QtCNETAPI.Services.ApiService
{
private User? user;
private RestClient _client;
private LoggingService _loggingService;
private CredentialService _credService = new();
internal string? sessionToken;
internal string apiUri;
public event EventHandler? OnCurrentUserUpdate;
public string? SessionToken
{
get { return sessionToken; }
@ -28,9 +33,10 @@ namespace QtCNETAPI.Services.ApiService
get { return user; }
}
public ApiService(string apiUrl)
public ApiService(string apiUrl, LoggingService loggingService)
{
apiUri = apiUrl;
_loggingService = loggingService;
_client = new RestClient(apiUri);
}
@ -151,10 +157,9 @@ namespace QtCNETAPI.Services.ApiService
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
// update currentuser model
CurrentUser!.Username = response.Data!.Username;
CurrentUser.Bio = response.Data.Bio;
CurrentUser.DateOfBirth = response.Data.DateOfBirth;
// 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;
@ -180,6 +185,10 @@ namespace QtCNETAPI.Services.ApiService
{
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
{
@ -228,9 +237,9 @@ namespace QtCNETAPI.Services.ApiService
}
}
public async Task<ServiceResponse<User>> LoginAsync(UserLoginDto userLoginDto)
public async Task<ServiceResponse<string>> LoginAsync(UserLoginDto userLoginDto)
{
var serviceResponse = new ServiceResponse<User>();
var serviceResponse = new ServiceResponse<string>();
try
{
@ -250,13 +259,11 @@ namespace QtCNETAPI.Services.ApiService
{
SessionToken = response.Data!;
await File.WriteAllTextAsync("./session.token", response.Message);
var user = await SetCurrentUser();
serviceResponse.Success = true;
if (response.Message != null) serviceResponse.Message = response.Message;
serviceResponse.Data = user;
serviceResponse.Data = response.Message;
}
else
{
@ -273,16 +280,85 @@ namespace QtCNETAPI.Services.ApiService
return serviceResponse;
}
private async Task<User> SetCurrentUser()
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!.Data != null)
if (userResponse != null && userResponse.Success && userResponse.Data != null)
{
user = userResponse.Data;
return 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.");
@ -337,21 +413,23 @@ namespace QtCNETAPI.Services.ApiService
public async Task<ServiceResponse<string>> RefreshSessionIfInvalid()
{
var tokenHandler = new JwtSecurityTokenHandler();
var refToken = await File.ReadAllTextAsync("./session.token");
var refToken = _credService.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)
{
if (!File.Exists("./session.token")) { return new ServiceResponse<string> { Success = false, Message = "Session File Not Found. Session Expired." }; }
var result = await RefreshLogin(refToken);
if (result == null || result.Success == false)
{
File.Delete("./session.token");
return new ServiceResponse<string> { Success = false, Message = "Session Expired." };
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 };
}
@ -590,6 +668,10 @@ namespace QtCNETAPI.Services.ApiService
{
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;
@ -637,6 +719,10 @@ namespace QtCNETAPI.Services.ApiService
{
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;
@ -661,6 +747,10 @@ namespace QtCNETAPI.Services.ApiService
{
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;
@ -713,5 +803,151 @@ namespace QtCNETAPI.Services.ApiService
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

@ -8,6 +8,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using QtCNETAPI.Enums;
using QtCNETAPI.Schema;
namespace QtCNETAPI.Services.ApiService
{
@ -16,12 +17,19 @@ namespace QtCNETAPI.Services.ApiService
public string? SessionToken { get; set; }
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>> LoginAsync(UserLoginDto userLoginDto);
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);
@ -40,5 +48,10 @@ namespace QtCNETAPI.Services.ApiService
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,26 @@
using Meziantou.Framework.Win32;
namespace QtCNETAPI.Services
{
public class CredentialService()
{
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 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

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.Logging;
using QtCNETAPI.Dtos.User;
using QtCNETAPI.Events;
using QtCNETAPI.Models;
@ -6,47 +7,55 @@ using QtCNETAPI.Services.ApiService;
namespace QtCNETAPI.Services.GatewayService
{
public class GatewayService : IGatewayService, IAsyncDisposable
public class GatewayService(string GWUrl, IApiService apiService, LoggingService loggingService) : IGatewayService, IAsyncDisposable
{
internal string gwBaseUri = "127.0.0.1";
internal string gwBaseUri = GWUrl;
public Room? CurrentRoom { get; private set; }
public bool InLobby { get; private set; }
public HubConnection? HubConnection { get; private set; }
public event EventHandler OnRoomMessageReceived;
public event EventHandler OnRoomUserListReceived;
public event EventHandler OnRefreshUserListsReceived;
public event EventHandler OnRefreshRoomListReceived;
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? 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;
public GatewayService(string GWUrl, IApiService apiService)
{
gwBaseUri = GWUrl;
_apiService = apiService;
}
private IApiService _apiService = apiService;
private LoggingService _loggingService = loggingService;
public async Task StartAsync()
{
// just to be safe (it doesn't load the server since it shouldn't request a new one unless its actually expired)
await _apiService.RefreshSessionIfInvalid();
// 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(gwBaseUri, options =>
{
options.AccessTokenProvider = () => Task.FromResult(_apiService.SessionToken);
});
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
@ -58,13 +67,27 @@ namespace QtCNETAPI.Services.GatewayService
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
await HubConnection.StartAsync();
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()
@ -99,8 +122,6 @@ namespace QtCNETAPI.Services.GatewayService
public async Task JoinLobbyAsync()
{
await _apiService.RefreshSessionIfInvalid();
if (HubConnection == null || HubConnection.State != HubConnectionState.Connected) throw new InvalidOperationException("Function was called before connection was made.");
await HubConnection.SendAsync("JoinLobby", _apiService.CurrentUser);
@ -110,8 +131,6 @@ namespace QtCNETAPI.Services.GatewayService
public async Task JoinRoomAsync(Room room)
{
await _apiService.RefreshSessionIfInvalid();
if (HubConnection == null || HubConnection.State != HubConnectionState.Connected) throw new InvalidOperationException("Function was called before connection was made.");
if (InLobby == true)
@ -130,8 +149,6 @@ namespace QtCNETAPI.Services.GatewayService
public async Task LeaveRoomAsync()
{
await _apiService.RefreshSessionIfInvalid();
if (HubConnection == null || HubConnection.State != HubConnectionState.Connected) throw new InvalidOperationException("Function was called before connection was made.");
if (InLobby)
@ -146,19 +163,8 @@ namespace QtCNETAPI.Services.GatewayService
}
}
public async Task RefreshContactsForUser(UserInformationDto user)
{
await _apiService.RefreshSessionIfInvalid();
if (HubConnection == null || HubConnection.State != HubConnectionState.Connected) throw new InvalidOperationException("Function was called before connection was made.");
await HubConnection.SendAsync("RefreshContactsListOnUser", user, _apiService.CurrentUser);
}
public async Task PostMessageAsync(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("SendMessage", _apiService.CurrentUser, message, InLobby, CurrentRoom);
@ -175,11 +181,12 @@ namespace QtCNETAPI.Services.GatewayService
public async Task UpdateStatus(int status)
{
await _apiService.RefreshSessionIfInvalid();
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();
}

View File

@ -85,14 +85,6 @@ namespace QtCNETAPI.Services.GatewayService
/// <returns></returns>
public Task SendDirectMessageAsync(UserInformationDto user, Message message);
/// <summary>
/// Refreshes Contacts List For A Specified User
/// </summary>
/// <param name="user">The User You Wish To Refresh</param>
/// <param name="currentUser">Yourself</param>
/// <returns></returns>
public Task RefreshContactsForUser(UserInformationDto user);
/// <summary>
/// Updates The Status For The Current User
/// </summary>
@ -112,6 +104,16 @@ namespace QtCNETAPI.Services.GatewayService
/// </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>
@ -156,5 +158,10 @@ namespace QtCNETAPI.Services.GatewayService
/// 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() { }
}
}

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace qtc_net_client_2.ClientModel
{
public class ComboBoxItem
{
public string? Name { get; set; }
public object? Value { get; set; }
public override string ToString()
{
return Name ?? string.Empty;
}
}
}

View File

@ -15,6 +15,9 @@ namespace qtc_net_client_2.ClientModel
[JsonPropertyName("minimizeToTray")]
[JsonRequired]
public bool MinimizeToTray { get; set; } = true;
[JsonPropertyName("enableDebugLogs")]
[JsonRequired]
public bool EnableDebugLogs { get; set; } = false;
[JsonPropertyName("serverUrl")]
[JsonRequired]

View File

@ -1,17 +1,7 @@
using qtc_net_client_2.Services;
using QtCNETAPI.Events;
using QtCNETAPI.Models;
using QtCNETAPI.Services.ApiService;
using QtCNETAPI.Services.GatewayService;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace qtc_net_client_2.Forms
{
@ -33,6 +23,8 @@ namespace qtc_net_client_2.Forms
// subscribe to server message event
_gatewayService.OnRoomMessageReceived += _gatewayService_OnServerMessageReceived;
_gatewayService.OnRoomUserListReceived += _gatewayService_OnRoomUserListReceived;
_gatewayService.OnRoomDeleted += _gatewayService_OnRoomDeleted;
_gatewayService.OnGuestUserJoin += _gatewayService_OnGuestUserJoin;
if (_gatewayService.CurrentRoom != null) { Text = $"QtC.NET Client - Chat Room - {_gatewayService.CurrentRoom.Name}"; lblRoomName.Text = _gatewayService.CurrentRoom.Name; }
else if (_gatewayService.InLobby) { Text = $"QtC.NET Client - Chat Room - Lobby"; lblRoomName.Text = "Lobby"; }
@ -93,13 +85,16 @@ namespace qtc_net_client_2.Forms
{
// get user info and open profile dialog
var user = mainForm.UserDirectory.FirstOrDefault(e => e.Username == selectedUser);
var res = await _apiService.GetUserInformationAsync(user!.Id);
var pfpRes = await _apiService.GetUserProfilePic(user!.Id);
if (res.Data != null && res.Success)
if (user != null)
{
Profile frmProfile = new Profile(res.Data, pfpRes, mainForm.Contacts, _apiService, _gatewayService);
frmProfile.Show();
var res = await _apiService.GetUserInformationAsync(user.Id);
var pfpRes = await _apiService.GetUserProfilePic(user.Id);
if (res.Data != null && res.Success)
{
Profile frmProfile = new Profile(res.Data, pfpRes, mainForm.Contacts, _apiService, _gatewayService);
frmProfile.Show();
}
}
}
}
@ -118,20 +113,50 @@ namespace qtc_net_client_2.Forms
{
var args = (RoomListEventArgs)e;
if (!IsHandleCreated) return;
lvUserList.BeginInvoke(lvUserList.Clear);
foreach (var user in args.UserList)
if (IsHandleCreated && !IsDisposed)
{
lvUserList.BeginInvoke(delegate () { lvUserList.Items.Add(user.Username, user.Status); });
Invoke(delegate ()
{
lvUserList.Clear();
foreach (var user in args.UserList)
{
lvUserList.Items.Add(user.Username, user.Status);
}
});
}
}
private void _gatewayService_OnGuestUserJoin(object? sender, EventArgs e)
{
var args = (GuestUserJoinEventArgs)e;
AddMessage($"[SERVER] Guest User {args.Username} Has Joined {_gatewayService.CurrentRoom?.Name}");
}
private void _gatewayService_OnRoomDeleted(object? sender, EventArgs e)
{
if(IsHandleCreated && !IsDisposed)
{
Invoke(delegate ()
{
AddMessage($"[SERVER] This Room Was Deleted By An Admin.");
lvUserList.Clear();
lvUserList.Enabled = false;
rtxtChatbox.Enabled = false;
btnSend.Enabled = false;
});
}
}
private void AddMessage(string message)
{
if (InvokeRequired)
Invoke(delegate { rtxtChat.AppendText(message + Environment.NewLine); });
else rtxtChat.AppendText(message + Environment.NewLine);
if(IsHandleCreated && !IsDisposed)
{
Invoke(delegate ()
{
rtxtChat.AppendText(message + Environment.NewLine);
});
}
}
}
}

View File

@ -31,18 +31,17 @@
lblHead = new Label();
btnReconnect = new Button();
btnQuit = new Button();
lblReason = new Label();
SuspendLayout();
//
// lblHead
//
lblHead.AutoSize = true;
lblHead.Font = new Font("Segoe UI", 11F, FontStyle.Bold);
lblHead.Location = new Point(12, 9);
lblHead.Name = "lblHead";
lblHead.Size = new Size(444, 20);
lblHead.TabIndex = 0;
lblHead.Text = "Your Connection To The QtC.NET Server Was Lost. What Now?";
lblHead.TextAlign = ContentAlignment.MiddleCenter;
//
// btnReconnect
//
@ -66,23 +65,12 @@
btnQuit.UseVisualStyleBackColor = true;
btnQuit.Click += btnQuit_Click;
//
// lblReason
//
lblReason.AutoSize = true;
lblReason.Font = new Font("Segoe UI", 5F, FontStyle.Bold);
lblReason.Location = new Point(88, 40);
lblReason.Name = "lblReason";
lblReason.Size = new Size(72, 10);
lblReason.TabIndex = 3;
lblReason.Text = "Reason: ${REASON}";
//
// frmConnectionClosed
// ConnectionClosed
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
BackColor = Color.DodgerBlue;
ClientSize = new Size(463, 65);
Controls.Add(lblReason);
Controls.Add(btnQuit);
Controls.Add(btnReconnect);
Controls.Add(lblHead);
@ -92,12 +80,10 @@
Margin = new Padding(4, 3, 4, 3);
MaximizeBox = false;
MinimizeBox = false;
Name = "frmConnectionClosed";
Name = "ConnectionClosed";
StartPosition = FormStartPosition.CenterScreen;
Text = "QtC.NET Client - Connection Lost";
Load += frmConnectionClosed_Load;
ResumeLayout(false);
PerformLayout();
}
#endregion
@ -105,6 +91,5 @@
private Label lblHead;
private Button btnReconnect;
private Button btnQuit;
private Label lblReason;
}
}

View File

@ -12,19 +12,12 @@ namespace qtc_net_client_2.Forms
{
public partial class ConnectionClosed : Form
{
public string? Reason { get; set; } = string.Empty;
public ConnectionClosed(string? reason = "")
public Label StatusLabel { get { return lblHead; } }
public ConnectionClosed()
{
Reason = reason;
InitializeComponent();
}
private void frmConnectionClosed_Load(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(Reason)) lblReason.Visible = false;
else lblReason.Text = $"Reason: {Reason}";
}
private void btnReconnect_Click(object sender, EventArgs e)
{
DialogResult = DialogResult.OK;

View File

@ -42,6 +42,11 @@ namespace qtc_net_client_2.Forms
Text = $"QtC.NET Client - Direct Message With {User.Username}";
Messages.CollectionChanged += Messages_CollectionChanged;
if (User.Role == "Admin")
{
Messages.Add($"[SERVER] This User Is A Server Admin. You should comply with anything this user asks. however admins should not ask for personal information.\n");
}
if (InitMessage != null)
{
Messages.Add($"[{User.Username}] {InitMessage.Content}\n");
@ -58,7 +63,7 @@ namespace qtc_net_client_2.Forms
if (!string.IsNullOrEmpty(rtxtChatbox.Text))
{
await _gatewayService.SendDirectMessageAsync(User, new QtCNETAPI.Models.Message { Content = rtxtChatbox.Text, AuthorId = _apiService.CurrentUser.Id });
Messages.Add($"[{_apiService.CurrentUser.Username}] {rtxtChatbox.Text}");
Messages.Add($"[{_apiService.CurrentUser.Username}] {rtxtChatbox.Text}\n");
rtxtChatbox.Clear();
AudioService.PlaySoundEffect("sndSendClick");
}
@ -69,7 +74,7 @@ namespace qtc_net_client_2.Forms
if (!string.IsNullOrEmpty(rtxtChatbox.Text))
{
await _gatewayService.SendDirectMessageAsync(User, new QtCNETAPI.Models.Message { Content = rtxtChatbox.Text, AuthorId = _apiService.CurrentUser.Id });
Messages.Add($"[{_apiService.CurrentUser.Username}] {rtxtChatbox.Text}");
Messages.Add($"[{_apiService.CurrentUser.Username}] {rtxtChatbox.Text}\n");
rtxtChatbox.Clear();
AudioService.PlaySoundEffect("sndSendClick");
}

View File

@ -0,0 +1,112 @@
namespace qtc_net_client_2.Forms
{
partial class DonationWindow
{
/// <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()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(DonationWindow));
lblHi = new Label();
lblInfo = new Label();
pbKofi = new PictureBox();
pbCobaltPuter = new PictureBox();
((System.ComponentModel.ISupportInitialize)pbKofi).BeginInit();
((System.ComponentModel.ISupportInitialize)pbCobaltPuter).BeginInit();
SuspendLayout();
//
// lblHi
//
lblHi.AutoSize = true;
lblHi.Font = new Font("Segoe UI", 30F, FontStyle.Bold | FontStyle.Italic);
lblHi.Location = new Point(231, 7);
lblHi.Name = "lblHi";
lblHi.Size = new Size(81, 54);
lblHi.TabIndex = 0;
lblHi.Text = "Hi!";
//
// lblInfo
//
lblInfo.Font = new Font("Segoe UI", 7F, FontStyle.Bold);
lblInfo.Location = new Point(12, 51);
lblInfo.Name = "lblInfo";
lblInfo.Size = new Size(290, 177);
lblInfo.TabIndex = 1;
lblInfo.Text = resources.GetString("lblInfo.Text");
lblInfo.TextAlign = ContentAlignment.MiddleLeft;
//
// pbKofi
//
pbKofi.Cursor = Cursors.Hand;
pbKofi.Image = Properties.Resources.support_me_on_kofi_badge_blue;
pbKofi.Location = new Point(89, 239);
pbKofi.Name = "pbKofi";
pbKofi.Size = new Size(149, 78);
pbKofi.SizeMode = PictureBoxSizeMode.Zoom;
pbKofi.TabIndex = 2;
pbKofi.TabStop = false;
pbKofi.Click += pbKofi_Click;
//
// pbCobaltPuter
//
pbCobaltPuter.Image = Properties.Resources.cobalt_sittingatputer;
pbCobaltPuter.Location = new Point(316, 29);
pbCobaltPuter.Name = "pbCobaltPuter";
pbCobaltPuter.Size = new Size(339, 270);
pbCobaltPuter.SizeMode = PictureBoxSizeMode.Zoom;
pbCobaltPuter.TabIndex = 3;
pbCobaltPuter.TabStop = false;
//
// DonationWindow
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
BackColor = Color.DodgerBlue;
ClientSize = new Size(667, 328);
Controls.Add(pbCobaltPuter);
Controls.Add(pbKofi);
Controls.Add(lblInfo);
Controls.Add(lblHi);
ForeColor = Color.White;
FormBorderStyle = FormBorderStyle.FixedDialog;
MaximizeBox = false;
MinimizeBox = false;
Name = "DonationWindow";
StartPosition = FormStartPosition.CenterScreen;
Text = "QtC.NET Client - Donate!";
((System.ComponentModel.ISupportInitialize)pbKofi).EndInit();
((System.ComponentModel.ISupportInitialize)pbCobaltPuter).EndInit();
ResumeLayout(false);
PerformLayout();
}
#endregion
private Label lblHi;
private Label lblInfo;
private PictureBox pbKofi;
private PictureBox pbCobaltPuter;
}
}

View File

@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Policy;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace qtc_net_client_2.Forms
{
public partial class DonationWindow : Form
{
public DonationWindow()
{
InitializeComponent();
}
private void pbKofi_Click(object sender, EventArgs e)
{
var url = "https://ko-fi.com/moonbase__";
try
{
Process.Start(url);
} catch
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
url = url.Replace("&", "^&");
Process.Start(new ProcessStartInfo("cmd", $"/c start {url}") { CreateNoWindow = true });
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
Process.Start("xdg-open", url);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
Process.Start("open", url);
}
else
{
throw;
}
}
}
}
}

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>
<data name="lblInfo.Text" xml:space="preserve">
<value>I'm Moonbase, Thanks For Using QtC.NET!
If you wish, you can send a few bucks to my KoFi
linked below. It will help imensely in improving the
performance of the platform by upgrading the VPS
this is currently being hosted on.
This project is and always will be mostly just a passion
project, similar to the likes of SillyPost. The protocol
has existed for a bit now, however
this is the most I've worked
on it.
Thanks again for your interest!</value>
</data>
</root>

View File

@ -32,11 +32,13 @@
pbLoginBanner = new PictureBox();
tbEmail = new TextBox();
lblEmail = new Label();
label1 = new Label();
lblPassword = new Label();
tbPassword = new TextBox();
btnLogin = new Button();
llblRegister = new LinkLabel();
cbRememberMe = new CheckBox();
llblResendEmail = new LinkLabel();
llblForgotPasswor = new LinkLabel();
((System.ComponentModel.ISupportInitialize)pbLoginBanner).BeginInit();
SuspendLayout();
//
@ -68,16 +70,16 @@
lblEmail.TabIndex = 2;
lblEmail.Text = "Email";
//
// label1
// lblPassword
//
label1.AutoSize = true;
label1.Font = new Font("Segoe UI Light", 9F);
label1.ForeColor = SystemColors.ControlLight;
label1.Location = new Point(11, 138);
label1.Name = "label1";
label1.Size = new Size(55, 15);
label1.TabIndex = 4;
label1.Text = "Password";
lblPassword.AutoSize = true;
lblPassword.Font = new Font("Segoe UI Light", 9F);
lblPassword.ForeColor = SystemColors.ControlLight;
lblPassword.Location = new Point(11, 138);
lblPassword.Name = "lblPassword";
lblPassword.Size = new Size(55, 15);
lblPassword.TabIndex = 4;
lblPassword.Text = "Password";
//
// tbPassword
//
@ -122,16 +124,44 @@
cbRememberMe.Text = "Remember Me For 7 Days";
cbRememberMe.UseVisualStyleBackColor = true;
//
// frmLogin
// llblResendEmail
//
llblResendEmail.AutoSize = true;
llblResendEmail.Font = new Font("Segoe UI Light", 9F);
llblResendEmail.LinkColor = SystemColors.ControlLight;
llblResendEmail.Location = new Point(369, 164);
llblResendEmail.Name = "llblResendEmail";
llblResendEmail.Size = new Size(129, 15);
llblResendEmail.TabIndex = 8;
llblResendEmail.TabStop = true;
llblResendEmail.Text = "Resend Verification Email";
llblResendEmail.LinkClicked += llblResendEmail_LinkClicked;
//
// llblForgotPasswor
//
llblForgotPasswor.AutoSize = true;
llblForgotPasswor.Font = new Font("Segoe UI Light", 9F);
llblForgotPasswor.LinkColor = SystemColors.ControlLight;
llblForgotPasswor.Location = new Point(401, 181);
llblForgotPasswor.Name = "llblForgotPasswor";
llblForgotPasswor.Size = new Size(98, 15);
llblForgotPasswor.TabIndex = 9;
llblForgotPasswor.TabStop = true;
llblForgotPasswor.Text = "Forgot Password?";
llblForgotPasswor.LinkClicked += llblForgotPasswor_LinkClicked;
//
// llblForgotPassword
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
BackColor = Color.DodgerBlue;
ClientSize = new Size(515, 203);
Controls.Add(llblForgotPasswor);
Controls.Add(llblResendEmail);
Controls.Add(cbRememberMe);
Controls.Add(llblRegister);
Controls.Add(btnLogin);
Controls.Add(label1);
Controls.Add(lblPassword);
Controls.Add(tbPassword);
Controls.Add(lblEmail);
Controls.Add(tbEmail);
@ -139,7 +169,7 @@
FormBorderStyle = FormBorderStyle.FixedDialog;
Icon = (Icon)resources.GetObject("$this.Icon");
MaximizeBox = false;
Name = "frmLogin";
Name = "llblForgotPassword";
StartPosition = FormStartPosition.CenterParent;
Text = "QtC.NET Client - Login";
Load += frmLogin_Load;
@ -153,10 +183,12 @@
private PictureBox pbLoginBanner;
private TextBox tbEmail;
private Label lblEmail;
private Label label1;
private Label lblPassword;
private TextBox tbPassword;
private Button btnLogin;
private LinkLabel llblRegister;
private CheckBox cbRememberMe;
private LinkLabel llblResendEmail;
private LinkLabel llblForgotPasswor;
}
}

View File

@ -1,4 +1,5 @@
using QtCNETAPI.Services.ApiService;
using QtCNETAPI.Services;
using QtCNETAPI.Dtos.User;
using System;
using System.Collections.Generic;
@ -9,12 +10,14 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using qtc_net_client_2.Services;
namespace qtc_net_client_2.Forms
{
public partial class Login : Form
{
private IApiService _apiService;
private CredentialService _credService = new();
public Login(IApiService apiService)
{
_apiService = apiService;
@ -24,14 +27,14 @@ namespace qtc_net_client_2.Forms
private async void frmLogin_Load(object sender, EventArgs e)
{
if (File.Exists("./session.token"))
string? accessToken = _credService.GetAccessToken();
if (accessToken != null)
{
ToggleControls(false, false);
// try logging in with the token in the file
string token = File.ReadAllText("./session.token");
var result = await _apiService.RefreshLogin(token);
// try logging in with the token in cred storage
var result = await _apiService.RefreshLogin(accessToken);
if (result.Success)
{
DialogResult = DialogResult.OK;
@ -55,8 +58,9 @@ namespace qtc_net_client_2.Forms
RememberMe = cbRememberMe.Checked
});
if (result.Success)
if (result.Success && result.Data != null)
{
_credService.SaveAccessToken(_apiService.CurrentUser.Username, result.Data);
DialogResult = DialogResult.OK;
Close();
}
@ -68,6 +72,24 @@ namespace qtc_net_client_2.Forms
}
}
private void llblRegister_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
Register frmRegister = new Register(_apiService);
frmRegister.ShowDialog();
}
private void llblResendEmail_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
ResendVerificationEmail resendVerificationEmail = new ResendVerificationEmail(_apiService);
resendVerificationEmail.ShowDialog();
}
private void llblForgotPasswor_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
ResetPassword resetPassword = new ResetPassword(_apiService);
resetPassword.ShowDialog();
}
private void ToggleControls(bool enable, bool clearText)
{
tbEmail.Enabled = enable;
@ -81,11 +103,5 @@ namespace qtc_net_client_2.Forms
tbPassword.Text = string.Empty;
}
}
private void llblRegister_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
Register frmRegister = new Register(_apiService);
frmRegister.ShowDialog();
}
}
}

View File

@ -40,7 +40,6 @@
refreshToolStripMenuItem = new ToolStripMenuItem();
ilProfilePics = new ImageList(components);
tbpRooms = new TabPage();
btnAddRoom = new Button();
lbRooms = new ListBox();
tbpUsers = new TabPage();
lvUserDirectory = new ListView();
@ -48,6 +47,9 @@
tbpGames = new TabPage();
lvGames = new ListView();
ilGames = new ImageList(components);
tbpStore = new TabPage();
lvStoreItems = new ListView();
ilStoreThumbnails = new ImageList(components);
ilTabIcons = new ImageList(components);
ctxmChangeStatus = new ContextMenuStrip(components);
onlineToolStripMenuItem = new ToolStripMenuItem();
@ -66,17 +68,34 @@
llblSignIn = new LinkLabel();
lblWelcome = new Label();
pbUserPfp = new PictureBox();
pbDonate = new PictureBox();
ctxmAdminUserList = new ContextMenuStrip(components);
refreshToolStripMenuItem1 = new ToolStripMenuItem();
toolStripSeparator1 = new ToolStripSeparator();
copyUserIDToClipboardToolStripMenuItem = new ToolStripMenuItem();
deleteUserToolStripMenuItem = new ToolStripMenuItem();
adminDirectMessageToolStripMenuItem = new ToolStripMenuItem();
ctxmAdminRoomList = new ContextMenuStrip(components);
toolStripMenuItem1 = new ToolStripMenuItem();
toolStripSeparator2 = new ToolStripSeparator();
addRoomToolStripMenuItem = new ToolStripMenuItem();
deleteRoomToolStripMenuItem = new ToolStripMenuItem();
lblConnectionLost = new Label();
tbcMain.SuspendLayout();
tbpContacts.SuspendLayout();
ctxmRefresh.SuspendLayout();
tbpRooms.SuspendLayout();
tbpUsers.SuspendLayout();
tbpGames.SuspendLayout();
tbpStore.SuspendLayout();
ctxmChangeStatus.SuspendLayout();
pCurrencyArea.SuspendLayout();
((System.ComponentModel.ISupportInitialize)pbCurrencyIcon).BeginInit();
pCurrentUser.SuspendLayout();
((System.ComponentModel.ISupportInitialize)pbUserPfp).BeginInit();
((System.ComponentModel.ISupportInitialize)pbDonate).BeginInit();
ctxmAdminUserList.SuspendLayout();
ctxmAdminRoomList.SuspendLayout();
SuspendLayout();
//
// tbcMain
@ -86,13 +105,15 @@
tbcMain.Controls.Add(tbpRooms);
tbcMain.Controls.Add(tbpUsers);
tbcMain.Controls.Add(tbpGames);
tbcMain.Controls.Add(tbpStore);
tbcMain.Enabled = false;
tbcMain.ImageList = ilTabIcons;
tbcMain.Location = new Point(12, 66);
tbcMain.Location = new Point(12, 83);
tbcMain.Name = "tbcMain";
tbcMain.SelectedIndex = 0;
tbcMain.Size = new Size(305, 535);
tbcMain.Size = new Size(352, 499);
tbcMain.TabIndex = 0;
tbcMain.SelectedIndexChanged += tbcMain_SelectedIndexChanged;
//
// tbpContacts
//
@ -101,7 +122,7 @@
tbpContacts.Location = new Point(4, 24);
tbpContacts.Name = "tbpContacts";
tbpContacts.Padding = new Padding(3);
tbpContacts.Size = new Size(297, 507);
tbpContacts.Size = new Size(344, 471);
tbpContacts.TabIndex = 0;
tbpContacts.Text = "Contacts";
tbpContacts.UseVisualStyleBackColor = true;
@ -109,13 +130,13 @@
// lvContacts
//
lvContacts.Alignment = ListViewAlignment.Left;
lvContacts.Anchor = AnchorStyles.Left | AnchorStyles.Right;
lvContacts.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
lvContacts.ContextMenuStrip = ctxmRefresh;
lvContacts.LargeImageList = ilProfilePics;
lvContacts.Location = new Point(0, 0);
lvContacts.MultiSelect = false;
lvContacts.Name = "lvContacts";
lvContacts.Size = new Size(297, 514);
lvContacts.Size = new Size(344, 478);
lvContacts.SmallImageList = ilProfilePics;
lvContacts.TabIndex = 1;
lvContacts.UseCompatibleStateImageBehavior = false;
@ -144,37 +165,25 @@
//
// tbpRooms
//
tbpRooms.Controls.Add(btnAddRoom);
tbpRooms.Controls.Add(lbRooms);
tbpRooms.ImageKey = "RoomsChatIcon.png";
tbpRooms.Location = new Point(4, 24);
tbpRooms.Name = "tbpRooms";
tbpRooms.Padding = new Padding(3);
tbpRooms.Size = new Size(297, 507);
tbpRooms.Size = new Size(344, 471);
tbpRooms.TabIndex = 1;
tbpRooms.Text = "Rooms";
tbpRooms.UseVisualStyleBackColor = true;
//
// btnAddRoom
//
btnAddRoom.FlatAppearance.BorderSize = 0;
btnAddRoom.FlatStyle = FlatStyle.Flat;
btnAddRoom.Location = new Point(342, 485);
btnAddRoom.Name = "btnAddRoom";
btnAddRoom.Size = new Size(20, 22);
btnAddRoom.TabIndex = 7;
btnAddRoom.UseVisualStyleBackColor = true;
btnAddRoom.Click += btnAddRoom_Click;
//
// lbRooms
//
lbRooms.Anchor = AnchorStyles.Left | AnchorStyles.Right;
lbRooms.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
lbRooms.ContextMenuStrip = ctxmRefresh;
lbRooms.FormattingEnabled = true;
lbRooms.ItemHeight = 15;
lbRooms.Location = new Point(0, 0);
lbRooms.Name = "lbRooms";
lbRooms.Size = new Size(297, 514);
lbRooms.Size = new Size(344, 484);
lbRooms.TabIndex = 0;
lbRooms.DoubleClick += lbRooms_DoubleClick;
//
@ -184,7 +193,7 @@
tbpUsers.ImageIndex = 1;
tbpUsers.Location = new Point(4, 24);
tbpUsers.Name = "tbpUsers";
tbpUsers.Size = new Size(297, 507);
tbpUsers.Size = new Size(344, 471);
tbpUsers.TabIndex = 3;
tbpUsers.Text = "Users";
tbpUsers.UseVisualStyleBackColor = true;
@ -192,12 +201,13 @@
// lvUserDirectory
//
lvUserDirectory.Alignment = ListViewAlignment.Left;
lvUserDirectory.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
lvUserDirectory.ContextMenuStrip = ctxmRefresh;
lvUserDirectory.Location = new Point(0, 0);
lvUserDirectory.MultiSelect = false;
lvUserDirectory.Name = "lvUserDirectory";
lvUserDirectory.RightToLeft = RightToLeft.Yes;
lvUserDirectory.Size = new Size(297, 514);
lvUserDirectory.Size = new Size(344, 484);
lvUserDirectory.SmallImageList = ilStatusIcons;
lvUserDirectory.TabIndex = 0;
lvUserDirectory.UseCompatibleStateImageBehavior = false;
@ -220,14 +230,14 @@
tbpGames.ImageIndex = 3;
tbpGames.Location = new Point(4, 24);
tbpGames.Name = "tbpGames";
tbpGames.Size = new Size(297, 507);
tbpGames.Size = new Size(344, 471);
tbpGames.TabIndex = 4;
tbpGames.Text = "Games";
tbpGames.UseVisualStyleBackColor = true;
//
// lvGames
//
lvGames.Anchor = AnchorStyles.Left | AnchorStyles.Right;
lvGames.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
listViewItem1.Tag = "StockMarketGame";
listViewItem2.Tag = "GuessTheNumberGame";
listViewItem3.Tag = "TicTacToeGame";
@ -236,7 +246,7 @@
lvGames.Location = new Point(0, 0);
lvGames.MultiSelect = false;
lvGames.Name = "lvGames";
lvGames.Size = new Size(297, 514);
lvGames.Size = new Size(344, 484);
lvGames.SmallImageList = ilGames;
lvGames.TabIndex = 2;
lvGames.UseCompatibleStateImageBehavior = false;
@ -251,6 +261,35 @@
ilGames.Images.SetKeyName(1, "NumberGuessGameIcon.png");
ilGames.Images.SetKeyName(2, "Tic-tac-toe.png");
//
// tbpStore
//
tbpStore.Controls.Add(lvStoreItems);
tbpStore.ImageIndex = 3;
tbpStore.Location = new Point(4, 24);
tbpStore.Name = "tbpStore";
tbpStore.Padding = new Padding(3);
tbpStore.Size = new Size(344, 471);
tbpStore.TabIndex = 5;
tbpStore.Text = "Store";
tbpStore.UseVisualStyleBackColor = true;
//
// lvStoreItems
//
lvStoreItems.LargeImageList = ilStoreThumbnails;
lvStoreItems.Location = new Point(0, 0);
lvStoreItems.Name = "lvStoreItems";
lvStoreItems.Size = new Size(344, 484);
lvStoreItems.SmallImageList = ilStoreThumbnails;
lvStoreItems.TabIndex = 0;
lvStoreItems.UseCompatibleStateImageBehavior = false;
lvStoreItems.DoubleClick += lvStoreItems_DoubleClick;
//
// ilStoreThumbnails
//
ilStoreThumbnails.ColorDepth = ColorDepth.Depth32Bit;
ilStoreThumbnails.ImageSize = new Size(64, 64);
ilStoreThumbnails.TransparentColor = Color.Transparent;
//
// ilTabIcons
//
ilTabIcons.ColorDepth = ColorDepth.Depth32Bit;
@ -298,10 +337,10 @@
//
// lblRequestNotif
//
lblRequestNotif.Anchor = AnchorStyles.Top | AnchorStyles.Right;
lblRequestNotif.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
lblRequestNotif.AutoSize = true;
lblRequestNotif.Font = new Font("Segoe UI", 6F, FontStyle.Bold);
lblRequestNotif.Location = new Point(215, 53);
lblRequestNotif.Location = new Point(12, 67);
lblRequestNotif.Name = "lblRequestNotif";
lblRequestNotif.Size = new Size(109, 11);
lblRequestNotif.TabIndex = 6;
@ -334,7 +373,7 @@
pCurrencyArea.Controls.Add(llblClaimSpin);
pCurrencyArea.Controls.Add(pbCurrencyIcon);
pCurrencyArea.Controls.Add(lblCurrencyAmount);
pCurrencyArea.Location = new Point(223, 5);
pCurrencyArea.Location = new Point(270, 5);
pCurrencyArea.Name = "pCurrencyArea";
pCurrencyArea.Size = new Size(95, 46);
pCurrencyArea.TabIndex = 12;
@ -442,16 +481,116 @@
pbUserPfp.TabStop = false;
pbUserPfp.Click += pbUserPfp_Click;
//
// pbDonate
//
pbDonate.Anchor = AnchorStyles.Top | AnchorStyles.Right;
pbDonate.Cursor = Cursors.Hand;
pbDonate.Image = Properties.Resources.donatebtn;
pbDonate.Location = new Point(288, 56);
pbDonate.Name = "pbDonate";
pbDonate.Size = new Size(77, 22);
pbDonate.SizeMode = PictureBoxSizeMode.Zoom;
pbDonate.TabIndex = 14;
pbDonate.TabStop = false;
pbDonate.Click += pbDonate_Click;
//
// ctxmAdminUserList
//
ctxmAdminUserList.Items.AddRange(new ToolStripItem[] { refreshToolStripMenuItem1, toolStripSeparator1, copyUserIDToClipboardToolStripMenuItem, deleteUserToolStripMenuItem, adminDirectMessageToolStripMenuItem });
ctxmAdminUserList.Name = "contextMenuStrip1";
ctxmAdminUserList.Size = new Size(213, 98);
ctxmAdminUserList.Opening += ctxmAdminUserList_Opening;
//
// refreshToolStripMenuItem1
//
refreshToolStripMenuItem1.Name = "refreshToolStripMenuItem1";
refreshToolStripMenuItem1.Size = new Size(212, 22);
refreshToolStripMenuItem1.Text = "Refresh";
refreshToolStripMenuItem1.Click += refreshToolStripMenuItem_Click;
//
// toolStripSeparator1
//
toolStripSeparator1.Name = "toolStripSeparator1";
toolStripSeparator1.Size = new Size(209, 6);
//
// copyUserIDToClipboardToolStripMenuItem
//
copyUserIDToClipboardToolStripMenuItem.Name = "copyUserIDToClipboardToolStripMenuItem";
copyUserIDToClipboardToolStripMenuItem.Size = new Size(212, 22);
copyUserIDToClipboardToolStripMenuItem.Text = "Copy User ID To Clipboard";
copyUserIDToClipboardToolStripMenuItem.Click += copyUserIDToClipboardToolStripMenuItem_Click;
//
// deleteUserToolStripMenuItem
//
deleteUserToolStripMenuItem.Name = "deleteUserToolStripMenuItem";
deleteUserToolStripMenuItem.Size = new Size(212, 22);
deleteUserToolStripMenuItem.Text = "Delete User";
deleteUserToolStripMenuItem.Click += deleteUserToolStripMenuItem_Click;
//
// adminDirectMessageToolStripMenuItem
//
adminDirectMessageToolStripMenuItem.Name = "adminDirectMessageToolStripMenuItem";
adminDirectMessageToolStripMenuItem.Size = new Size(212, 22);
adminDirectMessageToolStripMenuItem.Text = "Admin Direct Message";
adminDirectMessageToolStripMenuItem.Click += adminDirectMessageToolStripMenuItem_Click;
//
// ctxmAdminRoomList
//
ctxmAdminRoomList.Items.AddRange(new ToolStripItem[] { toolStripMenuItem1, toolStripSeparator2, addRoomToolStripMenuItem, deleteRoomToolStripMenuItem });
ctxmAdminRoomList.Name = "contextMenuStrip1";
ctxmAdminRoomList.Size = new Size(143, 76);
ctxmAdminRoomList.Opening += ctxmAdminRoomList_Opening;
//
// toolStripMenuItem1
//
toolStripMenuItem1.Name = "toolStripMenuItem1";
toolStripMenuItem1.Size = new Size(142, 22);
toolStripMenuItem1.Text = "Refresh";
toolStripMenuItem1.Click += refreshToolStripMenuItem_Click;
//
// toolStripSeparator2
//
toolStripSeparator2.Name = "toolStripSeparator2";
toolStripSeparator2.Size = new Size(139, 6);
//
// addRoomToolStripMenuItem
//
addRoomToolStripMenuItem.Name = "addRoomToolStripMenuItem";
addRoomToolStripMenuItem.Size = new Size(142, 22);
addRoomToolStripMenuItem.Text = "Add Room";
addRoomToolStripMenuItem.Click += addRoomToolStripMenuItem_Click;
//
// deleteRoomToolStripMenuItem
//
deleteRoomToolStripMenuItem.Name = "deleteRoomToolStripMenuItem";
deleteRoomToolStripMenuItem.Size = new Size(142, 22);
deleteRoomToolStripMenuItem.Text = "Delete Room";
deleteRoomToolStripMenuItem.Click += deleteRoomToolStripMenuItem_Click;
//
// lblConnectionLost
//
lblConnectionLost.AutoSize = true;
lblConnectionLost.Font = new Font("Segoe UI", 6F, FontStyle.Bold);
lblConnectionLost.ForeColor = Color.Red;
lblConnectionLost.Location = new Point(12, 67);
lblConnectionLost.Name = "lblConnectionLost";
lblConnectionLost.Size = new Size(175, 11);
lblConnectionLost.TabIndex = 15;
lblConnectionLost.Text = "Server Connection Lost. Trying To Reconnect...";
lblConnectionLost.Visible = false;
//
// Main
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
BackColor = Color.DodgerBlue;
ClientSize = new Size(329, 613);
ClientSize = new Size(376, 594);
Controls.Add(pbDonate);
Controls.Add(pCurrentUser);
Controls.Add(pCurrencyArea);
Controls.Add(lblRequestNotif);
Controls.Add(tbcMain);
Controls.Add(lblConnectionLost);
FormBorderStyle = FormBorderStyle.FixedDialog;
Icon = (Icon)resources.GetObject("$this.Icon");
MaximizeBox = false;
@ -467,6 +606,7 @@
tbpRooms.ResumeLayout(false);
tbpUsers.ResumeLayout(false);
tbpGames.ResumeLayout(false);
tbpStore.ResumeLayout(false);
ctxmChangeStatus.ResumeLayout(false);
pCurrencyArea.ResumeLayout(false);
pCurrencyArea.PerformLayout();
@ -474,6 +614,9 @@
pCurrentUser.ResumeLayout(false);
pCurrentUser.PerformLayout();
((System.ComponentModel.ISupportInitialize)pbUserPfp).EndInit();
((System.ComponentModel.ISupportInitialize)pbDonate).EndInit();
ctxmAdminUserList.ResumeLayout(false);
ctxmAdminRoomList.ResumeLayout(false);
ResumeLayout(false);
PerformLayout();
}
@ -494,7 +637,6 @@
private ToolStripMenuItem awayToolStripMenuItem;
private ToolStripMenuItem doNotDisturbToolStripMenuItem;
private ToolStripMenuItem invisibleToolStripMenuItem;
private Button btnAddRoom;
private LinkLabel llblClaimSpin;
private Panel pCurrencyArea;
private Panel pCurrentUser;
@ -513,5 +655,21 @@
private ImageList ilStatusIcons;
private TabPage tbpUsers;
private ListView lvUserDirectory;
private PictureBox pbDonate;
private TabPage tbpStore;
private ListView lvStoreItems;
private ImageList ilStoreThumbnails;
private ContextMenuStrip ctxmAdminUserList;
private ToolStripMenuItem refreshToolStripMenuItem1;
private ToolStripSeparator toolStripSeparator1;
private ToolStripMenuItem copyUserIDToClipboardToolStripMenuItem;
private ToolStripMenuItem deleteUserToolStripMenuItem;
private ToolStripMenuItem adminDirectMessageToolStripMenuItem;
private ContextMenuStrip ctxmAdminRoomList;
private ToolStripMenuItem toolStripMenuItem1;
private ToolStripSeparator toolStripSeparator2;
private ToolStripMenuItem addRoomToolStripMenuItem;
private ToolStripMenuItem deleteRoomToolStripMenuItem;
private Label lblConnectionLost;
}
}

View File

@ -6,6 +6,9 @@ using QtCNETAPI.Models;
using qtc_net_client_2.Forms;
using qtc_net_client_2.Services;
using qtc_net_client_2.ClientModel;
using System.Threading.Tasks;
using QtCNETAPI.Schema;
using QtCNETAPI.Services;
namespace qtc_net_client_2
{
@ -13,7 +16,9 @@ namespace qtc_net_client_2
{
private IApiService _apiService;
private IGatewayService _gatewayService;
private Config _config;
private ServerConfig _serverConfig;
private AudioService AudioService = new();
private LoggingService LoggingService;
@ -125,7 +130,8 @@ namespace qtc_net_client_2
pbUserPfp.Image = Image.FromStream(ms);
ms.Dispose();
}
} else LoggingService.LogString($"User Has No Profile Picture Or It Could Not Be Loaded.\n{pfpRes.Message}");
}
else LoggingService.LogString($"User Has No Profile Picture Or It Could Not Be Loaded.\n{pfpRes.Message}");
}
}
}
@ -147,11 +153,7 @@ namespace qtc_net_client_2
private void llblEditProfile_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
ProfileEdit frmProfileEdit = new ProfileEdit(_apiService);
var dialogResult = frmProfileEdit.ShowDialog();
if (dialogResult == DialogResult.OK)
{
MessageBox.Show("If you updated your username, hit the refresh button to see it update on your lists.\nThe top username will not update until you restart your client.");
}
frmProfileEdit.ShowDialog();
}
private async void lvContacts_DoubleClick(object sender, EventArgs e)
@ -170,12 +172,29 @@ namespace qtc_net_client_2
var res = await _apiService.GetUserInformationAsync(user!.Id);
var pfpRes = await _apiService.GetUserProfilePic(user!.Id);
// get cosmetic
byte[]? cosmeticData = null;
if (user.ProfileCosmeticId != 0)
{
var storeRes = await _apiService.GetStoreItem(user.ProfileCosmeticId);
if (storeRes != null && storeRes.Success && storeRes.Data != null)
{
using var client = new HttpClient();
using var response = await client.GetAsync(storeRes.Data.AssetUrl);
if (response.IsSuccessStatusCode)
{
cosmeticData = await response.Content.ReadAsByteArrayAsync();
}
else LoggingService.LogString($"Could Not Get User Cosmetic.\nStatus Code: {response.StatusCode}");
}
}
if (pfpRes != null && !pfpRes.Success) LoggingService.LogString($"User Has No Profile Picture Or It Could Not Be Loaded.\n{pfpRes.Message}");
if (res.Data != null && res.Success)
{
LoggingService.LogString($"Opening Profile For User '{res.Data.Username}'");
Profile frmProfile = new Profile(res.Data, pfpRes, Contacts, _apiService, _gatewayService);
Profile frmProfile = new Profile(res.Data, pfpRes, Contacts, _apiService, _gatewayService, cosmeticData);
frmProfile.Show();
}
}
@ -274,7 +293,7 @@ namespace qtc_net_client_2
var result = await _apiService.AddCurrencyToCurrentUser(tokenJackpotSpinner.TokensWon, true);
if (result.Success)
{
lblCurrencyAmount.Text = (_apiService.CurrentUser.CurrencyAmount + tokenJackpotSpinner.TokensWon).ToString("N0");
lblCurrencyAmount.Text = _apiService.CurrentUser.CurrencyAmount.ToString("N0");
llblClaimSpin.Visible = false;
}
else MessageBox.Show("We Were Unable To Claim Your Prize At This Time. Please Try Again Later.", "Uh Oh.", MessageBoxButtons.OK, MessageBoxIcon.Error);
@ -294,12 +313,29 @@ namespace qtc_net_client_2
var res = await _apiService.GetUserInformationAsync(user!.Id);
var pfpRes = await _apiService.GetUserProfilePic(user!.Id);
// get cosmetic
byte[]? cosmeticData = null;
if (user.ProfileCosmeticId != 0)
{
var storeRes = await _apiService.GetStoreItem(user.ProfileCosmeticId);
if (storeRes != null && storeRes.Success && storeRes.Data != null)
{
using var client = new HttpClient();
using var response = await client.GetAsync(storeRes.Data.AssetUrl);
if (response.IsSuccessStatusCode)
{
cosmeticData = await response.Content.ReadAsByteArrayAsync();
}
else LoggingService.LogString($"Could Not Get User Cosmetic.\nStatus Code: {response.StatusCode}");
}
}
if (pfpRes != null && !pfpRes.Success) LoggingService.LogString($"User Has No Profile Picture Or It Could Not Be Loaded.\n{pfpRes.Message}");
if (res.Data != null && res.Success)
{
LoggingService.LogString($"Opening Profile For User '{res.Data.Username}'");
Profile frmProfile = new Profile(res.Data, pfpRes, Contacts, _apiService, _gatewayService);
Profile frmProfile = new Profile(res.Data, pfpRes, Contacts, _apiService, _gatewayService, cosmeticData);
frmProfile.Show();
}
}
@ -332,10 +368,201 @@ namespace qtc_net_client_2
private async void refreshToolStripMenuItem_Click(object sender, EventArgs e)
{
// refresh all controls (TODO - probably restrict this to currently visible control)
await RefreshContactsList();
await RefreshRoomsList();
await RefreshUsers();
if (tbcMain.SelectedIndex == 0) await RefreshContactsList();
if (tbcMain.SelectedIndex == 1) await RefreshRoomsList();
if (tbcMain.SelectedIndex == 2) await RefreshUsers();
}
private void pbDonate_Click(object sender, EventArgs e)
{
DonationWindow donationWindow = new DonationWindow();
donationWindow.Show();
}
private async void tbcMain_SelectedIndexChanged(object sender, EventArgs e)
{
if (tbcMain.SelectedIndex == 4)
{
// get store items
var storeItems = await _apiService.GetStoreItems();
if (storeItems != null && storeItems.Success && storeItems.Data != null)
{
if (lvStoreItems.Items.Count == storeItems.Data.Count) return;
ilStoreThumbnails.Images.Clear();
foreach (var item in storeItems.Data)
{
await GetAndAddStoreThumbnail(item);
var lvitem = lvStoreItems.Items.Add(new ListViewItem { Text = item.Name, Name = item.Id.ToString() });
lvitem.ImageKey = item.Id.ToString();
}
}
}
}
private async void lvStoreItems_DoubleClick(object sender, EventArgs e)
{
if (lvStoreItems.SelectedItems.Count > 0)
{
string? itemSelected = (string?)lvStoreItems.SelectedItems[lvStoreItems.SelectedItems.Count - 1].Name;
if (itemSelected != null)
{
// get item
var item = await _apiService.GetStoreItem(int.Parse(itemSelected));
if (item != null && item.Success && item.Data != null)
{
StoreItemDisplay storeItemDisplay = new StoreItemDisplay(item.Data, LoggingService, _apiService);
storeItemDisplay.ShowDialog();
}
}
}
}
private void addRoomToolStripMenuItem_Click(object sender, EventArgs e)
{
CreateRoom createRoom = new CreateRoom(_apiService);
createRoom.ShowDialog();
}
private void ctxmAdminRoomList_Opening(object sender, System.ComponentModel.CancelEventArgs e)
{
if (lbRooms.SelectedItem == null)
deleteRoomToolStripMenuItem.Enabled = false;
else deleteRoomToolStripMenuItem.Enabled = true;
}
private void ctxmAdminUserList_Opening(object sender, System.ComponentModel.CancelEventArgs e)
{
if (lvUserDirectory.SelectedItems.Count > 0)
{
string? lvUserDirectoryItemSelected = (string?)lvUserDirectory.SelectedItems[lvUserDirectory.SelectedItems.Count - 1].Text;
if (lvUserDirectoryItemSelected != null && lvUserDirectoryItemSelected == _apiService.CurrentUser.Username)
{
deleteUserToolStripMenuItem.Enabled = false;
adminDirectMessageToolStripMenuItem.Enabled = false;
copyUserIDToClipboardToolStripMenuItem.Enabled = true;
return;
}
}
else
{
deleteUserToolStripMenuItem.Enabled = false;
adminDirectMessageToolStripMenuItem.Enabled = false;
copyUserIDToClipboardToolStripMenuItem.Enabled = false;
return;
}
deleteUserToolStripMenuItem.Enabled = true;
adminDirectMessageToolStripMenuItem.Enabled = true;
copyUserIDToClipboardToolStripMenuItem.Enabled = true;
}
private void copyUserIDToClipboardToolStripMenuItem_Click(object sender, EventArgs e)
{
if (lvUserDirectory.SelectedItems.Count > 0)
{
string? itemSelected = (string?)lvUserDirectory.SelectedItems[lvUserDirectory.SelectedItems.Count - 1].Text;
if (itemSelected != null)
{
// get user
var user = UserDirectory.FirstOrDefault(e => e.Username == itemSelected);
if (user != null)
{
// clipboard requires STA apartment state
Thread thread = new(delegate () { Clipboard.SetText(user.Id); });
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
MessageBox.Show("Copied!");
}
}
}
}
private async void deleteRoomToolStripMenuItem_Click(object sender, EventArgs e)
{
if (lbRooms.SelectedItems.Count > 0)
{
string? itemSelected = (string?)lbRooms.SelectedItems[lbRooms.SelectedItems.Count - 1];
if (itemSelected != null)
{
var dialogResult = MessageBox.Show("Are You Sure You Want To Delete This Room?\nThis will kick everyone currently in the room out.",
"are you sure..?",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question);
if (dialogResult == DialogResult.Yes)
{
// get the room
var room = RoomList.FirstOrDefault(e => e.Name == itemSelected);
if (room != null)
{
var apiResult = await _apiService.DeleteRoomAsync(room.Id);
if (apiResult != null && apiResult.Success)
MessageBox.Show("Deleted!");
else
MessageBox.Show("There was an error deleting the room. Try Again?", "", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else MessageBox.Show("This room is unknown. It may have already been deleted.");
}
}
}
}
private async void deleteUserToolStripMenuItem_Click(object sender, EventArgs e)
{
if (lvUserDirectory.SelectedItems.Count > 0)
{
string? itemSelected = (string?)lvUserDirectory.SelectedItems[lvUserDirectory.SelectedItems.Count - 1].Text;
if (itemSelected != null)
{
var dialogResult = MessageBox.Show("Are You Sure You Want To Delete This User?\n" +
"This should only be done as a last resort. You should take the proper percautions to ensure the user cannot make a new account.\n" +
"Proper moderation tools are coming soon.\n" +
"Doing this will also log out the user if they are logged in.",
"are you sure...?",
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning);
if (dialogResult == DialogResult.Yes)
{
// get the user
var user = UserDirectory.FirstOrDefault(e => e.Username == itemSelected);
if (user != null)
{
var apiResult = await _apiService.DeleteUserById(user.Id);
if (apiResult != null && apiResult.Success)
MessageBox.Show("User Deleted!");
else
MessageBox.Show("There was an error deleting the user. Try Again?", "", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else MessageBox.Show("This user is unknown. They may have already been deleted.");
}
}
}
}
private async void adminDirectMessageToolStripMenuItem_Click(object sender, EventArgs e)
{
if (lvUserDirectory.SelectedItems.Count > 0)
{
string? itemSelected = (string?)lvUserDirectory.SelectedItems[lvUserDirectory.SelectedItems.Count - 1].Text;
if (itemSelected != null)
{
// get the user
var user = UserDirectory.FirstOrDefault(e => e.Username == itemSelected);
if (user != null)
{
var userDto = await _apiService.GetUserInformationAsync(user.Id);
if(userDto != null && userDto.Success && userDto.Data != null)
{
// immediately start a dm chat with the user, the user will be informed immediately if the user is an admin
DirectMessage directMessage = new DirectMessage(_gatewayService, _apiService, userDto.Data, null);
directMessage.Show();
}
}
}
}
}
private async Task OnSuccessfulLogin()
@ -358,10 +585,14 @@ namespace qtc_net_client_2
_gatewayService.OnRefreshRoomListReceived += _gatewayService_OnRefreshRoomListReceived;
_gatewayService.OnRefreshContactsListReceived += _gatewayService_OnRefreshContactsListReceived;
_gatewayService.OnServerConfigReceived += _gatewayService_OnServerConfigReceived;
_gatewayService.OnUserForceLogout += _gatewayService_OnUserForceLogout;
_apiService.OnCurrentUserUpdate += _apiService_OnCurrentUserUpdate;
if (_gatewayService.HubConnection != null && _gatewayService.HubConnection.State == Microsoft.AspNetCore.SignalR.Client.HubConnectionState.Connected)
{
LoggingService.LogString("Connected To SignalR Succesfully.");
LoggingService.LogString("Building UI...");
// we are fully logged in, get current user profile pic and set up ui
llblSignIn.Visible = false;
@ -383,14 +614,14 @@ namespace qtc_net_client_2
}
}
if (lvUserDirectory.Items.Count <= 0)
await RefreshUsers(); // prevent edge case where the refresh event never gets sent to connecting client
await RefreshContactsList();
await RefreshRoomsList();
await RefreshUsers();
// TODO - figure out server side why online status is invisible on login
_apiService.CurrentUser.Status = 1;
// set status context menu checked
// TODO - figure out more efficient way to do this
UserStatus cuStatus = (UserStatus)_apiService.CurrentUser.Status;
switch (cuStatus)
{
@ -420,15 +651,19 @@ namespace qtc_net_client_2
break;
}
if (_apiService.CurrentUser.Role == "Admin") btnAddRoom.Enabled = true;
else btnAddRoom.Enabled = false;
var current = DateTime.UtcNow;
if (current > _apiService.CurrentUser.LastCurrencySpin.ToUniversalTime() || _apiService.CurrentUser.LastCurrencySpin == new DateTime()) llblClaimSpin.Visible = true;
else llblClaimSpin.Visible = false;
if (_config.StartMinimized) WindowState = FormWindowState.Minimized;
if (_apiService.CurrentUser.Role == "Admin")
{
LoggingService.LogString("Current User Is An Admin. Using Admin Context Menu Strips...");
lvUserDirectory.ContextMenuStrip = ctxmAdminUserList;
lbRooms.ContextMenuStrip = ctxmAdminRoomList;
}
LoggingService.LogString("Client Ready");
}
}
@ -438,29 +673,30 @@ namespace qtc_net_client_2
{
LoggingService.LogString("Refreshing All Users List...");
// get all users on server
var userList = await _apiService.GetAllUsersAsync();
if (userList != null && userList.Success && userList.Data != null)
if (IsHandleCreated && !IsDisposed)
{
// clear the list
if (lvUserDirectory.InvokeRequired) lvUserDirectory.BeginInvoke(lvUserDirectory.Items.Clear);
else lvUserDirectory.Items.Clear();
UserDirectory.Clear();
// populate list
foreach (var user in userList.Data)
await Invoke(async delegate ()
{
if (lvUserDirectory.InvokeRequired)
// get all users on server
var userList = await _apiService.GetAllUsersAsync();
if (userList != null && userList.Success && userList.Data != null)
{
lvUserDirectory.BeginInvoke(delegate () { lvUserDirectory.Items.Add(user.Username, user.Status); });
// clear the list
lvUserDirectory.Items.Clear();
UserDirectory.Clear();
// populate list
foreach (var user in userList.Data)
{
lvUserDirectory.Items.Add(user.Username, user.Status);
UserDirectory.Add(user);
}
if (System.Diagnostics.Debugger.IsAttached || _config.EnableDebugLogs)
LoggingService.LogModel(userList.Data);
}
else
{
lvUserDirectory.Items.Add(user.Username, user.Status);
}
UserDirectory.Add(user);
}
});
}
}
@ -468,7 +704,7 @@ namespace qtc_net_client_2
{
LoggingService.LogString("Refreshing Rooms List...");
if (InvokeRequired)
if (IsHandleCreated && !IsDisposed)
{
await Invoke(async delegate ()
{
@ -481,35 +717,21 @@ namespace qtc_net_client_2
lbRooms.Items.Add(room.Name);
}
RoomList = roomsRes.Data;
if (System.Diagnostics.Debugger.IsAttached || _config.EnableDebugLogs)
LoggingService.LogModel(roomsRes.Data);
}
// always add lobby room to rooms list
lbRooms.Items.Add("Lobby");
});
return;
}
lbRooms.Items.Clear();
var roomsRes = await _apiService.GetAllRoomsAsync();
if (roomsRes.Success && roomsRes.Data != null)
{
lbRooms.Items.Clear();
foreach (var room in roomsRes.Data)
{
lbRooms.Items.Add(room.Name);
}
RoomList = roomsRes.Data;
}
// always add lobby room to rooms list
lbRooms.Items.Add("Lobby");
}
private async Task RefreshContactsList()
{
LoggingService.LogString("Refreshing Contacts List...");
if (InvokeRequired)
if (IsHandleCreated && !IsDisposed)
{
await Invoke(async delegate ()
{
@ -578,76 +800,11 @@ namespace qtc_net_client_2
}
}
}
if (System.Diagnostics.Debugger.IsAttached || _config.EnableDebugLogs)
LoggingService.LogModel(contactsRes.Data);
}
});
return;
}
lvContacts.Items.Clear();
Contacts.Clear();
lblRequestNotif.Visible = false;
var contactsRes = await _apiService.GetCurrentUserContacts();
if (contactsRes.Success && contactsRes.Data != null)
{
if (contactsRes.Data.Where(e => e.UserId == _apiService.CurrentUser!.Id && e.UserStatus == Contact.ContactStatus.AwaitingApprovalFromSelf).Count() >= 1)
lblRequestNotif.Visible = true;
else
lblRequestNotif.Visible = false;
foreach (var contact in contactsRes.Data)
{
ServiceResponse<UserInformationDto> user = null!;
if (contact.OwnerId == _apiService.CurrentUser!.Id)
user = await _apiService.GetUserInformationAsync(contact.UserId);
else if (contact.UserId == _apiService.CurrentUser!.Id)
user = await _apiService.GetUserInformationAsync(contact.OwnerId);
if (user.Data != null)
{
Contacts.Add(contact);
if (contact.OwnerId == _apiService.CurrentUser!.Id)
{
switch (contact.OwnerStatus)
{
case Contact.ContactStatus.AwaitingApprovalFromOther:
var lvi = lvContacts.Items.Add($"{user.Data.Username} [Request Sent]");
await AddProfilePicToList(user.Data.Id);
if (ilProfilePics.Images.ContainsKey(user.Data.Id))
lvi.ImageKey = user.Data.Id;
else lvi.ImageKey = "DEFAULT";
break;
case Contact.ContactStatus.Accepted:
var lvi2 = lvContacts.Items.Add($"{user.Data.Username}");
await AddProfilePicToList(user.Data.Id);
if (ilProfilePics.Images.ContainsKey(user.Data.Id))
lvi2.ImageKey = user.Data.Id;
else lvi2.ImageKey = "DEFAULT";
break;
}
}
else if (contact.UserId == _apiService.CurrentUser!.Id)
{
switch (contact.UserStatus)
{
case Contact.ContactStatus.AwaitingApprovalFromSelf:
var lvi = lvContacts.Items.Add($"{user.Data.Username} [Contact Request]");
await AddProfilePicToList(user.Data.Id);
if (ilProfilePics.Images.ContainsKey(user.Data.Id))
lvi.ImageKey = user.Data.Id;
else lvi.ImageKey = "DEFAULT";
AudioService.PlaySoundEffect("sndContactRequest");
break;
case Contact.ContactStatus.Accepted:
var lvi2 = lvContacts.Items.Add($"{user.Data.Username}");
await AddProfilePicToList(user.Data.Id);
if (ilProfilePics.Images.ContainsKey(user.Data.Id))
lvi2.ImageKey = user.Data.Id;
else lvi2.ImageKey = "DEFAULT";
break;
}
}
}
}
}
}
@ -655,23 +812,16 @@ namespace qtc_net_client_2
{
while (true)
{
if (!IsHandleCreated) { LoggingService.LogString("Form Handle Wasn't Created"); return; }
if (InvokeRequired && !IsDisposed)
if(IsHandleCreated && !IsDisposed)
{
BeginInvoke(async delegate ()
{
label.ForeColor = Color.Red;
await Task.Delay(500);
label.ForeColor = Color.Blue;
await Task.Delay(500);
});
await Invoke(async delegate ()
{
label.ForeColor = Color.Red;
await Task.Delay(500);
label.ForeColor = Color.Blue;
await Task.Delay(500);
});
}
label.ForeColor = Color.Red;
await Task.Delay(500);
label.ForeColor = Color.Blue;
await Task.Delay(500);
}
}
@ -685,21 +835,38 @@ namespace qtc_net_client_2
ilProfilePics.Images.Add(userId, Image.FromStream(ms));
ms.Dispose();
}
} else if (result != null) LoggingService.LogString($"User Has No Profile Picture Or It Could Not Be Loaded.\n{result.Message}");
}
else if (result != null) LoggingService.LogString($"User Has No Profile Picture Or It Could Not Be Loaded.\n{result.Message}");
}
private async Task GetAndAddStoreThumbnail(StoreItem item)
{
try
{
using HttpClient client = new();
var response = await client.GetAsync(item.ThumbnailUrl);
if (response != null && response.IsSuccessStatusCode)
{
using var stream = await response.Content.ReadAsStreamAsync();
Image image = Image.FromStream(stream);
stream.Dispose();
ilStoreThumbnails.Images.Add(item.Id.ToString(), image);
}
else if (response != null) LoggingService.LogString($"Store Item Thumbnail Could Not Be Loaded Due To Status Code {response.StatusCode}");
else LoggingService.LogString("Store Item Thumbnail Could Not Be Loaded");
client.Dispose();
}
catch (Exception ex)
{
LoggingService.LogString("Store Item Thumbnail Could Not Be Loaded\n" + ex.Message);
}
}
public void RefreshCurrencyCounter()
{
if (lblCurrencyAmount.InvokeRequired)
if (IsHandleCreated && !IsDisposed)
{
lblCurrencyAmount.BeginInvoke(delegate ()
{
lblCurrencyAmount.Text = _apiService.CurrentUser.CurrencyAmount.ToString("N0");
});
}
else
{
lblCurrencyAmount.Text = _apiService.CurrentUser.CurrencyAmount.ToString("N0");
Invoke(delegate () { lblCurrencyAmount.Text = _apiService.CurrentUser.CurrencyAmount.ToString("N0"); });
}
}
@ -718,9 +885,9 @@ namespace qtc_net_client_2
await _gatewayService.StartAsync();
if (_gatewayService.HubConnection != null && _gatewayService.HubConnection.State == Microsoft.AspNetCore.SignalR.Client.HubConnectionState.Connected)
{ BeginInvoke(delegate () { Enabled = true; }); return; }
{ BeginInvoke(delegate () { tbcMain.Enabled = true; lblConnectionLost.Visible = false; }); return; }
ConnectionClosed frmConnectionClosed = new ConnectionClosed(error);
ConnectionClosed frmConnectionClosed = new ConnectionClosed();
var result = frmConnectionClosed.ShowDialog();
if (result == DialogResult.OK)
@ -729,18 +896,19 @@ namespace qtc_net_client_2
Reconnect:
if (_gatewayService.HubConnection != null)
{
try
await _gatewayService.StopAsync();
await _gatewayService.StartAsync();
if (_gatewayService.HubConnection.State == Microsoft.AspNetCore.SignalR.Client.HubConnectionState.Connected)
{
await _gatewayService.StopAsync();
await _gatewayService.StartAsync();
if (_gatewayService.HubConnection.State == Microsoft.AspNetCore.SignalR.Client.HubConnectionState.Connected)
BeginInvoke(delegate () { Enabled = true; });
Invoke(delegate () { tbcMain.Enabled = true; lblConnectionLost.Visible = false; });
}
catch (HttpRequestException ex)
else
{
// reshow the dialog
frmConnectionClosed.Reason = ex.Message;
frmConnectionClosed.StatusLabel.Text = "Couldn't Reconnect. The Server Could Be Down.";
var result1 = frmConnectionClosed.ShowDialog();
if (result1 == DialogResult.OK) goto Reconnect;
else Environment.Exit(0);
}
@ -749,8 +917,47 @@ namespace qtc_net_client_2
else Environment.Exit(0);
}
private void _gatewayService_OnServerReconnecting(object? sender, EventArgs e) => BeginInvoke(delegate () { Enabled = false; });
private void _gatewayService_OnServerReconnected(object? sender, EventArgs e) => BeginInvoke(delegate () { Enabled = true; });
private void _gatewayService_OnServerReconnecting(object? sender, EventArgs e)
{
var args = (ServerConnectionReconnectingEventArgs)e;
if (args.Error == null)
LoggingService.LogString("Server Requested Reconnect. Reconnecting...");
else
LoggingService.LogString($"SignalR Reconnecting Due To An Error.\n{args.Error.Message}\n{args.Error.StackTrace}");
if (IsHandleCreated && !IsDisposed)
{
Invoke(delegate ()
{
tbcMain.Enabled = false;
lblConnectionLost.Visible = true;
});
}
}
private async void _gatewayService_OnServerReconnected(object? sender, EventArgs e)
{
LoggingService.LogString("SignalR Reconnected");
if (IsHandleCreated && !IsDisposed)
{
Invoke(delegate ()
{
tbcMain.Enabled = true;
lblConnectionLost.Visible = false;
});
}
// ensure status is set to whatever the current user was set to
try
{
await _gatewayService.UpdateStatus(_apiService.CurrentUser.Status);
} catch (InvalidOperationException)
{
LoggingService.LogString("Could Not Set Status Back To Online");
}
}
private async void _gatewayService_OnServerConfigReceived(object? sender, EventArgs e)
{
@ -759,8 +966,11 @@ namespace qtc_net_client_2
LoggingService.LogString($"Server Config Received");
LoggingService.LogModel(args.ServerConfig);
if (_serverConfig != null) return; // only set server config upon client restart, not during reconnect (preventing log spam)
if (args.ServerConfig != null)
{
_serverConfig = args.ServerConfig;
if (args.ServerConfig.IsDown)
{
LoggingService.LogString("Server Is Marked As Down");
@ -796,6 +1006,23 @@ namespace qtc_net_client_2
}
}
private async void _gatewayService_OnUserForceLogout(object? sender, EventArgs e)
{
MessageBox.Show("Connection To Server Lost. You Were Logged Out.\nTry Signing In Again.\nThe Application Will Now Close.", "oops.", MessageBoxButtons.OK, MessageBoxIcon.Error);
await _gatewayService.StopAsync();
if (File.Exists("./session.token")) File.Delete("./session.token");
Environment.Exit(0); // dont want the user to try and make a new account right away
}
private void _apiService_OnCurrentUserUpdate(object? sender, EventArgs e)
{
lblWelcome.Text = $"Welcome, {_apiService.CurrentUser.Username}";
RefreshCurrencyCounter();
}
private async void _gatewayService_OnRefreshContactsListReceived(object? sender, EventArgs e) => await RefreshContactsList();
private async void _gatewayService_OnRefreshRoomListReceived(object? sender, EventArgs e) => await RefreshRoomsList();
private async void _gatewayService_OnRefreshUserListReceived(object? sender, EventArgs e) => await RefreshUsers();

View File

@ -128,7 +128,7 @@
AAEAAAD/////AQAAAAAAAAAMAgAAAEZTeXN0ZW0uV2luZG93cy5Gb3JtcywgQ3VsdHVyZT1uZXV0cmFs
LCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5BQEAAAAmU3lzdGVtLldpbmRvd3MuRm9ybXMu
SW1hZ2VMaXN0U3RyZWFtZXIBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAA0A0AAAJNU0Z0AUkBTAMBAQAB
4AEAAeABAAEgAQABIAEABP8BIQEACP8BQgFNATYHAAE2AwABKAMAAYADAAEgAwABAQEAASAGAAFAEgAD
mAEBAZgBAQEgAQABIAEABP8BIQEACP8BQgFNATYHAAE2AwABKAMAAYADAAEgAwABAQEAASAGAAFAEgAD
rQH/A7oB/wO6Af8DuQH/A7oB/wO6Af8D2gX/A/wB/wP+Df8D/QH/A/wR/wP8Af8D/g3/A/0B/wPZAf8D
ugH/A7oB/wO6Af8DugH/A7oB/wO6Af//AIEAA58B/wO6Af8DugH/A7oB/wO6Af8DugH/A9oJ/wP9Af8D
/g3/A/0B/wP9Ef8D/AH/A/4N/wPaAf8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af//AIEAA6IB/wO6Af8D
@ -196,90 +196,88 @@
<value>
AAEAAAD/////AQAAAAAAAAAMAgAAAEZTeXN0ZW0uV2luZG93cy5Gb3JtcywgQ3VsdHVyZT1uZXV0cmFs
LCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5BQEAAAAmU3lzdGVtLldpbmRvd3MuRm9ybXMu
SW1hZ2VMaXN0U3RyZWFtZXIBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAAGhQAAAJNU0Z0AUkBTAIBAQQB
AAGYAQABmAEAARABAAEQAQAE/wEhAQAI/wFCAU0BNgcAATYDAAEoAwABQAMAASADAAEBAQABIAYAASD/
SW1hZ2VMaXN0U3RyZWFtZXIBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAAohMAAAJNU0Z0AUkBTAIBAQQB
AAFQAQEBUAEBARABAAEQAQAE/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
YAHoA10B0QNQAZ8DMQFNAwUBBhQAAyABLQNUAasDWwHkA1oB9QMkAfsDTgH+A04B/gMkAfsDUwH0A2IB
4QNRAaEDHgEqEAADIAEtA1QBqwNbAeQDWgH1ASEBXgEhAfsBKgFqASoB/gEqAWoBKgH+ASEBXgEhAfsD
UwH0A2IB4QNRAaEDHgEqEAADIAEtA1QBqwNbAeQDWgH1ASECXgH7ASoCagH+ASoCagH+ASECXgH7A1MB
9ANiAeEDUQGhAx4BKhAAAyABLQNUAasDWwHkA1oB9QIhAV4B+wIqAWoB/gIqAWoB/gIhAV4B+wNTAfQD
YgHhA1EBoQMeASoMAAMbASUDWAG9A1oB8gNSAf4DMAH/AzkB/wM8Af8DNgH/AyoB/wMkAf8DQAH9A14B
8ANWAbIDGgEjCAADGwElA1gBvQNaAfIBKgFyASoB/gEAAVcBAAH/AQABZwEAAf8BAAFsAQAB/wEAAWEB
AAH/AQABTAEAAf8BAAFAAQAB/wNAAf0DXgHwA1YBsgEZARoBGQEjCAADGwElA1gBvQNaAfIBKgJyAf4B
AAJXAf8BAAJnAf8BAAJsAf8BAAJhAf8BAAJMAf8BAAJAAf8DQAH9A14B8ANWAbIBGQIaASMIAAMbASUD
WAG9A1oB8gIqAXIB/gIAAVcB/wIAAWcB/wIAAWwB/wIAAWEB/wIAAUwB/wIAAUAB/wNAAf0DXgHwA1YB
sgIZARoBIwQAAwMBBANSAaUDYAHzA0kB/wNVAf8DZQH/A3EB/wN1Af8DcQH/A2QB/wNMAf8DMQH/A04B
/gNiAe4DUAGaAwMBBAMDAQQBUgFTAVIBpQFgAW8BYAHzAQABggEAAf8BAAGZAQAB/wEAAbYBAAH/AQAB
zAEAAf8BAAHTAQAB/wEAAcsBAAH/AQABswEAAf8BAAGIAQAB/wEAAVcBAAH/ASoBagEqAf4DYgHuA1AB
mgMDAQQDAwEEAVICUwGlAWACbwHzAQACggH/AQACmQH/AQACtgH/AQACzAH/AQAC0wH/AQACywH/AQAC
swH/AQACiAH/AQACVwH/ASoCagH+A2IB7gNQAZoDAwEEAwMBBAJSAVMBpQJgAW8B8wIAAYIB/wIAAZkB
/wIAAbYB/wIAAcwB/wIAAdMB/wIAAcsB/wIAAbMB/wIAAYgB/wIAAVcB/wIqAWoB/gNiAe4DUAGaAwMB
BAMtAUQDYAHoA4AB/gNuAf8DewH/A4UB/wOKAf8DjAH/A4oB/wOFAf8DdgH/A1cB/wMyAf8DQAH9A14B
3QMqAT8DLQFEAWABaQFgAegBKgGAASoB/gEAAcYBAAH/AQAB3AEAAf8BAAHuAQAB/wEAAfgBAAH/AQAB
+wEAAf8BAAH5AQAB/wEAAe8BAAH/AQAB1AEAAf8BAAGcAQAB/wEAAVoBAAH/A0AB/QNeAd0DKgE/Ay0B
RAFgAmkB6AEqAoAB/gEAAsYB/wEAAtwB/wEAAu4B/wEAAvgB/wEAAvsB/wEAAvkB/wEAAu8B/wEAAtQB
/wEAApwB/wEAAloB/wNAAf0DXgHdAyoBPwMtAUQCYAFpAegCKgGAAf4CAAHGAf8CAAHcAf8CAAHuAf8C
AAH4Af8CAAH7Af8CAAH5Af8CAAHvAf8CAAHUAf8CAAGcAf8CAAFaAf8DQAH9A14B3QMqAT8DTgGVA3cB
+AN/Af8DhQH/A4oB/wONAf8DjgH/A44B/wOOAf8DjQH/A4kB/wN3Af8DTQH/AyUB/wNaAfIDSgGLA04B
lQFcAXwBXAH4AQAB5QEAAf8BAAHvAQAB/wEAAfgBAAH/AQAB/QEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB
/wEAAf8BAAH+AQAB/wEAAfYBAAH/AQAB1QEAAf8BAAGLAQAB/wEAAUEBAAH/A1oB8gNKAYsDTgGVAVwC
fAH4AQAC5QH/AQAC7wH/AQAC+AH/AQAC/QH/AQAD/wEAA/8BAAP/AQAC/gH/AQAC9gH/AQAC1QH/AQAC
iwH/AQACQQH/A1oB8gNKAYsDTgGVAlwBfAH4AgAB5QH/AgAB7wH/AgAB+AH/AgAB/QH/AgAC/wIAAv8C
AAL/AgAB/gH/AgAB9gH/AgAB1QH/AgABiwH/AgABQQH/A1oB8gNKAYsDXwHTA34B/AOTAf8DjgH/A40B
/wOOAf8DjgH/A44B/wOOAf8DjgH/A40B/wOFAf8DZwH/AzQB/wNBAfkDWgHEAVsBXwFbAdMBKwF+ASsB
/AEOAfsBDgH/AQMB/QEDAf8BAAH+AQAB/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB
/wEAAf8BAAH9AQAB/wEAAe8BAAH/AQABuQEAAf8BAAFdAQAB/wNBAfkDWgHEAVsCXwHTASsCfgH8AQ4C
+wH/AQMC/QH/AQAC/gH/AQAD/wEAA/8BAAP/AQAD/wEAA/8BAAL9Af8BAALvAf8BAAK5Af8BAAJdAf8D
QQH5A1oBxAJbAV8B0wIrAX4B/AIOAfsB/wIDAf0B/wIAAf4B/wIAAv8CAAL/AgAC/wIAAv8CAAL/AgAB
/QH/AgAB7wH/AgABuQH/AgABXQH/A0EB+QNaAcQDbgH1A4AB/gOfAf8DkwH/A48B/wOOAf8DjgH/A44B
/wOOAf8DjgH/A44B/wOLAf8DdwH/A0gB/wNAAf0DYgHhAVoBbgFaAfUBTgGrAU4B/gEnAf8BJwH/AQsB
/wELAf8BAQH/AQEB/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB
/wEAAfkBAAH/AQAB1gEAAf8BAAGBAQAB/wNAAf0DYgHhAVoCbgH1AU4CqwH+AScD/wELA/8BAQP/AQAD
/wEAA/8BAAP/AQAD/wEAA/8BAAP/AQAC+QH/AQAC1gH/AQACgQH/A0AB/QNiAeECWgFuAfUCTgGrAf4C
JwL/AgsC/wIBAv8CAAL/AgAC/wIAAv8CAAL/AgAC/wIAAv8CAAH5Af8CAAHWAf8CAAGBAf8DQAH9A2IB
4QNjAfYDgAH+A6sB/wOZAf8DkAH/A44B/wOOAf8DjgH/A44B/wOOAf8DjgH/A40B/wN/Af8DVQH/A0AB
/QNeAeIBSAFjAUgB9gFxAasBcQH+AUIB/wFCAf8BGQH/ARkB/wEEAf8BBAH/AQAB/wEAAf8BAAH/AQAB
/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB/QEAAf8BAAHkAQAB/wEAAZgBAAH/A0AB
/QNeAeIBSAJjAfYBcQKrAf4BQgP/ARkD/wEEA/8BAAP/AQAD/wEAA/8BAAP/AQAD/wEAA/8BAAL9Af8B
AALkAf8BAAKYAf8DQAH9A14B4gJIAWMB9gJxAasB/gJCAv8CGQL/AgQC/wIAAv8CAAL/AgAC/wIAAv8C
AAL/AgAC/wIAAf0B/wIAAeQB/wIAAZgB/wNAAf0DXgHiA2EB1gN+AfwDuAH/A6MB/wOTAf8DjgH/A44B
/wOOAf8DjgH/A44B/wOOAf8DjQH/A4IB/wNcAf8DTQH6A1oBxwFcAWEBXAHWAWQBgwFkAfwBXwH/AV8B
/wEvAf8BLwH/AQwB/wEMAf8BAQH/AQEB/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB
/wEAAf8BAAH+AQAB/wEAAeoBAAH/AQABpQEAAf8BSAFNAUgB+gNaAccBXAJhAdYBZAKDAfwBXwP/AS8D
/wEMA/8BAQP/AQAD/wEAA/8BAAP/AQAD/wEAA/8BAAL+Af8BAALqAf8BAAKlAf8BSAJNAfoDWgHHAlwB
YQHWAmQBgwH8Al8C/wIvAv8CDAL/AgEC/wIAAv8CAAL/AgAC/wIAAv8CAAL/AgAB/gH/AgAB6gH/AgAB
pQH/AkgBTQH6A1oBxwNQAZoDbQH5A8UB/wOyAf8DnAH/A5EB/wOOAf8DjgH/A44B/wOOAf8DjwH/A44B
/wODAf8DYAH/A1oB8gNMAZADUAGaAWoBfwFqAfkBfAH/AXwB/wFRAf8BUQH/AR8B/wEfAf8BBwH/AQcB
/wEBAf8BAQH/AQAB/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQIB/wECAf8BAgH+AQIB/wEAAesBAAH/AQAB
rQEAAf8BWgFrAVoB8gNMAZADUAGaAWoCfwH5AXwD/wFRA/8BHwP/AQcD/wEBA/8BAAP/AQAD/wEAA/8B
AgP/AQIC/gH/AQAC6wH/AQACrQH/AVoCawHyA0wBkANQAZoCagF/AfkCfAL/AlEC/wIfAv8CBwL/AgEC
/wIAAv8CAAL/AgAC/wICAv8CAgH+Af8CAAHrAf8CAAGtAf8CWgFrAfIDTAGQAy8BSQNsAesDgAH+A8YB
/wOuAf8DnAH/A5MB/wOQAf8DjwH/A5AB/wOTAf8DkwH/A4UB/wNAAf0DYAHgAy0BRQMvAUkDbAHrAYAB
qwGAAf4BfwH/AX8B/wFJAf8BSQH/AR8B/wEfAf8BDAH/AQwB/wEFAf8BBQH/AQMB/wEDAf8BBQH/AQUB
/wEKAf8BCgH/AQoB/gEKAf8BAQHtAQEB/wFAAbYBQAH9AWABZgFgAeADLQFFAy8BSQNsAesBgAKrAf4B
fwP/AUkD/wEfA/8BDAP/AQUD/wEDA/8BBQP/AQoD/wEKAv4B/wEBAu0B/wFAArYB/QFgAmYB4AMtAUUD
LwFJA2wB6wKAAasB/gJ/Av8CSQL/Ah8C/wIMAv8CBQL/AgMC/wIFAv8CCgL/AgoB/gH/AgEB7QH/AkAB
tgH9AmABZgHgAy0BRQMDAQQDVgGuA24B9QPZAf8DywH/A7cB/wOnAf8DnQH/A5oB/wOcAf8DnwH/A5sB
/wOJAf8DaAHwA1IBowMDAQQDAwEEA1YBrgNuAfUBqAH/AagB/wGJAf8BiQH/AVwB/wFcAf8BNwH/ATcB
/wEiAf8BIgH/ARsB/wEbAf8BHwH/AR8B/wEmAf8BJgH/AR0B/wEdAf8BBQHzAQUB/wFeAWgBXgHwA1IB
owMDAQQDAwEEA1YBrgNuAfUBqAP/AYkD/wFcA/8BNwP/ASID/wEbA/8BHwP/ASYD/wEdA/8BBQLzAf8B
XgJoAfADUgGjAwMBBAMDAQQDVgGuA24B9QKoAv8CiQL/AlwC/wI3Av8CIgL/AhsC/wIfAv8CJgL/Ah0C
/wIFAfMB/wJeAWgB8ANSAaMDAwEEBAADHAEnA10BxwNjAfYDiwH+A9cB/wPMAf8DwgH/A7sB/wO3Af8D
sQH/A4AB/gNoAfQDWQG8AxsBJggAAxwBJwNdAccDYwH2AYABqwGAAf4BpQH/AaUB/wGLAf8BiwH/AXQB
/wF0Af8BZgH/AWYB/wFcAf8BXAH/AU4B/wFOAf8BWgGrAVoB/gFTAWgBUwH0AVcBWQFXAbwDGwEmCAAD
HAEnA10BxwNjAfYBgAKrAf4BpQP/AYsD/wF0A/8BZgP/AVwD/wFOA/8BWgKrAf4BUwJoAfQBVwJZAbwD
GwEmCAADHAEnA10BxwNjAfYCgAGrAf4CpQL/AosC/wJ0Av8CZgL/AlwC/wJOAv8CWgGrAf4CUwFoAfQC
VwFZAbwDGwEmDAADIQEwA1kBtgNiAe4DfQH6A74B/QPUAf8DzAH/A74B/QNqAfkDbAHrA1UBrAMfASwQ
AAMhATADWQG2A2IB7gN9AfoBrgG+Aa4B/QGfAf8BnwH/AYwB/wGMAf8BTAG+AUwB/QFoAX8BaAH5AWEB
bAFhAesDVQGsAx8BLBAAAyEBMANZAbYDYgHuA30B+gGuAr4B/QGfA/8BjAP/AUwCvgH9AWgCfwH5AWEC
bAHrA1UBrAMfASwQAAMhATADWQG2A2IB7gN9AfoCrgG+Af0CnwL/AowC/wJMAb4B/QJoAX8B+QJhAWwB
6wNVAawDHwEsFAADBgEHAzYBWANVAawDZgHlA34B/AOBAfsDZQHiA1MBpwMzAVEDBgEHGAADBgEHAzYB
WANVAawDZgHlAX4BgwF+AfwBXwGMAV8B+wNlAeIDUwGnAzMBUQMGAQcYAAMGAQcDNgFYA1UBrANmAeUB
fgKDAfwBXwKMAfsDZQHiA1MBpwMzAVEDBgEHGAADBgEHAzYBWANVAawDZgHlAn4BgwH8Al8BjAH7A2UB
4gNTAacDMwFRAwYBBwwAAUIBTQE+BwABPgMAASgDAAFAAwABIAMAAQEBAAEBBgABARYAA/+BAAHgAQcB
4AEHAeABBwHgAQcBwAEDAcABAwHAAQMBwAEDAYABAQGAAQEBgAEBAYABAVAAAYABAQGAAQEBgAEBAYAB
AQHAAQMBwAEDAcABAwHAAQMB4AEHAeABBwHgAQcB4AEHCw==
@ -292,168 +290,171 @@
<value>
AAEAAAD/////AQAAAAAAAAAMAgAAAEZTeXN0ZW0uV2luZG93cy5Gb3JtcywgQ3VsdHVyZT1uZXV0cmFs
LCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5BQEAAAAmU3lzdGVtLldpbmRvd3MuRm9ybXMu
SW1hZ2VMaXN0U3RyZWFtZXIBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAAPCUAAAJNU0Z0AUkBTAIBAQMB
AAFYAQEBWAEBASABAAEgAQAE/wEhAQAI/wFCAU0BNgcAATYDAAEoAwABgAMAASADAAEBAQABIAYAAUB6
SW1hZ2VMaXN0U3RyZWFtZXIBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAAKCUAAAJNU0Z0AUkBTAIBAQMB
AAEQAQIBEAECASABAAEgAQAE/wEhAQAI/wFCAU0BNgcAATYDAAEoAwABgAMAASADAAEBAQABIAYAAUB6
AANcAecBCAEKAQAB/wMqAUAMAAMqAUADNgFXAz8BbAM/AWwDPwFsAz8BbAM/AWwDPwFsAz8BbAM/AWwD
PwFsAz8BbAM/AWwDPwFsAz8BbAM/AWwDPwFsAz8BbAI7AToBYgMzAVEDGAEhVAADZAGiA28BwCAAA2YB
pgNtAbzwAAMhATADRgGAFAABFQEfAQQB/wENARUBAAH/AQgBCgEAAf8BCAEKAQAB/wwAA0QBegNTAacC
PwFsAz8BbAM/AWwDPwFsAz8BbAM/AWwDPwFsAz8BbAI7AToBYgMzAVEDGAEhVAADUwGiA1sBwCAAA1QB
pgNZAbzwAAMhATADRgGAFAABFQEfAQQB/wENARUBAAH/AQgBCgEAAf8BCAEKAQAB/wwAA0QBegNTAacC
YQFdAc8CYQFdAc8CYQFdAc8CYQFdAc8CYQFdAc8CYQFdAc8CYQFdAc8CYQFdAc8CYQFdAc8CYQFdAc8C
YQFdAc8CYQFdAc8CYQFdAc8CYQFdAc8CYQFdAc8CYAFdAc4DWQG7AlABTwGbAyoBQFQAA3QB0AN7Ae4g
AAN1AdQDegHp5AADWgG/AQgBCgEAAf8BCAEKAQAB/wEIAQoBAAH/AQgBCgEAAf8BCAEKAQAB/wEIAQoB
YQFdAc8CYQFdAc8CYQFdAc8CYQFdAc8CYQFdAc8CYAFdAc4DWQG7AlABTwGbAyoBQFQAA18B0ANiAe4g
AANhAdQDYwHp5AADWgG/AQgBCgEAAf8BCAEKAQAB/wEIAQoBAAH/AQgBCgEAAf8BCAEKAQAB/wEIAQoB
AAH/AQgBCgEAAf8DWgG/A1oBvwFDAXQBBwH/AUcBdwEMAf8BJQFHAQAB/wEOARkBAAH/DAACSwFKAYoC
WgFYAb0CZwFdAeoCZwFdAeoCZwFdAeoCZwFdAeoCZwFdAeoCZwFdAeoCZwFdAeoCZwFdAeoCZwFdAeoC
ZwFdAeoCZwFdAeoCZwFdAeoCZwFdAeoCZwFdAeoCZwFdAeoCYwFaAekCXwFbAdMDVQGvAy4BSFQAA3QB
0AN7Ae4gAAN1AdQDegHp4AABCAEKAQAB/wEQAR4BAAH/ASEBQQEAAf8BJwFLAQAB/wEmAUoBAAH/ASEB
ZwFdAeoCZwFdAeoCZwFdAeoCZwFdAeoCZwFdAeoCZwFdAeoCYwFaAekCXwFbAdMDVQGvAy4BSFQAA18B
0ANiAe4gAANhAdQDYwHp4AABCAEKAQAB/wEQAR4BAAH/ASEBQQEAAf8BJwFLAQAB/wEmAUoBAAH/ASEB
QQEAAf8BEAEeAQAB/wEIAQoBAAH/AQgBCgEAAf8BCAEKAQAB/wEpAU4BAAH/AVoBlgEQAf8BWgGcARAB
/wFzAbYBJgH/ARgBLgEAAf8MAANJAYYCWgFYAbcCZQFgAeMCZQFgAeMCZQFgAeMCZQFgAeMCagFhAeYC
agFeAe0BcwFoAVMB9AGsAXcBKwH8Af8BeAEAAv8BeAEAAf8BbwFuAVEB9QJqAWEB5gJlAWAB4wJlAWAB
4wJlAWAB4wJlAV4B4gJeAVsBzQJVAVMBqgIuAS0BRlQAA3QB0AN7Ae4gAAN1AdQDegHp3AABCAEKAQAB
/wE9AXEBAgH/AUwBhgEHAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBEAH/AUwB
fwEPAf8BOwFnAQgB/wEhATQBCAH/AVMBjgELAf8BWgGcARAB/wFyAbMBJwH/AVMBgQEbAf8QAAMrAUED
NgFZAkABPwFuAkABPwFuAkABPwFuAkABPwFuA0QBewJRAVABnwJdAVsBxQFlAmIB7gH/AXgBAAL/AXgB
AAH/Al8BXQHJA0UBfAJAAT8BbgJAAT8BbgJAAT8BbgM/AW0DOwFjAzMBUgMZASJUAAN0AdADewHuIAAD
dQHUA3oB6dgAARUBKQEAAf8BSgGEAQQB/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIB
jAEIAf8BUgGMARAB/wFSAYwBEAH/AVIBjAEQAf8BWgGUARAB/wFWAZABDAH/AVoBmgEQAf8BWgGcARAB
/wFyAakBMAH/AQgBCgEAAf8BCAEKAQAB/wwAAwwBEAMRARYDFQEcAxUBHAMVARwDFQEcAyEBMAI+AT0B
aQJTAVIBpQJmAV8B5QH/AXgBAAL/AXgBAAH/AlUBUwGqAyIBMQMVARwDFQEcAxUBHAMUARsDEwEZAxAB
FQMHAQlUAAN0AdADewHuIAADdQHUA3oB6dQAARYBJwECAf8BSQGAAQcB/wFSAYwBCAH/AVIBjAEIAf8B
UgGMAQgB/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQsB/wFSAYwBEAH/AVIBjAEQAf8BWAGSARAB/wFaAZQB
EAH/AVoBmwEQAf8BWgGcARAB/wFcAZ4BEgH/ATsBbQEDAf8BCAEKAQAB/wEIAQoBAAH/AQgBCgEAAf8I
AAMFBAYECAEKAwgBCgMIAQoDCAEKAxcBHwI5ATgBXQNRAZ4CZQFgAeMB/wF4AQAC/wF4AQAB/wNSAaMD
GAEhAwgBCgMIAQoDCAEKAwgBCgMHAQkDBgEHAwIBA1QAA3QB0AN7Ae4gAAN1AdQDegHp0AADXQHfAUcB
gAEDAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEIAf8BUgGMARAB
/wFSAYwBEAH/AVIBjAEQAf8BWgGUARAB/wFaAZQBEAH/AVoBnAEQAf8BWgGcARAB/wFaAZwBEAH/AVoB
nAEQAf8BWgGYARAB/wEWASoBAAH/AQgBCgEAAf8BCAEKAQAB/xwAAxIBFwM1AVYDUAGaA2IB4QH/AXgB
AAL/AXgBAAH/AlEBUAGfAxIBGHAAA3QB0AN7Ae4gAAN1AdQDegHp0AABLAFQAQAB/wFKAYwBAAH/AVIB
jAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBDgH/AVUBkgEQAf8BWgGcARAB
/wFaAZwBEAH/AVoBlAEQAf8BWgGcARAB/wFaAZwBEAH/AVoBnAEQAf8BWgGcARAB/wFaAZwBEAH/AVoB
lAEYAf8BWAGSARYB/wEQAR4BAAH/AQgBCgEAAf8cAAMSARcDNQFWA1ABmgNiAeEB/wF4AQAC/wF4AQAB
/wJRAVABnwMSARhwAAN0AdADewHuIAADdQHUA3oB6dQAA0sBjwEzAV4BAAH/AVIBjAEIAf8BUgGMAQgB
/wFSAYwBCAH/AVIBjAEQAf8BfwHAATMB/wFfAZ4BFwH/ASABPwEAAf8BCAEKAQAB/wFSAYwBEAH/AVoB
nAEQAf8BWgGcARAB/wFaAZwBEAH/AVoBnAEQAf8BWgGVARcB/wFaAZQBGAH/AVoBlAEYAf8BWgGUARgB
/wEIAQoBAAH/AQgBCgEAAf8YAAMSARcDNQFWA1ABmgNiAeEB/wF4AQAC/wF4AQAB/wJRAVABnwMSARgU
AAQBAwMBBAMGBAcBCQMHAQkDBAEFBAIEARQAAxIBFgMkAS8DJAEvAyQBLwMkAS8DJAEvAyQBLwMkAS8D
JAEvAyQBLwN1AdkDfAHxAyQBLwMkAS8DJAEvAyQBLwMkAS8DJAEvAyQBLwMkAS8DdwHcA3sB7QMkAS8D
JAEvAyQBLwMkAS8DJAEvAyQBLwMkAS8DJAEvAyQBLwMOARG4AAFCAXsBAAH/AVIBjAEIAf8BewHGASkB
/wMzAVAEAAMMARABOQFrAQAB/wFaAZwBEAH/AVoBnAEQAf8BXQGXARMB/wFWAZQBDAH/AVoBnAEQAf8B
WgGUARgB/wFaAZQBGAH/AWMBnAEYAf8BYwGlARcB/wEpAVABAAH/AQgBCgEAAf8YAAMSARcDNQFWA1AB
mgNiAeEB/wF4AQAC/wF4AQAB/wJRAVABnwMSARgUAAQCAwkBDAMPARQDEwEaAxIBGAMKAQ0DBAEFBAEU
AAN7AegDfwH/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/Af8DcwHPvAABCAEKAQAB/wwAARMBIwEAAf8BWgGUARAB/wFaAZwBEAH/AZQB1gFKAf8BDQERAQIB
/wExAVoBAAH/AVoBlAESAf8BWgGUARgB/wFaAZQBGAH/AWMBnAEYAf8BYwGlARcB/wFrAaUBIQH/ARAB
HgEAAf8YAAMSARcDNQFWA1ABmgNiAeEB/wF4AQAC/wF4AQAB/wJRAVABnwMSARgUAAMFAQYDHQEpAzAB
SgI7ATwBZQI7ATwBZAMoATwCFQEWAR0DBgEIFAADRgFkA1wBjwNcAY8DXAGPA1wBjwNcAY8DXAGPA1wB
jwNcAY8DXAGPA3oB6gN9AfgDXAGPA1wBjwNcAY8DXAGPA1wBjwNcAY8DXAGPA1wBjwN7AewDfAH1A1wB
jwNcAY8DXAGPA1wBjwNcAY8DXAGPA1wBjwNcAY8DXAGPAz4BVqgAA10B3wEIAQoBAAH/AQgBCgEAAf8B
CAEKAQAB/wEIAQoBAAH/AQgBCgEAAf8BCAEKAQAB/wMzAVADOgFgAUsBhQEHAf8BWgGUARAB/wFaAZwB
EAH/ATkBawEAAf8DCQEMARABHgEAAf8BVAGOARIB/wFaAZQBGAH/AWMBnAEYAf8BYwGcARgB/wFjAaUB
FwH/AXMBtAEpAf8BEAEeAQAB/xgAAxIBFwM1AVYDUAGaA2IB4QH/AXgBAAL/AXgBAAH/AlEBUAGfAxIB
GBQAAwkBDAMzAVACTAFNAZECXQFfAckCWwFdAcoDRAF6AykBPQMMARA8AAN0AdADewHuIAADdQHUA3oB
6cwAAQgBCgEAAf8BCAEKAQAB/wEIAQoBAAH/AQgBCgEAAf8BCAEKAQAB/wEIAQoBAAH/AQgBCgEAAf8B
CAEKAQAB/wEIAQoBAAH/ATMBXwECAf8BWgGUARAB/wFaAZwBEAH/Aa0B5wFjAf8BEAEeAQAB/wEIAQoB
AAH/ARQBJgEAAf8BVAGOARIB/wFiAZsBGAH/AWMBnAEYAf8BYwGlARcB/wFjAaUBFwH/AWMBnAEhAf8B
GAEuAQAB/xgAAxIBFwM1AVYDUAGaA2IB4QH/AXgBAAL/AXgBAAH/AlEBUAGfAxIBGBQAAwkBCwMuAUcD
SAGEAlkBXAHDAlwBXwHLA0kBhQMuAUcDDwETPAADdAHQA3sB7iAAA3UB1AN6AenIAAEIAQoBAAH/ASsB
UgEAAf8BQQFyAQUB/wFKAYQBBgH/AUoBhAEGAf8BSgGEAQYB/wFCAXMBBgH/ASkBUAEAAf8BEAEeAQAB
/wEfAToBAAH/AVUBjwELAf8BWgGVARAB/wFaAZwBEAH/AdYB/wGMAf8BEAEeAQAB/wEYAS4BAAH/AUIB
eAEDAf8BWgGUARgB/wFiAZsBGAH/AWMBnAEYAf8BYwGlARcB/wFjAaUBFwH/AXMBtAEpAf8BEAEeAQAB
/xgAAxIBFwM1AVYDUAGaA2IB4QH/AXgBAAL/AXgBAAH/AlEBUAGfAxIBGBQAAwYBCAMkATQDPAFmA1MB
pwNYAbgCRwFIAYMDMQFNAw8BFDwAA3QB0AN7Ae4gAAN1AdQDegHpxAABKAFNAQAB/wFOAYgBBwH/AVIB
jAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEQAf8BUgGMARAB
/wFSAYwBEAH/AVoBlAEQAf8BWgGcARAB/wFjAaUBFwH/AUIBewEAAf8BSgGEAQYB/wFSAYwBCAH/AVoB
mAEUAf8BWgGUARgB/wFjAZwBGAH/AWMBpQEXAf8BYwGlARcB/wFjAaUBFwH/AZQByQFNAf8EAAMQARUD
GgEkAyEBLwMRARYEAgQBAxIBFwM1AVYDUAGaA2IB4QH/AXgBAAL/AXgBAAH/AlEBUAGfAxIBGBQAAwMB
BAMTARkDJQE2AjsBPAFkAkEBQgFyAzMBUQMhAS8DCQEMPAADdAHQA3sB7iAAA3UB1AN6AenAAAEpAU4B
AgH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEIAf8B
UgGMARAB/wFSAYwBEAH/AVIBjAEQAf8BWgGUARAB/wFaAZUBEAH/AVoBnAEQAf8BWgGcARAB/wFaAZwB
EAH/AVoBnAEQAf8BWgGUARgB/wFaAZQBGAH/AWMBnAEYAf8BYwGcARgB/wFjAaUBFwH/AWMBpQEXAf8B
YwGlARcB/wFlAaABIQH/BAADHgErAjEBMAFMAzsBYwMiATEDBwEJAwQBBQMSARgDNgFXA1ABmgNiAeEB
/wF4AQAC/wF4AQAB/wJRAVABnwMSARgcAAMHAQkDHAEnAyMBMgIZARoBIwMPARQDBAEFPAADdAHQA3sB
7iAAA3UB1AN6Aem8AANGAYABTgGIAQcB/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIB
jAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEQAf8BUgGMARAB/wFSAYwBEAH/AVoBlAEQAf8BWgGXARAB
/wFaAZwBEAH/AVoBnAEQAf8BWgGcARAB/wFaAZwBEAH/AVoBlAEYAf8BWgGUARgB/wFjAZwBGAH/AWMB
oQEYAf8BYwGlARcB/wFjAaUBFwH/AXgBugEsAf8BCAEKAQAB/wQAAyQBNANFAXwCWgFYAbcDTgGUAz8B
bQMoATsDHwEsAzkBXgNQAZoDYgHhAf8BeAEAAv8BeAEAAf8CUQFQAZ8DEgEYHAADEgEXAzwBZgNKAYkD
QAFvAy4BRwMOARI8AAN0AdADewHuIAADdQHUA3oB6bwAATUBXgEHAf8BUgGMAQgB/wFSAYwBCAH/AVIB
jAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEQAf8BUgGMARAB
/wFaAZQBEAH/AVoBlAEQAf8BWgGbARAB/wFaAZwBEAH/AVoBnAEQAf8BWgGcARAB/wFaAZYBFgH/AVoB
lAEYAf8BXQGXARgB/wFjAZwBGAH/AWMBpQEXAf8BYwGlARcB/wFwAbMBIAH/ASsBUAEEAf8IAAMnAToC
UwFRAaIBmgFfASEB+wJlAVwB5wJcAVoBxAJDAUIBdQMxAU0CQAE/AW4CUQFQAZ8CZQFgAeMB/wF4AQAC
/wF4AQAB/wJRAVABnwMSARgcAAMZASIDTgGVAlsBXgHNAlYBVwGyA0QBegMdASkDBgEIBAIEATAAA3QB
0AN7Ae4gAAN1AdQDegHpvAABSgGEAQYB/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIB
jAEIAf8BZwGpARcB/wFSAYwBCAH/AUoBhAEGAf8BSgGEAQYB/wFSAYwBEAH/AVoBlAEQAf8BWgGXARAB
/wFaAZwBEAH/AVoBnAEQAf8BWgGcARAB/wFaAZwBEAH/AVoBlAEYAf8BWgGUARgB/wFfAZgBGAH/AWMB
ngEYAf8BYwGlARcB/wFrAakBHQH/ASkBUAEAAf8MAAMiATEDTgGYAm8BXAHzAmgBXgHwAmoBYQHmAloB
VwHCAlUBUwGqA1MBpwJZAVcBvAJsAWEB6wH/AXgBAAL/AXgBAAH/AlEBUAGfAxIBGBwAAxQBGwJDAUQB
dwJWAVkBtgJbAV4BzQJWAVgBswM/AWwDJgE4AwwBDwMCAQMIAAMCAQMDDgERAw4BEQMOAREDDgERAw4B
EQMOAREDDgERAw4BEQMOAREDdAHTA3sB7wMOAREDDgERAw4BEQMOAREDDgERAw4BEQMOAREDDgERA3QB
1wN7AeoDDgERAw4BEQMOAREDDgERAw4BEQMOAREDDgERAw4BEQMOAREDBAEFlAABSgGEAQYB/wFSAYwB
CAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBCAH/AWkBpAEeAf8BLAFQAQMB/wEIAQoBAAH/ATcBYQEHAf8B
UgGMAQ4B/wFSAYwBEAH/AX8BwQEyAf8BWwFeAVsB0wNaAb8BLwFZAQAB/wFEAXoBBQH/AVABigEIAf8B
WgGcAQ4B/wFaAZwBEAH/AVoBnAEQAf8BTwGGARAB/wFEAXQBDAH/A1oBvxAAAhoBGQEjA0QBewJdAVsB
ygJjAV8B2gJpAWAB6AJnAVkB7wJlAV0B7AJhAVwB1gJfAVsB2AFzAWgBUwH0Af8BeAEAAv8BeAEAAf8C
UQFQAZ8DEgEYHAADDwETAzQBUwNNAZICXQFhAc8CXQFhAdQCUgFUAagCQQFCAXIDJgE4AxQBGwMGAQgD
AgEDA3ABwgN/Af8DfwH/A38B/wN/Af8DfwH/A38B/wN/Af8DfwH/A38B/wN/Af8DfwH/A38B/wN/Af8D
agFeAe0CaAFTAfQBfgF3ASsB/AH/AXgBAAL/AXgBAAH/Am4BWgH1AmoBYQHmAmUBYAHjAmUBYAHjAmUB
YAHjAmUBXgHiAl4BWwHNAlUBUwGqAi4BLQFGVAADXwHQA2IB7iAAA2EB1ANjAencAAEIAQoBAAH/AT0B
cQECAf8BTAGGAQcB/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEQAf8BTAF/AQ8B
/wE7AWcBCAH/ASEBNAEIAf8BUwGOAQsB/wFaAZwBEAH/AXIBswEnAf8BUwGBARsB/xAAAysBQQM2AVkC
QAE/AW4CQAE/AW4CQAE/AW4CQAE/AW4DRAF7AlEBUAGfAl0BWwHFA2IB7gH/AXgBAAL/AXgBAAH/Al8B
XQHJA0UBfAJAAT8BbgJAAT8BbgJAAT8BbgM/AW0DOwFjAzMBUgMZASJUAANfAdADYgHuIAADYQHUA2MB
6dgAARUBKQEAAf8BSgGEAQQB/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEIAf8B
UgGMARAB/wFSAYwBEAH/AVIBjAEQAf8BWgGUARAB/wFWAZABDAH/AVoBmgEQAf8BWgGcARAB/wFyAakB
MAH/AQgBCgEAAf8BCAEKAQAB/wwAAwwBEAMRARYDFQEcAxUBHAMVARwDFQEcAyEBMAI+AT0BaQJTAVIB
pQJmAV8B5QH/AXgBAAL/AXgBAAH/AlUBUwGqAyIBMQMVARwDFQEcAxUBHAMUARsDEwEZAxABFQMHAQlU
AANfAdADYgHuIAADYQHUA2MB6dQAARYBJwECAf8BSQGAAQcB/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB
/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQsB/wFSAYwBEAH/AVIBjAEQAf8BWAGSARAB/wFaAZQBEAH/AVoB
mwEQAf8BWgGcARAB/wFcAZ4BEgH/ATsBbQEDAf8BCAEKAQAB/wEIAQoBAAH/AQgBCgEAAf8IAAMFBAYE
CAEKAwgBCgMIAQoDCAEKAxcBHwI5ATgBXQNRAZ4CZQFgAeMB/wF4AQAC/wF4AQAB/wNSAaMDGAEhAwgB
CgMIAQoDCAEKAwgBCgMHAQkDBgEHAwIBA1QAA18B0ANiAe4gAANhAdQDYwHp0AADXQHfAUcBgAEDAf8B
UgGMAQgB/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEIAf8BUgGMARAB/wFSAYwB
EAH/AVIBjAEQAf8BWgGUARAB/wFaAZQBEAH/AVoBnAEQAf8BWgGcARAB/wFaAZwBEAH/AVoBnAEQAf8B
WgGYARAB/wEWASoBAAH/AQgBCgEAAf8BCAEKAQAB/xwAAxIBFwM1AVYDUAGaA2IB4QH/AXgBAAL/AXgB
AAH/AlEBUAGfAxIBGHAAA18B0ANiAe4gAANhAdQDYwHp0AABLAFQAQAB/wFKAYwBAAH/AVIBjAEIAf8B
UgGMAQgB/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBDgH/AVUBkgEQAf8BWgGcARAB/wFaAZwB
EAH/AVoBlAEQAf8BWgGcARAB/wFaAZwBEAH/AVoBnAEQAf8BWgGcARAB/wFaAZwBEAH/AVoBlAEYAf8B
WAGSARYB/wEQAR4BAAH/AQgBCgEAAf8cAAMSARcDNQFWA1ABmgNiAeEB/wF4AQAC/wF4AQAB/wJRAVAB
nwMSARhwAANfAdADYgHuIAADYQHUA2MB6dQAA0sBjwEzAV4BAAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwB
CAH/AVIBjAEQAf8BfwHAATMB/wFfAZ4BFwH/ASABPwEAAf8BCAEKAQAB/wFSAYwBEAH/AVoBnAEQAf8B
WgGcARAB/wFaAZwBEAH/AVoBnAEQAf8BWgGVARcB/wFaAZQBGAH/AVoBlAEYAf8BWgGUARgB/wEIAQoB
AAH/AQgBCgEAAf8YAAMSARcDNQFWA1ABmgNiAeEB/wF4AQAC/wF4AQAB/wJRAVABnwMSARgUAAQBAwMB
BAMGBAcBCQMHAQkDBAEFBAIEARQAAxEBFgMhAS8DIQEvAyEBLwMhAS8DIQEvAyEBLwMhAS8DIQEvAyEB
LwNhAdkDcAHxAyEBLwMhAS8DIQEvAyEBLwMhAS8DIQEvAyEBLwMhAS8DYgHcA2oB7QMhAS8DIQEvAyEB
LwMhAS8DIQEvAyEBLwMhAS8DIQEvAyEBLwMNARG4AAFCAXsBAAH/AVIBjAEIAf8BewHGASkB/wMzAVAE
AAMMARABOQFrAQAB/wFaAZwBEAH/AVoBnAEQAf8BXQGXARMB/wFWAZQBDAH/AVoBnAEQAf8BWgGUARgB
/wFaAZQBGAH/AWMBnAEYAf8BYwGlARcB/wEpAVABAAH/AQgBCgEAAf8YAAMSARcDNQFWA1ABmgNiAeEB
/wF4AQAC/wF4AQAB/wJRAVABnwMSARgUAAQCAwkBDAMPARQDEwEaAxIBGAMKAQ0DBAEFBAEUAANpAegD
fwH/A38B/wN/Af8DfwH/A38B/wN/Af8DfwH/A38B/wN/Af8DfwH/A38B/wN/Af8DfwH/A38B/wN/Af8D
fwH/A38B/wN4AdyUAAFKAYwBAAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEIAf8BZwGdAScB
/wEIAQoBAAH/AQoBDwEAAf8BQgF3AQMB/wFSAYwBEAH/AVoBnAEQAf8BJQFBAQUB/wQAAzoBYAEIAQoB
AAH/AQgBCgEAAf8EAANGAYADRgGAA0YBgBwAAwoBDQMiATEDNAFUA0QBeAJRAVABnwJhAV0BzwJqAV4B
7QJlAV0B7AJoAV4B8AGaAV8BIQH7Af8BeAEAAv8BeAEAAf8CUQFQAZ8DEgEYHAADBgEIAxgBIQMxAU0C
TAFNAZECWAFaAb0CXQFhAdECWQFcAcMCUQFTAaIDPQFnAhkBGgEjAwwBDwNMAW8DaAGtA2gBrQNoAa0D
aAGtA2gBrQNoAa0DaAGtA2gBrQNoAa0DewHwA30B+gNoAa0DaAGtA2gBrQNoAa0DaAGtA2gBrQNoAa0D
aAGtA3wB8QN9AfgDaAGtA2gBrQNoAa0DaAGtA2gBrQNoAa0DaAGtA2gBrQNoAa0DVQGBlAABOwFtAQIB
/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBCAH/AVMBjgEQAf8BDwEbAQAB/wEjAUMBAAH/AVIB
jAEQAf8BUgGMARAB/wF7Ab0BMAH/AQgBCgEAAf8BCAEKAQAB/wEIAQoBAAH/AQgBCgEAAf8BCAEKAQAB
/wEIAQoBAAH/AQgBCgEAAf8oAAMDAQQDCQELAyIBMQM4AVsCTwFOAZcCXwFcAcgCZgFfAeUBhQFvAVEB
9wG+AVwBOAH9Af8BeAEAAv8BeAEAAf8CUQFQAZ8DEgEYHAAEAQMCAQMDFQEcAzUBVgJLAUwBjwJcAV8B
yAJdAWMB3wJgAWUB4wJSAVMBpQM0AVMDHAEnKAADdAHQA3sB7iAAA3UB1AN6Aem8AAEIAQoBAAH/AVIB
jAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFOAYIBDwH/AUoBhAEGAf8BUgGMARAB
/wFjAaUBFwH/AQgBCgEAAf8BCAEKAQAB/wETASMBAAH/AUIBcwEGAf8BIAE/AQAB/wEIAQoBAAH/AQgB
CgEAAf8BCAEKAQAB/wEIAQoBAAH/AQgBCgEAAf8gAAQBAwMBBAMMARADGAEgAykBPQM+AWoCVwFWAbUC
ZgFfAeUBhQFvAVEB9wH/AXgBAAL/AXgBAAH/AlEBUAGfAxIBGCAABAEDBwEJAxQBGwMqAUADQwF2A1UB
rwJaAWMB6QJbAV8B0AJQAVEBnwMzAVEoAAN0AdADewHuIAADdQHUA3oB6cAAAUIBcwEGAf8BUgGMAQgB
/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEQAf8BUgGMARAB/wFKAYQBBgH/ARAB
HgEAAf8BMQFaAQAB/wFNAYQBDQH/AVoBnAEQAf8BWgGcARAB/wFSAYwBCAH/ARABHgEAAf8BCAEKAQAB
/wEIAQoBAAH/A0YBgCwAAwIBAwMLAQ4DIAEuA0MBdgJXAVUBsQJmAWAB4AF2AWMBSAH2AY0BagFBAfkC
UQFQAZwDEgEXDAAEAQMMARADFwEfAx0BKAMdASgDDAEPCAADDwEUAiYBJwE5AkcBSAGDA2IB4QJcAWUB
5wNeAdICPwFAAW4oAAN0AdADewHuIAADdQHUA3oB6cQAAUkBiQEAAf8BUgGMAQgB/wFSAYwBCAH/AVIB
jAEIAf8BUgGMAQgB/wFSAYwBEAH/AVIBjAEQAf8BUgGMARAB/wFSAYwBEAH/AVoBlAEQAf8BWgGcARAB
/wFaAZwBEAH/AVoBnAEQAf8BWgGcARAB/wFaAZQBGAH/AUIBcwEGAf8BCgEOAQAB/zAABAEDAwEEAwwB
DwMeASoDOgFhAlkBVwG5AmMBWwHkAWUCYgHuAk8BTgGXAxIBFwwAAwIBAwMhAS8DNgFZAkIBQwF1AkEB
QgFzAx4BKwgAAwQBBQMMAQ8DOAFcAl0BYAHOAl0BZQHsAlkBZwHvA0YBfygAA3QB0AN7Ae4gAAN1AdQD
egHpyAABOgFrAQAB/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBEAH/AVIBjAEQAf8BWgGUARAB
/wFaAZQBEAH/AVoBnAEQAf8BWgGcARAB/wFaAZwBEAH/AVoBnAEQAf8BWgGUARgB/wFaAZQBGAH/AWMB
pQEXAf8BGQEsAQMB/zgAAwIBAwMIAQoDIAEtAkEBQAFxAk0BTAGRA1ABmgI7AToBYgMMAQ8MAAMDAQQD
KwFBA0QBegNTAacDVQGtAzcBWgMYASADCQELAwYBBwMNAREDOQFfAl0BYQHRAlkBZwHvAlwBbwHzAkYB
RwGBKAADdAHQA3sB7iAAA3UB1AN6AenMAAEsAVUBAAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBEAH/AVIB
jAEQAf8BWgGUARAB/wFaAZQBEAH/AVoBnAEQAf8BWgGcARAB/wFaAZwBEAH/AVoBnAEQAf8BWgGUARgB
/wFaAZQBGAH/ASEBQQEAAf9AAAQCAwcBCQMQBBUBHAIWARUBHQMPARMDAgEDDAADAwEEAzADSwFMAY8C
WwFdAcoCWwFhAd4DTgGUAzQBUwMVARwDDQERAx4BKwJDAUQBdwJdAWMB3wJiAXAB8QJaAWMB6QNEAXso
AAN0AdADewHuIAADdQHUA3oB6cgAARIBIgEAAf8BTAGGAQYB/wFSAYwBCAH/AVoBnAEQAf8BUAGKAQgB
/wFSAYwBEAH/AVoBlAEQAf8BWgGcARAB/wFaAZwBEAH/AVoBnAEQAf8BWgGcARAB/wFaAZwBEQH/AWkB
qwEfAf8BCAEKAQAB/2wAAwIBAwMhAS8DOgFhA1UBrQJfAWMB2gNaAb8CUAFRAZ8DRAF6AkEBQgFyA0kB
hQJWAVcBsgJeAWoB7QJgAWQB2wJVAVcBsQM4AVsoAAN0AdADewHuIAADdQHUA3oB6cgAARABHgEAAf8B
SAF9AQcB/wFZAZMBEwH/AVUBiAEYAf8IAAEhAUEBAAH/AUIBewEAAf8BUgGMAQgB/wFVAZEBCAH/ATkB
ZgEEAf8DWQHveAADBgEIAxcBHwNEAXsCWQFcAcYCWwFjAeQCUwFzAfQCYgFlAe4CYgFlAe4CYgFwAfEC
SAF2AfYCKwGsAfwCWQFcAb4DPgFrAyQBNCgAA3QB0AN7Ae4gAAN1AdQDegHp0AABHgE6AQAB/5wABAID
CQEMAyoDPwFAAW4DTgGWAlYBWAGzAlkBXAHBAloBXQHHAloBXQHHAlcBWQG5AlEBUgGkAkABQQFxAyUB
NgMTARkoAAN0AdADewHuIAADdQHUA3oB6f8AeQAEAQMLAQ4DJgE4AzkBXgNGAX0CSgFLAYsDSgGJAz4B
awMqAT8DFwEfAwIBAywAA2QBogNuAb8gAANmAaYDbQG7qAABQgFNAT4HAAE+AwABKAMAAYADAAEgAwAB
AQEAAQEGAAECFgAD/wEAA/8BxwIAAQcC/wHPAfMB/wQAAv8BzwGHAgABBwL/Ac8B8wH/BAAB/wH+AQAB
BwIAAQcC/wHPAfMB/wQAAf8B/AEAAQcCAAEHAv8BzwHzAf8EAAH/AfgBAAEPAgABBwL/Ac8B8wH/BAAB
/wHwAQABBwIAAQcC/wHPAfMB/wQAAf8B4AEAAQMCAAEHAv8BzwHzAf8EAAH/AcABAAEBAfwBAwP/Ac8B
8wH/BAAB/wHAAQABAQH8AQMD/wHPAfMB/wQAAf8B4AIAAfwBAwHgAR8IAAH/AfwBIAEAAfwBAwHgAR8I
AAH/Af4B4AEAAfwBAwHgAR8IAAH/AcACAAH8AQMB4AEfAf8BzwHzAf8EAAH/AYACAAH8AQMB4AEfAf8B
zwHzAf8EAAH/AwAB/AEDAeABHwH/Ac8B8wH/BAAB/gIAAQEBAAEDAeABHwH/Ac8B8wH/BAAB/AIAAQEB
AAEDAfgBHwH/Ac8B8wH/BAAB+AIAAQEBAAEDAfgBHwH/Ac8B8wH/BAAB+AIAAQMBAAEDAfgBAwH/Ac8B
8wH/BAAB+AIAAQcBAAEDAfgBAwgAAfgCAAEPAQABAwH4CQAB+AEAAUQBfwEAAQMB+AkAAfgBAAEBAf8B
gAEDAfgBAAH/Ac8B8wH/BAAB+AIAAX8BgAEDAfwBAAH/Ac8B8wH/BAAB/AIAAX8B8AEDAYEBgAH/Ac8B
8wH/BAAB/gIAAf8B8AEDAYEBgAH/Ac8B8wH/BAAB/wIAAf8B/AEDAYABAAH/Ac8B8wH/BAAB/wGAAQEB
/wH+AQMBgAEAAf8BzwHzAf8EAAH/AQABAwP/AYABAAH/Ac8B8wH/BAAB/wEMAQ8D/wHAAQAB/wHPAfMB
/wQAAf8B3wT/AcABAAH/Ac8B8wH/BAAG/wHwAQEB/wHPAfMB/wQACw==
fwH/A38B/wN/Af8DfwH/A38B/wN/Af8DfwH/A38B/wN/Af8DfwH/A38B/wN/Af8DfwH/A38B/wN/Af8D
YQHPvAABCAEKAQAB/wwAARMBIwEAAf8BWgGUARAB/wFaAZwBEAH/AZQB1gFKAf8BDQERAQIB/wExAVoB
AAH/AVoBlAESAf8BWgGUARgB/wFaAZQBGAH/AWMBnAEYAf8BYwGlARcB/wFrAaUBIQH/ARABHgEAAf8Y
AAMSARcDNQFWA1ABmgNiAeEB/wF4AQAC/wF4AQAB/wJRAVABnwMSARgUAAMFAQYDHQEpAzABSgI7ATwB
ZQI7ATwBZAMoATwCFQEWAR0DBgEIFAADPAFkA0wBjwNMAY8DTAGPA0wBjwNMAY8DTAGPA0wBjwNMAY8D
TAGPA2cB6gN8AfgDTAGPA0wBjwNMAY8DTAGPA0wBjwNMAY8DTAGPA0wBjwNlAewDbgH1A0wBjwNMAY8D
TAGPA0wBjwNMAY8DTAGPA0wBjwNMAY8DTAGPAzUBVqgAA10B3wEIAQoBAAH/AQgBCgEAAf8BCAEKAQAB
/wEIAQoBAAH/AQgBCgEAAf8BCAEKAQAB/wMzAVADOgFgAUsBhQEHAf8BWgGUARAB/wFaAZwBEAH/ATkB
awEAAf8DCQEMARABHgEAAf8BVAGOARIB/wFaAZQBGAH/AWMBnAEYAf8BYwGcARgB/wFjAaUBFwH/AXMB
tAEpAf8BEAEeAQAB/xgAAxIBFwM1AVYDUAGaA2IB4QH/AXgBAAL/AXgBAAH/AlEBUAGfAxIBGBQAAwkB
DAMzAVACTAFNAZECXQFfAckCWwFdAcoDRAF6AykBPQMMARA8AANfAdADYgHuIAADYQHUA2MB6cwAAQgB
CgEAAf8BCAEKAQAB/wEIAQoBAAH/AQgBCgEAAf8BCAEKAQAB/wEIAQoBAAH/AQgBCgEAAf8BCAEKAQAB
/wEIAQoBAAH/ATMBXwECAf8BWgGUARAB/wFaAZwBEAH/Aa0B5wFjAf8BEAEeAQAB/wEIAQoBAAH/ARQB
JgEAAf8BVAGOARIB/wFiAZsBGAH/AWMBnAEYAf8BYwGlARcB/wFjAaUBFwH/AWMBnAEhAf8BGAEuAQAB
/xgAAxIBFwM1AVYDUAGaA2IB4QH/AXgBAAL/AXgBAAH/AlEBUAGfAxIBGBQAAwkBCwMuAUcDSAGEAlkB
XAHDAlwBXwHLA0kBhQMuAUcDDwETPAADXwHQA2IB7iAAA2EB1ANjAenIAAEIAQoBAAH/ASsBUgEAAf8B
QQFyAQUB/wFKAYQBBgH/AUoBhAEGAf8BSgGEAQYB/wFCAXMBBgH/ASkBUAEAAf8BEAEeAQAB/wEfAToB
AAH/AVUBjwELAf8BWgGVARAB/wFaAZwBEAH/AdYB/wGMAf8BEAEeAQAB/wEYAS4BAAH/AUIBeAEDAf8B
WgGUARgB/wFiAZsBGAH/AWMBnAEYAf8BYwGlARcB/wFjAaUBFwH/AXMBtAEpAf8BEAEeAQAB/xgAAxIB
FwM1AVYDUAGaA2IB4QH/AXgBAAL/AXgBAAH/AlEBUAGfAxIBGBQAAwYBCAMkATQDPAFmA1MBpwNYAbgC
RwFIAYMDMQFNAw8BFDwAA18B0ANiAe4gAANhAdQDYwHpxAABKAFNAQAB/wFOAYgBBwH/AVIBjAEIAf8B
UgGMAQgB/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEQAf8BUgGMARAB/wFSAYwB
EAH/AVoBlAEQAf8BWgGcARAB/wFjAaUBFwH/AUIBewEAAf8BSgGEAQYB/wFSAYwBCAH/AVoBmAEUAf8B
WgGUARgB/wFjAZwBGAH/AWMBpQEXAf8BYwGlARcB/wFjAaUBFwH/AZQByQFNAf8EAAMQARUDGgEkAyEB
LwMRARYEAgQBAxIBFwM1AVYDUAGaA2IB4QH/AXgBAAL/AXgBAAH/AlEBUAGfAxIBGBQAAwMBBAMTARkD
JQE2AjsBPAFkAkEBQgFyAzMBUQMhAS8DCQEMPAADXwHQA2IB7iAAA2EB1ANjAenAAAEpAU4BAgH/AVIB
jAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEIAf8BUgGMARAB
/wFSAYwBEAH/AVIBjAEQAf8BWgGUARAB/wFaAZUBEAH/AVoBnAEQAf8BWgGcARAB/wFaAZwBEAH/AVoB
nAEQAf8BWgGUARgB/wFaAZQBGAH/AWMBnAEYAf8BYwGcARgB/wFjAaUBFwH/AWMBpQEXAf8BYwGlARcB
/wFlAaABIQH/BAADHgErAjEBMAFMAzsBYwMiATEDBwEJAwQBBQMSARgDNgFXA1ABmgNiAeEB/wF4AQAC
/wF4AQAB/wJRAVABnwMSARgcAAMHAQkDHAEnAyMBMgIZARoBIwMPARQDBAEFPAADXwHQA2IB7iAAA2EB
1ANjAem8AANGAYABTgGIAQcB/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEIAf8B
UgGMAQgB/wFSAYwBCAH/AVIBjAEQAf8BUgGMARAB/wFSAYwBEAH/AVoBlAEQAf8BWgGXARAB/wFaAZwB
EAH/AVoBnAEQAf8BWgGcARAB/wFaAZwBEAH/AVoBlAEYAf8BWgGUARgB/wFjAZwBGAH/AWMBoQEYAf8B
YwGlARcB/wFjAaUBFwH/AXgBugEsAf8BCAEKAQAB/wQAAyQBNANFAXwCWgFYAbcDTgGUAz8BbQMoATsD
HwEsAzkBXgNQAZoDYgHhAf8BeAEAAv8BeAEAAf8CUQFQAZ8DEgEYHAADEgEXAzwBZgNKAYkDQAFvAy4B
RwMOARI8AANfAdADYgHuIAADYQHUA2MB6bwAATUBXgEHAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEIAf8B
UgGMAQgB/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEQAf8BUgGMARAB/wFaAZQB
EAH/AVoBlAEQAf8BWgGbARAB/wFaAZwBEAH/AVoBnAEQAf8BWgGcARAB/wFaAZYBFgH/AVoBlAEYAf8B
XQGXARgB/wFjAZwBGAH/AWMBpQEXAf8BYwGlARcB/wFwAbMBIAH/ASsBUAEEAf8IAAMnAToCUwFRAaIB
gwFfASEB+wJlAVwB5wJcAVoBxAJDAUIBdQMxAU0CQAE/AW4CUQFQAZ8CZQFgAeMB/wF4AQAC/wF4AQAB
/wJRAVABnwMSARgcAAMZASIDTgGVAlsBXgHNAlYBVwGyA0QBegMdASkDBgEIBAIEATAAA18B0ANiAe4g
AANhAdQDYwHpvAABSgGEAQYB/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEIAf8B
ZwGpARcB/wFSAYwBCAH/AUoBhAEGAf8BSgGEAQYB/wFSAYwBEAH/AVoBlAEQAf8BWgGXARAB/wFaAZwB
EAH/AVoBnAEQAf8BWgGcARAB/wFaAZwBEAH/AVoBlAEYAf8BWgGUARgB/wFfAZgBGAH/AWMBngEYAf8B
YwGlARcB/wFrAakBHQH/ASkBUAEAAf8MAAMiATEDTgGYAm8BYAHzAmgBXgHwAmoBYQHmAloBVwHCAlUB
UwGqA1MBpwJZAVcBvAJsAWEB6wH/AXgBAAL/AXgBAAH/AlEBUAGfAxIBGBwAAxQBGwJDAUQBdwJWAVkB
tgJbAV4BzQJWAVgBswM/AWwDJgE4AwwBDwMCAQMIAAMCAQMDDQERAw0BEQMNAREDDQERAw0BEQMNARED
DQERAw0BEQMNAREDXwHTA2cB7wMNAREDDQERAw0BEQMNAREDDQERAw0BEQMNAREDDQERA2IB1wNnAeoD
DQERAw0BEQMNAREDDQERAw0BEQMNAREDDQERAw0BEQMNAREDBAEFlAABSgGEAQYB/wFSAYwBCAH/AVIB
jAEIAf8BUgGMAQgB/wFSAYwBCAH/AWkBpAEeAf8BLAFQAQMB/wEIAQoBAAH/ATcBYQEHAf8BUgGMAQ4B
/wFSAYwBEAH/AX8BwQEyAf8BWwFeAVsB0wNaAb8BLwFZAQAB/wFEAXoBBQH/AVABigEIAf8BWgGcAQ4B
/wFaAZwBEAH/AVoBnAEQAf8BTwGGARAB/wFEAXQBDAH/A1oBvxAAAhoBGQEjA0QBewJdAVsBygJjAV8B
2gJpAWAB6AJnAVkB7wJlAV0B7AJhAVwB1gJfAVsB2AJoAVMB9AH/AXgBAAL/AXgBAAH/AlEBUAGfAxIB
GBwAAw8BEwM0AVMDTQGSAl0BYQHPAl0BYQHUAlIBVAGoAkEBQgFyAyYBOAMUARsDBgEIAwIBAwNaAcID
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/A38B/wN/Af8DfwH/A38B/wN/Af8D
YgHclAABSgGMAQAB/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBCAH/AWcBnQEnAf8BCAEKAQAB
/wEKAQ8BAAH/AUIBdwEDAf8BUgGMARAB/wFaAZwBEAH/ASUBQQEFAf8EAAM6AWABCAEKAQAB/wEIAQoB
AAH/BAADRgGAA0YBgANGAYAcAAMKAQ0DIgExAzQBVANEAXgCUQFQAZ8CYQFdAc8CagFeAe0CZQFdAewC
aAFeAfABgwFfASEB+wH/AXgBAAL/AXgBAAH/AlEBUAGfAxIBGBwAAwYBCAMYASEDMQFNAkwBTQGRAlgB
WgG9Al0BYQHRAlkBXAHDAlEBUwGiAz0BZwIZARoBIwMMAQ8DQAFvA1UBrQNVAa0DVQGtA1UBrQNVAa0D
VQGtA1UBrQNVAa0DVQGtA2gB8AN9AfoDVQGtA1UBrQNVAa0DVQGtA1UBrQNVAa0DVQGtA1UBrQNwAfED
fAH4A1UBrQNVAa0DVQGtA1UBrQNVAa0DVQGtA1UBrQNVAa0DVQGtA0cBgZQAATsBbQECAf8BUgGMAQgB
/wFSAYwBCAH/AVIBjAEIAf8BUgGMAQgB/wFTAY4BEAH/AQ8BGwEAAf8BIwFDAQAB/wFSAYwBEAH/AVIB
jAEQAf8BewG9ATAB/wEIAQoBAAH/AQgBCgEAAf8BCAEKAQAB/wEIAQoBAAH/AQgBCgEAAf8BCAEKAQAB
/wEIAQoBAAH/KAADAwEEAwkBCwMiATEDOAFbAk8BTgGXAl8BXAHIAmYBXwHlAm8BUQH3Ab4BRQFAAf0B
/wF4AQAC/wF4AQAB/wJRAVABnwMSARgcAAQBAwIBAwMVARwDNQFWAksBTAGPAlwBXwHIAl0BYwHfAmAB
ZQHjAlIBUwGlAzQBUwMcAScoAANfAdADYgHuIAADYQHUA2MB6bwAAQgBCgEAAf8BUgGMAQgB/wFSAYwB
CAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBCAH/AU4BggEPAf8BSgGEAQYB/wFSAYwBEAH/AWMBpQEXAf8B
CAEKAQAB/wEIAQoBAAH/ARMBIwEAAf8BQgFzAQYB/wEgAT8BAAH/AQgBCgEAAf8BCAEKAQAB/wEIAQoB
AAH/AQgBCgEAAf8BCAEKAQAB/yAABAEDAwEEAwwBEAMYASADKQE9Az4BagJXAVYBtQJmAV8B5QJvAVEB
9wH/AXgBAAL/AXgBAAH/AlEBUAGfAxIBGCAABAEDBwEJAxQBGwMqAUADQwF2A1UBrwJaAWMB6QJbAV8B
0AJQAVEBnwMzAVEoAANfAdADYgHuIAADYQHUA2MB6cAAAUIBcwEGAf8BUgGMAQgB/wFSAYwBCAH/AVIB
jAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEQAf8BUgGMARAB/wFKAYQBBgH/ARABHgEAAf8BMQFaAQAB
/wFNAYQBDQH/AVoBnAEQAf8BWgGcARAB/wFSAYwBCAH/ARABHgEAAf8BCAEKAQAB/wEIAQoBAAH/A0YB
gCwAAwIBAwMLAQ4DIAEuA0MBdgJXAVUBsQJmAWAB4AJjAUgB9gFtAWoBQQH5AlEBUAGcAxIBFwwABAED
DAEQAxcBHwMdASgDHQEoAwwBDwgAAw8BFAImAScBOQJHAUgBgwNiAeECXAFlAecDXgHSAj8BQAFuKAAD
XwHQA2IB7iAAA2EB1ANjAenEAAFJAYkBAAH/AVIBjAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEIAf8B
UgGMARAB/wFSAYwBEAH/AVIBjAEQAf8BUgGMARAB/wFaAZQBEAH/AVoBnAEQAf8BWgGcARAB/wFaAZwB
EAH/AVoBnAEQAf8BWgGUARgB/wFCAXMBBgH/AQoBDgEAAf8wAAQBAwMBBAMMAQ8DHgEqAzoBYQJZAVcB
uQJjAVsB5ANiAe4CTwFOAZcDEgEXDAADAgEDAyEBLwM2AVkCQgFDAXUCQQFCAXMDHgErCAADBAEFAwwB
DwM4AVwCXQFgAc4CXQFlAewCWQFnAe8DRgF/KAADXwHQA2IB7iAAA2EB1ANjAenIAAE6AWsBAAH/AVIB
jAEIAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEQAf8BUgGMARAB/wFaAZQBEAH/AVoBlAEQAf8BWgGcARAB
/wFaAZwBEAH/AVoBnAEQAf8BWgGcARAB/wFaAZQBGAH/AVoBlAEYAf8BYwGlARcB/wEZASwBAwH/OAAD
AgEDAwgBCgMgAS0CQQFAAXECTQFMAZEDUAGaAjsBOgFiAwwBDwwAAwMBBAMrAUEDRAF6A1MBpwNVAa0D
NwFaAxgBIAMJAQsDBgEHAw0BEQM5AV8CXQFhAdECWQFnAe8CYAFvAfMCRgFHAYEoAANfAdADYgHuIAAD
YQHUA2MB6cwAASwBVQEAAf8BUgGMAQgB/wFSAYwBCAH/AVIBjAEQAf8BUgGMARAB/wFaAZQBEAH/AVoB
lAEQAf8BWgGcARAB/wFaAZwBEAH/AVoBnAEQAf8BWgGcARAB/wFaAZQBGAH/AVoBlAEYAf8BIQFBAQAB
/0AABAIDBwEJAxAEFQEcAhYBFQEdAw8BEwMCAQMMAAMDAQQDMANLAUwBjwJbAV0BygJbAWEB3gNOAZQD
NAFTAxUBHAMNAREDHgErAkMBRAF3Al0BYwHfAmUBcAHxAloBYwHpA0QBeygAA18B0ANiAe4gAANhAdQD
YwHpyAABEgEiAQAB/wFMAYYBBgH/AVIBjAEIAf8BWgGcARAB/wFQAYoBCAH/AVIBjAEQAf8BWgGUARAB
/wFaAZwBEAH/AVoBnAEQAf8BWgGcARAB/wFaAZwBEAH/AVoBnAERAf8BaQGrAR8B/wEIAQoBAAH/bAAD
AgEDAyEBLwM6AWEDVQGtAl8BYwHaA1oBvwJQAVEBnwNEAXoCQQFCAXIDSQGFAlYBVwGyAl4BagHtAmAB
ZAHbAlUBVwGxAzgBWygAA18B0ANiAe4gAANhAdQDYwHpyAABEAEeAQAB/wFIAX0BBwH/AVkBkwETAf8B
VQGIARgB/wgAASEBQQEAAf8BQgF7AQAB/wFSAYwBCAH/AVUBkQEIAf8BOQFmAQQB/wNZAe94AAMGAQgD
FwEfA0QBewJZAVwBxgJbAWMB5AJTAWgB9ANiAe4DYgHuAmUBcAHxAkgBYwH2AisBfgH8AlkBXAG+Az4B
awMkATQoAANfAdADYgHuIAADYQHUA2MB6dAAAR4BOgEAAf+cAAQCAwkBDAMqAz8BQAFuA04BlgJWAVgB
swJZAVwBwQJaAV0BxwJaAV0BxwJXAVkBuQJRAVIBpAJAAUEBcQMlATYDEwEZKAADXwHQA2IB7iAAA2EB
1ANjAen/AHkABAEDCwEOAyYBOAM5AV4DRgF9AkoBSwGLA0oBiQM+AWsDKgE/AxcBHwMCAQMsAANTAaID
WgG/IAADVAGmA1kBu6gAAUIBTQE+BwABPgMAASgDAAGAAwABIAMAAQEBAAEBBgABAhYAA/8BAAP/AccC
AAEHAv8BzwHzAf8EAAL/Ac8BhwIAAQcC/wHPAfMB/wQAAf8B/gEAAQcCAAEHAv8BzwHzAf8EAAH/AfwB
AAEHAgABBwL/Ac8B8wH/BAAB/wH4AQABDwIAAQcC/wHPAfMB/wQAAf8B8AEAAQcCAAEHAv8BzwHzAf8E
AAH/AeABAAEDAgABBwL/Ac8B8wH/BAAB/wHAAQABAQH8AQMD/wHPAfMB/wQAAf8BwAEAAQEB/AEDA/8B
zwHzAf8EAAH/AeACAAH8AQMB4AEfCAAB/wH8ASABAAH8AQMB4AEfCAAB/wH+AeABAAH8AQMB4AEfCAAB
/wHAAgAB/AEDAeABHwH/Ac8B8wH/BAAB/wGAAgAB/AEDAeABHwH/Ac8B8wH/BAAB/wMAAfwBAwHgAR8B
/wHPAfMB/wQAAf4CAAEBAQABAwHgAR8B/wHPAfMB/wQAAfwCAAEBAQABAwH4AR8B/wHPAfMB/wQAAfgC
AAEBAQABAwH4AR8B/wHPAfMB/wQAAfgCAAEDAQABAwH4AQMB/wHPAfMB/wQAAfgCAAEHAQABAwH4AQMI
AAH4AgABDwEAAQMB+AkAAfgBAAFEAX8BAAEDAfgJAAH4AQABAQH/AYABAwH4AQAB/wHPAfMB/wQAAfgC
AAF/AYABAwH8AQAB/wHPAfMB/wQAAfwCAAF/AfABAwGBAYAB/wHPAfMB/wQAAf4CAAH/AfABAwGBAYAB
/wHPAfMB/wQAAf8CAAH/AfwBAwGAAQAB/wHPAfMB/wQAAf8BgAEBAf8B/gEDAYABAAH/Ac8B8wH/BAAB
/wEAAQMD/wGAAQAB/wHPAfMB/wQAAf8BDAEPA/8BwAEAAf8BzwHzAf8EAAH/Ad8E/wHAAQAB/wHPAfMB
/wQABv8B8AEBAf8BzwHzAf8EAAs=
</value>
</data>
<metadata name="ilStoreThumbnails.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>12, 128</value>
</metadata>
<metadata name="ilTabIcons.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>20, 44</value>
</metadata>
@ -461,8 +462,8 @@
<value>
AAEAAAD/////AQAAAAAAAAAMAgAAAEZTeXN0ZW0uV2luZG93cy5Gb3JtcywgQ3VsdHVyZT1uZXV0cmFs
LCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5BQEAAAAmU3lzdGVtLldpbmRvd3MuRm9ybXMu
SW1hZ2VMaXN0U3RyZWFtZXIBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAAuhIAAAJNU0Z0AUkBTAIBAQQB
AAFYAQIBWAECARABAAEQAQAE/wEhAQAI/wFCAU0BNgcAATYDAAEoAwABQAMAASADAAEBAQABIAYAASD/
SW1hZ2VMaXN0U3RyZWFtZXIBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAAshIAAAJNU0Z0AUkBTAIBAQQB
AAEQAQMBEAEDARABAAEQAQAE/wEhAQAI/wFCAU0BNgcAATYDAAEoAwABQAMAASADAAEBAQABIAYAASD/
AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/ACIAA2cB7wJnAVkB7wFnAV0BWQHvAWcBWwFZAe8B
ZwFbAVkB7wFnAlkB7wFnAWQBWQHvA2cB7wNnAe8DZwHvA2cB7wNnAe8DZwHvA2cB7wNnAe8DZwHvAwcB
CQMqAT8DRQF8A1kBuwNjAd8DaAH0A4AB/gOBAf8DgQH/A4EB/wOAAf4DaAH0A2MB3wNaAboDRAF6AycB
@ -488,7 +489,7 @@
YAH/AYMBggF/Af8BhQGCAX0B/wGzAZMBRAH/Ac8BlAEAAf8B3gHMAZ8B/wOwAf8DsAH/A7AB/wOwAf8D
sAH/A7AB/wOwAf8DsAH/A6gB/wMCAQMDGgEjAzgBXANUAagDYgHXA3AB8QOAAf4DgQH/A4EB/wOBAf8D
gQH9A2gB8ANhAdQDUwGlAzYBWQMYASAIAAMaASQDUgGgAmMBSAH2AaIBcwEAAf8BrgF8AQAB/wGwAX0B
AAH/AagBeAEAAf8BlQFqAQAB/wKAAUsB/gFcAlkBxgNXAbUDFgEeCAAB/wGyAW8B/wH9AYABEQH/AfwB
AAH/AagBeAEAAf8BlQFqAQAB/wKAAWIB/gFcAlkBxgNXAbUDFgEeCAAB/wGyAW8B/wH9AYABEQH/AfwB
dwEBAf8B/QF3AQAC/wF4AQAC/wF/AQ8C/wGSATsC/wGzAYMC/wHqAeAC/wGQAT0C/wF7AQoB/wH8AXcB
AAH/AeUBbAEAAf8BsQFUAQAB/wGEAUsBGgH/A2MB3wT/AdkBqgE3Af8BvgGYATgB/wHAAZgBNgH/AdwB
ogEUAf8BzwGUAQAB/wHeAcwBnwH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DkwH/RAAD
@ -524,24 +525,24 @@
AwH/AeIBhAEyAf8DYwHfBP8BpwGUAWcB/wGDAYIBfwH/AYUBgwF+Af8BsAGUAU4B/wHTAZcBAgH/AeAB
zgGfAf8D5wH/A+cB/wPnAf8D5wH/A+cB/wPnAf8D5wH/A+cB/wPAAf8DZAHbA2gB9AOBAf8DgQH/A4EB
/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DVQGvAwcBCQNiAe4B8QG8ATsB
/wH6AeoBwgH/A9wB/wN3Af8DaAH/A2gB/wNoAf8DMgn/AfIB3QGpAf8B6gGpAQgB/wM+AWoEAANeAdIB
fQFvAWoB+QH/AbsBfQL/AaMBUgL/AZABLAL/AYIBEwL/AX0BCAL/AXoBBAL/AXkBAgL/AXoBBAL/AX0B
CQL/AYABDwL/AX8BDQH/AfgBhgEgAf8DaAH0A1gBuAT/AdcBsQFSAf8BmgGPAXQB/wGgAZIBbQH/AeAB
rwE3Af8B5wGpARAB/wHrAdUBoAH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DkwH/AzwB
ZANfAckDfQH6A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DfQH6A1UB
qgQAAzMBUQNoAfAB8wHGAVgB/wH6AecBuBb/Af4B+wH/AfkB4gGqAf8B7wG4AS0B/wNOAZYEAgQAAzwB
ZgNjAdUDfAH4Af8BvgGFAv8BqgFfAv8BmQE+Av8BjQEnAv8BhgEaAv8BgwEVAv8BhQEZAv8BigEiAv8B
jQEoAv8BkwEzAf8CbgFoAfUDXQHFAzYBWQT/AfQBzQFsAf8B9AHLAWYB/wH0AcsBZQH/AfQBywFlAf8B
8QHBAUkB/wH5AeMBrAH/A4kB/wOJAf8DiQH/A4kB/wOJAf8DiQH/A4kB/wOJAf8DmgH/AwYBCAMxAUwD
UAGbA2UB7AN9AfoDgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A30B+gNqAe0DUAGbAy8BSQgAAyIB
MQNfAckCfQFnAfoB8wHKAWUB/wH5AeEBpgH/AfsB7QHMAf8B+wHsAcgB/wH4Ad0BmwH/A4AB/gJlAV4B
4gM9AWgEAQwAA0IBcgNjAdUBfQFzAWoB+QH/AckBmQL/AbwBfwL/Aa4BZgL/AaQBUwL/AZ8BSgL/AZ8B
SwL/AaMBUQL/AacBWAH/A3wB+ANdAccDOwFiBABA/wgAAwUBBgMSARcDOgFgA1EBnwNfAdMDZwHvA2MB
9gNiAe4DXgHSA1EBngM5AV8DEQEWAwUBBhQAAw8BEwNHAYIDZAHbAn4BbwH8A2cB6gNUAagDKAE7HAAD
PAFlA14B0gH/Ae8B4AL/AdwBvAL/Ac0BnwL/AcEBigL/AbsBfwL/Ab8BhgL/Ac0BoQL/AekB1gH/A1wB
xAM1AVUIAAFCAU0BPgcAAT4DAAEoAwABQAMAASADAAEBAQABAQYAAQEWAAP/hQAB/wH8AcABAwQAAf8B
/AGAAQEEAAH/AfkGAAH/AfkGAAHwARMGAAHAAQMEAAL/AYABAQQAAcABAQGAAQEEAAGAAgABAQcAAQEH
AAEBBwABAQcAAQEGAAGAAQEGAAHAAQMBgAEBAgABwAEBAfABHwHAAQML
/wH6AeoBwgH/A9wB/wN3Af8DaAH/A2gB/wNoAf8DMgn/AfIB3QGpAf8B6gGpAQgB/wM+AWoEAANeAdID
agH5Af8BuwF9Av8BowFSAv8BkAEsAv8BggETAv8BfQEIAv8BegEEAv8BeQECAv8BegEEAv8BfQEJAv8B
gAEPAv8BfwENAf8B+AGGASAB/wNoAfQDWAG4BP8B1wGxAVIB/wGaAY8BdAH/AaABkgFtAf8B4AGvATcB
/wHnAakBEAH/AesB1QGgAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOTAf8DPAFkA18B
yQN9AfoDgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wN9AfoDVQGqBAAD
MwFRA2gB8AHzAcYBWAH/AfoB5wG4Fv8B/gH7Af8B+QHiAaoB/wHvAbgBLQH/A04BlgQCBAADPAFmA2MB
1QN8AfgB/wG+AYUC/wGqAV8C/wGZAT4C/wGNAScC/wGGARoC/wGDARUC/wGFARkC/wGKASIC/wGNASgC
/wGTATMB/wJuAWgB9QNdAcUDNgFZBP8B9AHNAWwB/wH0AcsBZgH/AfQBywFlAf8B9AHLAWUB/wHxAcEB
SQH/AfkB4wGsAf8DiQH/A4kB/wOJAf8DiQH/A4kB/wOJAf8DiQH/A4kB/wOaAf8DBgEIAzEBTANQAZsD
ZQHsA30B+gOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DfQH6A2oB7QNQAZsDLwFJCAADIgExA18B
yQJ9AWcB+gHzAcoBZQH/AfkB4QGmAf8B+wHtAcwB/wH7AewByAH/AfgB3QGbAf8DgAH+AmUBXgHiAz0B
aAQBDAADQgFyA2MB1QNqAfkB/wHJAZkC/wG8AX8C/wGuAWYC/wGkAVMC/wGfAUoC/wGfAUsC/wGjAVEC
/wGnAVgB/wN8AfgDXQHHAzsBYgQAQP8IAAMFAQYDEgEXAzoBYANRAZ8DXwHTA2cB7wNjAfYDYgHuA14B
0gNRAZ4DOQFfAxEBFgMFAQYUAAMPARMDRwGCA2QB2wJ+AW8B/ANnAeoDVAGoAygBOxwAAzwBZQNeAdIB
/wHvAeAC/wHcAbwC/wHNAZ8C/wHBAYoC/wG7AX8C/wG/AYYC/wHNAaEC/wHpAdYB/wNcAcQDNQFVCAAB
QgFNAT4HAAE+AwABKAMAAUADAAEgAwABAQEAAQEGAAEBFgAD/4UAAf8B/AHAAQMEAAH/AfwBgAEBBAAB
/wH5BgAB/wH5BgAB8AETBgABwAEDBAAC/wGAAQEEAAHAAQEBgAEBBAABgAIAAQEHAAEBBwABAQcAAQEH
AAEBBgABgAEBBgABwAEDAYABAQIAAcABAQHwAR8BwAEDCw==
</value>
</data>
<metadata name="ctxmChangeStatus.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
@ -627,8 +628,14 @@
AB/+AAA//wAAf/+AAP//4AP///4///////8=
</value>
</data>
<metadata name="ctxmAdminUserList.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>461, 24</value>
</metadata>
<metadata name="ctxmAdminRoomList.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>461, 49</value>
</metadata>
<metadata name="$this.TrayHeight" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>133</value>
<value>90</value>
</metadata>
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>

View File

@ -37,25 +37,22 @@
btnDecline = new Button();
btnCancelRequest = new Button();
btnMessage = new Button();
pbUserStatus = new PictureBox();
pbCurrencyIcon = new PictureBox();
lblCurrencyAmount = new Label();
flpUsernameCurrency = new FlowLayoutPanel();
panel1 = new Panel();
pCurrency = new Panel();
((System.ComponentModel.ISupportInitialize)pbUserPfp).BeginInit();
((System.ComponentModel.ISupportInitialize)pbUserStatus).BeginInit();
((System.ComponentModel.ISupportInitialize)pbCurrencyIcon).BeginInit();
flpUsernameCurrency.SuspendLayout();
panel1.SuspendLayout();
pCurrency.SuspendLayout();
SuspendLayout();
//
// pbUserPfp
//
pbUserPfp.BorderStyle = BorderStyle.FixedSingle;
pbUserPfp.Image = Properties.Resources.DefaultPfp;
pbUserPfp.Location = new Point(12, 12);
pbUserPfp.Location = new Point(9, 5);
pbUserPfp.Name = "pbUserPfp";
pbUserPfp.Size = new Size(128, 126);
pbUserPfp.Size = new Size(139, 138);
pbUserPfp.SizeMode = PictureBoxSizeMode.StretchImage;
pbUserPfp.TabIndex = 2;
pbUserPfp.TabStop = false;
@ -64,7 +61,7 @@
//
lblUsername.AutoSize = true;
lblUsername.Font = new Font("Segoe UI Light", 15F, FontStyle.Bold);
lblUsername.ForeColor = SystemColors.ControlLight;
lblUsername.ForeColor = Color.White;
lblUsername.Location = new Point(3, 0);
lblUsername.Name = "lblUsername";
lblUsername.Size = new Size(105, 28);
@ -149,17 +146,6 @@
btnMessage.Visible = false;
btnMessage.Click += btnMessage_Click;
//
// pbUserStatus
//
pbUserStatus.BackColor = Color.Transparent;
pbUserStatus.Image = Properties.Resources.OfflineIcon;
pbUserStatus.Location = new Point(115, 1);
pbUserStatus.Name = "pbUserStatus";
pbUserStatus.Size = new Size(32, 32);
pbUserStatus.SizeMode = PictureBoxSizeMode.StretchImage;
pbUserStatus.TabIndex = 10;
pbUserStatus.TabStop = false;
//
// pbCurrencyIcon
//
pbCurrencyIcon.Image = Properties.Resources.CurrencyIcon;
@ -188,21 +174,21 @@
//
flpUsernameCurrency.BackColor = Color.Transparent;
flpUsernameCurrency.Controls.Add(lblUsername);
flpUsernameCurrency.Controls.Add(panel1);
flpUsernameCurrency.Controls.Add(pCurrency);
flpUsernameCurrency.Location = new Point(152, 8);
flpUsernameCurrency.Name = "flpUsernameCurrency";
flpUsernameCurrency.Size = new Size(246, 33);
flpUsernameCurrency.TabIndex = 13;
flpUsernameCurrency.WrapContents = false;
//
// panel1
// pCurrency
//
panel1.Controls.Add(pbCurrencyIcon);
panel1.Controls.Add(lblCurrencyAmount);
panel1.Location = new Point(114, 3);
panel1.Name = "panel1";
panel1.Size = new Size(73, 24);
panel1.TabIndex = 14;
pCurrency.Controls.Add(pbCurrencyIcon);
pCurrency.Controls.Add(lblCurrencyAmount);
pCurrency.Location = new Point(114, 3);
pCurrency.Name = "pCurrency";
pCurrency.Size = new Size(73, 24);
pCurrency.TabIndex = 14;
//
// Profile
//
@ -214,12 +200,12 @@
Controls.Add(btnAccept);
Controls.Add(btnDecline);
Controls.Add(btnCancelRequest);
Controls.Add(pbUserStatus);
Controls.Add(btnAddContact);
Controls.Add(rtxtBio);
Controls.Add(pbUserPfp);
Controls.Add(btnMessage);
Font = new Font("Segoe UI Light", 9F);
ForeColor = Color.White;
FormBorderStyle = FormBorderStyle.FixedDialog;
Icon = (Icon)resources.GetObject("$this.Icon");
MaximizeBox = false;
@ -230,12 +216,11 @@
FormClosed += Profile_FormClosed;
Load += frmProfile_Load;
((System.ComponentModel.ISupportInitialize)pbUserPfp).EndInit();
((System.ComponentModel.ISupportInitialize)pbUserStatus).EndInit();
((System.ComponentModel.ISupportInitialize)pbCurrencyIcon).EndInit();
flpUsernameCurrency.ResumeLayout(false);
flpUsernameCurrency.PerformLayout();
panel1.ResumeLayout(false);
panel1.PerformLayout();
pCurrency.ResumeLayout(false);
pCurrency.PerformLayout();
ResumeLayout(false);
}
@ -249,10 +234,9 @@
private Button btnDecline;
private Button btnCancelRequest;
private Button btnMessage;
private PictureBox pbUserStatus;
private PictureBox pbCurrencyIcon;
private Label lblCurrencyAmount;
private FlowLayoutPanel flpUsernameCurrency;
private Panel panel1;
private Panel pCurrency;
}
}

View File

@ -1,18 +1,25 @@
using QtCNETAPI.Dtos.User;
using QtCNETAPI.Services.ApiService;
using qtc_net_client_2.ClientModel;
using qtc_net_client_2.Properties;
using qtc_net_client_2.Services;
using QtCNETAPI.Dtos.User;
using QtCNETAPI.Models;
using QtCNETAPI.Schema;
using QtCNETAPI.Services.ApiService;
using QtCNETAPI.Services.GatewayService;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Design;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using QtCNETAPI.Services.GatewayService;
using qtc_net_client_2.ClientModel;
using qtc_net_client_2.Properties;
namespace qtc_net_client_2.Forms
{
@ -23,50 +30,66 @@ namespace qtc_net_client_2.Forms
private IGatewayService _gatewayService;
private ServiceResponse<byte[]>? pfpRes;
byte[]? cosmeticRes;
private List<Contact> contactsList;
public Profile(UserInformationDto userInfo, ServiceResponse<byte[]>? pfp, List<Contact> contacts, IApiService apiService, IGatewayService gatewayService)
public Profile(UserInformationDto userInfo, ServiceResponse<byte[]>? pfp, List<Contact> contacts, IApiService apiService, IGatewayService gatewayService, byte[]? cosmetic = null)
{
_userInformationDto = userInfo;
_apiService = apiService;
_gatewayService = gatewayService;
pfpRes = pfp;
cosmeticRes = cosmetic;
contactsList = contacts;
InitializeComponent();
}
private void frmProfile_Load(object sender, EventArgs e)
private async void frmProfile_Load(object sender, EventArgs e)
{
lblUsername.Text = _userInformationDto.Username;
lblCurrencyAmount.Text = _userInformationDto.CurrencyAmount.ToString("N0");
rtxtBio.Text = _userInformationDto.Bio;
Bitmap? pfp = null;
if (pfpRes != null && pfpRes.Success && pfpRes.Data != null)
{
using (var ms = new MemoryStream(pfpRes.Data))
{
pbUserPfp.Image = Image.FromStream(ms);
ms.Dispose();
pfp = new Bitmap(ms);
}
}
var userStatus = (UserStatus)_userInformationDto.Status;
Bitmap precenseImage = Resources.OnlineIcon;
switch (userStatus)
{
case UserStatus.Online:
pbUserStatus.Image = Resources.OnlineIcon;
precenseImage = Resources.OnlineIcon;
break;
case UserStatus.Away:
pbUserStatus.Image = Resources.AwayIcon;
precenseImage = Resources.AwayIcon;
break;
case UserStatus.DoNotDisturb:
pbUserStatus.Image = Resources.DNDIcon;
precenseImage = Resources.DNDIcon;
break;
case UserStatus.Offline:
pbUserStatus.Image = Resources.OfflineIcon;
precenseImage = Resources.OfflineIcon;
break;
}
Bitmap? cosmetic = null;
if(cosmeticRes != null)
{
using var ms = new MemoryStream(cosmeticRes);
cosmetic = new Bitmap(ms);
}
CreateProfileImage(precenseImage, pfp, cosmetic);
precenseImage.Dispose();
pfp?.Dispose();
cosmetic?.Dispose();
if (_userInformationDto.Id == _apiService.CurrentUser!.Id)
{
btnAddContact.Visible = false;
@ -153,8 +176,6 @@ namespace qtc_net_client_2.Forms
btnAddContact.Enabled = false;
using (var ms = new MemoryStream(Resources.RequestSentIcon)) { btnAddContact.Image = Image.FromStream(ms); ms.Dispose(); }
btnCancelRequest.Visible = true;
await _gatewayService.RefreshContactsForUser(_userInformationDto);
}
}
@ -166,8 +187,6 @@ namespace qtc_net_client_2.Forms
btnAddContact.Image = Resources.AddContactIcon;
btnAddContact.Click += btnAddContact_Click_Add;
btnMessage.Visible = false;
await _gatewayService.RefreshContactsForUser(_userInformationDto);
}
}
@ -183,8 +202,6 @@ namespace qtc_net_client_2.Forms
btnAddContact.Image = Resources.RemoveContactIcon;
btnAddContact.Click += btnAddContact_Click_Remove;
if (_userInformationDto.Status >= 1) btnMessage.Visible = true;
await _gatewayService.RefreshContactsForUser(_userInformationDto);
}
}
@ -199,8 +216,6 @@ namespace qtc_net_client_2.Forms
btnAddContact.Visible = true;
btnAddContact.Image = Resources.AddContactIcon;
btnAddContact.Click += btnAddContact_Click_Add;
await _gatewayService.RefreshContactsForUser(_userInformationDto);
}
}
@ -214,8 +229,6 @@ namespace qtc_net_client_2.Forms
btnAddContact.Click += btnAddContact_Click_Add;
btnCancelRequest.Visible = false;
await _gatewayService.RefreshContactsForUser(_userInformationDto);
}
}
@ -225,5 +238,32 @@ namespace qtc_net_client_2.Forms
Close();
frmDirectMessage.Show();
}
private void CreateProfileImage(Bitmap precenseImage, Bitmap? pfp = null, Bitmap? cosmetic = null)
{
Bitmap combined = new Bitmap(139, 138);
using Graphics g = Graphics.FromImage(combined);
g.Clear(Color.Transparent);
g.CompositingMode = CompositingMode.SourceOver;
if (pfp != null)
{
pfp.MakeTransparent();
g.DrawImage(pfp, 4, 6, 128, 128);
}
else g.DrawImage(pbUserPfp.Image, 4, 6, 128, 128);
if (cosmetic != null)
{
cosmetic.MakeTransparent();
g.DrawImage(cosmetic, 0, 0, 139, 138);
}
precenseImage.MakeTransparent();
g.DrawImage(precenseImage, 104, 0, 35, 35);
pbUserPfp.Image = combined;
}
}
}

View File

@ -33,6 +33,8 @@
rtxtBio = new RichTextBox();
lblBio = new Label();
btnSave = new Button();
cbCosmetic = new ComboBox();
lblCosmetic = new Label();
SuspendLayout();
//
// tbUsername
@ -71,7 +73,7 @@
// btnSave
//
btnSave.ForeColor = Color.Black;
btnSave.Location = new Point(76, 148);
btnSave.Location = new Point(76, 177);
btnSave.Name = "btnSave";
btnSave.Size = new Size(43, 23);
btnSave.TabIndex = 4;
@ -79,12 +81,32 @@
btnSave.UseVisualStyleBackColor = true;
btnSave.Click += btnSave_Click;
//
// frmProfileEdit
// cbCosmetic
//
cbCosmetic.FormattingEnabled = true;
cbCosmetic.Items.AddRange(new object[] { "(None)" });
cbCosmetic.Location = new Point(76, 148);
cbCosmetic.Name = "cbCosmetic";
cbCosmetic.Size = new Size(121, 23);
cbCosmetic.TabIndex = 5;
//
// lblCosmetic
//
lblCosmetic.AutoSize = true;
lblCosmetic.Location = new Point(21, 151);
lblCosmetic.Name = "lblCosmetic";
lblCosmetic.Size = new Size(53, 15);
lblCosmetic.TabIndex = 6;
lblCosmetic.Text = "Cosmetic";
//
// ProfileEdit
//
AutoScaleDimensions = new SizeF(6F, 15F);
AutoScaleMode = AutoScaleMode.Font;
BackColor = Color.DodgerBlue;
ClientSize = new Size(345, 187);
ClientSize = new Size(345, 208);
Controls.Add(lblCosmetic);
Controls.Add(cbCosmetic);
Controls.Add(btnSave);
Controls.Add(lblBio);
Controls.Add(rtxtBio);
@ -95,7 +117,7 @@
FormBorderStyle = FormBorderStyle.FixedDialog;
MaximizeBox = false;
MinimizeBox = false;
Name = "frmProfileEdit";
Name = "ProfileEdit";
StartPosition = FormStartPosition.CenterParent;
Text = "QtC.NET Client - Edit Profile";
Load += frmProfileEdit_Load;
@ -110,5 +132,7 @@
private RichTextBox rtxtBio;
private Label lblBio;
private Button btnSave;
private ComboBox cbCosmetic;
private Label lblCosmetic;
}
}

View File

@ -1,4 +1,6 @@
using QtCNETAPI.Dtos.User;
using Microsoft.AspNetCore.Mvc.TagHelpers;
using qtc_net_client_2.ClientModel;
using QtCNETAPI.Dtos.User;
using QtCNETAPI.Services.ApiService;
using System;
using System.Collections.Generic;
@ -21,38 +23,81 @@ namespace qtc_net_client_2.Forms
InitializeComponent();
}
private void frmProfileEdit_Load(object sender, EventArgs e)
private async void frmProfileEdit_Load(object sender, EventArgs e)
{
tbUsername.Text = _apiService.CurrentUser!.Username;
rtxtBio.Text = _apiService.CurrentUser!.Bio;
tbUsername.Text = _apiService.CurrentUser.Username;
rtxtBio.Text = _apiService.CurrentUser.Bio;
// get all owned cosmetics
var boughtItems = await _apiService.GetOwnedStoreItems();
List<ComboBoxItem> items = new List<ComboBoxItem>();
if(boughtItems != null && boughtItems.Success && boughtItems.Data != null)
{
items.Add(new ComboBoxItem
{
Name = "(None)",
Value = 0
});
foreach (var item in boughtItems.Data)
{
// get item from the store
var storeItem = await _apiService.GetStoreItem(item.StoreItemId);
if(storeItem != null && storeItem.Success && storeItem.Data != null)
{
var cbi = new ComboBoxItem
{
Name = storeItem.Data.Name,
Value = storeItem.Data.Id
};
items.Add(cbi);
}
}
} else
{
items.Add(new ComboBoxItem
{
Name = "(None)",
Value = 0
});
}
cbCosmetic.DataSource = items;
cbCosmetic.SelectedIndex = cbCosmetic.Items.IndexOf(items.FirstOrDefault(e => (int?)e.Value == _apiService.CurrentUser.ActiveProfileCosmetic));
}
private async void btnSave_Click(object sender, EventArgs e)
{
if(!string.IsNullOrEmpty(tbUsername.Text) && (tbUsername.Text != _apiService.CurrentUser!.Username || rtxtBio.Text != _apiService.CurrentUser!.Bio))
ComboBoxItem? selectedItem = (ComboBoxItem?)cbCosmetic.SelectedItem;
// update user info
UserUpdateInformationDto userUpdateInformationDto = new UserUpdateInformationDto
{
// update user info
UserUpdateInformationDto userUpdateInformationDto = new UserUpdateInformationDto
{
Id = _apiService.CurrentUser.Id,
Username = tbUsername.Text,
Bio = rtxtBio.Text,
DateOfBirth = _apiService.CurrentUser.DateOfBirth
};
Id = _apiService.CurrentUser.Id,
Username = tbUsername.Text,
Bio = rtxtBio.Text,
DateOfBirth = _apiService.CurrentUser.DateOfBirth
};
var res = await _apiService.UpdateUserInformationAsync(userUpdateInformationDto);
if (selectedItem != null)
{
int selectedItemId = (int?)selectedItem.Value ?? 0;
userUpdateInformationDto.ProfileCosmeticId = selectedItemId;
}
if (res.Success)
{
DialogResult = DialogResult.OK;
Close();
}
else
{
MessageBox.Show(res.Message, "Info Update Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
DialogResult = DialogResult.Cancel;
Close();
}
var res = await _apiService.UpdateUserInformationAsync(userUpdateInformationDto);
if (res.Success)
{
DialogResult = DialogResult.OK;
Close();
}
else
{
MessageBox.Show(res.Message, "Info Update Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
DialogResult = DialogResult.Cancel;
Close();
}
}
}

View File

@ -43,6 +43,7 @@ namespace qtc_net_client_2.Forms
if(registerResult.Success)
{
MessageBox.Show("Registration Complete. If the server has email verification on, you may need to check your email for a verification link.\nIf you do not receive one, try logging in.");
DialogResult = DialogResult.OK;
Close();
} else

View File

@ -0,0 +1,104 @@
namespace qtc_net_client_2.Forms
{
partial class ResendVerificationEmail
{
/// <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()
{
pbLoginBanner = new PictureBox();
tbEmail = new TextBox();
lblHeader = new Label();
btnSend = new Button();
((System.ComponentModel.ISupportInitialize)pbLoginBanner).BeginInit();
SuspendLayout();
//
// pbLoginBanner
//
pbLoginBanner.Image = Properties.Resources.LoginBanner;
pbLoginBanner.Location = new Point(-4, 0);
pbLoginBanner.Name = "pbLoginBanner";
pbLoginBanner.Size = new Size(521, 99);
pbLoginBanner.SizeMode = PictureBoxSizeMode.StretchImage;
pbLoginBanner.TabIndex = 1;
pbLoginBanner.TabStop = false;
//
// tbEmail
//
tbEmail.Location = new Point(50, 124);
tbEmail.Name = "tbEmail";
tbEmail.Size = new Size(424, 23);
tbEmail.TabIndex = 3;
//
// lblHeader
//
lblHeader.AutoSize = true;
lblHeader.Font = new Font("Segoe UI Light", 9F);
lblHeader.ForeColor = SystemColors.ControlLight;
lblHeader.Location = new Point(54, 106);
lblHeader.Name = "lblHeader";
lblHeader.Size = new Size(412, 15);
lblHeader.TabIndex = 5;
lblHeader.Text = "Please Enter Your Email, If An Account Exists With This Email, We'll Send You A Link";
//
// btnSend
//
btnSend.Location = new Point(224, 153);
btnSend.Name = "btnSend";
btnSend.Size = new Size(75, 23);
btnSend.TabIndex = 6;
btnSend.Text = "Send";
btnSend.UseVisualStyleBackColor = true;
btnSend.Click += btnSend_Click;
//
// ResendVerificationEmail
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
BackColor = Color.DodgerBlue;
ClientSize = new Size(515, 187);
Controls.Add(btnSend);
Controls.Add(lblHeader);
Controls.Add(tbEmail);
Controls.Add(pbLoginBanner);
FormBorderStyle = FormBorderStyle.FixedDialog;
MaximizeBox = false;
MinimizeBox = false;
Name = "ResendVerificationEmail";
StartPosition = FormStartPosition.CenterScreen;
Text = "QtC.NET Client - Resend Verification Email";
((System.ComponentModel.ISupportInitialize)pbLoginBanner).EndInit();
ResumeLayout(false);
PerformLayout();
}
#endregion
private PictureBox pbLoginBanner;
private TextBox tbEmail;
private Label lblHeader;
private Button btnSend;
}
}

View File

@ -0,0 +1,43 @@
using QtCNETAPI.Services.ApiService;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace qtc_net_client_2.Forms
{
public partial class ResendVerificationEmail : Form
{
private IApiService _apiService;
public ResendVerificationEmail(IApiService apiService)
{
_apiService = apiService;
InitializeComponent();
}
private async void btnSend_Click(object sender, EventArgs e)
{
if(!string.IsNullOrEmpty(tbEmail.Text))
{
tbEmail.Enabled = false;
btnSend.Enabled = false;
var result = await _apiService.ResendVerificationEmail(tbEmail.Text);
if(result != null && result.Success && result.Data)
{
MessageBox.Show("Got It! You should receive an email shortly.\nIf you do not receive an email, check your spam.", "", MessageBoxButtons.OK, MessageBoxIcon.Information);
Close();
} else
{
MessageBox.Show("Sorry, This Server Doesn't Have Email Features Enabled Or Something Went Wrong.\nIf you cannot login, you may need to contact the server admin.", "", MessageBoxButtons.OK, MessageBoxIcon.Error);
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,221 @@
namespace qtc_net_client_2.Forms
{
partial class ResetPassword
{
/// <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()
{
pEmailBox = new Panel();
btnSend = new Button();
lblHeader = new Label();
tbEmail = new TextBox();
pResetPasswordBox = new Panel();
btnResetPassword = new Button();
tbConfirmPassword = new TextBox();
lblConfirmPassword = new Label();
tbNewPassword = new TextBox();
lblNewPassword = new Label();
tbToken = new TextBox();
lblToken = new Label();
pbLoginBanner = new PictureBox();
pEmailBox.SuspendLayout();
pResetPasswordBox.SuspendLayout();
((System.ComponentModel.ISupportInitialize)pbLoginBanner).BeginInit();
SuspendLayout();
//
// pEmailBox
//
pEmailBox.Anchor = AnchorStyles.Bottom;
pEmailBox.BorderStyle = BorderStyle.FixedSingle;
pEmailBox.Controls.Add(btnSend);
pEmailBox.Controls.Add(lblHeader);
pEmailBox.Controls.Add(tbEmail);
pEmailBox.Location = new Point(101, 125);
pEmailBox.Name = "pEmailBox";
pEmailBox.Size = new Size(458, 94);
pEmailBox.TabIndex = 0;
//
// btnSend
//
btnSend.Location = new Point(193, 59);
btnSend.Name = "btnSend";
btnSend.Size = new Size(75, 23);
btnSend.TabIndex = 9;
btnSend.Text = "Send";
btnSend.UseVisualStyleBackColor = true;
btnSend.Click += btnSend_Click;
//
// lblHeader
//
lblHeader.AutoSize = true;
lblHeader.Font = new Font("Segoe UI Light", 9F);
lblHeader.ForeColor = SystemColors.ControlLight;
lblHeader.Location = new Point(23, 12);
lblHeader.Name = "lblHeader";
lblHeader.Size = new Size(412, 15);
lblHeader.TabIndex = 8;
lblHeader.Text = "Please Enter Your Email, If An Account Exists With This Email, We'll Send You A Link";
//
// tbEmail
//
tbEmail.Location = new Point(19, 30);
tbEmail.Name = "tbEmail";
tbEmail.Size = new Size(424, 23);
tbEmail.TabIndex = 7;
//
// pResetPasswordBox
//
pResetPasswordBox.Anchor = AnchorStyles.Bottom;
pResetPasswordBox.BorderStyle = BorderStyle.FixedSingle;
pResetPasswordBox.Controls.Add(btnResetPassword);
pResetPasswordBox.Controls.Add(tbConfirmPassword);
pResetPasswordBox.Controls.Add(lblConfirmPassword);
pResetPasswordBox.Controls.Add(tbNewPassword);
pResetPasswordBox.Controls.Add(lblNewPassword);
pResetPasswordBox.Controls.Add(tbToken);
pResetPasswordBox.Controls.Add(lblToken);
pResetPasswordBox.Location = new Point(17, 106);
pResetPasswordBox.Name = "pResetPasswordBox";
pResetPasswordBox.Size = new Size(596, 138);
pResetPasswordBox.TabIndex = 1;
pResetPasswordBox.Visible = false;
//
// btnResetPassword
//
btnResetPassword.Location = new Point(270, 102);
btnResetPassword.Name = "btnResetPassword";
btnResetPassword.Size = new Size(100, 23);
btnResetPassword.TabIndex = 15;
btnResetPassword.Text = "Reset Password";
btnResetPassword.UseVisualStyleBackColor = true;
btnResetPassword.Click += btnResetPassword_Click;
//
// tbConfirmPassword
//
tbConfirmPassword.Location = new Point(123, 73);
tbConfirmPassword.Name = "tbConfirmPassword";
tbConfirmPassword.PasswordChar = '*';
tbConfirmPassword.Size = new Size(424, 23);
tbConfirmPassword.TabIndex = 14;
//
// lblConfirmPassword
//
lblConfirmPassword.AutoSize = true;
lblConfirmPassword.Font = new Font("Segoe UI Light", 9F);
lblConfirmPassword.ForeColor = SystemColors.ControlLight;
lblConfirmPassword.Location = new Point(20, 76);
lblConfirmPassword.Name = "lblConfirmPassword";
lblConfirmPassword.Size = new Size(97, 15);
lblConfirmPassword.TabIndex = 13;
lblConfirmPassword.Text = "Confirm Password";
//
// tbNewPassword
//
tbNewPassword.Location = new Point(123, 44);
tbNewPassword.Name = "tbNewPassword";
tbNewPassword.PasswordChar = '*';
tbNewPassword.Size = new Size(424, 23);
tbNewPassword.TabIndex = 12;
//
// lblNewPassword
//
lblNewPassword.AutoSize = true;
lblNewPassword.Font = new Font("Segoe UI Light", 9F);
lblNewPassword.ForeColor = SystemColors.ControlLight;
lblNewPassword.Location = new Point(36, 47);
lblNewPassword.Name = "lblNewPassword";
lblNewPassword.Size = new Size(81, 15);
lblNewPassword.TabIndex = 11;
lblNewPassword.Text = "New Password";
//
// tbToken
//
tbToken.Location = new Point(123, 15);
tbToken.Name = "tbToken";
tbToken.Size = new Size(424, 23);
tbToken.TabIndex = 10;
//
// lblToken
//
lblToken.AutoSize = true;
lblToken.Font = new Font("Segoe UI Light", 9F);
lblToken.ForeColor = SystemColors.ControlLight;
lblToken.Location = new Point(82, 18);
lblToken.Name = "lblToken";
lblToken.Size = new Size(35, 15);
lblToken.TabIndex = 9;
lblToken.Text = "Token";
//
// pbLoginBanner
//
pbLoginBanner.Image = Properties.Resources.LoginBanner;
pbLoginBanner.Location = new Point(-3, -1);
pbLoginBanner.Name = "pbLoginBanner";
pbLoginBanner.Size = new Size(521, 99);
pbLoginBanner.SizeMode = PictureBoxSizeMode.StretchImage;
pbLoginBanner.TabIndex = 2;
pbLoginBanner.TabStop = false;
//
// ResetPassword
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
BackColor = Color.DodgerBlue;
ClientSize = new Size(622, 256);
Controls.Add(pEmailBox);
Controls.Add(pResetPasswordBox);
Controls.Add(pbLoginBanner);
FormBorderStyle = FormBorderStyle.FixedDialog;
MaximizeBox = false;
MinimizeBox = false;
Name = "ResetPassword";
StartPosition = FormStartPosition.CenterScreen;
Text = "QtC.NET Client - Reset Password";
pEmailBox.ResumeLayout(false);
pEmailBox.PerformLayout();
pResetPasswordBox.ResumeLayout(false);
pResetPasswordBox.PerformLayout();
((System.ComponentModel.ISupportInitialize)pbLoginBanner).EndInit();
ResumeLayout(false);
}
#endregion
private Panel pEmailBox;
private Button btnSend;
private Label lblHeader;
private TextBox tbEmail;
private Panel pResetPasswordBox;
private TextBox tbConfirmPassword;
private Label lblConfirmPassword;
private TextBox tbNewPassword;
private Label lblNewPassword;
private TextBox tbToken;
private Label lblToken;
private Button btnResetPassword;
private PictureBox pbLoginBanner;
}
}

View File

@ -0,0 +1,68 @@
using QtCNETAPI.Services.ApiService;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace qtc_net_client_2.Forms
{
public partial class ResetPassword : Form
{
private IApiService _apiService;
public ResetPassword(IApiService apiService)
{
_apiService = apiService;
InitializeComponent();
}
private async void btnSend_Click(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(tbEmail.Text))
{
tbEmail.Enabled = false;
btnSend.Enabled = false;
var result = await _apiService.SendPasswordResetEmail(tbEmail.Text);
if (result != null && result.Success && result.Data)
{
pEmailBox.Visible = false;
pResetPasswordBox.Visible = true;
MessageBox.Show("Got It! You should receive an email shortly.\nIf you do not receive an email, check your spam.");
}
else
{
MessageBox.Show("Sorry, This Server Doesn't Have Email Features Enabled Or Something Went Wrong.\nIf you cannot login, you may need to contact the server admin.", "", MessageBoxButtons.OK, MessageBoxIcon.Error);
Close();
}
}
}
private async void btnResetPassword_Click(object sender, EventArgs e)
{
if(!string.IsNullOrEmpty(tbToken.Text) && !string.IsNullOrEmpty(tbNewPassword.Text) && !string.IsNullOrEmpty(tbConfirmPassword.Text))
{
tbToken.Enabled = false;
tbNewPassword.Enabled = false;
tbConfirmPassword.Enabled = false;
btnResetPassword.Enabled = false;
var result = await _apiService.ResetPassword(new QtCNETAPI.Dtos.User.UserPasswordResetDto { Token = tbToken.Text, Password = tbNewPassword.Text });
if(result != null && result.Success && result.Data)
{
MessageBox.Show("Your Password Has Been Reset. You may now login with the new password.", "", MessageBoxButtons.OK, MessageBoxIcon.Information);
Close();
} else
{
MessageBox.Show(result?.Message, "", MessageBoxButtons.OK, MessageBoxIcon.Error);
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

@ -52,7 +52,7 @@
pStockManagement.Controls.Add(btnSell);
pStockManagement.Controls.Add(btnBuy);
pStockManagement.Controls.Add(lblStockCount);
pStockManagement.Location = new Point(242, 134);
pStockManagement.Location = new Point(230, 138);
pStockManagement.Name = "pStockManagement";
pStockManagement.Size = new Size(135, 81);
pStockManagement.TabIndex = 3;
@ -106,7 +106,7 @@
pMarketStatus.Controls.Add(lblMarketStatus);
pMarketStatus.Controls.Add(pbDollarSignRight);
pMarketStatus.Controls.Add(pbDollarSignLeft);
pMarketStatus.Location = new Point(9, 7);
pMarketStatus.Location = new Point(3, 7);
pMarketStatus.Name = "pMarketStatus";
pMarketStatus.Size = new Size(586, 100);
pMarketStatus.TabIndex = 4;
@ -135,7 +135,7 @@
// pbDollarSignRight
//
pbDollarSignRight.Image = Properties.Resources.dollar_money;
pbDollarSignRight.Location = new Point(498, 3);
pbDollarSignRight.Location = new Point(500, 3);
pbDollarSignRight.Name = "pbDollarSignRight";
pbDollarSignRight.Size = new Size(83, 94);
pbDollarSignRight.SizeMode = PictureBoxSizeMode.Zoom;
@ -145,7 +145,7 @@
// pbDollarSignLeft
//
pbDollarSignLeft.Image = Properties.Resources.dollar_money;
pbDollarSignLeft.Location = new Point(6, 3);
pbDollarSignLeft.Location = new Point(3, 3);
pbDollarSignLeft.Name = "pbDollarSignLeft";
pbDollarSignLeft.Size = new Size(83, 94);
pbDollarSignLeft.SizeMode = PictureBoxSizeMode.Zoom;
@ -156,7 +156,7 @@
//
btnRefresh.Enabled = false;
btnRefresh.ForeColor = Color.Black;
btnRefresh.Location = new Point(282, 109);
btnRefresh.Location = new Point(270, 113);
btnRefresh.Name = "btnRefresh";
btnRefresh.Size = new Size(56, 23);
btnRefresh.TabIndex = 10;
@ -169,7 +169,7 @@
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
BackColor = Color.DodgerBlue;
ClientSize = new Size(606, 227);
ClientSize = new Size(595, 227);
Controls.Add(btnRefresh);
Controls.Add(pMarketStatus);
Controls.Add(pStockManagement);

View File

@ -44,19 +44,6 @@ namespace qtc_net_client_2.Forms
_apiService.CurrentUser.StockAmount = result.Data.StockAmount;
_apiService.CurrentUser.CurrencyAmount = result.Data.CurrencyAmount;
Main? mainWindow = (Main?)Application.OpenForms[0];
if (mainWindow != null)
{
if (mainWindow.InvokeRequired)
{
mainWindow.Invoke(mainWindow.RefreshCurrencyCounter);
}
else
{
mainWindow.RefreshCurrencyCounter();
}
}
nudStockBuySellAmount.Enabled = true;
nudStockBuySellAmount.Value = 0;
btnBuy.Enabled = true;
@ -95,19 +82,6 @@ namespace qtc_net_client_2.Forms
_apiService.CurrentUser.StockAmount = result.Data.StockAmount;
_apiService.CurrentUser.CurrencyAmount = result.Data.CurrencyAmount;
Main? mainWindow = (Main?)Application.OpenForms[0];
if (mainWindow != null)
{
if (mainWindow.InvokeRequired)
{
mainWindow.Invoke(mainWindow.RefreshCurrencyCounter);
}
else
{
mainWindow.RefreshCurrencyCounter();
}
}
nudStockBuySellAmount.Enabled = true;
nudStockBuySellAmount.Value = 0;
btnBuy.Enabled = true;

View File

@ -0,0 +1,135 @@
namespace qtc_net_client_2.Forms
{
partial class StoreItemDisplay
{
/// <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()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(StoreItemDisplay));
pbItemThumbnail = new PictureBox();
lblName = new Label();
lblDescription = new Label();
btnBuy = new Button();
pbCurrencyIcon = new PictureBox();
lblPrice = new Label();
((System.ComponentModel.ISupportInitialize)pbItemThumbnail).BeginInit();
((System.ComponentModel.ISupportInitialize)pbCurrencyIcon).BeginInit();
SuspendLayout();
//
// pbItemThumbnail
//
pbItemThumbnail.Location = new Point(12, 12);
pbItemThumbnail.Name = "pbItemThumbnail";
pbItemThumbnail.Size = new Size(148, 141);
pbItemThumbnail.SizeMode = PictureBoxSizeMode.StretchImage;
pbItemThumbnail.TabIndex = 0;
pbItemThumbnail.TabStop = false;
//
// lblName
//
lblName.AutoSize = true;
lblName.Font = new Font("Segoe UI", 21.75F, FontStyle.Bold | FontStyle.Italic, GraphicsUnit.Point, 0);
lblName.Location = new Point(165, 7);
lblName.Name = "lblName";
lblName.Size = new Size(239, 40);
lblName.TabIndex = 1;
lblName.Text = "Item Name Here";
//
// lblDescription
//
lblDescription.Location = new Point(169, 44);
lblDescription.Name = "lblDescription";
lblDescription.Size = new Size(448, 109);
lblDescription.TabIndex = 2;
lblDescription.Text = resources.GetString("lblDescription.Text");
//
// btnBuy
//
btnBuy.ForeColor = Color.Black;
btnBuy.Location = new Point(542, 157);
btnBuy.Name = "btnBuy";
btnBuy.Size = new Size(75, 23);
btnBuy.TabIndex = 3;
btnBuy.Text = "Buy";
btnBuy.UseVisualStyleBackColor = true;
btnBuy.Click += btnBuy_Click;
//
// pbCurrencyIcon
//
pbCurrencyIcon.Image = Properties.Resources.CurrencyIcon;
pbCurrencyIcon.Location = new Point(48, 161);
pbCurrencyIcon.Name = "pbCurrencyIcon";
pbCurrencyIcon.Size = new Size(15, 14);
pbCurrencyIcon.SizeMode = PictureBoxSizeMode.StretchImage;
pbCurrencyIcon.TabIndex = 19;
pbCurrencyIcon.TabStop = false;
//
// lblPrice
//
lblPrice.AutoEllipsis = true;
lblPrice.Font = new Font("Segoe UI", 8F, FontStyle.Bold, GraphicsUnit.Point, 0);
lblPrice.Location = new Point(64, 162);
lblPrice.Name = "lblPrice";
lblPrice.Size = new Size(61, 13);
lblPrice.TabIndex = 20;
lblPrice.Text = "99,999,999";
//
// StoreItemDisplay
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
BackColor = Color.DodgerBlue;
ClientSize = new Size(629, 188);
Controls.Add(lblPrice);
Controls.Add(pbCurrencyIcon);
Controls.Add(btnBuy);
Controls.Add(lblDescription);
Controls.Add(lblName);
Controls.Add(pbItemThumbnail);
ForeColor = Color.White;
FormBorderStyle = FormBorderStyle.FixedDialog;
MaximizeBox = false;
MinimizeBox = false;
Name = "StoreItemDisplay";
StartPosition = FormStartPosition.CenterScreen;
Text = "QtC.NET Client - Store Item";
Load += StoreItemDisplay_Load;
((System.ComponentModel.ISupportInitialize)pbItemThumbnail).EndInit();
((System.ComponentModel.ISupportInitialize)pbCurrencyIcon).EndInit();
ResumeLayout(false);
PerformLayout();
}
#endregion
private PictureBox pbItemThumbnail;
private Label lblName;
private Label lblDescription;
private Button btnBuy;
private PictureBox pbCurrencyIcon;
private Label lblPrice;
}
}

View File

@ -0,0 +1,97 @@
using QtCNETAPI.Dtos.User;
using QtCNETAPI.Schema;
using QtCNETAPI.Services;
using QtCNETAPI.Services.ApiService;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace qtc_net_client_2.Forms
{
public partial class StoreItemDisplay : Form
{
private StoreItem StoreItem { get; set; }
private LoggingService _loggingService;
private IApiService _apiService;
public StoreItemDisplay(StoreItem item, LoggingService loggingService, IApiService apiService)
{
StoreItem = item;
_loggingService = loggingService;
_apiService = apiService;
InitializeComponent();
}
private async void StoreItemDisplay_Load(object sender, EventArgs e)
{
lblName.Text = StoreItem.Name;
lblDescription.Text = StoreItem.Description;
lblPrice.Text = StoreItem.Price.ToString("N0");
// does the user already own this item?
var ownedItem = await _apiService.GetOwnedStoreItem(StoreItem.Id);
if (ownedItem != null && ownedItem.Success && ownedItem.Data != null)
{
btnBuy.Enabled = false;
btnBuy.Text = "Bought";
}
try
{
using HttpClient client = new();
var response = await client.GetAsync(StoreItem.ThumbnailUrl);
if (response != null && response.IsSuccessStatusCode)
{
using var stream = await response.Content.ReadAsStreamAsync();
Image image = Image.FromStream(stream);
stream.Dispose();
pbItemThumbnail.Image = image;
}
else if (response != null) _loggingService.LogString($"Store Item Thumbnail Could Not Be Loaded Due To Status Code {response.StatusCode}");
else _loggingService.LogString("Store Item Thumbnail Could Not Be Loaded");
client.Dispose();
}
catch (Exception ex)
{
_loggingService.LogString("Store Item Thumbnail Could Not Be Loaded\n" + ex.Message);
}
}
private async void btnBuy_Click(object sender, EventArgs e)
{
Enabled = false;
// attempt to buy item
var ownedStoreItem = await _apiService.BuyStoreItem(StoreItem.Id);
if (ownedStoreItem != null && ownedStoreItem.Success)
{
Enabled = true;
var result = MessageBox.Show($"Successfully Bought '{StoreItem.Name}'! Would You Like To Wear It Now?", "Success!", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
if (result == DialogResult.Yes)
{
// send an update dto that only updates the current users cosmetic
UserUpdateInformationDto updateDto = new UserUpdateInformationDto
{
Id = _apiService.CurrentUser.Id,
Bio = _apiService.CurrentUser.Bio,
DateOfBirth = _apiService.CurrentUser.DateOfBirth,
Username = _apiService.CurrentUser.Username,
ProfileCosmeticId = StoreItem.Id
};
await _apiService.UpdateUserInformationAsync(updateDto);
}
}
else MessageBox.Show("We Weren't Able To Complete Your Purchase.\nYou May Not Have Enough Funds For This Item.", "Oops.", MessageBoxButtons.OK, MessageBoxIcon.Error);
Close();
}
}
}

View File

@ -0,0 +1,123 @@
<?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>
<data name="lblDescription.Text" xml:space="preserve">
<value>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum</value>
</data>
</root>

View File

@ -261,11 +261,6 @@ namespace qtc_net_client_2
{
await _apiService.AddCurrencyToCurrentUser(currencyJackpotSpinner.TokensWon, false);
_apiService.CurrentUser.CurrencyAmount += currencyJackpotSpinner.TokensWon;
// find the main form to refresh currency count
Main? mainForm = (Main?)Application.OpenForms[0];
if (mainForm != null)
mainForm.RefreshCurrencyCounter();
}
}

View File

@ -1,5 +1,6 @@
using qtc_net_client_2.ClientModel;
using qtc_net_client_2.Services;
using QtCNETAPI.Services;
using QtCNETAPI.Services.ApiService;
using QtCNETAPI.Services.GatewayService;
using System.Text.Json;
@ -52,8 +53,8 @@ namespace qtc_net_client_2
// instantiate new ApiService and GatewayService for this session
// this will keep the gateway thread in the main thread (i think)
IApiService api = new ApiService($"{clientConfig.ServerUrl}/api");
IGatewayService gateway = new GatewayService($"{clientConfig.ServerUrl}/chat", api);
IApiService api = new ApiService($"{clientConfig.ServerUrl}/api", loggingService);
IGatewayService gateway = new GatewayService($"{clientConfig.ServerUrl}/chat", api, loggingService);
// ping api
var pingResult = await api.PingServerAsync();

View File

@ -81,7 +81,7 @@ namespace qtc_net_client_2.Properties {
}
/// <summary>
/// Looks up a localized string similar to 5.1.
/// Looks up a localized string similar to 6.3.4.
/// </summary>
internal static string AssemblyVersion {
get {
@ -109,6 +109,16 @@ namespace qtc_net_client_2.Properties {
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap cobalt_sittingatputer {
get {
object obj = ResourceManager.GetObject("cobalt_sittingatputer", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
@ -159,6 +169,16 @@ namespace qtc_net_client_2.Properties {
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap donatebtn {
get {
object obj = ResourceManager.GetObject("donatebtn", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
@ -259,6 +279,16 @@ namespace qtc_net_client_2.Properties {
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap support_me_on_kofi_badge_blue {
get {
object obj = ResourceManager.GetObject("support_me_on_kofi_badge_blue", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>

View File

@ -154,12 +154,18 @@
<data name="CurrencyIcon" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Icons\CurrencyIcon.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<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 name="AddContactIcon" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Icons\AddContactIcon.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="RequestSentIcon" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Icons\RequestSentIcon.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="support_me_on_kofi_badge_blue" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\support_me_on_kofi_badge_blue.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="O" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\O.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="RemoveContactIcon" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Icons\RemoveContactIcon.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
@ -167,7 +173,10 @@
<value>..\Icons\MessageIcon.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="AssemblyVersion" xml:space="preserve">
<value>5.1</value>
<value>6.3.4</value>
</data>
<data name="cobalt_sittingatputer" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\cobalt_sittingatputer.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="LoginBanner" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\LoginBanner.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
@ -175,13 +184,13 @@
<data name="DeclineContactIcon" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Icons\DeclineContactIcon.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="AddContactIcon" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Icons\AddContactIcon.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
<data name="donatebtn" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\donatebtn.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="AcceptContactIcon" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Icons\AcceptContactIcon.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="O" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\O.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
<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>
</root>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -1,52 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Text.Json;
namespace qtc_net_client_2.Services
{
public class LoggingService : IDisposable
{
private DateTime LogDate { get; set; }
private string LogFilePath { get; set; }
private StreamWriter LogFile { get; set; }
public LoggingService()
{
LogDate = DateTime.Now;
LogFilePath = $"./Logs/QtCClientLog_{LogDate.ToString("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)
{
Debug.WriteLine($"({DateTime.Now.ToLocalTime().ToString("hh:mm")}) {message}");
LogFile.WriteLine($"({DateTime.Now.ToLocalTime().ToString("hh:mm")}) {message}");
}
public void LogModel<T>(T model)
{
// serialize the model as json
string modelSerialized = JsonSerializer.Serialize(model, options: new JsonSerializerOptions { WriteIndented = true });
// log it
Debug.WriteLine($"({DateTime.Now.ToLocalTime().ToString("hh:mm")}) {modelSerialized}");
LogFile.WriteLine($"({DateTime.Now.ToLocalTime().ToString("hh:mm")}) {modelSerialized}");
}
public void Dispose()
{
LogFile.WriteLine("--- END OF LOG ---");
LogFile.Close();
LogFile.Dispose();
}
}
}

View File

@ -68,6 +68,9 @@ namespace qtc_net_client_2.Services
try
{
// if bak file already exists, delete it
if (File.Exists($"/{updateInfo.ClientFileName}.bak")) File.Delete($"/{updateInfo.ClientFileName}.bak");
// move old client to backup file
File.Move($"./{updateInfo.ClientFileName}", $"{updateInfo.ClientFileName}.bak");