Compare commits

...

61 Commits
3.3 ... 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
37a7807008 Prepare For Release 5.1 2025-07-05 13:51:27 -07:00
53fab11f88 Rework Client Config To Use Single URL 2025-07-05 13:49:19 -07:00
042671da91 Implement Tic-Tac-Toe Game
Fix Currency Counter Sometimes Adding Decimals
2025-07-05 13:40:16 -07:00
7b599863d8 Implement Room User Lists
Rename `Chat` Form To `ChatRoom`
Version Bump
2025-06-30 15:44:31 -07:00
ee79d53aab Remove Online Users Tab
Add Status Icons To User List
Optimize Profile Window Initialization
2025-06-30 13:54:12 -07:00
a008c7c942 Format Currency Counters 2025-06-30 12:25:34 -07:00
43c0b8fd24 4.2.1 2025-06-29 19:49:31 -07:00
e73b7d8704 Fix Profile Picture PictureBox Click Event Being Unassigned 2025-06-29 19:48:59 -07:00
e6238fcc9a Implement Logging
Fix Invocation Exceptions In Some Animated Form Controls
Remove Call To `Login` Gateway Method
2025-06-29 15:56:27 -07:00
315be1d4ad Fix Profile Area LinkLabel Event Being Unassigned 2025-06-28 14:28:35 -07:00
98111778db Fix Assembly Version Value 2025-06-28 14:17:55 -07:00
9055e6524d Add "Refresh" Context Menu Strip To All List Controls
Use `Show` Instead Of `ShowDialog` When Opening Profile Form
Rename `TokenJackpotSpinner` To `CurrencyJackpotSpinner`
Fix Contacts ListView Control Not Having Default Profile Picture On Users Without A Profile Picture
Fix Contacts ListView Alignment
2025-06-28 14:06:14 -07:00
54a0c0d963 Implement Number Guessing Game 2025-06-27 12:57:07 -07:00
eac4631e12 Implemented First Currency Game - Stock Market
Reworked `GatewayService` To Use New Hub Command Set
Implemented User Directory
Changed Icon On Users Online Tab
2025-06-26 16:14:44 -07:00
82c66aad44 god damn it 2025-06-24 16:08:46 -07:00
fb15211f23 Don't Show Balloon Tip Every Minimize (it's annoying) 2025-06-24 16:04:50 -07:00
68def5ac1f Implement Auto Update Mechanism
Add Config Options `startMinimized` and `minimizeToTray`
Added `AssemblyVersion` String To Resources
2025-06-24 15:54:55 -07:00
85 changed files with 6454 additions and 800 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

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

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,13 @@
namespace QtCNETAPI.Enums
{
public enum GameStatus
{
WaitingForPlayer,
SelectingSymbol,
Ongoing,
P1Win,
P2Win,
NoWin,
PlayerDisconnected
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,14 @@
using 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

@ -13,10 +13,13 @@
public DateTime CreatedAt { get; set; }
public int Status { get; set; } = 0;
public int CurrencyAmount { get; set; } = 0;
public int StockAmount { get; set; } = 0;
public DateTime LastCurrencySpin { get; set; }
public int ActiveProfileCosmetic { get; set; } = 0;
public 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,16 @@
using QtCNETAPI.Enums;
using QtCNETAPI.Models;
namespace QtCNETAPI.Schema
{
public class GameRoom
{
public string Id { get; set; } = string.Empty;
public GameStatus Status { get; set; }
public TicTacToeBoard Board { get; set; } = new();
public User? Player1 { get; set; }
public TicTacToeSymbol P1Symbol { get; set; } = TicTacToeSymbol.Blank;
public User? Player2 { get; set; }
public TicTacToeSymbol P2Symbol { get; set; } = TicTacToeSymbol.Blank;
}
}

View File

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

View File

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

View File

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

View File

@ -1,6 +1,8 @@
using QtCNETAPI.Dtos.Room;
using QtCNETAPI.Dtos.User;
using QtCNETAPI.Enums;
using QtCNETAPI.Models;
using QtCNETAPI.Schema;
using RestSharp;
using System.IdentityModel.Tokens.Jwt;
using System.Text.Json;
@ -11,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; }
@ -26,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);
}
@ -56,6 +64,32 @@ namespace QtCNETAPI.Services.ApiService
return serviceResponse;
}
public async Task<ServiceResponse<List<UserInformationDto>>> GetAllUsersAsync()
{
await RefreshSessionIfInvalid();
var serviceResponse = new ServiceResponse<List<UserInformationDto>>();
if (SessionToken == null) throw new NullReferenceException("Function Was Called Before A Session Was Made.");
var request = new RestRequest("users/all")
.AddHeader("Authorization", $"Bearer {SessionToken}");
var response = await _client.GetAsync<ServiceResponse<List<UserInformationDto>>>(request);
if (response != null || response!.Data != null)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
}
else
{
serviceResponse.Success = false;
serviceResponse.Message = "API didn't respond with users.";
}
return serviceResponse;
}
public async Task<ServiceResponse<List<UserInformationDto>>> GetOnlineUsersAsync()
{
await RefreshSessionIfInvalid();
@ -123,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;
@ -152,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
{
@ -200,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
{
@ -222,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
{
@ -245,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.");
@ -309,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 };
}
@ -558,6 +664,283 @@ namespace QtCNETAPI.Services.ApiService
if (response == null) { serviceResponse.Success = false; serviceResponse.Message = "API did not respond."; return serviceResponse; }
if (response.Success)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
// anything that changes the user should tell the api service to set it again
await SetCurrentUser();
OnCurrentUserUpdate?.Invoke(this, EventArgs.Empty);
}
return serviceResponse;
}
public async Task<ServiceResponse<int>> GetCurrentStockPrice()
{
await RefreshSessionIfInvalid();
var serviceResponse = new ServiceResponse<int>();
if (SessionToken == null) throw new NullReferenceException("Function Was Called Before A Session Was Made.");
var restRequest = new RestRequest("games/stock-market/current-price")
.AddHeader("Authorization", $"Bearer {SessionToken}");
var response = await _client.GetAsync<ServiceResponse<int>>(restRequest);
if (response == null) { serviceResponse.Success = false; serviceResponse.Message = "API did not respond."; return serviceResponse; }
if (response.Success)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
}
return serviceResponse;
}
public async Task<ServiceResponse<UserStockActionResultDto>> BuyStock(int amount)
{
await RefreshSessionIfInvalid();
var serviceResponse = new ServiceResponse<UserStockActionResultDto>();
if (SessionToken == null) throw new NullReferenceException("Function Was Called Before A Session Was Made.");
var restRequest = new RestRequest("games/stock-market/buy-stock")
.AddHeader("Authorization", $"Bearer {SessionToken}")
.AddQueryParameter("amount", amount);
var response = await _client.PostAsync<ServiceResponse<UserStockActionResultDto>>(restRequest);
if (response == null) { serviceResponse.Success = false; serviceResponse.Message = "API did not respond."; return serviceResponse; }
if (response.Success)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
// anything that changes the user should tell the api service to set it again
await SetCurrentUser();
OnCurrentUserUpdate?.Invoke(this, EventArgs.Empty);
}
return serviceResponse;
}
public async Task<ServiceResponse<UserStockActionResultDto>> SellStock(int amount)
{
await RefreshSessionIfInvalid();
var serviceResponse = new ServiceResponse<UserStockActionResultDto>();
if (SessionToken == null) throw new NullReferenceException("Function Was Called Before A Session Was Made.");
var restRequest = new RestRequest("games/stock-market/sell-stock")
.AddHeader("Authorization", $"Bearer {SessionToken}")
.AddQueryParameter("amount", amount);
var response = await _client.PostAsync<ServiceResponse<UserStockActionResultDto>>(restRequest);
if (response == null) { serviceResponse.Success = false; serviceResponse.Message = "API did not respond."; return serviceResponse; }
if (response.Success)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
// anything that changes the user should tell the api service to set it again
await SetCurrentUser();
OnCurrentUserUpdate?.Invoke(this, EventArgs.Empty);
}
return serviceResponse;
}
public async Task<ServiceResponse<int>> GetRandomNumber()
{
await RefreshSessionIfInvalid();
var serviceResponse = new ServiceResponse<int>();
if (SessionToken == null) throw new NullReferenceException("Function Was Called Before A Session Was Made.");
var restRequest = new RestRequest("games/number-guess/get-number")
.AddHeader("Authorization", $"Bearer {SessionToken}");
var response = await _client.GetAsync<ServiceResponse<int>>(restRequest);
if (response == null) { serviceResponse.Success = false; serviceResponse.Message = "API did not respond."; return serviceResponse; }
if (response.Success)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
}
return serviceResponse;
}
public async Task<ServiceResponse<NumberGuessResult>> GuessRandomNumber(int original, int guess)
{
await RefreshSessionIfInvalid();
var serviceResponse = new ServiceResponse<NumberGuessResult>();
if (SessionToken == null) throw new NullReferenceException("Function Was Called Before A Session Was Made.");
var restRequest = new RestRequest("games/number-guess/guess-number")
.AddHeader("Authorization", $"Bearer {SessionToken}")
.AddQueryParameter("original", original)
.AddQueryParameter("guess", guess);
var response = await _client.GetAsync<ServiceResponse<NumberGuessResult>>(restRequest);
if (response == null) { serviceResponse.Success = false; serviceResponse.Message = "API did not respond."; return serviceResponse; }
if (response.Success)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
}
return serviceResponse;
}
public async Task<ServiceResponse<List<StoreItem>>> GetStoreItems()
{
await RefreshSessionIfInvalid();
var serviceResponse = new ServiceResponse<List<StoreItem>>();
if (SessionToken == null) throw new NullReferenceException("Function Was Called Before A Session Was Made.");
var restRequest = new RestRequest("store/all-items")
.AddHeader("Authorization", $"Bearer {SessionToken}");
var response = await _client.GetAsync<ServiceResponse<List<StoreItem>>>(restRequest);
if (response == null) { serviceResponse.Success = false; serviceResponse.Message = "API did not respond."; return serviceResponse; }
if (response.Success)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
}
return serviceResponse;
}
public async Task<ServiceResponse<StoreItem>> GetStoreItem(int id)
{
await RefreshSessionIfInvalid();
var serviceResponse = new ServiceResponse<StoreItem>();
if (SessionToken == null) throw new NullReferenceException("Function Was Called Before A Session Was Made.");
var restRequest = new RestRequest("store/item")
.AddHeader("Authorization", $"Bearer {SessionToken}")
.AddQueryParameter("id", id);
var response = await _client.GetAsync<ServiceResponse<StoreItem>>(restRequest);
if (response == null) { serviceResponse.Success = false; serviceResponse.Message = "API did not respond."; return serviceResponse; }
if (response.Success)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
}
return serviceResponse;
}
public async Task<ServiceResponse<bool>> BuyStoreItem(int id)
{
await RefreshSessionIfInvalid();
var serviceResponse = new ServiceResponse<bool>();
if (SessionToken == null) throw new NullReferenceException("Function Was Called Before A Session Was Made.");
var restRequest = new RestRequest("store/buy-item")
.AddHeader("Authorization", $"Bearer {SessionToken}")
.AddQueryParameter("id", id);
var response = await _client.PostAsync<ServiceResponse<bool>>(restRequest);
if (response == null) { serviceResponse.Success = false; serviceResponse.Message = "API did not respond."; return serviceResponse; }
if (response.Success)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
// anything that changes the user should tell the api service to set it again
await SetCurrentUser();
OnCurrentUserUpdate?.Invoke(this, EventArgs.Empty);
}
return serviceResponse;
}
public async Task<ServiceResponse<List<OwnedStoreItem>>> GetOwnedStoreItems()
{
await RefreshSessionIfInvalid();
var serviceResponse = new ServiceResponse<List<OwnedStoreItem>>();
if (SessionToken == null) throw new NullReferenceException("Function Was Called Before A Session Was Made.");
var restRequest = new RestRequest("store/bought-items")
.AddHeader("Authorization", $"Bearer {SessionToken}");
var response = await _client.GetAsync<ServiceResponse<List<OwnedStoreItem>>>(restRequest);
if (response == null) { serviceResponse.Success = false; serviceResponse.Message = "API did not respond."; return serviceResponse; }
if (response.Success)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
}
return serviceResponse;
}
public async Task<ServiceResponse<OwnedStoreItem>> GetOwnedStoreItem(int id)
{
await RefreshSessionIfInvalid();
var serviceResponse = new ServiceResponse<OwnedStoreItem>();
if (SessionToken == null) throw new NullReferenceException("Function Was Called Before A Session Was Made.");
var restRequest = new RestRequest("store/bought-item")
.AddHeader("Authorization", $"Bearer {SessionToken}")
.AddQueryParameter("id", id);
var response = await _client.GetAsync<ServiceResponse<OwnedStoreItem>>(restRequest);
if (response == null) { serviceResponse.Success = false; serviceResponse.Message = "API did not respond."; return serviceResponse; }
if (response.Success)
{
serviceResponse.Success = true;
serviceResponse.Data = response.Data;
}
return serviceResponse;
}
public async Task<ServiceResponse<User>> DeleteUserById(string id)
{
await RefreshSessionIfInvalid();
var serviceResponse = new ServiceResponse<User>();
if (SessionToken == null) throw new NullReferenceException("Function Was Called Before A Session Was Made.");
var restRequest = new RestRequest("users/delete-user")
.AddHeader("Authorization", $"Bearer {SessionToken}")
.AddQueryParameter("id", id);
var response = await _client.DeleteAsync<ServiceResponse<User>>(restRequest);
if (response == null) { serviceResponse.Success = false; serviceResponse.Message = "API did not respond."; return serviceResponse; }
if (response.Success)
{
serviceResponse.Success = true;

View File

@ -7,6 +7,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using QtCNETAPI.Enums;
using QtCNETAPI.Schema;
namespace QtCNETAPI.Services.ApiService
{
@ -15,11 +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<User>> LoginAsync(UserLoginDto userLoginDto);
public Task<ServiceResponse<List<UserInformationDto>>> GetAllUsersAsync();
public Task<ServiceResponse<User>> DeleteUserById(string id);
public Task<ServiceResponse<string>> LoginAsync(UserLoginDto userLoginDto);
public Task<ServiceResponse<bool>> ResendVerificationEmail(string email);
public Task<ServiceResponse<bool>> SendPasswordResetEmail(string email);
public Task<ServiceResponse<bool>> ResetPassword(UserPasswordResetDto request);
public Task<ServiceResponse<User>> RefreshLogin(string refreshToken);
public Task<ServiceResponse<string>> RefreshSessionIfInvalid();
public Task<User> SetCurrentUser();
public Task<ServiceResponse<User>> RegisterAsync(UserDto userDto);
public Task<ServiceResponse<UserInformationDto>> GetUserInformationAsync(string id);
public Task<ServiceResponse<UserInformationDto>> UpdateUserInformationAsync(UserUpdateInformationDto request);
@ -33,5 +43,15 @@ namespace QtCNETAPI.Services.ApiService
public Task<ServiceResponse<bool>> AcceptContactRequest(string userId);
public Task<ServiceResponse<Contact>> RemoveContactFromCurrentUser(string userId);
public Task<ServiceResponse<int>> AddCurrencyToCurrentUser(int amount, bool isSpinClaim);
public Task<ServiceResponse<int>> GetCurrentStockPrice();
public Task<ServiceResponse<UserStockActionResultDto>> BuyStock(int amount);
public Task<ServiceResponse<UserStockActionResultDto>> SellStock(int amount);
public Task<ServiceResponse<int>> GetRandomNumber();
public Task<ServiceResponse<NumberGuessResult>> GuessRandomNumber(int original, int guess);
public Task<ServiceResponse<List<StoreItem>>> GetStoreItems();
public Task<ServiceResponse<StoreItem>> GetStoreItem(int id);
public Task<ServiceResponse<bool>> BuyStoreItem(int id);
public Task<ServiceResponse<List<OwnedStoreItem>>> GetOwnedStoreItems();
public Task<ServiceResponse<OwnedStoreItem>> GetOwnedStoreItem(int id);
}
}

View File

@ -0,0 +1,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,60 +7,87 @@ using QtCNETAPI.Services.ApiService;
namespace QtCNETAPI.Services.GatewayService
{
public class GatewayService : IGatewayService
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 OnServerMessageReceived;
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();
// start connection
await HubConnection.StartAsync();
// register events
HubConnection.On<string>("rm", (serverMessage) => OnServerMessageReceived?.Invoke(this, new ServerMessageEventArgs{Message = serverMessage}));
HubConnection.On<string>("RoomMessage", (serverMessage) => OnRoomMessageReceived?.Invoke(this, new ServerMessageEventArgs { Message = serverMessage }));
HubConnection.On<string>("cf", (function) => OnClientFunctionReceived?.Invoke(this, new ClientFunctionEventArgs { Function = function }));
HubConnection.On<Message, UserInformationDto>("rdm", (message, user) => OnDirectMessageReceived?.Invoke(this, new DirectMessageEventArgs { Message = message, User = user }));
HubConnection.On<ServerConfig>("rc", (serverConfig) => OnServerConfigReceived?.Invoke(this, new ServerConfigEventArgs{ServerConfig = serverConfig}));
HubConnection.On<Message, UserInformationDto>("ReceiveDirectMessage", (message, user) => OnDirectMessageReceived?.Invoke(this, new DirectMessageEventArgs { Message = message, User = user }));
HubConnection.On("RefreshUserLists", () => OnRefreshUserListsReceived?.Invoke(this, EventArgs.Empty));
HubConnection.On("RefreshRoomList", () => OnRefreshRoomListReceived?.Invoke(this, EventArgs.Empty));
HubConnection.On("RefreshContactsList", () => OnRefreshContactsListReceived?.Invoke(this, EventArgs.Empty));
HubConnection.On<ServerConfig>("ReceiveServerConfig", (serverConfig) => OnServerConfigReceived?.Invoke(this, new ServerConfigEventArgs { ServerConfig = serverConfig }));
HubConnection.On<List<User>>("RoomUserList", (userList) => OnRoomUserListReceived?.Invoke(this, new RoomListEventArgs { UserList = userList }));
HubConnection.On<string>("GuestJoin", (username) => OnGuestUserJoin?.Invoke(this, new GuestUserJoinEventArgs { Username = username }));
HubConnection.On("RoomDeleted", () => OnRoomDeleted?.Invoke(this, EventArgs.Empty));
HubConnection.On("ForceSignOut", () => OnUserForceLogout?.Invoke(this, EventArgs.Empty));
HubConnection.Closed += HubConnection_Closed;
HubConnection.Reconnecting += HubConnection_Reconnecting;
HubConnection.Reconnected += HubConnection_Reconnected;
if (HubConnection != null && HubConnection.State == HubConnectionState.Connected)
await HubConnection.SendAsync("l", _apiService.CurrentUser);
// start connection
try
{
await HubConnection.StartAsync();
}
catch (HttpRequestException ex)
{
_loggingService.LogString($"Unable To Connect To SignalR.\n{ex.Message}\n{ex.StackTrace}");
return;
}
// ensure current user is up to date (particularly status)
await _apiService.SetCurrentUser();
}
public async Task StopAsync()
@ -70,6 +98,17 @@ namespace QtCNETAPI.Services.GatewayService
}
}
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (HubConnection != null && HubConnection.State == HubConnectionState.Disconnected)
{
await HubConnection.DisposeAsync();
HubConnection = null;
CurrentRoom = null;
}
}
public async Task DisposeAsync()
{
if (HubConnection != null && HubConnection.State == HubConnectionState.Disconnected)
@ -83,69 +122,52 @@ 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("jl", _apiService.CurrentUser);
await HubConnection.SendAsync("JoinLobby", _apiService.CurrentUser);
InLobby = true;
CurrentRoom = null;
}
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)
{
await HubConnection.SendAsync("ll", _apiService.CurrentUser);
await HubConnection.SendAsync("LeaveLobby", _apiService.CurrentUser);
InLobby = false;
}
else if (CurrentRoom != null)
{
await HubConnection.SendAsync("lr", _apiService.CurrentUser, CurrentRoom);
await HubConnection.SendAsync("LeaveRoom", _apiService.CurrentUser, CurrentRoom);
}
await HubConnection.SendAsync("jr", _apiService.CurrentUser, room);
await HubConnection.SendAsync("JoinRoom", _apiService.CurrentUser, room);
CurrentRoom = room;
}
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)
{
await HubConnection.SendAsync("ll", _apiService.CurrentUser);
await HubConnection.SendAsync("LeaveLobby", _apiService.CurrentUser);
InLobby = false;
}
else
{
await HubConnection.SendAsync("lr", _apiService.CurrentUser, CurrentRoom);
await HubConnection.SendAsync("LeaveRoom", _apiService.CurrentUser, CurrentRoom);
CurrentRoom = null;
}
}
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("rcl", 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("s", _apiService.CurrentUser, message, InLobby, CurrentRoom);
await HubConnection.SendAsync("SendMessage", _apiService.CurrentUser, message, InLobby, CurrentRoom);
}
public async Task SendDirectMessageAsync(UserInformationDto user, Message message)
@ -154,16 +176,17 @@ namespace QtCNETAPI.Services.GatewayService
if (HubConnection == null || HubConnection.State != HubConnectionState.Connected) throw new InvalidOperationException("Function was called before connection was made.");
await HubConnection.SendAsync("sdm", _apiService.CurrentUser, user, message);
await HubConnection.SendAsync("SendDirectMessage", _apiService.CurrentUser, user, message);
}
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("us", _apiService.CurrentUser, status);
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>
@ -103,9 +95,24 @@ namespace QtCNETAPI.Services.GatewayService
// EVENTS
/// <summary>
/// When A Server Message Is Received, This Event Fires
/// When A Room Message Is Received, This Event Fires
/// </summary>
public event EventHandler OnServerMessageReceived;
public event EventHandler OnRoomMessageReceived;
/// <summary>
/// Fires When The User List For A Room Is Received
/// </summary>
public event EventHandler OnRoomUserListReceived;
/// <summary>
/// Fires When The Room The User Is In Gets Deleted By An Admin
/// </summary>
public event EventHandler OnRoomDeleted;
/// <summary>
/// Fires When A Guest User Joins Your Room
/// </summary>
public event EventHandler OnGuestUserJoin;
/// <summary>
/// When A Client Function/Event Is Received, This Event Fires
@ -117,6 +124,21 @@ namespace QtCNETAPI.Services.GatewayService
/// </summary>
public event EventHandler OnDirectMessageReceived;
/// <summary>
/// Fires When The Client Receives The Request To Refresh Its User List
/// </summary>
public event EventHandler OnRefreshUserListsReceived;
/// <summary>
/// Fires When The Client Receives The Request To Refresh Its Room List
/// </summary>
public event EventHandler OnRefreshRoomListReceived;
/// <summary>
/// Fires When The Client Receives The Request To Refresh Its Contacts List
/// </summary>
public event EventHandler OnRefreshContactsListReceived;
/// <summary>
/// When The Server Config Is Received, This Event Fires
/// </summary>
@ -136,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() { }
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace qtc_net_client_2.ClientModel
{
public class ClientUpdateInfo
{
[JsonPropertyName("clientFileName")]
[JsonRequired]
public string ClientFileName { get; set; } = "QtCNetChat.exe";
[JsonPropertyName("version")]
[JsonRequired]
public string Version { get; set; } = string.Empty;
[JsonPropertyName("downloadUri")]
[JsonRequired]
public string DownloadUrl { get; set; } = string.Empty;
[JsonPropertyName("updateMandatory")]
[JsonRequired]
public bool IsUpdateMandatory { get; set; } = false;
}
}

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

@ -9,9 +9,18 @@ namespace qtc_net_client_2.ClientModel
{
public class Config
{
[JsonPropertyName("apiEndpoint")]
public string ApiEndpoint { get; set; } = "https://qtc.alanmoon.net/api";
[JsonPropertyName("gatewayEndpoint")]
public string GatewayEndpoint { get; set; } = "https://qtc.alanmoon.net/chat";
[JsonPropertyName("startMinimized")]
[JsonRequired]
public bool StartMinimized { get; set; } = false;
[JsonPropertyName("minimizeToTray")]
[JsonRequired]
public bool MinimizeToTray { get; set; } = true;
[JsonPropertyName("enableDebugLogs")]
[JsonRequired]
public bool EnableDebugLogs { get; set; } = false;
[JsonPropertyName("serverUrl")]
[JsonRequired]
public string ServerUrl { get; set; } = "https://qtc.alanmoon.net";
}
}

View File

@ -1,86 +0,0 @@
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
{
public partial class Chat : Form
{
IGatewayService _gatewayService;
IApiService _apiService;
AudioService AudioService = new();
public Chat(IGatewayService gatewayService, IApiService apiService)
{
_gatewayService = gatewayService;
_apiService = apiService;
InitializeComponent();
}
private void frmChat_Load(object sender, EventArgs e)
{
// subscribe to server message event
_gatewayService.OnServerMessageReceived += _gatewayService_OnServerMessageReceived;
if (_gatewayService.CurrentRoom != null) Text = $"QtC.NET Client - Chat Room - {_gatewayService.CurrentRoom.Name}";
else if (_gatewayService.InLobby) Text = $"QtC.NET Client - Chat Room - Lobby";
}
private async void frmChat_FormClosing(object sender, FormClosingEventArgs e)
{
// unsubscribe from server message event
_gatewayService.OnServerMessageReceived -= _gatewayService_OnServerMessageReceived;
if (_gatewayService.CurrentRoom != null || _gatewayService.InLobby)
{
// leave any room user is in
await _gatewayService.LeaveRoomAsync();
}
}
private async void btnSend_Click(object sender, EventArgs e)
{
if (!string.IsNullOrWhiteSpace(rtxtChatbox.Text))
{
// construct message
QtCNETAPI.Models.Message message = new() { Content = rtxtChatbox.Text };
// send it and clear text box
await _gatewayService.PostMessageAsync(message);
rtxtChatbox.Clear();
AudioService.PlaySoundEffect("sndSendClick");
}
}
private void rtxtChatbox_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
btnSend_Click(sender, e);
}
private void _gatewayService_OnServerMessageReceived(object? sender, EventArgs e)
{
var msgEventArgs = (ServerMessageEventArgs)e;
AddMessage(msgEventArgs.Message);
if (!msgEventArgs.Message.Contains(_apiService.CurrentUser.Username)) AudioService.PlaySoundEffect("sndMessage");
}
private void AddMessage(string message)
{
if (InvokeRequired)
Invoke(delegate { rtxtChat.AppendText(message + Environment.NewLine); });
else rtxtChat.AppendText(message + Environment.NewLine);
}
}
}

View File

@ -1,6 +1,6 @@
namespace qtc_net_client_2.Forms
{
partial class Chat
partial class ChatRoom
{
/// <summary>
/// Required designer variable.
@ -28,10 +28,14 @@
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Chat));
components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ChatRoom));
rtxtChatbox = new RichTextBox();
btnSend = new Button();
rtxtChat = new RichTextBox();
lblRoomName = new Label();
lvUserList = new ListView();
ilStatusIcons = new ImageList(components);
SuspendLayout();
//
// rtxtChatbox
@ -57,37 +61,79 @@
//
// rtxtChat
//
rtxtChat.Font = new Font("Segoe UI", 10F);
rtxtChat.HideSelection = false;
rtxtChat.Location = new Point(12, 12);
rtxtChat.Location = new Point(142, 43);
rtxtChat.Name = "rtxtChat";
rtxtChat.ReadOnly = true;
rtxtChat.Size = new Size(593, 250);
rtxtChat.Size = new Size(463, 219);
rtxtChat.TabIndex = 3;
rtxtChat.Text = "";
//
// Chat
// lblRoomName
//
lblRoomName.AutoSize = true;
lblRoomName.Font = new Font("Segoe UI", 25F, FontStyle.Bold | FontStyle.Italic);
lblRoomName.ForeColor = Color.White;
lblRoomName.Location = new Point(6, -4);
lblRoomName.Margin = new Padding(4, 0, 4, 0);
lblRoomName.Name = "lblRoomName";
lblRoomName.Size = new Size(113, 46);
lblRoomName.TabIndex = 8;
lblRoomName.Text = "Room";
//
// lvUserList
//
lvUserList.Alignment = ListViewAlignment.Left;
lvUserList.Location = new Point(12, 43);
lvUserList.MultiSelect = false;
lvUserList.Name = "lvUserList";
lvUserList.Size = new Size(124, 219);
lvUserList.SmallImageList = ilStatusIcons;
lvUserList.TabIndex = 9;
lvUserList.UseCompatibleStateImageBehavior = false;
lvUserList.View = View.SmallIcon;
lvUserList.DoubleClick += lvUserList_DoubleClick;
//
// ilStatusIcons
//
ilStatusIcons.ColorDepth = ColorDepth.Depth32Bit;
ilStatusIcons.ImageStream = (ImageListStreamer)resources.GetObject("ilStatusIcons.ImageStream");
ilStatusIcons.TransparentColor = Color.Transparent;
ilStatusIcons.Images.SetKeyName(0, "Offline");
ilStatusIcons.Images.SetKeyName(1, "Online");
ilStatusIcons.Images.SetKeyName(2, "Away");
ilStatusIcons.Images.SetKeyName(3, "DND");
//
// ChatRoom
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
BackColor = Color.DodgerBlue;
ClientSize = new Size(617, 334);
Controls.Add(lvUserList);
Controls.Add(lblRoomName);
Controls.Add(rtxtChat);
Controls.Add(btnSend);
Controls.Add(rtxtChatbox);
FormBorderStyle = FormBorderStyle.FixedSingle;
Icon = (Icon)resources.GetObject("$this.Icon");
MaximizeBox = false;
Name = "Chat";
Name = "ChatRoom";
StartPosition = FormStartPosition.CenterScreen;
Text = "QtC.NET Client - Chat Room";
FormClosing += frmChat_FormClosing;
Load += frmChat_Load;
ResumeLayout(false);
PerformLayout();
}
#endregion
private RichTextBox rtxtChatbox;
private Button btnSend;
private RichTextBox rtxtChat;
private Label lblRoomName;
private ListView lvUserList;
private ImageList ilStatusIcons;
}
}

View File

@ -0,0 +1,162 @@
using qtc_net_client_2.Services;
using QtCNETAPI.Events;
using QtCNETAPI.Services.ApiService;
using QtCNETAPI.Services.GatewayService;
namespace qtc_net_client_2.Forms
{
public partial class ChatRoom : Form
{
IGatewayService _gatewayService;
IApiService _apiService;
AudioService AudioService = new();
public ChatRoom(IGatewayService gatewayService, IApiService apiService)
{
_gatewayService = gatewayService;
_apiService = apiService;
InitializeComponent();
}
private void frmChat_Load(object sender, EventArgs e)
{
// 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"; }
lvUserList.Clear();
// always add current user to list i guess
lvUserList.Items.Add(_apiService.CurrentUser.Username, _apiService.CurrentUser.Status);
}
private async void frmChat_FormClosing(object sender, FormClosingEventArgs e)
{
// unsubscribe from server message event
_gatewayService.OnRoomMessageReceived -= _gatewayService_OnServerMessageReceived;
_gatewayService.OnRoomUserListReceived -= _gatewayService_OnRoomUserListReceived;
if (_gatewayService.CurrentRoom != null || _gatewayService.InLobby)
{
// leave any room user is in
await _gatewayService.LeaveRoomAsync();
}
}
private async void btnSend_Click(object sender, EventArgs e)
{
if (!string.IsNullOrWhiteSpace(rtxtChatbox.Text))
{
// construct message
QtCNETAPI.Models.Message message = new() { Content = rtxtChatbox.Text };
// send it and clear text box
await _gatewayService.PostMessageAsync(message);
rtxtChatbox.Clear();
AudioService.PlaySoundEffect("sndSendClick");
}
}
private void rtxtChatbox_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
btnSend_Click(sender, e);
}
private async void lvUserList_DoubleClick(object sender, EventArgs e)
{
if (lvUserList.SelectedItems.Count > 0)
{
string? selectedUser = (string?)lvUserList.SelectedItems[lvUserList.SelectedItems.Count - 1].Text;
if (selectedUser != null)
{
// split from [ if it exists
if (selectedUser.Contains('[')) selectedUser = selectedUser.Split('[', options: StringSplitOptions.TrimEntries)[0];
// in this context, we need to find the main form to get the current user directory
Main? mainForm = (Main?)Application.OpenForms[0];
if (mainForm != null)
{
// get user info and open profile dialog
var user = mainForm.UserDirectory.FirstOrDefault(e => e.Username == selectedUser);
if (user != null)
{
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();
}
}
}
}
}
}
private void _gatewayService_OnServerMessageReceived(object? sender, EventArgs e)
{
var msgEventArgs = (ServerMessageEventArgs)e;
AddMessage(msgEventArgs.Message);
if (!msgEventArgs.Message.Contains(_apiService.CurrentUser.Username)) AudioService.PlaySoundEffect("sndMessage");
}
private void _gatewayService_OnRoomUserListReceived(object? sender, EventArgs e)
{
var args = (RoomListEventArgs)e;
if (IsHandleCreated && !IsDisposed)
{
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(IsHandleCreated && !IsDisposed)
{
Invoke(delegate ()
{
rtxtChat.AppendText(message + Environment.NewLine);
});
}
}
}
}

View File

@ -0,0 +1,244 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="ilStatusIcons.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>12, 12</value>
</metadata>
<data name="ilStatusIcons.ImageStream" mimetype="application/x-microsoft.net.object.binary.base64">
<value>
AAEAAAD/////AQAAAAAAAAAMAgAAAEZTeXN0ZW0uV2luZG93cy5Gb3JtcywgQ3VsdHVyZT1uZXV0cmFs
LCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5BQEAAAAmU3lzdGVtLldpbmRvd3MuRm9ybXMu
SW1hZ2VMaXN0U3RyZWFtZXIBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAA+hMAAAJNU0Z0AUkBTAIBAQQB
AAHAAQABwAEAARABAAEQAQAE/wEhAQAI/wFCAU0BNgcAATYDAAEoAwABQAMAASADAAEBAQABIAYAASD/
AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AC4AAwYBBwM0AVQDUQGiA14B0gNaAekDYAHoA10B
0QNQAZ8DMQFNAwUBBhgAAwYBBwM0AVQDUQGiA14B0gNaAekDYAHoA10B0QNQAZ8DMQFNAwUBBhgAAwYB
BwM0AVQDUQGiA14B0gNaAekDYAHoA10B0QNQAZ8DMQFNAwUBBhgAAwYBBwM0AVQDUQGiA14B0gNaAekD
YAHoA10B0QNQAZ8DMQFNAwUBBhQAAyABLQNUAasDWwHkA1UB9QMkAfsDPAH+AzwB/gMkAfsDUwH0A2IB
4QNRAaEDHgEqEAADIAEtA1QBqwNbAeQBSwFaAUsB9QEhAVgBIQH7ARgBWAEYAf4BGAFYARgB/gEhAVgB
IQH7A1MB9ANiAeEDUQGhAx4BKhAAAyABLQNUAasDWwHkAUsCWgH1ASECWAH7ARgCWAH+ARgCWAH+ASEC
WAH7A1MB9ANiAeEDUQGhAx4BKhAAAyABLQNUAasDWwHkAksBWgH1AiEBWAH7AhgBWAH+AhgBWAH+AiEB
WAH7A1MB9ANiAeEDUQGhAx4BKgwAAxsBJQNYAb0DWgHyA0AB/gMwAf8DOQH/AzwB/wM2Af8DKgH/AyQB
/wNAAf0DXgHwA1YBsgMaASMIAAMbASUDWAG9A1oB8gEYAWABGAH+AQABVwEAAf8BAAFnAQAB/wEAAWwB
AAH/AQABYQEAAf8BAAFMAQAB/wEAAUABAAH/ATABQAEwAf0BWgFeAVoB8ANWAbIBGQEaARkBIwgAAxsB
JQNYAb0DWgHyARgCYAH+AQACVwH/AQACZwH/AQACbAH/AQACYQH/AQACTAH/AQACQAH/ATACQAH9AVoC
XgHwA1YBsgEZAhoBIwgAAxsBJQNYAb0DWgHyAhgBYAH+AgABVwH/AgABZwH/AgABbAH/AgABYQH/AgAB
TAH/AgABQAH/AjABQAH9AloBXgHwA1YBsgIZARoBIwQAAwMBBANSAaUDYAHzA0kB/wNVAf8DZQH/A3EB
/wN1Af8DcQH/A2QB/wNMAf8DMQH/AzwB/gNiAe4DUAGaAwMBBAMDAQQBUgFTAVIBpQFWAW8BVgHzAQAB
ggEAAf8BAAGZAQAB/wEAAbYBAAH/AQABzAEAAf8BAAHTAQAB/wEAAcsBAAH/AQABswEAAf8BAAGIAQAB
/wEAAVcBAAH/ARgBWAEYAf4BXwFiAV8B7gNQAZoDAwEEAwMBBAFSAlMBpQFWAm8B8wEAAoIB/wEAApkB
/wEAArYB/wEAAswB/wEAAtMB/wEAAssB/wEAArMB/wEAAogB/wEAAlcB/wEYAlgB/gFfAmIB7gNQAZoD
AwEEAwMBBAJSAVMBpQJWAW8B8wIAAYIB/wIAAZkB/wIAAbYB/wIAAcwB/wIAAdMB/wIAAcsB/wIAAbMB
/wIAAYgB/wIAAVcB/wIYAVgB/gJfAWIB7gNQAZoDAwEEAy0BRANgAegDewH+A24B/wN7Af8DhQH/A4oB
/wOMAf8DigH/A4UB/wN2Af8DVwH/AzIB/wNAAf0DXgHdAyoBPwMtAUQBYAFpAWAB6AEYAYQBGAH+AQAB
xgEAAf8BAAHcAQAB/wEAAe4BAAH/AQAB+AEAAf8BAAH7AQAB/wEAAfkBAAH/AQAB7wEAAf8BAAHUAQAB
/wEAAZwBAAH/AQABWgEAAf8BMAFAATAB/QNeAd0DKgE/Ay0BRAFgAmkB6AEYAoQB/gEAAsYB/wEAAtwB
/wEAAu4B/wEAAvgB/wEAAvsB/wEAAvkB/wEAAu8B/wEAAtQB/wEAApwB/wEAAloB/wEwAkAB/QNeAd0D
KgE/Ay0BRAJgAWkB6AIYAYQB/gIAAcYB/wIAAdwB/wIAAe4B/wIAAfgB/wIAAfsB/wIAAfkB/wIAAe8B
/wIAAdQB/wIAAZwB/wIAAVoB/wIwAUAB/QNeAd0DKgE/A04BlQN3AfgDfwH/A4UB/wOKAf8DjQH/A44B
/wOOAf8DjgH/A40B/wOJAf8DdwH/A00B/wMlAf8DWgHyA0oBiwNOAZUBQgGBAUIB+AEAAeUBAAH/AQAB
7wEAAf8BAAH4AQAB/wEAAf0BAAH/AQAB/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB/gEAAf8BAAH2AQAB
/wEAAdUBAAH/AQABiwEAAf8BAAFBAQAB/wNaAfIDSgGLA04BlQFCAoEB+AEAAuUB/wEAAu8B/wEAAvgB
/wEAAv0B/wEAA/8BAAP/AQAD/wEAAv4B/wEAAvYB/wEAAtUB/wEAAosB/wEAAkEB/wNaAfIDSgGLA04B
lQJCAYEB+AIAAeUB/wIAAe8B/wIAAfgB/wIAAf0B/wIAAv8CAAL/AgAC/wIAAf4B/wIAAfYB/wIAAdUB
/wIAAYsB/wIAAUEB/wNaAfIDSgGLA18B0wN+AfwDkwH/A44B/wONAf8DjgH/A44B/wOOAf8DjgH/A44B
/wONAf8DhQH/A2cB/wM0Af8DQQH5A1oBxAFbAV8BWwHTASsBsAErAfwBDgH7AQ4B/wEDAf0BAwH/AQAB
/gEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB/QEAAf8BAAHvAQAB
/wEAAbkBAAH/AQABXQEAAf8DQQH5A1oBxAFbAl8B0wErArAB/AEOAvsB/wEDAv0B/wEAAv4B/wEAA/8B
AAP/AQAD/wEAA/8BAAP/AQAC/QH/AQAC7wH/AQACuQH/AQACXQH/A0EB+QNaAcQCWwFfAdMCKwGwAfwC
DgH7Af8CAwH9Af8CAAH+Af8CAAL/AgAC/wIAAv8CAAL/AgAC/wIAAf0B/wIAAe8B/wIAAbkB/wIAAV0B
/wNBAfkDWgHEA24B9QOAAf4DnwH/A5MB/wOPAf8DjgH/A44B/wOOAf8DjgH/A44B/wOOAf8DiwH/A3cB
/wNIAf8DQAH9A2IB4QFTAXYBUwH1ATwBzwE8Af4BJwH/AScB/wELAf8BCwH/AQEB/wEBAf8BAAH/AQAB
/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB/wEAAf8BAAH5AQAB/wEAAdYBAAH/AQAB
gQEAAf8BMAFAATAB/QNiAeEBUwJ2AfUBPALPAf4BJwP/AQsD/wEBA/8BAAP/AQAD/wEAA/8BAAP/AQAD
/wEAA/8BAAL5Af8BAALWAf8BAAKBAf8BMAJAAf0DYgHhAlMBdgH1AjwBzwH+AicC/wILAv8CAQL/AgAC
/wIAAv8CAAL/AgAC/wIAAv8CAAL/AgAB+QH/AgAB1gH/AgABgQH/AjABQAH9A2IB4QNtAfYDgAH+A6sB
/wOZAf8DkAH/A44B/wOOAf8DjgH/A44B/wOOAf8DjgH/A40B/wN/Af8DVQH/A0AB/QNeAeIBSAF6AUgB
9gFfAc8BXwH+AUIB/wFCAf8BGQH/ARkB/wEEAf8BBAH/AQAB/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB
/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB/QEAAf8BAAHkAQAB/wEAAZgBAAH/ATABQAEwAf0DXgHiAUgC
egH2AV8CzwH+AUID/wEZA/8BBAP/AQAD/wEAA/8BAAP/AQAD/wEAA/8BAAP/AQAC/QH/AQAC5AH/AQAC
mAH/ATACQAH9A14B4gJIAXoB9gJfAc8B/gJCAv8CGQL/AgQC/wIAAv8CAAL/AgAC/wIAAv8CAAL/AgAC
/wIAAf0B/wIAAeQB/wIAAZgB/wIwAUAB/QNeAeIDYQHWA34B/AO4Af8DowH/A5MB/wOOAf8DjgH/A44B
/wOOAf8DjgH/A44B/wONAf8DggH/A1wB/wNNAfoDWgHHAVwBYQFcAdYBZAG0AWQB/AFfAf8BXwH/AS8B
/wEvAf8BDAH/AQwB/wEBAf8BAQH/AQAB/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB/wEAAf8BAAH/AQAB
/wEAAf4BAAH/AQAB6gEAAf8BAAGlAQAB/wEsAU0BLAH6A1oBxwFcAmEB1gFkArQB/AFfA/8BLwP/AQwD
/wEBA/8BAAP/AQAD/wEAA/8BAAP/AQAD/wEAAv4B/wEAAuoB/wEAAqUB/wEsAk0B+gNaAccCXAFhAdYC
ZAG0AfwCXwL/Ai8C/wIMAv8CAQL/AgAC/wIAAv8CAAL/AgAC/wIAAv8CAAH+Af8CAAHqAf8CAAGlAf8C
LAFNAfoDWgHHA1ABmgOIAfkDxQH/A7IB/wOcAf8DkQH/A44B/wOOAf8DjgH/A44B/wOPAf8DjgH/A4MB
/wNgAf8DWgHyA0wBkANQAZoBagGRAWoB+QF8Af8BfAH/AVEB/wFRAf8BHwH/AR8B/wEHAf8BBwH/AQEB
/wEBAf8BAAH/AQAB/wEAAf8BAAH/AQAB/wEAAf8BAgH/AQIB/wECAf4BAgH/AQAB6wEAAf8BAAGtAQAB
/wFaAWsBWgHyA0wBkANQAZoBagKRAfkBfAP/AVED/wEfA/8BBwP/AQED/wEAA/8BAAP/AQAD/wECA/8B
AgL+Af8BAALrAf8BAAKtAf8BWgJrAfIDTAGQA1ABmgJqAZEB+QJ8Av8CUQL/Ah8C/wIHAv8CAQL/AgAC
/wIAAv8CAAL/AgIC/wICAf4B/wIAAesB/wIAAa0B/wJaAWsB8gNMAZADLwFJA2wB6wOgAf4DxgH/A64B
/wOcAf8DkwH/A5AB/wOPAf8DkAH/A5MB/wOTAf8DhQH/A04B/QNgAeADLQFFAy8BSQNsAesBgAHPAYAB
/gF/Af8BfwH/AUkB/wFJAf8BHwH/AR8B/wEMAf8BDAH/AQUB/wEFAf8BAwH/AQMB/wEFAf8BBQH/AQoB
/wEKAf8BCgH+AQoB/wEBAe0BAQH/ATABtgEwAf0BYAFmAWAB4AMtAUUDLwFJA2wB6wGAAs8B/gF/A/8B
SQP/AR8D/wEMA/8BBQP/AQMD/wEFA/8BCgP/AQoC/gH/AQEC7QH/ATACtgH9AWACZgHgAy0BRQMvAUkD
bAHrAoABzwH+An8C/wJJAv8CHwL/AgwC/wIFAv8CAwL/AgUC/wIKAv8CCgH+Af8CAQHtAf8CMAG2Af0C
YAFmAeADLQFFAwMBBANWAa4DcAH1A9kB/wPLAf8DtwH/A6cB/wOdAf8DmgH/A5wB/wOfAf8DmwH/A4kB
/wNoAfADUgGjAwMBBAMDAQQDVgGuAW4BdgFuAfUBqAH/AagB/wGJAf8BiQH/AVwB/wFcAf8BNwH/ATcB
/wEiAf8BIgH/ARsB/wEbAf8BHwH/AR8B/wEmAf8BJgH/AR0B/wEdAf8BBQHzAQUB/wFaAWgBWgHwA1IB
owMDAQQDAwEEA1YBrgFuAnYB9QGoA/8BiQP/AVwD/wE3A/8BIgP/ARsD/wEfA/8BJgP/AR0D/wEFAvMB
/wFaAmgB8ANSAaMDAwEEAwMBBANWAa4CbgF2AfUCqAL/AokC/wJcAv8CNwL/AiIC/wIbAv8CHwL/AiYC
/wIdAv8CBQHzAf8CWgFoAfADUgGjAwMBBAQAAxwBJwNdAccDdwH2A68B/gPXAf8DzAH/A8IB/wO7Af8D
twH/A7EB/wOAAf4DaAH0A1kBvAMbASYIAAMcAScDXQHHAXIBegFyAfYBhwHPAYcB/gGlAf8BpQH/AYsB
/wGLAf8BdAH/AXQB/wFmAf8BZgH/AVwB/wFcAf8BTgH/AU4B/wFIAc8BSAH+AVMBdwFTAfQBVwFZAVcB
vAMbASYIAAMcAScDXQHHAXICegH2AYcCzwH+AaUD/wGLA/8BdAP/AWYD/wFcA/8BTgP/AUgCzwH+AVMC
dwH0AVcCWQG8AxsBJggAAxwBJwNdAccCcgF6AfYChwHPAf4CpQL/AosC/wJ0Av8CZgL/AlwC/wJOAv8C
SAHPAf4CUwF3AfQCVwFZAbwDGwEmDAADIQEwA1kBtgNnAe4DkwH6A74B/QPUAf8DzAH/A74B/QOEAfkD
bAHrA1UBrAMfASwQAAMhATADWQG2AWUBaQFlAe4BfQGfAX0B+gGuAb4BrgH9AZ8B/wGfAf8BjAH/AYwB
/wFeAb4BXgH9AWgBkQFoAfkBYQFsAWEB6wNVAawDHwEsEAADIQEwA1kBtgFlAmkB7gF9Ap8B+gGuAr4B
/QGfA/8BjAP/AV4CvgH9AWgCkQH5AWECbAHrA1UBrAMfASwQAAMhATADWQG2AmUBaQHuAn0BnwH6Aq4B
vgH9Ap8C/wKMAv8CXgG+Af0CaAGRAfkCYQFsAesDVQGsAx8BLBQAAwYBBwM2AVgDVQGsA2YB5QOgAfwD
kwH7A2UB4gNTAacDMwFRAwYBBxgAAwYBBwM2AVgDVQGsA2YB5QF+AbQBfgH8AW0BngFtAfsDZQHiA1MB
pwMzAVEDBgEHGAADBgEHAzYBWANVAawDZgHlAX4CtAH8AW0CngH7A2UB4gNTAacDMwFRAwYBBxgAAwYB
BwM2AVgDVQGsA2YB5QJ+AbQB/AJtAZ4B+wNlAeIDUwGnAzMBUQMGAQcMAAFCAU0BPgcAAT4DAAEoAwAB
QAMAASADAAEBAQABAQYAAQEWAAP/gQAB4AEHAeABBwHgAQcB4AEHAcABAwHAAQMBwAEDAcABAwGAAQEB
gAEBAYABAQGAAQFQAAGAAQEBgAEBAYABAQGAAQEBwAEDAcABAwHAAQMBwAEDAeABBwHgAQcB4AEHAeAB
Bws=
</value>
</data>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
AAABAAEAEA8AAAEACABDBQAAFgAAACgAAAAQAAAAHgAAAAEACAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAA9dN/APXTgAD00XoA9M5wAPPLZwDzyF8A8sZZAPLEVADywk4A8L9FAPC8PAD104AA9dF6APTL
aQD0z3UA9dSDAPC9PwDvtisA7rIeAPTOcgD0zW8A8sRVAPC7OgDsqgkA4J8AAPPJYwDxwUkA4KIMAM2S
AADxwUoAtYEAAO+3LQCbbgAA7K0PAOuoBACneQYAfloAANubAADYmQAAgVsAAGRHAADMkQAAuIMAALqF
AAB2VAAAWT8AAF5CAACpeAAAmm4AAJhsAACfcQAAfFgAAG9VFgCjlncAxr6sAI9mAACCXQAAfVkAAHdU
AABxUAAAa0wAAGRHAABcQgAAVTwAAN3a0QDt7e0A3t7eAOnp6QDi4uIA+fn5APPz8wD09PQA4uLiAM7O
zgD///8A/Pz8APPz8wDc3NwA+OOtAPzy2QD9+e4A/fblAPrpwAD22I8A6+fcAN3d3QDZ2dkA19fXAL6+
vgD+/foA+uvEAPTMawD++/QAoqKiAF9fXwBdXV0AW1tbAFRUVAD///8A+vr6AOPPnADusyIA+eSyAP7+
/gDk5OQA4uLiAN7e3gDo6OgA4+PjALOvpQDLmR4A7awPAPnmuAD9/f0A9/f3AEFBQQA9PT0ANTU1AIiI
iAC9vb0Afnx1ALWHFgDrxGUA39/dAKurqwCjo6MAlpaWAHl5eQDExMQAdHR0AGhbOgDgogkAw6dkAGlp
aABGRkYATExMAEVFRQA6OjoAXFxcAFRMOACgdg0AzJIDALuQJQCJdkgAaGFSAFZVUwBTUEgAXFI2AH9k
IgChdAUAq3oDAKx8BQCoegYAoXQGAJJpAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAABNTgAAAAAAAAAAAAAAAAAAS0wAAAAAAAAAAAAAAABISUoAAAAAAAAA
AAAAAABERUZHAAAAODk6Ozw9Pj9AQUJDAAAAMDEyM5eYmZqbNDU2NwAAKissjo+QkZKTlJWWLS4vACYn
hIWGh4iIiYqLjI0oKQAiI3t8WX1+Xn+AgYKDJCUAIHBxcnN0dXV1dnd4eXohAB5mZ2NoaWpqamtsbW5v
HwAaG1xdXl9gYGFiY2RlHB0AFBUWVFVWV1dYWVpbFxgZAAAMDQ4PT1BRUlMQERITAAAAAAECAwQFBgcI
CQoLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
</value>
</data>
</root>

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

@ -1,6 +1,6 @@
namespace qtc_net_client_2.Forms
{
partial class TokenJackpotSpinner
partial class CurrencyJackpotSpinner
{
/// <summary>
/// Required designer variable.
@ -44,7 +44,7 @@
lblTokensWon.Name = "lblTokensWon";
lblTokensWon.Size = new Size(129, 25);
lblTokensWon.TabIndex = 0;
lblTokensWon.Text = "300 Q's Won";
lblTokensWon.Text = "999 Q's Won";
lblTokensWon.TextAlign = ContentAlignment.MiddleCenter;
//
// btnClaim
@ -97,7 +97,7 @@
MinimizeBox = false;
Name = "TokenJackpotSpinner";
StartPosition = FormStartPosition.CenterScreen;
Text = "QtC.NET Client - Daily Jackpot Spin";
Text = "QtC.NET Client - Jackpot Spin";
FormClosing += TokenJackpotSpinner_FormClosing;
FormClosed += TokenJackpotSpinner_FormClosed;
Load += TokenJackpotSpinner_Load;

View File

@ -11,13 +11,13 @@ using System.Windows.Forms;
namespace qtc_net_client_2.Forms
{
public partial class TokenJackpotSpinner : Form
public partial class CurrencyJackpotSpinner : Form
{
public int TokensWon { get; private set; }
private bool AllowClose = false;
private AudioService _audioService = new();
public TokenJackpotSpinner()
public CurrencyJackpotSpinner()
{
InitializeComponent();
}
@ -61,19 +61,21 @@ namespace qtc_net_client_2.Forms
public async Task StartSpinAnimation(Label label)
{
if (label.IsHandleCreated)
if (IsHandleCreated && label.IsHandleCreated)
{
Random rnd = new Random();
_audioService.PlaySoundEffect("sndTokenSpinLoop");
while (_audioService.OutputDevice?.PlaybackState == NAudio.Wave.PlaybackState.Playing)
{
if (!IsHandleCreated) return;
label.BeginInvoke(delegate () { label.Text = $"{rnd.Next(0, 300)} Q's Won"; });
await Task.Delay(10);
}
var win = rnd.Next(0, 300);
if (!IsHandleCreated) return;
label.BeginInvoke(delegate ()
{
label.Text = $"{win} Q's Won";

View File

@ -38,11 +38,11 @@
// rtxtChat
//
rtxtChat.HideSelection = false;
rtxtChat.Location = new Point(12, 56);
rtxtChat.Location = new Point(12, 48);
rtxtChat.Margin = new Padding(4, 3, 4, 3);
rtxtChat.Name = "rtxtChat";
rtxtChat.ReadOnly = true;
rtxtChat.Size = new Size(593, 317);
rtxtChat.Size = new Size(593, 325);
rtxtChat.TabIndex = 6;
rtxtChat.Text = "";
//
@ -74,7 +74,7 @@
//
lblUsername.AutoSize = true;
lblUsername.Font = new Font("Segoe UI", 25F, FontStyle.Bold | FontStyle.Italic);
lblUsername.Location = new Point(6, 7);
lblUsername.Location = new Point(6, 0);
lblUsername.Margin = new Padding(4, 0, 4, 0);
lblUsername.Name = "lblUsername";
lblUsername.Size = new Size(181, 46);
@ -86,7 +86,7 @@
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
BackColor = Color.DodgerBlue;
ClientSize = new Size(618, 450);
ClientSize = new Size(618, 443);
Controls.Add(lblUsername);
Controls.Add(rtxtChat);
Controls.Add(btnSend);

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");
@ -51,9 +56,9 @@ namespace qtc_net_client_2.Forms
private async void btnSend_Click(object sender, EventArgs e)
{
if (InvokeRequired)
if (rtxtChatbox.InvokeRequired)
{
await Invoke(async delegate ()
await rtxtChatbox.Invoke(async delegate ()
{
if (!string.IsNullOrEmpty(rtxtChatbox.Text))
{
@ -69,8 +74,8 @@ 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 });
BeginInvoke(delegate () { Messages.Add($"[{_apiService.CurrentUser.Username}] {rtxtChatbox.Text}\n"); });
BeginInvoke(delegate () { rtxtChatbox.Clear(); });
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

@ -117,32 +117,20 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
AAABAAEAEA8AAAEACABDBQAAFgAAACgAAAAQAAAAHgAAAAEACAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAA9dN/APXTgAD00XoA9M5wAPPLZwDzyF8A8sZZAPLEVADywk4A8L9FAPC8PAD104AA9dF6APTL
aQD0z3UA9dSDAPC9PwDvtisA7rIeAPTOcgD0zW8A8sRVAPC7OgDsqgkA4J8AAPPJYwDxwUkA4KIMAM2S
AADxwUoAtYEAAO+3LQCbbgAA7K0PAOuoBACneQYAfloAANubAADYmQAAgVsAAGRHAADMkQAAuIMAALqF
AAB2VAAAWT8AAF5CAACpeAAAmm4AAJhsAACfcQAAfFgAAG9VFgCjlncAxr6sAI9mAACCXQAAfVkAAHdU
AABxUAAAa0wAAGRHAABcQgAAVTwAAN3a0QDt7e0A3t7eAOnp6QDi4uIA+fn5APPz8wD09PQA4uLiAM7O
zgD///8A/Pz8APPz8wDc3NwA+OOtAPzy2QD9+e4A/fblAPrpwAD22I8A6+fcAN3d3QDZ2dkA19fXAL6+
vgD+/foA+uvEAPTMawD++/QAoqKiAF9fXwBdXV0AW1tbAFRUVAD///8A+vr6AOPPnADusyIA+eSyAP7+
/gDk5OQA4uLiAN7e3gDo6OgA4+PjALOvpQDLmR4A7awPAPnmuAD9/f0A9/f3AEFBQQA9PT0ANTU1AIiI
iAC9vb0Afnx1ALWHFgDrxGUA39/dAKurqwCjo6MAlpaWAHl5eQDExMQAdHR0AGhbOgDgogkAw6dkAGlp
aABGRkYATExMAEVFRQA6OjoAXFxcAFRMOACgdg0AzJIDALuQJQCJdkgAaGFSAFZVUwBTUEgAXFI2AH9k
IgChdAUAq3oDAKx8BQCoegYAoXQGAJJpAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAABNTgAAAAAAAAAAAAAAAAAAS0wAAAAAAAAAAAAAAABISUoAAAAAAAAA
AAAAAABERUZHAAAAODk6Ozw9Pj9AQUJDAAAAMDEyM5eYmZqbNDU2NwAAKissjo+QkZKTlJWWLS4vACYn
hIWGh4iIiYqLjI0oKQAiI3t8WX1+Xn+AgYKDJCUAIHBxcnN0dXV1dnd4eXohAB5mZ2NoaWpqamtsbW5v
HwAaG1xdXl9gYGFiY2RlHB0AFBUWVFVWV1dYWVpbFxgZAAAMDQ4PT1BRUlMQERITAAAAAAECAwQFBgcI
CQoLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
</value>
<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

@ -0,0 +1,135 @@
namespace qtc_net_client_2.Forms
{
partial class GuessTheNumber
{
/// <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()
{
lblHeader = new Label();
nudNumberGuess = new NumericUpDown();
btnGuess = new Button();
lblPrizeInfo = new Label();
lblResult = new Label();
btnClaimPrize = new Button();
((System.ComponentModel.ISupportInitialize)nudNumberGuess).BeginInit();
SuspendLayout();
//
// lblHeader
//
lblHeader.Font = new Font("Segoe UI", 15F, FontStyle.Bold);
lblHeader.Location = new Point(12, 9);
lblHeader.Name = "lblHeader";
lblHeader.Size = new Size(486, 62);
lblHeader.TabIndex = 0;
lblHeader.Text = "Please Wait, Getting Number...";
lblHeader.TextAlign = ContentAlignment.MiddleCenter;
//
// nudNumberGuess
//
nudNumberGuess.Location = new Point(190, 127);
nudNumberGuess.Maximum = new decimal(new int[] { 500, 0, 0, 0 });
nudNumberGuess.Name = "nudNumberGuess";
nudNumberGuess.Size = new Size(120, 23);
nudNumberGuess.TabIndex = 1;
//
// btnGuess
//
btnGuess.ForeColor = Color.Black;
btnGuess.Location = new Point(225, 156);
btnGuess.Name = "btnGuess";
btnGuess.Size = new Size(46, 23);
btnGuess.TabIndex = 2;
btnGuess.Text = "Guess";
btnGuess.UseVisualStyleBackColor = true;
btnGuess.Click += btnGuess_Click;
//
// lblPrizeInfo
//
lblPrizeInfo.Font = new Font("Segoe UI", 9F, FontStyle.Bold);
lblPrizeInfo.ForeColor = Color.Black;
lblPrizeInfo.Location = new Point(142, 180);
lblPrizeInfo.Name = "lblPrizeInfo";
lblPrizeInfo.Size = new Size(212, 63);
lblPrizeInfo.TabIndex = 3;
lblPrizeInfo.Text = "Prize Possibilities:\r\nGuess Is Higher By 10 - Jackpot Spin\r\nGuess Is Lower By 10 - 200 Q's\r\nGuess Is Equal - Jackpot";
lblPrizeInfo.TextAlign = ContentAlignment.TopCenter;
//
// lblResult
//
lblResult.Font = new Font("Segoe UI", 9F, FontStyle.Bold);
lblResult.Location = new Point(12, 81);
lblResult.Name = "lblResult";
lblResult.Size = new Size(477, 15);
lblResult.TabIndex = 4;
lblResult.Text = "lblResult";
lblResult.TextAlign = ContentAlignment.MiddleCenter;
lblResult.Visible = false;
//
// btnClaimPrize
//
btnClaimPrize.ForeColor = Color.Black;
btnClaimPrize.Location = new Point(200, 100);
btnClaimPrize.Name = "btnClaimPrize";
btnClaimPrize.Size = new Size(99, 23);
btnClaimPrize.TabIndex = 5;
btnClaimPrize.Text = "PRIZE_CLAIM";
btnClaimPrize.UseVisualStyleBackColor = true;
btnClaimPrize.Visible = false;
//
// GuessTheNumber
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
BackColor = Color.DodgerBlue;
ClientSize = new Size(511, 253);
Controls.Add(btnClaimPrize);
Controls.Add(lblResult);
Controls.Add(lblPrizeInfo);
Controls.Add(btnGuess);
Controls.Add(nudNumberGuess);
Controls.Add(lblHeader);
ForeColor = Color.White;
FormBorderStyle = FormBorderStyle.FixedDialog;
MaximizeBox = false;
MinimizeBox = false;
Name = "GuessTheNumber";
StartPosition = FormStartPosition.CenterScreen;
Text = "QtC.NET qGame - Guess The Number";
Load += GuessTheNumber_Load;
((System.ComponentModel.ISupportInitialize)nudNumberGuess).EndInit();
ResumeLayout(false);
}
#endregion
private Label lblHeader;
private NumericUpDown nudNumberGuess;
private Button btnGuess;
private Label lblPrizeInfo;
private Label lblResult;
private Button btnClaimPrize;
}
}

View File

@ -0,0 +1,125 @@
using QtCNETAPI.Services.ApiService;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace qtc_net_client_2.Forms
{
public partial class GuessTheNumber : Form
{
private IApiService _apiService;
private int _number;
private int _guesses;
public GuessTheNumber(IApiService apiService)
{
_apiService = apiService;
InitializeComponent();
}
private async void GuessTheNumber_Load(object sender, EventArgs e) => await InitializeGTNGame();
private async void btnGuess_Click(object sender, EventArgs e)
{
if(nudNumberGuess.Value > 0)
{
btnGuess.Enabled = false;
int guess = int.Parse(nudNumberGuess.Value.ToString());
var result = await _apiService.GuessRandomNumber(_number, guess);
if(result != null && result.Success)
{
switch(result.Data)
{
case QtCNETAPI.Enums.NumberGuessResult.Higher:
lblResult.Text = "Your Guess Was Higher By 10! You've Earned A Jackpot Spin!";
btnClaimPrize.Text = "Spin";
btnClaimPrize.Click += BtnClaimPrize_Click_JackpotSpin;
lblResult.Visible = true;
btnClaimPrize.Visible = true;
break;
case QtCNETAPI.Enums.NumberGuessResult.Lower:
lblResult.Text = "Your Guess Was Lower By 10! You've Earned 200 Q's!";
btnClaimPrize.Text = "Claim";
btnClaimPrize.Click += BtnClaimPrize_Click_QClaim;
lblResult.Visible = true;
btnClaimPrize.Visible = true;
break;
case QtCNETAPI.Enums.NumberGuessResult.Correct:
lblResult.Text = "Your Correct! You've Earned The Jackpot!";
btnClaimPrize.Text = "Claim";
btnClaimPrize.Click += BtnClaimPrize_Click_QClaimJackpot;
lblResult.Visible = true;
btnClaimPrize.Visible = true;
break;
case QtCNETAPI.Enums.NumberGuessResult.Incorrect:
if (_guesses >= 2)
{
lblResult.Text = "Unfortunately you've used up all your guesses for this round.";
lblResult.Visible = true;
btnGuess.Visible = false;
break;
}
else _guesses++;
lblResult.Text = $"Incorrect. You Get Three Chances. You Guessed {_guesses} Time(s)";
lblResult.Visible = true;
btnGuess.Enabled = true;
break;
}
}
}
}
private void BtnClaimPrize_Click_QClaimJackpot(object? sender, EventArgs e)
{
throw new NotImplementedException("need to figure out how to do this, sorry :)");
}
private async void BtnClaimPrize_Click_QClaim(object? sender, EventArgs e)
{
var result = await _apiService.AddCurrencyToCurrentUser(200, false);
if (result != null && result.Success) Close();
else
{
MessageBox.Show("We Weren't Able To Claim Your Prize. You Can Try Again.", "Uh Oh...", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private async void BtnClaimPrize_Click_JackpotSpin(object? sender, EventArgs e)
{
CurrencyJackpotSpinner tokenJackpotSpinner = new CurrencyJackpotSpinner();
var formResult = tokenJackpotSpinner.ShowDialog();
if (formResult == DialogResult.OK)
{
var result = await _apiService.AddCurrencyToCurrentUser(tokenJackpotSpinner.TokensWon, false);
if (result != null && result.Success) Close();
else
{
MessageBox.Show("We Weren't Able To Claim Your Prize. You Can Try Again.", "Uh Oh...", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
private async Task InitializeGTNGame()
{
var result = await _apiService.GetRandomNumber();
if (result != null && result.Success)
{
_number = result.Data;
lblHeader.Text = "I've picked a number between 1 and 500.\nWhat Is It?";
}
}
}
}

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

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

@ -30,52 +30,90 @@
{
components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Main));
ListViewItem listViewItem1 = new ListViewItem("Stock Market", 0);
ListViewItem listViewItem2 = new ListViewItem("Guess The Number", 1);
ListViewItem listViewItem3 = new ListViewItem("Tic-Tac-Toe (Multiplayer)", "Tic-tac-toe.png");
tbcMain = new TabControl();
tbpContacts = new TabPage();
lvContacts = new ListView();
ctxmRefresh = new ContextMenuStrip(components);
refreshToolStripMenuItem = new ToolStripMenuItem();
ilProfilePics = new ImageList(components);
tbpRooms = new TabPage();
lbRooms = new ListBox();
tbpOnlineUsers = new TabPage();
lbOnlineUsers = new ListBox();
tbpUsers = new TabPage();
lvUserDirectory = new ListView();
ilStatusIcons = new ImageList(components);
tbpGames = new TabPage();
lvGames = new ListView();
ilGames = new ImageList(components);
tbpStore = new TabPage();
lvStoreItems = new ListView();
ilStoreThumbnails = new ImageList(components);
ilTabIcons = new ImageList(components);
pbUserPfp = new PictureBox();
ctxmChangeStatus = new ContextMenuStrip(components);
onlineToolStripMenuItem = new ToolStripMenuItem();
awayToolStripMenuItem = new ToolStripMenuItem();
doNotDisturbToolStripMenuItem = new ToolStripMenuItem();
invisibleToolStripMenuItem = new ToolStripMenuItem();
lblWelcome = new Label();
llblSignIn = new LinkLabel();
llblSignOut = new LinkLabel();
llblEditProfile = new LinkLabel();
lblRequestNotif = new Label();
niMain = new NotifyIcon(components);
btnAddRoom = new Button();
llblClaimSpin = new LinkLabel();
pCurrencyArea = new Panel();
pbCurrencyIcon = new PictureBox();
lblCurrencyAmount = new Label();
llblClaimSpin = new LinkLabel();
pCurrentUser = new Panel();
llblEditProfile = new LinkLabel();
llblSignOut = new LinkLabel();
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();
tbpOnlineUsers.SuspendLayout();
((System.ComponentModel.ISupportInitialize)pbUserPfp).BeginInit();
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
//
tbcMain.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
tbcMain.Controls.Add(tbpContacts);
tbcMain.Controls.Add(tbpRooms);
tbcMain.Controls.Add(tbpOnlineUsers);
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(258, 535);
tbcMain.Size = new Size(352, 499);
tbcMain.TabIndex = 0;
tbcMain.SelectedIndexChanged += tbcMain_SelectedIndexChanged;
//
// tbpContacts
//
@ -84,29 +122,46 @@
tbpContacts.Location = new Point(4, 24);
tbpContacts.Name = "tbpContacts";
tbpContacts.Padding = new Padding(3);
tbpContacts.Size = new Size(250, 507);
tbpContacts.Size = new Size(344, 471);
tbpContacts.TabIndex = 0;
tbpContacts.Text = "Contacts";
tbpContacts.UseVisualStyleBackColor = true;
//
// lvContacts
//
lvContacts.Alignment = ListViewAlignment.Left;
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(250, 507);
lvContacts.Size = new Size(344, 478);
lvContacts.SmallImageList = ilProfilePics;
lvContacts.TabIndex = 1;
lvContacts.UseCompatibleStateImageBehavior = false;
lvContacts.View = View.SmallIcon;
lvContacts.DoubleClick += lvContacts_DoubleClick;
//
// ctxmRefresh
//
ctxmRefresh.Items.AddRange(new ToolStripItem[] { refreshToolStripMenuItem });
ctxmRefresh.Name = "ctxmRefresh";
ctxmRefresh.Size = new Size(114, 26);
//
// refreshToolStripMenuItem
//
refreshToolStripMenuItem.Name = "refreshToolStripMenuItem";
refreshToolStripMenuItem.Size = new Size(113, 22);
refreshToolStripMenuItem.Text = "Refresh";
refreshToolStripMenuItem.Click += refreshToolStripMenuItem_Click;
//
// ilProfilePics
//
ilProfilePics.ColorDepth = ColorDepth.Depth32Bit;
ilProfilePics.ImageSize = new Size(32, 32);
ilProfilePics.ImageStream = (ImageListStreamer)resources.GetObject("ilProfilePics.ImageStream");
ilProfilePics.TransparentColor = Color.Transparent;
ilProfilePics.Images.SetKeyName(0, "DEFAULT");
//
// tbpRooms
//
@ -115,42 +170,125 @@
tbpRooms.Location = new Point(4, 24);
tbpRooms.Name = "tbpRooms";
tbpRooms.Padding = new Padding(3);
tbpRooms.Size = new Size(250, 507);
tbpRooms.Size = new Size(344, 471);
tbpRooms.TabIndex = 1;
tbpRooms.Text = "Rooms";
tbpRooms.UseVisualStyleBackColor = true;
//
// lbRooms
//
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(250, 514);
lbRooms.Size = new Size(344, 484);
lbRooms.TabIndex = 0;
lbRooms.DoubleClick += lbRooms_DoubleClick;
//
// tbpOnlineUsers
// tbpUsers
//
tbpOnlineUsers.Controls.Add(lbOnlineUsers);
tbpOnlineUsers.ImageIndex = 0;
tbpOnlineUsers.Location = new Point(4, 24);
tbpOnlineUsers.Name = "tbpOnlineUsers";
tbpOnlineUsers.Padding = new Padding(3);
tbpOnlineUsers.Size = new Size(250, 507);
tbpOnlineUsers.TabIndex = 2;
tbpOnlineUsers.Text = "Online Users";
tbpOnlineUsers.UseVisualStyleBackColor = true;
tbpUsers.Controls.Add(lvUserDirectory);
tbpUsers.ImageIndex = 1;
tbpUsers.Location = new Point(4, 24);
tbpUsers.Name = "tbpUsers";
tbpUsers.Size = new Size(344, 471);
tbpUsers.TabIndex = 3;
tbpUsers.Text = "Users";
tbpUsers.UseVisualStyleBackColor = true;
//
// lbOnlineUsers
// lvUserDirectory
//
lbOnlineUsers.FormattingEnabled = true;
lbOnlineUsers.ItemHeight = 15;
lbOnlineUsers.Location = new Point(0, 0);
lbOnlineUsers.Name = "lbOnlineUsers";
lbOnlineUsers.Size = new Size(250, 514);
lbOnlineUsers.TabIndex = 0;
lbOnlineUsers.DoubleClick += lbOnlineUsers_DoubleClick;
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(344, 484);
lvUserDirectory.SmallImageList = ilStatusIcons;
lvUserDirectory.TabIndex = 0;
lvUserDirectory.UseCompatibleStateImageBehavior = false;
lvUserDirectory.View = View.SmallIcon;
lvUserDirectory.DoubleClick += lbUserDirectory_DoubleClick;
//
// ilStatusIcons
//
ilStatusIcons.ColorDepth = ColorDepth.Depth32Bit;
ilStatusIcons.ImageStream = (ImageListStreamer)resources.GetObject("ilStatusIcons.ImageStream");
ilStatusIcons.TransparentColor = Color.Transparent;
ilStatusIcons.Images.SetKeyName(0, "Offline");
ilStatusIcons.Images.SetKeyName(1, "Online");
ilStatusIcons.Images.SetKeyName(2, "Away");
ilStatusIcons.Images.SetKeyName(3, "DND");
//
// tbpGames
//
tbpGames.Controls.Add(lvGames);
tbpGames.ImageIndex = 3;
tbpGames.Location = new Point(4, 24);
tbpGames.Name = "tbpGames";
tbpGames.Size = new Size(344, 471);
tbpGames.TabIndex = 4;
tbpGames.Text = "Games";
tbpGames.UseVisualStyleBackColor = true;
//
// lvGames
//
lvGames.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
listViewItem1.Tag = "StockMarketGame";
listViewItem2.Tag = "GuessTheNumberGame";
listViewItem3.Tag = "TicTacToeGame";
lvGames.Items.AddRange(new ListViewItem[] { listViewItem1, listViewItem2, listViewItem3 });
lvGames.LargeImageList = ilGames;
lvGames.Location = new Point(0, 0);
lvGames.MultiSelect = false;
lvGames.Name = "lvGames";
lvGames.Size = new Size(344, 484);
lvGames.SmallImageList = ilGames;
lvGames.TabIndex = 2;
lvGames.UseCompatibleStateImageBehavior = false;
lvGames.DoubleClick += lvGames_DoubleClick;
//
// ilGames
//
ilGames.ColorDepth = ColorDepth.Depth32Bit;
ilGames.ImageStream = (ImageListStreamer)resources.GetObject("ilGames.ImageStream");
ilGames.TransparentColor = Color.Transparent;
ilGames.Images.SetKeyName(0, "dollar-money.gif");
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
//
@ -158,20 +296,9 @@
ilTabIcons.ImageStream = (ImageListStreamer)resources.GetObject("ilTabIcons.ImageStream");
ilTabIcons.TransparentColor = Color.Transparent;
ilTabIcons.Images.SetKeyName(0, "ContactsIcon.png");
ilTabIcons.Images.SetKeyName(1, "RoomsChatIcon.png");
//
// pbUserPfp
//
pbUserPfp.BorderStyle = BorderStyle.FixedSingle;
pbUserPfp.ContextMenuStrip = ctxmChangeStatus;
pbUserPfp.Image = Properties.Resources.DefaultPfp;
pbUserPfp.Location = new Point(12, 10);
pbUserPfp.Name = "pbUserPfp";
pbUserPfp.Size = new Size(51, 50);
pbUserPfp.SizeMode = PictureBoxSizeMode.StretchImage;
pbUserPfp.TabIndex = 1;
pbUserPfp.TabStop = false;
pbUserPfp.Click += pbUserPfp_Click;
ilTabIcons.Images.SetKeyName(1, "UserIcon.png");
ilTabIcons.Images.SetKeyName(2, "RoomsChatIcon.png");
ilTabIcons.Images.SetKeyName(3, "CurrencyIcon.png");
//
// ctxmChangeStatus
//
@ -208,118 +335,29 @@
invisibleToolStripMenuItem.Size = new Size(153, 22);
invisibleToolStripMenuItem.Text = "Invisible";
//
// lblWelcome
//
lblWelcome.AutoSize = true;
lblWelcome.Font = new Font("Segoe UI Light", 9F);
lblWelcome.ForeColor = SystemColors.ControlLightLight;
lblWelcome.Location = new Point(65, 17);
lblWelcome.Name = "lblWelcome";
lblWelcome.Size = new Size(60, 15);
lblWelcome.TabIndex = 2;
lblWelcome.Text = "Welcome, ";
//
// llblSignIn
//
llblSignIn.AutoSize = true;
llblSignIn.Font = new Font("Segoe UI Light", 9F);
llblSignIn.LinkColor = Color.White;
llblSignIn.Location = new Point(118, 17);
llblSignIn.Name = "llblSignIn";
llblSignIn.Size = new Size(40, 15);
llblSignIn.TabIndex = 3;
llblSignIn.TabStop = true;
llblSignIn.Text = "Sign In";
llblSignIn.LinkClicked += llblSignIn_LinkClicked;
//
// llblSignOut
//
llblSignOut.AutoSize = true;
llblSignOut.Font = new Font("Segoe UI Light", 9F);
llblSignOut.LinkColor = Color.White;
llblSignOut.Location = new Point(65, 35);
llblSignOut.Name = "llblSignOut";
llblSignOut.Size = new Size(50, 15);
llblSignOut.TabIndex = 4;
llblSignOut.TabStop = true;
llblSignOut.Text = "Sign Out";
llblSignOut.Visible = false;
llblSignOut.LinkClicked += llblSignOut_LinkClicked;
//
// llblEditProfile
//
llblEditProfile.AutoSize = true;
llblEditProfile.Font = new Font("Segoe UI Light", 9F);
llblEditProfile.LinkColor = Color.White;
llblEditProfile.Location = new Point(113, 35);
llblEditProfile.Name = "llblEditProfile";
llblEditProfile.Size = new Size(60, 15);
llblEditProfile.TabIndex = 5;
llblEditProfile.TabStop = true;
llblEditProfile.Text = "Edit Profile";
llblEditProfile.Visible = false;
llblEditProfile.LinkClicked += llblEditProfile_LinkClicked;
//
// lblRequestNotif
//
lblRequestNotif.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
lblRequestNotif.AutoSize = true;
lblRequestNotif.Font = new Font("Segoe UI", 7F, FontStyle.Bold);
lblRequestNotif.Location = new Point(128, 54);
lblRequestNotif.Font = new Font("Segoe UI", 6F, FontStyle.Bold);
lblRequestNotif.Location = new Point(12, 67);
lblRequestNotif.Name = "lblRequestNotif";
lblRequestNotif.Size = new Size(138, 12);
lblRequestNotif.Size = new Size(109, 11);
lblRequestNotif.TabIndex = 6;
lblRequestNotif.Text = "You Have Contacts Requests!";
lblRequestNotif.Text = "You Have Contact Requests!";
lblRequestNotif.Visible = false;
//
// niMain
//
niMain.Icon = (Icon)resources.GetObject("niMain.Icon");
niMain.Text = "QtC.NET Client";
niMain.Visible = true;
niMain.DoubleClick += niMain_DoubleClick;
//
// btnAddRoom
//
btnAddRoom.FlatAppearance.BorderSize = 0;
btnAddRoom.FlatStyle = FlatStyle.Flat;
btnAddRoom.Location = new Point(260, 1);
btnAddRoom.Name = "btnAddRoom";
btnAddRoom.Size = new Size(20, 22);
btnAddRoom.TabIndex = 7;
btnAddRoom.UseVisualStyleBackColor = true;
btnAddRoom.Click += btnAddRoom_Click;
//
// pbCurrencyIcon
//
pbCurrencyIcon.Image = Properties.Resources.CurrencyIcon;
pbCurrencyIcon.Location = new Point(223, 5);
pbCurrencyIcon.Name = "pbCurrencyIcon";
pbCurrencyIcon.Size = new Size(15, 14);
pbCurrencyIcon.SizeMode = PictureBoxSizeMode.StretchImage;
pbCurrencyIcon.TabIndex = 9;
pbCurrencyIcon.TabStop = false;
pbCurrencyIcon.Visible = false;
//
// lblCurrencyAmount
//
lblCurrencyAmount.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
lblCurrencyAmount.AutoSize = true;
lblCurrencyAmount.BackColor = Color.Transparent;
lblCurrencyAmount.Font = new Font("Segoe UI", 9F, FontStyle.Bold);
lblCurrencyAmount.ForeColor = Color.White;
lblCurrencyAmount.Location = new Point(239, 5);
lblCurrencyAmount.Name = "lblCurrencyAmount";
lblCurrencyAmount.Size = new Size(14, 15);
lblCurrencyAmount.TabIndex = 10;
lblCurrencyAmount.Text = "0";
lblCurrencyAmount.TextAlign = ContentAlignment.MiddleCenter;
lblCurrencyAmount.Visible = false;
//
// llblClaimSpin
//
llblClaimSpin.AutoSize = true;
llblClaimSpin.Font = new Font("Segoe UI", 9F, FontStyle.Bold);
llblClaimSpin.Location = new Point(185, 35);
llblClaimSpin.Location = new Point(1, 25);
llblClaimSpin.Name = "llblClaimSpin";
llblClaimSpin.Size = new Size(94, 15);
llblClaimSpin.TabIndex = 11;
@ -328,23 +366,231 @@
llblClaimSpin.Visible = false;
llblClaimSpin.LinkClicked += llblClaimSpin_LinkClicked;
//
// pCurrencyArea
//
pCurrencyArea.Anchor = AnchorStyles.Top | AnchorStyles.Right;
pCurrencyArea.BorderStyle = BorderStyle.FixedSingle;
pCurrencyArea.Controls.Add(llblClaimSpin);
pCurrencyArea.Controls.Add(pbCurrencyIcon);
pCurrencyArea.Controls.Add(lblCurrencyAmount);
pCurrencyArea.Location = new Point(270, 5);
pCurrencyArea.Name = "pCurrencyArea";
pCurrencyArea.Size = new Size(95, 46);
pCurrencyArea.TabIndex = 12;
//
// pbCurrencyIcon
//
pbCurrencyIcon.Image = Properties.Resources.CurrencyIcon;
pbCurrencyIcon.Location = new Point(20, 6);
pbCurrencyIcon.Name = "pbCurrencyIcon";
pbCurrencyIcon.Size = new Size(15, 14);
pbCurrencyIcon.SizeMode = PictureBoxSizeMode.StretchImage;
pbCurrencyIcon.TabIndex = 18;
pbCurrencyIcon.TabStop = false;
pbCurrencyIcon.Visible = false;
//
// lblCurrencyAmount
//
lblCurrencyAmount.BackColor = Color.Transparent;
lblCurrencyAmount.Font = new Font("Segoe UI", 9F, FontStyle.Bold);
lblCurrencyAmount.ForeColor = Color.White;
lblCurrencyAmount.Location = new Point(35, 3);
lblCurrencyAmount.Name = "lblCurrencyAmount";
lblCurrencyAmount.Size = new Size(69, 20);
lblCurrencyAmount.TabIndex = 19;
lblCurrencyAmount.Text = "99999";
lblCurrencyAmount.TextAlign = ContentAlignment.MiddleLeft;
lblCurrencyAmount.Visible = false;
//
// pCurrentUser
//
pCurrentUser.BorderStyle = BorderStyle.FixedSingle;
pCurrentUser.Controls.Add(llblEditProfile);
pCurrentUser.Controls.Add(llblSignOut);
pCurrentUser.Controls.Add(llblSignIn);
pCurrentUser.Controls.Add(lblWelcome);
pCurrentUser.Controls.Add(pbUserPfp);
pCurrentUser.Location = new Point(5, 4);
pCurrentUser.Name = "pCurrentUser";
pCurrentUser.Size = new Size(204, 59);
pCurrentUser.TabIndex = 13;
//
// llblEditProfile
//
llblEditProfile.AutoSize = true;
llblEditProfile.Font = new Font("Segoe UI Light", 9F);
llblEditProfile.LinkColor = Color.White;
llblEditProfile.Location = new Point(104, 29);
llblEditProfile.Name = "llblEditProfile";
llblEditProfile.Size = new Size(60, 15);
llblEditProfile.TabIndex = 10;
llblEditProfile.TabStop = true;
llblEditProfile.Text = "Edit Profile";
llblEditProfile.Visible = false;
llblEditProfile.LinkClicked += llblEditProfile_LinkClicked;
//
// llblSignOut
//
llblSignOut.AutoSize = true;
llblSignOut.Font = new Font("Segoe UI Light", 9F);
llblSignOut.LinkColor = Color.White;
llblSignOut.Location = new Point(56, 29);
llblSignOut.Name = "llblSignOut";
llblSignOut.Size = new Size(50, 15);
llblSignOut.TabIndex = 9;
llblSignOut.TabStop = true;
llblSignOut.Text = "Sign Out";
llblSignOut.Visible = false;
llblSignOut.LinkClicked += llblSignOut_LinkClicked;
//
// llblSignIn
//
llblSignIn.AutoSize = true;
llblSignIn.Font = new Font("Segoe UI Light", 9F);
llblSignIn.LinkColor = Color.White;
llblSignIn.Location = new Point(109, 11);
llblSignIn.Name = "llblSignIn";
llblSignIn.Size = new Size(40, 15);
llblSignIn.TabIndex = 8;
llblSignIn.TabStop = true;
llblSignIn.Text = "Sign In";
llblSignIn.LinkClicked += llblSignIn_LinkClicked;
//
// lblWelcome
//
lblWelcome.AutoEllipsis = true;
lblWelcome.Font = new Font("Segoe UI Light", 9F);
lblWelcome.ForeColor = SystemColors.ControlLightLight;
lblWelcome.Location = new Point(56, 11);
lblWelcome.Name = "lblWelcome";
lblWelcome.Size = new Size(143, 15);
lblWelcome.TabIndex = 7;
lblWelcome.Text = "Welcome, ";
//
// pbUserPfp
//
pbUserPfp.BorderStyle = BorderStyle.FixedSingle;
pbUserPfp.ContextMenuStrip = ctxmChangeStatus;
pbUserPfp.Cursor = Cursors.Hand;
pbUserPfp.Image = Properties.Resources.DefaultPfp;
pbUserPfp.Location = new Point(3, 4);
pbUserPfp.Name = "pbUserPfp";
pbUserPfp.Size = new Size(51, 50);
pbUserPfp.SizeMode = PictureBoxSizeMode.StretchImage;
pbUserPfp.TabIndex = 6;
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(282, 613);
Controls.Add(llblClaimSpin);
Controls.Add(pbCurrencyIcon);
Controls.Add(lblCurrencyAmount);
Controls.Add(btnAddRoom);
ClientSize = new Size(376, 594);
Controls.Add(pbDonate);
Controls.Add(pCurrentUser);
Controls.Add(pCurrencyArea);
Controls.Add(lblRequestNotif);
Controls.Add(llblEditProfile);
Controls.Add(llblSignOut);
Controls.Add(llblSignIn);
Controls.Add(lblWelcome);
Controls.Add(pbUserPfp);
Controls.Add(tbcMain);
Controls.Add(lblConnectionLost);
FormBorderStyle = FormBorderStyle.FixedDialog;
Icon = (Icon)resources.GetObject("$this.Icon");
MaximizeBox = false;
@ -356,11 +602,21 @@
Resize += frmMain_Resize;
tbcMain.ResumeLayout(false);
tbpContacts.ResumeLayout(false);
ctxmRefresh.ResumeLayout(false);
tbpRooms.ResumeLayout(false);
tbpOnlineUsers.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)pbUserPfp).EndInit();
tbpUsers.ResumeLayout(false);
tbpGames.ResumeLayout(false);
tbpStore.ResumeLayout(false);
ctxmChangeStatus.ResumeLayout(false);
pCurrencyArea.ResumeLayout(false);
pCurrencyArea.PerformLayout();
((System.ComponentModel.ISupportInitialize)pbCurrencyIcon).EndInit();
pCurrentUser.ResumeLayout(false);
pCurrentUser.PerformLayout();
((System.ComponentModel.ISupportInitialize)pbUserPfp).EndInit();
((System.ComponentModel.ISupportInitialize)pbDonate).EndInit();
ctxmAdminUserList.ResumeLayout(false);
ctxmAdminRoomList.ResumeLayout(false);
ResumeLayout(false);
PerformLayout();
}
@ -371,16 +627,9 @@
private TabPage tbpContacts;
private TabPage tbpRooms;
private ListBox lbRooms;
private PictureBox pbUserPfp;
private Label lblWelcome;
private LinkLabel llblSignIn;
private LinkLabel llblSignOut;
private TabPage tbpOnlineUsers;
private ListBox lbOnlineUsers;
private LinkLabel llblEditProfile;
private Label lblRequestNotif;
private ListView lvContacts;
private ImageList ilProfilePics;
private System.Windows.Forms.ImageList ilProfilePics;
private NotifyIcon niMain;
private ImageList ilTabIcons;
private ContextMenuStrip ctxmChangeStatus;
@ -388,9 +637,39 @@
private ToolStripMenuItem awayToolStripMenuItem;
private ToolStripMenuItem doNotDisturbToolStripMenuItem;
private ToolStripMenuItem invisibleToolStripMenuItem;
private Button btnAddRoom;
private LinkLabel llblClaimSpin;
private Panel pCurrencyArea;
private Panel pCurrentUser;
private LinkLabel llblEditProfile;
private LinkLabel llblSignOut;
private LinkLabel llblSignIn;
private Label lblWelcome;
private PictureBox pbUserPfp;
private TabPage tbpGames;
private ListView lvGames;
private ImageList ilGames;
private PictureBox pbCurrencyIcon;
private Label lblCurrencyAmount;
private LinkLabel llblClaimSpin;
private ContextMenuStrip ctxmRefresh;
private ToolStripMenuItem refreshToolStripMenuItem;
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,21 +16,31 @@ namespace qtc_net_client_2
{
private IApiService _apiService;
private IGatewayService _gatewayService;
private AudioService AudioService = new();
private List<Room> RoomList = [];
private List<UserInformationDto> OnlineUsers = [];
private List<UserInformationDto> Contacts = [];
public Main(IApiService apiService, IGatewayService gatewayService)
private Config _config;
private ServerConfig _serverConfig;
private AudioService AudioService = new();
private LoggingService LoggingService;
public List<Room> RoomList = [];
public List<UserInformationDto> UserDirectory = [];
public List<Contact> Contacts = [];
private bool FirstMinimize = true;
public Main(IApiService apiService, IGatewayService gatewayService, Config config, LoggingService loggingService)
{
_apiService = apiService;
_gatewayService = gatewayService;
_config = config;
LoggingService = loggingService;
InitializeComponent();
}
private async void frmMain_Load(object sender, EventArgs e)
{
LoggingService.LogString("Main Form Loaded");
// start request notif blink task
Thread blinkThread = new Thread(async () => await StartRequestNotifBlankLoop(lblRequestNotif));
blinkThread.Start();
@ -66,8 +79,10 @@ namespace qtc_net_client_2
// join lobby
if (!_gatewayService.InLobby) await _gatewayService.JoinLobbyAsync();
Chat frmChat = new Chat(_gatewayService, _apiService);
ChatRoom frmChat = new ChatRoom(_gatewayService, _apiService);
frmChat.Show();
LoggingService.LogString("User Has Joined Lobby Room");
return;
}
@ -77,8 +92,10 @@ namespace qtc_net_client_2
{
if (_gatewayService.CurrentRoom != room) await _gatewayService.JoinRoomAsync(room);
Chat frmChat = new Chat(_gatewayService, _apiService);
ChatRoom frmChat = new ChatRoom(_gatewayService, _apiService);
frmChat.Show();
LoggingService.LogString($"User Has Joined {room.Name}");
}
}
}
@ -114,6 +131,7 @@ namespace qtc_net_client_2
ms.Dispose();
}
}
else LoggingService.LogString($"User Has No Profile Picture Or It Could Not Be Loaded.\n{pfpRes.Message}");
}
}
}
@ -132,36 +150,10 @@ namespace qtc_net_client_2
}
}
private async void lbOnlineUsers_DoubleClick(object sender, EventArgs e)
{
if (lbOnlineUsers.SelectedItems.Count > 0)
{
string? selectedUser = (string?)lbOnlineUsers.SelectedItems[lbOnlineUsers.SelectedItems.Count - 1];
if (selectedUser != null)
{
// get user info and open profile dialog
var user = OnlineUsers.FirstOrDefault(e => e.Username == selectedUser);
var res = await _apiService.GetUserInformationAsync(user!.Id);
var pfpRes = await _apiService.GetUserProfilePic(user!.Id);
if (res.Data != null && res.Success)
{
Profile frmProfile = new Profile(res.Data, pfpRes, _apiService, _gatewayService);
frmProfile.ShowDialog();
}
}
}
}
private void llblEditProfile_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
ProfileEdit frmProfileEdit = new ProfileEdit(_apiService);
var dialogResult = frmProfileEdit.ShowDialog();
if (dialogResult == DialogResult.OK)
{
MessageBox.Show("If you updated your username, hit the refresh button to see it update on your lists.\nThe top username will not update until you restart your client.");
}
frmProfileEdit.ShowDialog();
}
private async void lvContacts_DoubleClick(object sender, EventArgs e)
@ -176,36 +168,61 @@ namespace qtc_net_client_2
if (selectedUser.Contains('[')) selectedUser = selectedUser.Split('[', options: StringSplitOptions.TrimEntries)[0];
// get user info and open profile dialog
var user = Contacts.FirstOrDefault(e => e.Username == selectedUser);
var user = 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)
// get cosmetic
byte[]? cosmeticData = null;
if (user.ProfileCosmeticId != 0)
{
Profile frmProfile = new Profile(res.Data, pfpRes, _apiService, _gatewayService);
frmProfile.ShowDialog();
}
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}");
}
}
private async void btnRefresh_Click(object sender, EventArgs e)
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)
{
// refresh all
await RefreshContactsList();
await RefreshRoomsList();
await RefreshOnlineUsersList();
LoggingService.LogString($"Opening Profile For User '{res.Data.Username}'");
Profile frmProfile = new Profile(res.Data, pfpRes, Contacts, _apiService, _gatewayService, cosmeticData);
frmProfile.Show();
}
}
}
}
private void frmMain_Resize(object sender, EventArgs e)
{
if (WindowState == FormWindowState.Minimized) Hide();
if (WindowState == FormWindowState.Minimized && _config.MinimizeToTray)
{
Hide();
niMain.Visible = true;
if (FirstMinimize)
{
niMain.ShowBalloonTip(10,
"I'm over here!",
"QtC.NET Mimimizes To Tray By Default. To Change This Behaviour, Refer To config.json",
ToolTipIcon.Info);
FirstMinimize = false;
}
}
}
private void niMain_DoubleClick(object sender, EventArgs e)
{
Show();
WindowState = FormWindowState.Normal;
niMain.Visible = false;
}
private async void frmMain_FormClosed(object sender, FormClosedEventArgs e)
@ -267,7 +284,7 @@ namespace qtc_net_client_2
private async void llblClaimSpin_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
TokenJackpotSpinner tokenJackpotSpinner = new TokenJackpotSpinner();
CurrencyJackpotSpinner tokenJackpotSpinner = new CurrencyJackpotSpinner();
var frmResult = tokenJackpotSpinner.ShowDialog();
if (frmResult == DialogResult.OK && tokenJackpotSpinner.TokensWon > 0)
@ -276,18 +293,286 @@ namespace qtc_net_client_2
var result = await _apiService.AddCurrencyToCurrentUser(tokenJackpotSpinner.TokensWon, true);
if (result.Success)
{
lblCurrencyAmount.Text = (_apiService.CurrentUser.CurrencyAmount + tokenJackpotSpinner.TokensWon).ToString();
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);
}
}
private async void lbUserDirectory_DoubleClick(object sender, EventArgs e)
{
if (lvUserDirectory.SelectedItems.Count > 0)
{
string? selectedUser = (string?)lvUserDirectory.SelectedItems[lvUserDirectory.SelectedItems.Count - 1].Text;
if (selectedUser != null)
{
// get user info and open profile dialog
var user = UserDirectory.FirstOrDefault(e => e.Username == selectedUser);
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, cosmeticData);
frmProfile.Show();
}
}
}
}
private void lvGames_DoubleClick(object sender, EventArgs e)
{
if (lvGames.SelectedItems.Count > 0)
{
string? gameSelected = (string?)lvGames.SelectedItems[lvGames.SelectedItems.Count - 1].Tag;
switch (gameSelected)
{
case "StockMarketGame":
StockMarketGame stockMarketGame = new StockMarketGame(_apiService);
stockMarketGame.Show();
break;
case "GuessTheNumberGame":
GuessTheNumber guessTheNumber = new GuessTheNumber(_apiService);
guessTheNumber.Show();
break;
case "TicTacToeGame":
TicTacToeGame ticTacToeGame = new TicTacToeGame(_apiService, _config);
ticTacToeGame.Show();
break;
}
}
}
private async void refreshToolStripMenuItem_Click(object sender, EventArgs e)
{
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()
{
// double check
if (_apiService.CurrentUser != null && _apiService.SessionToken != null)
{
LoggingService.LogString($"Logged In As '{_apiService.CurrentUser.Username}'");
LoggingService.LogString("Starting SignalR Connection...");
// start gateway connection
await _gatewayService.StartAsync();
@ -295,18 +580,26 @@ namespace qtc_net_client_2
_gatewayService.OnServerReconnecting += _gatewayService_OnServerReconnecting;
_gatewayService.OnServerReconnected += _gatewayService_OnServerReconnected;
_gatewayService.OnServerDisconnect += _gatewayService_OnServerDisconnect;
_gatewayService.OnClientFunctionReceived += _gatewayService_OnClientFunctionReceived;
_gatewayService.OnDirectMessageReceived += _gatewayService_OnDirectMessageReceived;
_gatewayService.OnRefreshUserListsReceived += _gatewayService_OnRefreshUserListReceived;
_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;
lblWelcome.Text = $"Welcome, {_apiService.CurrentUser.Username}";
lblCurrencyAmount.Visible = true;
pbCurrencyIcon.Visible = true;
lblCurrencyAmount.Text = _apiService.CurrentUser.CurrencyAmount.ToString();
lblCurrencyAmount.Text = _apiService.CurrentUser.CurrencyAmount.ToString("N0");
llblSignOut.Visible = true;
llblEditProfile.Visible = true;
tbcMain.Enabled = true;
@ -321,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 RefreshOnlineUsersList();
// 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)
{
@ -358,52 +651,60 @@ 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");
}
}
}
private async Task RefreshOnlineUsersList()
private async Task RefreshUsers()
{
if (InvokeRequired)
LoggingService.LogString("Refreshing All Users List...");
if (IsHandleCreated && !IsDisposed)
{
await Invoke(async delegate ()
{
lbOnlineUsers.Items.Clear();
var usersOnlineRes = await _apiService.GetOnlineUsersAsync();
if (usersOnlineRes.Success && usersOnlineRes.Data != null)
// get all users on server
var userList = await _apiService.GetAllUsersAsync();
if (userList != null && userList.Success && userList.Data != null)
{
foreach (var user in usersOnlineRes.Data)
// clear the list
lvUserDirectory.Items.Clear();
UserDirectory.Clear();
// populate list
foreach (var user in userList.Data)
{
lbOnlineUsers.Items.Add(user.Username);
}
OnlineUsers = usersOnlineRes.Data;
}
});
return;
lvUserDirectory.Items.Add(user.Username, user.Status);
UserDirectory.Add(user);
}
lbOnlineUsers.Items.Clear();
var usersOnlineRes = await _apiService.GetOnlineUsersAsync();
if (usersOnlineRes.Success && usersOnlineRes.Data != null)
{
lbOnlineUsers.Items.Clear();
foreach (var user in usersOnlineRes.Data)
{
lbOnlineUsers.Items.Add(user.Username);
if (System.Diagnostics.Debugger.IsAttached || _config.EnableDebugLogs)
LoggingService.LogModel(userList.Data);
}
OnlineUsers = usersOnlineRes.Data;
});
}
}
private async Task RefreshRoomsList()
{
if (InvokeRequired)
LoggingService.LogString("Refreshing Rooms List...");
if (IsHandleCreated && !IsDisposed)
{
await Invoke(async delegate ()
{
@ -416,33 +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()
{
if (InvokeRequired)
LoggingService.LogString("Refreshing Contacts List...");
if (IsHandleCreated && !IsDisposed)
{
await Invoke(async delegate ()
{
@ -467,7 +756,7 @@ namespace qtc_net_client_2
if (user.Data != null)
{
Contacts.Add(user.Data);
Contacts.Add(contact);
if (contact.OwnerId == _apiService.CurrentUser!.Id)
{
switch (contact.OwnerStatus)
@ -475,12 +764,16 @@ namespace qtc_net_client_2
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;
}
}
@ -491,80 +784,27 @@ namespace qtc_net_client_2
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;
}
}
}
}
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(user.Data);
if (contact.OwnerId == _apiService.CurrentUser!.Id)
{
switch (contact.OwnerStatus)
{
case Contact.ContactStatus.AwaitingApprovalFromOther:
var lvi = lvContacts.Items.Add($"{user.Data.Username} [Request Sent]");
await AddProfilePicToList(user.Data.Id);
lvi.ImageKey = user.Data.Id;
break;
case Contact.ContactStatus.Accepted:
var lvi2 = lvContacts.Items.Add($"{user.Data.Username}");
await AddProfilePicToList(user.Data.Id);
lvi2.ImageKey = user.Data.Id;
break;
}
}
else if (contact.UserId == _apiService.CurrentUser!.Id)
{
switch (contact.UserStatus)
{
case Contact.ContactStatus.AwaitingApprovalFromSelf:
var lvi = lvContacts.Items.Add($"{user.Data.Username} [Contact Request]");
await AddProfilePicToList(user.Data.Id);
lvi.ImageKey = user.Data.Id;
AudioService.PlaySoundEffect("sndContactRequest");
break;
case Contact.ContactStatus.Accepted:
var lvi2 = lvContacts.Items.Add($"{user.Data.Username}");
await AddProfilePicToList(user.Data.Id);
lvi2.ImageKey = user.Data.Id;
break;
}
}
}
}
}
}
@ -572,7 +812,7 @@ namespace qtc_net_client_2
{
while (true)
{
if (InvokeRequired && label.IsHandleCreated)
if(IsHandleCreated && !IsDisposed)
{
await Invoke(async delegate ()
{
@ -582,13 +822,6 @@ namespace qtc_net_client_2
await Task.Delay(500);
});
}
else if (label.IsHandleCreated)
{
label.ForeColor = Color.Red;
await Task.Delay(500);
label.ForeColor = Color.Blue;
await Task.Delay(500);
}
}
}
@ -603,24 +836,58 @@ namespace qtc_net_client_2
ms.Dispose();
}
}
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 (IsHandleCreated && !IsDisposed)
{
Invoke(delegate () { lblCurrencyAmount.Text = _apiService.CurrentUser.CurrencyAmount.ToString("N0"); });
}
}
private async void _gatewayService_OnServerDisconnect(object? sender, EventArgs e)
{
LoggingService.LogString("Disconnected From SignalR");
if (DialogResult == DialogResult.OK) return;
var args = (ServerConnectionClosedEventArgs)e;
string? error = string.Empty;
if (args.Error != null) error = args.Error.Message;
if (args.Error != null) { error = args.Error.Message; LoggingService.LogString($"{args.Error.Message}\n{args.Error.StackTrace}"); }
// disconnect can sometimes be caused by an expired JWT token, attempt connection restart
await _gatewayService.StopAsync();
await _gatewayService.StartAsync();
if (_gatewayService.HubConnection != null && _gatewayService.HubConnection.State == Microsoft.AspNetCore.SignalR.Client.HubConnectionState.Connected)
{ BeginInvoke(delegate () { Enabled = true; }); return; }
{ 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)
@ -628,19 +895,20 @@ namespace qtc_net_client_2
// tell the gateway service to attempt reconnection
Reconnect:
if (_gatewayService.HubConnection != null)
{
try
{
await _gatewayService.StopAsync();
await _gatewayService.StartAsync();
if (_gatewayService.HubConnection.State == Microsoft.AspNetCore.SignalR.Client.HubConnectionState.Connected)
BeginInvoke(delegate () { Enabled = true; });
{
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);
}
@ -649,24 +917,45 @@ 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 async void _gatewayService_OnClientFunctionReceived(object? sender, EventArgs e)
private void _gatewayService_OnServerReconnecting(object? sender, EventArgs e)
{
var args = (ClientFunctionEventArgs)e;
var args = (ServerConnectionReconnectingEventArgs)e;
switch (args.Function)
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)
{
case "rul":
await RefreshOnlineUsersList();
break;
case "rr":
await RefreshRoomsList();
break;
case "rcl":
await RefreshContactsList();
break;
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");
}
}
@ -674,10 +963,17 @@ namespace qtc_net_client_2
{
var args = (ServerConfigEventArgs)e;
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");
MessageBox.Show($"Sorry, This Server Is Currently Down.\nMessage: {args.ServerConfig.IsDownMessage}\n\nPlease Try Again Later");
if (_gatewayService.HubConnection != null && _gatewayService.HubConnection.State == Microsoft.AspNetCore.SignalR.Client.HubConnectionState.Connected)
@ -687,8 +983,6 @@ namespace qtc_net_client_2
}
Environment.Exit(0);
}
BeginInvoke(delegate () { Text = $"QtC.NET Client = Connected To {args.ServerConfig.Name}"; });
}
}
@ -696,6 +990,8 @@ namespace qtc_net_client_2
{
var args = (DirectMessageEventArgs)e;
LoggingService.LogString($"Direct Message Received From '{args.User.Username}'");
DirectMessage? existingForm = (DirectMessage?)Application.OpenForms.Cast<Form>().FirstOrDefault(e => e.Name == "DirectMessage");
if (existingForm != null && existingForm.User.Id == args.User.Id)
{
@ -709,5 +1005,26 @@ namespace qtc_net_client_2
Task.Run(frmDirectMessage.ShowDialog);
}
}
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

@ -112,67 +112,444 @@
<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>
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=8.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>
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="ctxmRefresh.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>356, 21</value>
</metadata>
<metadata name="ilProfilePics.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<data name="ilProfilePics.ImageStream" mimetype="application/x-microsoft.net.object.binary.base64">
<value>
AAEAAAD/////AQAAAAAAAAAMAgAAAEZTeXN0ZW0uV2luZG93cy5Gb3JtcywgQ3VsdHVyZT1uZXV0cmFs
LCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5BQEAAAAmU3lzdGVtLldpbmRvd3MuRm9ybXMu
SW1hZ2VMaXN0U3RyZWFtZXIBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAA0A0AAAJNU0Z0AUkBTAMBAQAB
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
ugH/A7oB/wO6Af8DugH/A9oN/wP8Af8D/iX/A/wN/wPaAf8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af//
AIEAA6UB/wO6Af8DugH/A7oB/wO6Af8DugH/A9kR/wP+Ff8D/R3/A9oB/wO6Af8DugH/A7oB/wO6Af8D
ugH/A7oB//8AgQADpAH/A7kB/wO6Af8DugH/A7oB/wO6Af8D2QH/A/wV/wP+Df8D/QH/A/0R/wP+Af8D
/gH/A9oB/wO6Af8DugH/A7kB/wO6Af8DugH/A7oB//8AgQADoQH/A7oB/wO6Af8DugH/A7oB/wO6Af8D
2gH/A/4B/wP9Ef8D/AH/A/4N/wP9Af8D/RH/A/wB/wPZAf8DugH/A7oB/wO6Af8DuQH/A7oB/wO6Af//
AIEAA58B/wO6Af8DugH/A7oB/wO6Af8DugH/A9oF/wP9Af8D/RH/A/0B/wP+Df8D/QH/A/0R/wPZAf8D
ugH/A7oB/wO6Af8DugH/A7kB/wO6Af//AIEAA7EB/wO6Af8DugH/A7oB/wO6Af8DugH/A9MJ/wP+Ff8D
/AH/A/4h/wPTAf8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af//AIEAA7oB/wO6Af8DugH/A7oB/wO6Af8D
uQH/A8AB/wP9Df8D/hH/A/4V/wP9Bf8D/QH/A8AB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB//8AgQAD
ugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A9oN/wP+Af8D/RX/A/4N/wP9Af8D/AH/A9oB/wO6Af8D
ugH/A7oB/wO6Af8DugH/A7oB/wO6Af//AIEAA7oB/wO5Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8D
3Q3/A/0B/wP9Ef8D/QH/A/4N/wPcAf8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB//8AgQAD
ugH/A7oB/wO5Af8DuQH/A7oB/wO6Af8DugH/A7oB/wO5Af8DyQH/A+wB/wP+Bf8D/gH/A/wR/wP9Af8D
/QH/A+wB/wPKAf8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af//AIEAA7oB/wO6Af8D
ugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7kB/wO6Af8DwQH/A9IB/wPfAf8D5gH/A+oB/wPrAf8D
5wH/A98B/wPSAf8DwAH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af//
AIEAA7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8D
vQH/A8cB/wPGAf8DvQH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8D
ugH/A7oB/wO6Af//AIEAA7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8D
ugH/A8IB/wPnAf8D/QX/A/4B/wP6Af8D5gH/A8IB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8D
ugH/A7oB/wO6Af8DugH/A7oB//8AgQADugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8D
ugH/A7oB/wPIAf8D+QH/A/0N/wP9Af8D/QH/A/oB/wPIAf8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8D
ugH/A7oB/wO5Af8DugH/A7oB//8AgQADugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8D
ugH/A74B/wP4Bf8D/AH/A/4N/wP9Af8D/AH/A/gB/wO+Af8DugH/A7oB/wO6Af8DuQH/A7oB/wO6Af8D
ugH/A7oB/wO5Af8DugH//wCBAAO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7kB/wO6Af8D
2w3/A/0B/wP9Df8D/QH/A/4B/wPbAf8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8D
ugH//wCBAAO6Af8DugH/A7oB/wO6Af8DugH/A7kB/wO6Af8DugH/A7oB/wO6Af8D8BH/A/wB/wP+Ef8D
8AH/A7oB/wO6Af8DugH/A7oB/wO5Af8DugH/A7oB/wO6Af8DugH/A7oB//8AgQADugH/A7oB/wO6Af8D
ugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A/gB/wP9Jf8D+AH/A7oB/wO6Af8DugH/A7oB/wO6Af8D
ugH/A7oB/wO6Af8DugH/A7oB//8AgQADugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8D
ugH/A/QB/wP9Af8D/BH/A/4B/wP+Cf8D9QH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8D
ugH/A7oB//8AgQADugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A+UF/wP+Af8D
/BH/A/wB/wP9Bf8D5QH/A7oB/wO5Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB//8AgQAD
ugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DuQH/A8gB/wP+Bf8D/gH/A/wR/wP9Af8D
/QH/A8gB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7kB/wO6Af//AIEAA7oB/wO6Af8D
ugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8D3Qn/A/4V/wPcAf8DugH/A7oB/wO6Af8D
ugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB//8AgQADugH/A7oB/wO6Af8DugH/A7oB/wO6Af8D
ugH/A7oB/wO6Af8DugH/A7oB/wO6Af8D2QH/A/wJ/wP+Bf8D/AH/A9kB/wO6Af8DugH/A7oB/wO6Af8D
ugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB//8AgQADugH/A7oB/wO6Af8DugH/A7oB/wO6Af8D
ugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A8AB/wPWAf8D4gH/A+EB/wPVAf8DwAH/A7oB/wO6Af8D
ugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB//8AgQADugH/A7oB/wO6Af8D
ugH/A7oB/wO6Af8DugH/A7kB/wO6Af8DugH/A7oB/wO6Af8DugH/A7kB/wO6Af8DugH/A7oB/wO6Af8D
ugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB//8AgQAD
ugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO5Af8D
ugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO5Af8D
uQH/A7oB//8AgQADugH/A7oB/wO6Af8DuQH/A7oB/wO6Af8DugH/A7oB/wO5Af8DugH/A7oB/wO6Af8D
ugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO5Af8D
ugH/A7oB/wO6Af8DugH/A7oB//8AgQADugH/A7oB/wO6Af8DugH/A7kB/wO6Af8DugH/A7oB/wO6Af8D
ugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DuQH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8D
ugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB//8AgQADugH/A7oB/wO6Af8DugH/A7oB/wO6Af8D
ugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8D
ugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB//8AgQADugH/A7oB/wO6Af8D
ugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8D
ugH/A7oB/wO6Af8DugH/A7kB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB/wO6Af8DugH/A7oB//8AgQAB
QgFNAT4HAAE+AwABKAMAAYADAAEgAwABAQEAAQEGAAECFgAD//8A/wADAAs=
</value>
</data>
<metadata name="ilStatusIcons.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>13, 101</value>
</metadata>
<data name="ilStatusIcons.ImageStream" mimetype="application/x-microsoft.net.object.binary.base64">
<value>
AAEAAAD/////AQAAAAAAAAAMAgAAAEZTeXN0ZW0uV2luZG93cy5Gb3JtcywgQ3VsdHVyZT1uZXV0cmFs
LCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5BQEAAAAmU3lzdGVtLldpbmRvd3MuRm9ybXMu
SW1hZ2VMaXN0U3RyZWFtZXIBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAAohMAAAJNU0Z0AUkBTAIBAQQB
AAFQAQEBUAEBARABAAEQAQAE/wEhAQAI/wFCAU0BNgcAATYDAAEoAwABQAMAASADAAEBAQABIAYAASD/
AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AC4AAwYBBwM0AVQDUQGiA14B0gNaAekDYAHoA10B
0QNQAZ8DMQFNAwUBBhgAAwYBBwM0AVQDUQGiA14B0gNaAekDYAHoA10B0QNQAZ8DMQFNAwUBBhgAAwYB
BwM0AVQDUQGiA14B0gNaAekDYAHoA10B0QNQAZ8DMQFNAwUBBhgAAwYBBwM0AVQDUQGiA14B0gNaAekD
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==
</value>
</data>
<metadata name="ilGames.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>24, 73</value>
</metadata>
<data name="ilGames.ImageStream" mimetype="application/x-microsoft.net.object.binary.base64">
<value>
AAEAAAD/////AQAAAAAAAAAMAgAAAEZTeXN0ZW0uV2luZG93cy5Gb3JtcywgQ3VsdHVyZT1uZXV0cmFs
LCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5BQEAAAAmU3lzdGVtLldpbmRvd3MuRm9ybXMu
SW1hZ2VMaXN0U3RyZWFtZXIBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAAKCUAAAJNU0Z0AUkBTAIBAQMB
AAEQAQIBEAECASABAAEgAQAE/wEhAQAI/wFCAU0BNgcAATYDAAEoAwABgAMAASADAAEBAQABIAYAAUB6
AANcAecBCAEKAQAB/wMqAUAMAAMqAUADNgFXAz8BbAM/AWwDPwFsAz8BbAM/AWwDPwFsAz8BbAM/AWwD
PwFsAz8BbAM/AWwDPwFsAz8BbAM/AWwDPwFsAz8BbAI7AToBYgMzAVEDGAEhVAADUwGiA1sBwCAAA1QB
pgNZAbzwAAMhATADRgGAFAABFQEfAQQB/wENARUBAAH/AQgBCgEAAf8BCAEKAQAB/wwAA0QBegNTAacC
YQFdAc8CYQFdAc8CYQFdAc8CYQFdAc8CYQFdAc8CYQFdAc8CYQFdAc8CYQFdAc8CYQFdAc8CYQFdAc8C
YQFdAc8CYQFdAc8CYQFdAc8CYQFdAc8CYQFdAc8CYAFdAc4DWQG7AlABTwGbAyoBQFQAA18B0ANiAe4g
AANhAdQDYwHp5AADWgG/AQgBCgEAAf8BCAEKAQAB/wEIAQoBAAH/AQgBCgEAAf8BCAEKAQAB/wEIAQoB
AAH/AQgBCgEAAf8DWgG/A1oBvwFDAXQBBwH/AUcBdwEMAf8BJQFHAQAB/wEOARkBAAH/DAACSwFKAYoC
WgFYAb0CZwFdAeoCZwFdAeoCZwFdAeoCZwFdAeoCZwFdAeoCZwFdAeoCZwFdAeoCZwFdAeoCZwFdAeoC
ZwFdAeoCZwFdAeoCZwFdAeoCZwFdAeoCZwFdAeoCZwFdAeoCYwFaAekCXwFbAdMDVQGvAy4BSFQAA18B
0ANiAe4gAANhAdQDYwHp4AABCAEKAQAB/wEQAR4BAAH/ASEBQQEAAf8BJwFLAQAB/wEmAUoBAAH/ASEB
QQEAAf8BEAEeAQAB/wEIAQoBAAH/AQgBCgEAAf8BCAEKAQAB/wEpAU4BAAH/AVoBlgEQAf8BWgGcARAB
/wFzAbYBJgH/ARgBLgEAAf8MAANJAYYCWgFYAbcCZQFgAeMCZQFgAeMCZQFgAeMCZQFgAeMCagFhAeYC
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/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>122, 19</value>
<value>20, 44</value>
</metadata>
<data name="ilTabIcons.ImageStream" mimetype="application/x-microsoft.net.object.binary.base64">
<value>
AAEAAAD/////AQAAAAAAAAAMAgAAAEZTeXN0ZW0uV2luZG93cy5Gb3JtcywgQ3VsdHVyZT1uZXV0cmFs
LCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5BQEAAAAmU3lzdGVtLldpbmRvd3MuRm9ybXMu
SW1hZ2VMaXN0U3RyZWFtZXIBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAA5ggAAAJNU0Z0AUkBTAIBAQIB
AAGoAQABqAEAARABAAEQAQAE/wEhAQAI/wFCAU0BNgcAATYDAAEoAwABQAMAARADAAEBAQABIAYAARAS
AANnAe8CZwFZAe8BZwFdAVkB7wFnAVsBWQHvAWcBWwFZAe8BZwJZAe8BZwFkAVkB7wNnAe8DZwHvA2cB
7wNnAe8DZwHvA2cB7wNnAe8DZwHvA2cB7zgAAzMBUQN8AfWAAAP4Af8BuQGVATwB/wGDAX0BbgH/AYQB
fQFsAf8BqgGEAScB/wGsAXsBAAH/AcwBvAGUAf8DfgH/A34B/wN+Af8DfgH/A34B/wN+Af8DfgH/A34B
/wOOAf84AAMSARgDPwFtgAAE/wGXAYsBbQH/AoEBgAH/AYIBgQGAAf8BmAGIAWAB/wHKAZABAAH/Ad0B
zAGfAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOTAf80AANfAdMDPQFnhAAE/wGGAYQB
fQH/A4EB/wOBAf8BhwGDAXoB/wHPAZQBAAH/Ad4BzAGfAf8D4AH/A+AB/wPgAf8D4AH/A+AB/wPgAf8D
4AH/A+AB/wO8Af80AANaAcIDNAFThAAE/wGLAYYBegH/A4EB/wOBAf8BjgGGAXEB/wHPAZQBAAH/Ad4B
zAGfIf8DygH/EAADDQERAz8BbANTAacBXAJZAb4BWAJWAbMBSAJHAYMDIQEwBAADdgHzAzoBYIgABP8B
rAGWAWAB/wGDAYIBfwH/AYUBggF9Af8BswGTAUQB/wHPAZQBAAH/Ad4BzAGfAf8DsAH/A7AB/wOwAf8D
sAH/A7AB/wOwAf8DsAH/A7AB/wOoAf8IAAMaASQDUgGgAWkBYwFIAfYBogFzAQAB/wGuAXwBAAH/AbAB
fQEAAf8BqAF4AQAB/wGVAWoBAAH/AYABaQEVAf4BXAJZAcYDVwG1AxYBHogABP8B2QGqATcB/wG+AZgB
OAH/AcABmAE2Af8B3AGiARQB/wHPAZQBAAH/Ad4BzAGfAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8D
gQH/A4EB/wOTAf8EAAMgAS0CYwFaAekBvwGIAQAB/wHNAZUBCgH/AbABiAEnAf8BcwFkAT8B/wFNAUsB
RwH/AU4BSwFCAf8BZgFXATEB/wGaAXQBFwH/AaQBdgEDAf8BcAFPAQAB/wNDAXYEAYQABP8B4QGuATEB
/wG9AZcBOwH/AcABmAE1Af8B4wGlAQoB/wHPAZQBAAH/Ad4BzAGfAf8DwAH/A8AB/wPAAf8DwAH/A8AB
/wPAAf8DwAH/A8AB/wOvAf8EAAJjAVoB6QHZAZoBAAH/AdoBowEcAf8CjgGMAf8DigH/A5cB/wObAf8D
kQH/A3QB/wNIAf8BQwFCAT8B/wG1AYMBBwH/AXoBVwEAAf8DNgFYhAAE/wG7AZ0BUwH/AYgBhAF5Af8B
jAGFAXQB/wHEAZkBMAH/Ac8BlAEAAf8B3gHMAZ8h/wPKAf8DQwF2AekBpwECAf8B6QGrARIB/wHQAcoB
uwH/A6wB/wNdAf8DTAH/A0sB/wNEAf8DDwH/A7MB/wNmAf8BVAFMAToB/wGuAX0BBAH/A10BzIQABP8B
lAGKAXMB/wOBAf8DgQH/AZoBigFjAf8BzwGUAQAB/wHeAcwBnwH/A9AB/wPQAf8D0AH/A9AB/wPQAf8D
0AH/A9AB/wPQAf8DtQH/AmoBYQHmAe0BrQEQAf8B9AHQAXYB/wP6Af8D+gH/A30B/wN/Af8DgAH/A4AB
/wN+Af8DhAH/A7sB/wNqAf8BqAGAARwB/wFrAWMBSAH2hAAE/wGGAYMBfgH/A4EB/wOBAf8BhgGDAXsB
/wHPAZQBAAH/Ad4BzAGfAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOTAf8BawFoAWIB
7gHvAbQBIQH/AfcB3AGXCf8DkQH/A4gB/wOHAf8DhwH/A4EB/wNXAf8D5gH/A6MB/wG2AZEBNgH/AYYB
agE+AfmEAAT/AYsBhgF5Af8DgQH/A4EB/wGOAYYBcQH/Ac8BlAEAAf8B3gHMAZ8B/wOhAf8DoQH/A6EB
/wOhAf8DoQH/A6EB/wOhAf8DoQH/A6EB/wNLAY0B8AG+AT8B/wH0Ac0BbCH/A/sB/wPQAf8B1wGnATEB
/wJhAV0B0YQABP8BpwGUAWcB/wGDAYIBfwH/AYUBgwF+Af8BsAGUAU4B/wHTAZcBAgH/AeABzgGfAf8D
5wH/A+cB/wPnAf8D5wH/A+cB/wPnAf8D5wH/A+cB/wPAAf8DBwEJAWsBaQFiAe4B8QG8ATsB/wH6AeoB
wgH/A9wB/wN3Af8DaAH/A2gB/wNoAf8DMgn/AfIB3QGpAf8B6gGpAQgB/wM+AWqEAAT/AdcBsQFSAf8B
mgGPAXQB/wGgAZIBbQH/AeABrwE3Af8B5wGpARAB/wHrAdUBoAH/A4EB/wOBAf8DgQH/A4EB/wOBAf8D
gQH/A4EB/wOBAf8DkwH/BAADMwFRAWsBaQFoAfAB8wHGAVgB/wH6AecBuBb/Af4B+wH/AfkB4gGqAf8B
7wG4AS0B/wNOAZYEAoQABP8B9AHNAWwB/wH0AcsBZgH/AfQBywFlAf8B9AHLAWUB/wHxAcEBSQH/AfkB
4wGsAf8DiQH/A4kB/wOJAf8DiQH/A4kB/wOJAf8DiQH/A4kB/wOaAf8IAAMiATEDXwHJAaIBjQFnAfoB
8wHKAWUB/wH5AeEBpgH/AfsB7QHMAf8B+wHsAcgB/wH4Ad0BmwH/AcgBmgFmAf4CZQFeAeIDPQFoBAGI
AED/EAADDwETA0cBggNkAdsBtgGhAW8B/ANnAeoDVAGoAygBO5QAAUIBTQE+BwABPgMAASgDAAFAAwAB
EAMAAQEBAAEBBQABgBcAA/8DAAH/AfwGAAH/AfwGAAH/AfkGAAH/AfkGAAHwARMGAAHAAQMGAAGAAQEG
AAGAAQEHAAEBBwABAQcAAQEHAAEBBwABAQYAAYABAQYAAcABAwYAAfABHwQACw==
SW1hZ2VMaXN0U3RyZWFtZXIBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAAshIAAAJNU0Z0AUkBTAIBAQQB
AAEQAQMBEAEDARABAAEQAQAE/wEhAQAI/wFCAU0BNgcAATYDAAEoAwABQAMAASADAAEBAQABIAYAASD/
AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/ACIAA2cB7wJnAVkB7wFnAV0BWQHvAWcBWwFZAe8B
ZwFbAVkB7wFnAlkB7wFnAWQBWQHvA2cB7wNnAe8DZwHvA2cB7wNnAe8DZwHvA2cB7wNnAe8DZwHvAwcB
CQMqAT8DRQF8A1kBuwNjAd8DaAH0A4AB/gOBAf8DgQH/A4EB/wOAAf4DaAH0A2MB3wNaAboDRAF6AycB
OjgAAzMBUQNuAfUIAAM3AVoDWAG4A2MB3wJjAV0B3wFiAl0B3wNdAd8DXQHfAWECXQHfA2MB3wNjAd8D
VQGsAzABSwgAA/gB/wG5AZUBPAH/AYMBfQFuAf8BhAF9AWwB/wGqAYQBJwH/AawBewEAAf8BzAG8AZQB
/wN+Af8DfgH/A34B/wN+Af8DfgH/A34B/wN+Af8DfgH/A44B/wNDAXUDXQHMA3wB+AOBAf8DgQH/A4EB
/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A3wB+ANUAag4AAMSARgDPwFtBAADOwFiA10B
xQNoAfQBnwFlATEB/wGXAVMBFwH/AZYBSwEJAf8BkwFGAQEB/wGMAUMBAwH/AX0BQAELAf8BawFAARoB
/wFuAVABNgH/A2gB8ANaAbcDNAFUBAAE/wGXAYsBbQH/AoEBgAH/AYIBgQGAAf8BmAGIAWAB/wHKAZAB
AAH/Ad0BzAGfAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOTAf8DagHtA30B+gOBAf8D
gQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DVQGvNAADXwHTAz0B
ZwQAAzUBVQNdAccCbgFaAfUBuAFlARsB/wG5AVgBAgH/AckBXwEAAf8B2AFlAQAB/wHcAWcBAAH/AdYB
ZAEAAf8BwwFcAQAB/wGiAUwBAAH/AXwBOwEDAf8BbgFGASMB/wNoAfADWgG3AzABSgT/AYYBhAF9Af8D
gQH/A4EB/wGHAYMBegH/Ac8BlAEAAf8B3gHMAZ8B/wPgAf8D4AH/A+AB/wPgAf8D4AH/A+AB/wPgAf8D
4AH/A7wB/wNjAd8DbgH1A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8D
gQH/A4EB/wNVAa80AANaAcIDNAFTBAADXAHEAnwBXAH4AdQBcQEYAf8B1wFlAQAB/wHlAWwBAAH/AfIB
cgEAAf8B+gF1AQAB/wH8AXYBAAH/AfoBdgEAAf8B8wFyAQAB/wHiAWsBAAH/Ab0BWQEAAf8BhwFAAQAB
/wFxAUgBIwH/A2gB8ANWAasE/wGLAYYBegH/A4EB/wOBAf8BjgGGAXEB/wHPAZQBAAH/Ad4BzAGfIf8D
ygH/AzYBWANbAcADbgH1A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8D
aAH0A1IBpBAAAw0BEQM/AWwDUwGnAVwCWQG+AVgCVgGzAUgCRwGDAyEBMAQAA28B8wM6AWAIAAH+Ad0B
wQH/Ae0BgAEgAf8B7QFxAQIB/wHzAXMBAAH/AfoBdgEAAf8B/gF4AQAC/wF7AQgC/wGIAScC/wGiAVMB
/wH+AYEBFwH/AfwBeAEEAf8B7AFvAQAB/wHBAVsBAAH/AYYBQQEDAf8BeAFWATYB/wNjAd8E/wGsAZYB
YAH/AYMBggF/Af8BhQGCAX0B/wGzAZMBRAH/Ac8BlAEAAf8B3gHMAZ8B/wOwAf8DsAH/A7AB/wOwAf8D
sAH/A7AB/wOwAf8DsAH/A6gB/wMCAQMDGgEjAzgBXANUAagDYgHXA3AB8QOAAf4DgQH/A4EB/wOBAf8D
gQH9A2gB8ANhAdQDUwGlAzYBWQMYASAIAAMaASQDUgGgAmMBSAH2AaIBcwEAAf8BrgF8AQAB/wGwAX0B
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
IAEtAmMBWgHpAb8BiAEAAf8BzQGVAQoB/wGwAYgBJwH/AXMBZAE/Af8BTQFLAUcB/wFOAUsBQgH/AWYB
VwExAf8BmgF0ARcB/wGkAXYBAwH/AXABTwEAAf8DQwF2BAEEAAH/AZoBQgL/AYMBFQH/Af4BegEEAv8B
eAEAAv8BeAEAAv8BlQE/Av8BygGuAv8B2gHGAv8B7QHlAv8BlgFJAv8BfAENAf8B/gF4AQAB/wH0AXMB
AAH/AdABYgEAAf8BmgFOAQoB/wFjAl0B3wT/AeEBrgExAf8BvQGXATsB/wHAAZgBNQH/AeMBpQEKAf8B
zwGUAQAB/wHeAcwBnwH/A8AB/wPAAf8DwAH/A8AB/wPAAf8DwAH/A8AB/wPAAf8DrwH/CAADAgEDAwgB
CgMhAS8DMQFOAz0BaANDAXYDRAF6A0MBdQM9AWcDMQFNAyABLgMHAQkEAggAAmMBWgHpAdkBmgEAAf8B
2gGjARwB/wKOAYwB/wOKAf8DlwH/A5sB/wORAf8DdAH/A0gB/wFDAUIBPwH/AbUBgwEHAf8BegFXAQAB
/wM2AVgEAAH/AZUBNwL/AYkBHwL/AX0BCAL/AXgBAAL/AXgBAAL/AagBXAL/AeABzQL/AaEBZgL/AdgB
xQL/AbkBlAL/AYcBIwL/AXgBAAH/AfsBdgEAAf8B4gFqAQAB/wGvAVQBAwH/AWMCXQHfBP8BuwGdAVMB
/wGIAYQBeQH/AYwBhQF0Af8BxAGZATAB/wHPAZQBAAH/Ad4BzAGfIf8DygH/BAADEwEaAzkBXQNZAbwD
ZAHbA2oB7QNjAfYDXwH7A4EB/QNfAfsDYwH2A2UB7ANjAdoDWgG6AzgBXAMTARoDQwF2AekBpwECAf8B
6QGrARIB/wHQAcoBuwH/A6wB/wNdAf8DTAH/A0sB/wNEAf8DDwH/A7MB/wNmAf8BVAFMAToB/wGuAX0B
BAH/A10BzAQAAf8BoAFJAv8BkgExAv8BgQERAv8BeQEDAv8BeAEAAv8BqAFcAv8B4AHNAv8BoQFmAv8B
2AHFAv8BwAGfAv8BiQEnAv8BeAEAAf8B/gF4AQAB/wHsAW8BAAH/Ab8BWgECAf8BYwJdAd8E/wGUAYoB
cwH/A4EB/wOBAf8BmgGKAWMB/wHPAZQBAAH/Ad4BzAGfAf8D0AH/A9AB/wPQAf8D0AH/A9AB/wPQAf8D
0AH/A9AB/wO1Af8DGgEkA1YBrgNoAfQDgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8D
gQH/A4EB/wNrAfIDUgGhAmoBYQHmAe0BrQEQAf8B9AHQAXYB/wP6Af8D+gH/A30B/wN/Af8DgAH/A4AB
/wN+Af8DhAH/A7sB/wNqAf8BqAGAARwB/wJjAUgB9gQAAf8BswFvAv8BngFIAv8BiAEeAv8BfAEHAv8B
eAEAAv8BmAFDAv8BzgG0Av8B1wHAAv8B6gHgAv8BnQFXAv8BfgERAv8BeAEAAv8BeAEAAf8B8AFxAQAB
/wHLAWQBCQH/AWMBYQFdAd8E/wGGAYMBfgH/A4EB/wOBAf8BhgGDAXsB/wHPAZQBAAH/Ad4BzAGfAf8D
gQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOTAf8DVwGyA2UB5wOBAf8DgQH/A4EB/wOBAf8D
gQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DVQGvA2IB7gHvAbQBIQH/AfcB3AGXCf8D
kQH/A4gB/wOHAf8DhwH/A4EB/wNXAf8D5gH/A6MB/wG2AZEBNgH/AmoBQQH5BAAB/wHMAZ8C/wGsAWMC
/wGTATMC/wGBAREC/wF5AQIC/wGCARYC/wGaAUsC/wGuAXgC/wGlAVcC/wGBARcC/wF5AQQC/wF4AQAC
/wF4AQAB/wHwAXEBAAH/AdUBcQEXAf8CYwFdAd8E/wGLAYYBeQH/A4EB/wOBAf8BjgGGAXEB/wHPAZQB
AAH/Ad4BzAGfAf8DoQH/A6EB/wOhAf8DoQH/A6EB/wOhAf8DoQH/A6EB/wOhAf8DbwHzA18B+wOBAf8D
gQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DVQGvA0sBjQHwAb4B
PwH/AfQBzQFsIf8D+wH/A9AB/wHXAacBMQH/AmEBXQHRBAAB/wHpAdUC/wG8AYEC/wGkAVQC/wGOASoC
/wF/AQ0C/wF5AQIC/wF7AQkC/wGCAR0C/wF4AQAC/wF4AQAC/wF4AQEC/wF6AQQC/wF6AQMB/wHzAXQB
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+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">
<value>297, 21</value>
<value>205, 21</value>
</metadata>
<metadata name="niMain.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>216, 21</value>
<value>125, 19</value>
</metadata>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="niMain.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
@ -251,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>55</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,28 +200,27 @@
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;
MinimizeBox = false;
Name = "Profile";
StartPosition = FormStartPosition.CenterParent;
StartPosition = FormStartPosition.CenterScreen;
Text = "QtC.NET Client - User Profile";
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,72 +1,95 @@
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
{
public partial class Profile : Form
{
UserInformationDto _userInformationDto;
IApiService _apiService;
IGatewayService _gatewayService;
private UserInformationDto _userInformationDto;
private IApiService _apiService;
private IGatewayService _gatewayService;
ServiceResponse<byte[]>? pfpRes;
public Profile(UserInformationDto userInfo, ServiceResponse<byte[]>? pfp, IApiService apiService, 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, byte[]? cosmetic = null)
{
_userInformationDto = userInfo;
_apiService = apiService;
_gatewayService = gatewayService;
pfpRes = pfp;
cosmeticRes = cosmetic;
contactsList = contacts;
InitializeComponent();
}
private async void frmProfile_Load(object sender, EventArgs e)
{
var contactsList = await _apiService.GetCurrentUserContacts();
lblUsername.Text = _userInformationDto.Username;
lblCurrencyAmount.Text = _userInformationDto.CurrencyAmount.ToString();
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;
@ -75,9 +98,9 @@ namespace qtc_net_client_2.Forms
}
else btnAddContact.Visible = true;
if (contactsList != null && contactsList.Success && contactsList.Data != null)
if (contactsList != null)
{
var contact = contactsList.Data.FirstOrDefault(e => e.UserId == _userInformationDto.Id || e.OwnerId == _userInformationDto.Id);
var contact = contactsList.FirstOrDefault(e => e.UserId == _userInformationDto.Id || e.OwnerId == _userInformationDto.Id);
if (contact != null)
{
if (contact.OwnerId == _apiService.CurrentUser.Id)
@ -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,16 +23,54 @@ 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
{
@ -40,6 +80,12 @@ namespace qtc_net_client_2.Forms
DateOfBirth = _apiService.CurrentUser.DateOfBirth
};
if (selectedItem != null)
{
int selectedItemId = (int?)selectedItem.Value ?? 0;
userUpdateInformationDto.ProfileCosmeticId = selectedItemId;
}
var res = await _apiService.UpdateUserInformationAsync(userUpdateInformationDto);
if (res.Success)
@ -56,4 +102,3 @@ namespace qtc_net_client_2.Forms
}
}
}
}

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

@ -0,0 +1,206 @@
namespace qtc_net_client_2.Forms
{
partial class StockMarketGame
{
/// <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()
{
pStockManagement = new Panel();
nudStockBuySellAmount = new NumericUpDown();
btnSell = new Button();
btnBuy = new Button();
lblStockCount = new Label();
pMarketStatus = new Panel();
lblCurrentStockPrice = new Label();
lblMarketStatus = new Label();
pbDollarSignRight = new PictureBox();
pbDollarSignLeft = new PictureBox();
btnRefresh = new Button();
pStockManagement.SuspendLayout();
((System.ComponentModel.ISupportInitialize)nudStockBuySellAmount).BeginInit();
pMarketStatus.SuspendLayout();
((System.ComponentModel.ISupportInitialize)pbDollarSignRight).BeginInit();
((System.ComponentModel.ISupportInitialize)pbDollarSignLeft).BeginInit();
SuspendLayout();
//
// pStockManagement
//
pStockManagement.Controls.Add(nudStockBuySellAmount);
pStockManagement.Controls.Add(btnSell);
pStockManagement.Controls.Add(btnBuy);
pStockManagement.Controls.Add(lblStockCount);
pStockManagement.Location = new Point(230, 138);
pStockManagement.Name = "pStockManagement";
pStockManagement.Size = new Size(135, 81);
pStockManagement.TabIndex = 3;
//
// nudStockBuySellAmount
//
nudStockBuySellAmount.Enabled = false;
nudStockBuySellAmount.Location = new Point(48, 7);
nudStockBuySellAmount.Name = "nudStockBuySellAmount";
nudStockBuySellAmount.Size = new Size(42, 23);
nudStockBuySellAmount.TabIndex = 9;
//
// btnSell
//
btnSell.Enabled = false;
btnSell.ForeColor = Color.Black;
btnSell.Location = new Point(88, 52);
btnSell.Name = "btnSell";
btnSell.Size = new Size(39, 23);
btnSell.TabIndex = 8;
btnSell.Text = "Sell";
btnSell.UseVisualStyleBackColor = true;
btnSell.Click += btnSell_Click;
//
// btnBuy
//
btnBuy.Enabled = false;
btnBuy.ForeColor = Color.Black;
btnBuy.Location = new Point(9, 52);
btnBuy.Name = "btnBuy";
btnBuy.Size = new Size(39, 23);
btnBuy.TabIndex = 7;
btnBuy.Text = "Buy";
btnBuy.UseVisualStyleBackColor = true;
btnBuy.Click += btnBuy_Click;
//
// lblStockCount
//
lblStockCount.Font = new Font("Segoe UI", 7F, FontStyle.Bold);
lblStockCount.ImageAlign = ContentAlignment.MiddleRight;
lblStockCount.Location = new Point(11, 34);
lblStockCount.Name = "lblStockCount";
lblStockCount.Size = new Size(115, 12);
lblStockCount.TabIndex = 6;
lblStockCount.Text = "Your Stock Count - 999";
lblStockCount.TextAlign = ContentAlignment.MiddleCenter;
//
// pMarketStatus
//
pMarketStatus.Controls.Add(lblCurrentStockPrice);
pMarketStatus.Controls.Add(lblMarketStatus);
pMarketStatus.Controls.Add(pbDollarSignRight);
pMarketStatus.Controls.Add(pbDollarSignLeft);
pMarketStatus.Location = new Point(3, 7);
pMarketStatus.Name = "pMarketStatus";
pMarketStatus.Size = new Size(586, 100);
pMarketStatus.TabIndex = 4;
//
// lblCurrentStockPrice
//
lblCurrentStockPrice.Font = new Font("Segoe UI", 9F, FontStyle.Bold);
lblCurrentStockPrice.ForeColor = Color.Black;
lblCurrentStockPrice.Location = new Point(194, 73);
lblCurrentStockPrice.Name = "lblCurrentStockPrice";
lblCurrentStockPrice.Size = new Size(199, 15);
lblCurrentStockPrice.TabIndex = 5;
lblCurrentStockPrice.Text = "Current Price Per Stock Is 999 Q's";
lblCurrentStockPrice.TextAlign = ContentAlignment.MiddleCenter;
//
// lblMarketStatus
//
lblMarketStatus.Font = new Font("Segoe UI", 35F, FontStyle.Bold | FontStyle.Italic);
lblMarketStatus.Location = new Point(90, 4);
lblMarketStatus.Name = "lblMarketStatus";
lblMarketStatus.Size = new Size(408, 62);
lblMarketStatus.TabIndex = 5;
lblMarketStatus.Text = "MARKET_STATUS";
lblMarketStatus.TextAlign = ContentAlignment.MiddleCenter;
//
// pbDollarSignRight
//
pbDollarSignRight.Image = Properties.Resources.dollar_money;
pbDollarSignRight.Location = new Point(500, 3);
pbDollarSignRight.Name = "pbDollarSignRight";
pbDollarSignRight.Size = new Size(83, 94);
pbDollarSignRight.SizeMode = PictureBoxSizeMode.Zoom;
pbDollarSignRight.TabIndex = 4;
pbDollarSignRight.TabStop = false;
//
// pbDollarSignLeft
//
pbDollarSignLeft.Image = Properties.Resources.dollar_money;
pbDollarSignLeft.Location = new Point(3, 3);
pbDollarSignLeft.Name = "pbDollarSignLeft";
pbDollarSignLeft.Size = new Size(83, 94);
pbDollarSignLeft.SizeMode = PictureBoxSizeMode.Zoom;
pbDollarSignLeft.TabIndex = 3;
pbDollarSignLeft.TabStop = false;
//
// btnRefresh
//
btnRefresh.Enabled = false;
btnRefresh.ForeColor = Color.Black;
btnRefresh.Location = new Point(270, 113);
btnRefresh.Name = "btnRefresh";
btnRefresh.Size = new Size(56, 23);
btnRefresh.TabIndex = 10;
btnRefresh.Text = "Refresh";
btnRefresh.UseVisualStyleBackColor = true;
btnRefresh.Click += btnRefresh_Click;
//
// StockMarketGame
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
BackColor = Color.DodgerBlue;
ClientSize = new Size(595, 227);
Controls.Add(btnRefresh);
Controls.Add(pMarketStatus);
Controls.Add(pStockManagement);
ForeColor = Color.White;
FormBorderStyle = FormBorderStyle.FixedDialog;
MaximizeBox = false;
MinimizeBox = false;
Name = "StockMarketGame";
StartPosition = FormStartPosition.CenterScreen;
Text = "QtC.NET qGame - Stock Market";
Load += StockMarketGame_Load;
pStockManagement.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)nudStockBuySellAmount).EndInit();
pMarketStatus.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)pbDollarSignRight).EndInit();
((System.ComponentModel.ISupportInitialize)pbDollarSignLeft).EndInit();
ResumeLayout(false);
}
#endregion
private Panel pStockManagement;
private Button btnSell;
private Button btnBuy;
private Label lblStockCount;
private Panel pMarketStatus;
private Label lblCurrentStockPrice;
private Label lblMarketStatus;
private PictureBox pbDollarSignRight;
private PictureBox pbDollarSignLeft;
private Button btnRefresh;
private NumericUpDown nudStockBuySellAmount;
}
}

View File

@ -0,0 +1,151 @@
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 StockMarketGame : Form
{
private IApiService _apiService;
private int StockPrice = 0;
public StockMarketGame(IApiService apiService)
{
_apiService = apiService;
InitializeComponent();
}
private async void StockMarketGame_Load(object sender, EventArgs e) => await InitializeStockMarketGame();
private async void btnRefresh_Click(object sender, EventArgs e) => await InitializeStockMarketGame();
private async void btnBuy_Click(object sender, EventArgs e)
{
if (nudStockBuySellAmount.Value > 0)
{
nudStockBuySellAmount.Enabled = false;
btnBuy.Enabled = false;
btnSell.Enabled = false;
btnRefresh.Enabled = false;
// buy stock
var amountToBuy = int.Parse(nudStockBuySellAmount.Value.ToString());
var result = await _apiService.BuyStock(amountToBuy);
if (result != null && result.Success && result.Data != null)
{
lblStockCount.Text = $"Your Stock Count - {result.Data.StockAmount}";
_apiService.CurrentUser.StockAmount = result.Data.StockAmount;
_apiService.CurrentUser.CurrencyAmount = result.Data.CurrencyAmount;
nudStockBuySellAmount.Enabled = true;
nudStockBuySellAmount.Value = 0;
btnBuy.Enabled = true;
btnSell.Enabled = true;
btnRefresh.Enabled = true;
}
else
{
MessageBox.Show("Buy Failed. Either You Don't Have Enough Q's Or A Server Side Error Occured.", "Oops.", MessageBoxButtons.OK, MessageBoxIcon.Error);
nudStockBuySellAmount.Enabled = true;
nudStockBuySellAmount.Value = 0;
btnBuy.Enabled = true;
btnSell.Enabled = true;
btnRefresh.Enabled = true;
}
}
}
private async void btnSell_Click(object sender, EventArgs e)
{
if(nudStockBuySellAmount.Value > 0)
{
nudStockBuySellAmount.Enabled = false;
btnBuy.Enabled = false;
btnSell.Enabled = false;
btnRefresh.Enabled = false;
// sell stock
var amountToSell = int.Parse(nudStockBuySellAmount.Value.ToString());
var result = await _apiService.SellStock(amountToSell);
if (result != null && result.Success && result.Data != null)
{
lblStockCount.Text = $"Your Stock Count - {result.Data.StockAmount}";
_apiService.CurrentUser.StockAmount = result.Data.StockAmount;
_apiService.CurrentUser.CurrencyAmount = result.Data.CurrencyAmount;
nudStockBuySellAmount.Enabled = true;
nudStockBuySellAmount.Value = 0;
btnBuy.Enabled = true;
btnSell.Enabled = true;
btnRefresh.Enabled = true;
} else
{
MessageBox.Show("Sell Failed. Either You Don't Have Enough Stock Or A Server Side Error Occured.", "Oops.", MessageBoxButtons.OK, MessageBoxIcon.Error);
nudStockBuySellAmount.Enabled = true;
nudStockBuySellAmount.Value = 0;
btnBuy.Enabled = true;
btnSell.Enabled = true;
btnRefresh.Enabled = true;
}
}
}
private async Task InitializeStockMarketGame()
{
var currentStockPriceResult = await _apiService.GetCurrentStockPrice();
if (currentStockPriceResult != null && currentStockPriceResult.Success)
{
lblCurrentStockPrice.Text = $"Current Price Per Stock Is {currentStockPriceResult.Data} Q's";
lblStockCount.Text = $"Your Stock Count - {_apiService.CurrentUser.StockAmount}";
switch (currentStockPriceResult.Data)
{
case < 49:
lblMarketStatus.Text = "not good cheif :(";
lblMarketStatus.ForeColor = Color.Red;
break;
case < 99:
lblMarketStatus.Text = "its aight :/";
lblMarketStatus.ForeColor = Color.DarkGreen;
break;
case < 149:
lblMarketStatus.Text = "its good :)";
lblMarketStatus.ForeColor = Color.Green;
break;
case < 199:
lblMarketStatus.Text = "very good :D";
lblMarketStatus.ForeColor = Color.Blue;
break;
case > 200:
lblMarketStatus.Text = "$ AWESOME $";
lblMarketStatus.ForeColor = Color.LightGreen;
break;
default:
lblMarketStatus.Text = "Cannot Determine";
lblMarketStatus.ForeColor = Color.White;
break;
}
nudStockBuySellAmount.Enabled = true;
btnBuy.Enabled = true;
btnSell.Enabled = true;
btnRefresh.Enabled = true;
}
else
{
MessageBox.Show("The Stock Market Endpoint Could Not Be Reached. Please Check Your Internet Connection", "Uh Oh.", 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,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

@ -0,0 +1,457 @@
namespace qtc_net_client_2
{
partial class TicTacToeGame
{
/// <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()
{
pBoard = new Panel();
btnSquare9 = new Button();
btnSquare1 = new Button();
btnSquare7 = new Button();
btnSquare8 = new Button();
btnSquare6 = new Button();
btnSquare4 = new Button();
btnSquare5 = new Button();
btnSquare3 = new Button();
btnSquare2 = new Button();
pbBoard = new PictureBox();
pSymbolSelection = new Panel();
btnOSelect = new Button();
btnXSelect = new Button();
label1 = new Label();
pLoading = new Panel();
btnPlayAgain = new Button();
lblPlayAgain = new Label();
lblLoadStatus = new Label();
lblPlayerOne = new Label();
lblP1Username = new Label();
lblP2Username = new Label();
lblPlayerTwo = new Label();
lvUserlist = new ListView();
rtxtChatbox = new RichTextBox();
btnSend = new Button();
rtxtChat = new RichTextBox();
lblJackpotWon = new Label();
pBoard.SuspendLayout();
((System.ComponentModel.ISupportInitialize)pbBoard).BeginInit();
pSymbolSelection.SuspendLayout();
pLoading.SuspendLayout();
SuspendLayout();
//
// pBoard
//
pBoard.Controls.Add(btnSquare9);
pBoard.Controls.Add(btnSquare1);
pBoard.Controls.Add(btnSquare7);
pBoard.Controls.Add(btnSquare8);
pBoard.Controls.Add(btnSquare6);
pBoard.Controls.Add(btnSquare4);
pBoard.Controls.Add(btnSquare5);
pBoard.Controls.Add(btnSquare3);
pBoard.Controls.Add(btnSquare2);
pBoard.Controls.Add(pbBoard);
pBoard.Enabled = false;
pBoard.Location = new Point(30, 18);
pBoard.Name = "pBoard";
pBoard.Size = new Size(541, 474);
pBoard.TabIndex = 0;
//
// btnSquare9
//
btnSquare9.BackgroundImageLayout = ImageLayout.Stretch;
btnSquare9.FlatAppearance.BorderSize = 0;
btnSquare9.FlatStyle = FlatStyle.Flat;
btnSquare9.Location = new Point(374, 341);
btnSquare9.Name = "btnSquare9";
btnSquare9.Size = new Size(99, 90);
btnSquare9.TabIndex = 11;
btnSquare9.UseVisualStyleBackColor = true;
btnSquare9.Click += btnSquareX_Click;
//
// btnSquare1
//
btnSquare1.BackgroundImageLayout = ImageLayout.Stretch;
btnSquare1.FlatAppearance.BorderSize = 0;
btnSquare1.FlatStyle = FlatStyle.Flat;
btnSquare1.Location = new Point(79, 49);
btnSquare1.Name = "btnSquare1";
btnSquare1.Size = new Size(99, 90);
btnSquare1.TabIndex = 4;
btnSquare1.UseVisualStyleBackColor = true;
btnSquare1.Click += btnSquareX_Click;
//
// btnSquare7
//
btnSquare7.BackgroundImageLayout = ImageLayout.Stretch;
btnSquare7.FlatAppearance.BorderSize = 0;
btnSquare7.FlatStyle = FlatStyle.Flat;
btnSquare7.Location = new Point(79, 341);
btnSquare7.Name = "btnSquare7";
btnSquare7.Size = new Size(99, 90);
btnSquare7.TabIndex = 10;
btnSquare7.UseVisualStyleBackColor = true;
btnSquare7.Click += btnSquareX_Click;
//
// btnSquare8
//
btnSquare8.BackgroundImageLayout = ImageLayout.Stretch;
btnSquare8.FlatAppearance.BorderSize = 0;
btnSquare8.FlatStyle = FlatStyle.Flat;
btnSquare8.Location = new Point(229, 341);
btnSquare8.Name = "btnSquare8";
btnSquare8.Size = new Size(99, 90);
btnSquare8.TabIndex = 9;
btnSquare8.UseVisualStyleBackColor = true;
btnSquare8.Click += btnSquareX_Click;
//
// btnSquare6
//
btnSquare6.BackgroundImageLayout = ImageLayout.Stretch;
btnSquare6.FlatAppearance.BorderSize = 0;
btnSquare6.FlatStyle = FlatStyle.Flat;
btnSquare6.Location = new Point(374, 195);
btnSquare6.Name = "btnSquare6";
btnSquare6.Size = new Size(99, 90);
btnSquare6.TabIndex = 8;
btnSquare6.UseVisualStyleBackColor = true;
btnSquare6.Click += btnSquareX_Click;
//
// btnSquare4
//
btnSquare4.BackgroundImageLayout = ImageLayout.Stretch;
btnSquare4.FlatAppearance.BorderSize = 0;
btnSquare4.FlatStyle = FlatStyle.Flat;
btnSquare4.Location = new Point(79, 195);
btnSquare4.Name = "btnSquare4";
btnSquare4.Size = new Size(99, 90);
btnSquare4.TabIndex = 7;
btnSquare4.UseVisualStyleBackColor = true;
btnSquare4.Click += btnSquareX_Click;
//
// btnSquare5
//
btnSquare5.BackgroundImageLayout = ImageLayout.Stretch;
btnSquare5.FlatAppearance.BorderSize = 0;
btnSquare5.FlatStyle = FlatStyle.Flat;
btnSquare5.Location = new Point(229, 195);
btnSquare5.Name = "btnSquare5";
btnSquare5.Size = new Size(99, 90);
btnSquare5.TabIndex = 6;
btnSquare5.UseVisualStyleBackColor = true;
btnSquare5.Click += btnSquareX_Click;
//
// btnSquare3
//
btnSquare3.BackgroundImageLayout = ImageLayout.Stretch;
btnSquare3.FlatAppearance.BorderSize = 0;
btnSquare3.FlatStyle = FlatStyle.Flat;
btnSquare3.Location = new Point(374, 49);
btnSquare3.Name = "btnSquare3";
btnSquare3.Size = new Size(99, 90);
btnSquare3.TabIndex = 5;
btnSquare3.UseVisualStyleBackColor = true;
btnSquare3.Click += btnSquareX_Click;
//
// btnSquare2
//
btnSquare2.BackgroundImageLayout = ImageLayout.Stretch;
btnSquare2.FlatAppearance.BorderSize = 0;
btnSquare2.FlatStyle = FlatStyle.Flat;
btnSquare2.Location = new Point(229, 49);
btnSquare2.Name = "btnSquare2";
btnSquare2.Size = new Size(99, 90);
btnSquare2.TabIndex = 3;
btnSquare2.UseVisualStyleBackColor = true;
btnSquare2.Click += btnSquareX_Click;
//
// pbBoard
//
pbBoard.Image = Properties.Resources.Tic_tac_toe;
pbBoard.Location = new Point(27, 15);
pbBoard.Name = "pbBoard";
pbBoard.Size = new Size(501, 444);
pbBoard.SizeMode = PictureBoxSizeMode.Zoom;
pbBoard.TabIndex = 0;
pbBoard.TabStop = false;
//
// pSymbolSelection
//
pSymbolSelection.BorderStyle = BorderStyle.FixedSingle;
pSymbolSelection.Controls.Add(btnOSelect);
pSymbolSelection.Controls.Add(btnXSelect);
pSymbolSelection.Controls.Add(label1);
pSymbolSelection.Location = new Point(161, 109);
pSymbolSelection.Name = "pSymbolSelection";
pSymbolSelection.Size = new Size(275, 255);
pSymbolSelection.TabIndex = 3;
pSymbolSelection.Visible = false;
//
// btnOSelect
//
btnOSelect.BackgroundImage = Properties.Resources.O;
btnOSelect.BackgroundImageLayout = ImageLayout.Stretch;
btnOSelect.FlatAppearance.BorderSize = 0;
btnOSelect.FlatStyle = FlatStyle.Flat;
btnOSelect.Location = new Point(157, 104);
btnOSelect.Name = "btnOSelect";
btnOSelect.Size = new Size(91, 89);
btnOSelect.TabIndex = 2;
btnOSelect.UseVisualStyleBackColor = true;
btnOSelect.Click += btnOSelect_Click;
//
// btnXSelect
//
btnXSelect.BackgroundImage = Properties.Resources.X;
btnXSelect.BackgroundImageLayout = ImageLayout.Stretch;
btnXSelect.FlatAppearance.BorderSize = 0;
btnXSelect.FlatStyle = FlatStyle.Flat;
btnXSelect.Location = new Point(34, 103);
btnXSelect.Name = "btnXSelect";
btnXSelect.Size = new Size(91, 89);
btnXSelect.TabIndex = 1;
btnXSelect.UseVisualStyleBackColor = true;
btnXSelect.Click += btnXSelect_Click;
//
// label1
//
label1.Font = new Font("Segoe UI", 15F, FontStyle.Bold, GraphicsUnit.Point, 0);
label1.ForeColor = Color.White;
label1.Location = new Point(11, 14);
label1.Name = "label1";
label1.Size = new Size(250, 49);
label1.TabIndex = 0;
label1.Text = "Please Select Symbol";
label1.TextAlign = ContentAlignment.MiddleCenter;
//
// pLoading
//
pLoading.BorderStyle = BorderStyle.FixedSingle;
pLoading.Controls.Add(btnPlayAgain);
pLoading.Controls.Add(lblPlayAgain);
pLoading.Controls.Add(lblLoadStatus);
pLoading.Location = new Point(161, 109);
pLoading.Name = "pLoading";
pLoading.Size = new Size(275, 255);
pLoading.TabIndex = 2;
pLoading.Visible = false;
//
// btnPlayAgain
//
btnPlayAgain.Location = new Point(97, 223);
btnPlayAgain.Name = "btnPlayAgain";
btnPlayAgain.Size = new Size(75, 23);
btnPlayAgain.TabIndex = 2;
btnPlayAgain.Text = "Play Again";
btnPlayAgain.UseVisualStyleBackColor = true;
btnPlayAgain.Visible = false;
btnPlayAgain.Click += btnPlayAgain_Click;
//
// lblPlayAgain
//
lblPlayAgain.ForeColor = Color.White;
lblPlayAgain.Location = new Point(28, 134);
lblPlayAgain.Name = "lblPlayAgain";
lblPlayAgain.Size = new Size(218, 86);
lblPlayAgain.TabIndex = 1;
lblPlayAgain.Text = "If you wish to play with a different user, you can close\r\nthis window and match up again. Otherwise click\r\n\"Play Again\"";
lblPlayAgain.TextAlign = ContentAlignment.MiddleCenter;
lblPlayAgain.Visible = false;
//
// lblLoadStatus
//
lblLoadStatus.Font = new Font("Segoe UI", 21.75F, FontStyle.Bold, GraphicsUnit.Point, 0);
lblLoadStatus.ForeColor = Color.White;
lblLoadStatus.Location = new Point(11, 4);
lblLoadStatus.Name = "lblLoadStatus";
lblLoadStatus.Size = new Size(250, 130);
lblLoadStatus.TabIndex = 0;
lblLoadStatus.Text = "Text";
lblLoadStatus.TextAlign = ContentAlignment.MiddleCenter;
//
// lblPlayerOne
//
lblPlayerOne.Font = new Font("Segoe UI", 12F, FontStyle.Bold | FontStyle.Italic);
lblPlayerOne.ForeColor = Color.White;
lblPlayerOne.Location = new Point(30, 501);
lblPlayerOne.Name = "lblPlayerOne";
lblPlayerOne.Size = new Size(29, 23);
lblPlayerOne.TabIndex = 2;
lblPlayerOne.Text = "P1";
//
// lblP1Username
//
lblP1Username.AutoSize = true;
lblP1Username.Font = new Font("Segoe UI", 15F, FontStyle.Bold | FontStyle.Italic);
lblP1Username.ForeColor = Color.White;
lblP1Username.Location = new Point(26, 519);
lblP1Username.Name = "lblP1Username";
lblP1Username.Size = new Size(105, 28);
lblP1Username.TabIndex = 3;
lblP1Username.Text = "Username";
//
// lblP2Username
//
lblP2Username.AutoSize = true;
lblP2Username.Font = new Font("Segoe UI", 15F, FontStyle.Bold | FontStyle.Italic);
lblP2Username.ForeColor = Color.White;
lblP2Username.Location = new Point(466, 519);
lblP2Username.Name = "lblP2Username";
lblP2Username.Size = new Size(105, 28);
lblP2Username.TabIndex = 5;
lblP2Username.Text = "Username";
lblP2Username.TextAlign = ContentAlignment.MiddleRight;
//
// lblPlayerTwo
//
lblPlayerTwo.Font = new Font("Segoe UI", 12F, FontStyle.Bold | FontStyle.Italic);
lblPlayerTwo.ForeColor = Color.White;
lblPlayerTwo.Location = new Point(540, 501);
lblPlayerTwo.Name = "lblPlayerTwo";
lblPlayerTwo.Size = new Size(29, 23);
lblPlayerTwo.TabIndex = 4;
lblPlayerTwo.Text = "P2";
//
// lvUserlist
//
lvUserlist.Alignment = ListViewAlignment.Left;
lvUserlist.Location = new Point(577, 48);
lvUserlist.MultiSelect = false;
lvUserlist.Name = "lvUserlist";
lvUserlist.Size = new Size(309, 211);
lvUserlist.TabIndex = 6;
lvUserlist.UseCompatibleStateImageBehavior = false;
lvUserlist.View = View.SmallIcon;
//
// rtxtChatbox
//
rtxtChatbox.Location = new Point(577, 497);
rtxtChatbox.Name = "rtxtChatbox";
rtxtChatbox.Size = new Size(230, 49);
rtxtChatbox.TabIndex = 8;
rtxtChatbox.Text = "";
rtxtChatbox.KeyDown += rtxtChatbox_KeyDown;
//
// btnSend
//
btnSend.BackgroundImage = Properties.Resources.SendIcon;
btnSend.BackgroundImageLayout = ImageLayout.Zoom;
btnSend.FlatAppearance.BorderSize = 0;
btnSend.FlatStyle = FlatStyle.Flat;
btnSend.Location = new Point(811, 497);
btnSend.Name = "btnSend";
btnSend.Size = new Size(75, 50);
btnSend.TabIndex = 9;
btnSend.UseVisualStyleBackColor = true;
btnSend.Click += btnSend_Click;
//
// rtxtChat
//
rtxtChat.Font = new Font("Segoe UI", 10F);
rtxtChat.HideSelection = false;
rtxtChat.Location = new Point(577, 265);
rtxtChat.Name = "rtxtChat";
rtxtChat.ReadOnly = true;
rtxtChat.Size = new Size(309, 227);
rtxtChat.TabIndex = 10;
rtxtChat.Text = "";
//
// lblJackpotWon
//
lblJackpotWon.AutoSize = true;
lblJackpotWon.Font = new Font("Segoe UI", 12F, FontStyle.Bold | FontStyle.Italic);
lblJackpotWon.ForeColor = Color.Blue;
lblJackpotWon.Location = new Point(744, 18);
lblJackpotWon.Name = "lblJackpotWon";
lblJackpotWon.Size = new Size(142, 21);
lblJackpotWon.TabIndex = 11;
lblJackpotWon.Text = "3 Wins = Jackpot!";
//
// TicTacToeGame
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
BackColor = Color.DodgerBlue;
ClientSize = new Size(898, 558);
Controls.Add(lblJackpotWon);
Controls.Add(rtxtChat);
Controls.Add(btnSend);
Controls.Add(rtxtChatbox);
Controls.Add(lvUserlist);
Controls.Add(lblP2Username);
Controls.Add(lblPlayerTwo);
Controls.Add(lblP1Username);
Controls.Add(lblPlayerOne);
Controls.Add(pLoading);
Controls.Add(pSymbolSelection);
Controls.Add(pBoard);
FormBorderStyle = FormBorderStyle.FixedDialog;
MaximizeBox = false;
Name = "TicTacToeGame";
StartPosition = FormStartPosition.CenterScreen;
Text = "QtC.NET qGame - qTic-Tac-Toe";
FormClosed += TicTacToeGame_FormClosed;
Load += Main_Load;
pBoard.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)pbBoard).EndInit();
pSymbolSelection.ResumeLayout(false);
pLoading.ResumeLayout(false);
ResumeLayout(false);
PerformLayout();
}
#endregion
private Panel pBoard;
private PictureBox pbBoard;
private Button btnSquare9;
private Button btnSquare7;
private Button btnSquare8;
private Button btnSquare6;
private Button btnSquare4;
private Button btnSquare5;
private Button btnSquare3;
private Button btnSquare1;
private Button btnSquare2;
private Panel pLoading;
private Label lblLoadStatus;
private Label lblPlayerOne;
private Label lblP1Username;
private Label lblP2Username;
private Label lblPlayerTwo;
private Panel pSymbolSelection;
private Button btnOSelect;
private Button btnXSelect;
private Label label1;
private ListView lvUserlist;
private RichTextBox rtxtChatbox;
private Button btnSend;
private Label lblPlayAgain;
private Button btnPlayAgain;
private RichTextBox rtxtChat;
private Label lblJackpotWon;
}
}

View File

@ -0,0 +1,520 @@
using Microsoft.AspNetCore.SignalR.Client;
using qtc_net_client_2.Forms;
using qtc_net_client_2.Services;
using QtCNETAPI.Enums;
using QtCNETAPI.Models;
using QtCNETAPI.Schema;
using QtCNETAPI.Services.ApiService;
using System.Threading.Tasks;
using qtc_net_client_2.ClientModel;
namespace qtc_net_client_2
{
public partial class TicTacToeGame : Form
{
private HubConnection? GameHubConnection { get; set; }
private GameRoom? CurrentRoom { get; set; }
private IApiService _apiService;
private Config _config;
private AudioService _audioService = new();
private int WinCount { get; set; }
private bool JackpotWon { get; set; } = false;
public TicTacToeGame(IApiService apiService, Config config)
{
_apiService = apiService;
_config = config;
InitializeComponent();
}
private async void Main_Load(object sender, EventArgs e)
{
// first, connect to the game hub
GameHubConnection = new HubConnectionBuilder()
.WithUrl($"{_config.ServerUrl}/tttgame", options =>
{
options.AccessTokenProvider = () => Task.FromResult(_apiService.SessionToken)!;
})
.WithAutomaticReconnect()
.Build();
pLoading.Visible = true;
lblLoadStatus.Text = "Connecting\nTo\nHub...";
GameHubConnection.On<GameRoom>("WaitingForPlayer", (room) =>
{
CurrentRoom = room;
BeginInvoke(delegate ()
{
if (room.Player1 != null)
{
lblP1Username.Text = room.Player1.Username;
lvUserlist.Items.Add(room.Player1.Id, room.Player1.Username, room.Player1.Status);
}
lblLoadStatus.Text = "Waiting\nFor\nPlayer...";
});
});
GameHubConnection.On<GameRoom>("GameStart", (room) =>
{
CurrentRoom = room;
BeginInvoke(delegate ()
{
pLoading.Visible = false;
pSymbolSelection.Visible = false;
});
});
GameHubConnection.On<GameRoom, string?>("GameEnd", (room, usernameDisconnected) =>
{
CurrentRoom = room;
BeginInvoke(delegate ()
{
if (room.Player1 == null) lblP1Username.Text = "Username";
if (room.Player2 == null) lblP2Username.Text = "Username";
ToggleBoardControl(false);
switch (room.Status)
{
case GameStatus.P1Win:
{
if (_apiService.CurrentUser.Id == room.Player1?.Id)
{
lblLoadStatus.Text = "You Won!";
WinCount++;
if (WinCount >= 3)
{
JackpotWon = true;
lblJackpotWon.Text = "You Won A Spin!";
}
}
else lblLoadStatus.Text = "You Lost!";
lblPlayAgain.Visible = true;
btnPlayAgain.Visible = true;
break;
}
case GameStatus.P2Win:
{
if (_apiService.CurrentUser.Id == room.Player2?.Id)
{
lblLoadStatus.Text = "You Won!";
WinCount++;
if (WinCount >= 3)
{
JackpotWon = true;
lblJackpotWon.Text = "You Won A Spin!";
}
}
else lblLoadStatus.Text = "You Lost!";
lblPlayAgain.Visible = true;
btnPlayAgain.Visible = true;
break;
}
case GameStatus.PlayerDisconnected:
{
if (!string.IsNullOrEmpty(usernameDisconnected))
lblLoadStatus.Text = $"Game Ended.\nUser {usernameDisconnected}\nDisconnected.";
lblPlayAgain.Visible = false;
btnPlayAgain.Visible = false;
break;
}
case GameStatus.NoWin:
lblLoadStatus.Text = "It's a draw!";
lblPlayAgain.Visible = true;
btnPlayAgain.Visible = true;
break;
}
pLoading.Visible = true;
});
});
GameHubConnection.On<GameRoom>("RestartGame", (room) =>
{
CurrentRoom = room;
BeginInvoke(delegate ()
{
if (pLoading.Visible) pLoading.Visible = false;
});
ClearBoard();
ToggleBoardControl(true);
BeginInvoke(delegate ()
{
List<Button> buttons = [btnSquare1, btnSquare2, btnSquare3, btnSquare4, btnSquare5, btnSquare6, btnSquare7, btnSquare8, btnSquare9];
foreach (Button button in buttons)
{
if (button.BackgroundImage == null)
button.Enabled = true;
}
});
});
GameHubConnection.On<GameRoom>("SelectSymbol", (room) =>
{
CurrentRoom = room;
BeginInvoke(delegate ()
{
if (room.Player1 != null)
{
lblP1Username.Text = room.Player1.Username;
if (!lvUserlist.Items.ContainsKey(room.Player1.Id)) lvUserlist.Items.Add(room.Player1.Id, room.Player1.Username, room.Player1.Status);
}
if (room.Player2 != null)
{
lblP2Username.Text = room.Player2.Username;
if (!lvUserlist.Items.ContainsKey(room.Player2.Id)) lvUserlist.Items.Add(room.Player2.Id, room.Player2.Username, room.Player2.Status);
}
if (pLoading.Visible) pLoading.Visible = false;
pSymbolSelection.Visible = true;
pBoard.Enabled = true;
ToggleAllSquareButtons(false);
});
});
GameHubConnection.On<GameRoom>("SelectingSymbol", (room) =>
{
CurrentRoom = room;
BeginInvoke(delegate ()
{
if (pSymbolSelection.Visible) return;
if (room.Player1 != null)
{
lblP1Username.Text = room.Player1.Username;
if (!lvUserlist.Items.ContainsKey(room.Player1.Id)) lvUserlist.Items.Add(room.Player1.Id, room.Player1.Username, room.Player1.Status);
}
if (room.Player2 != null)
{
lblP2Username.Text = room.Player2.Username;
if (!lvUserlist.Items.ContainsKey(room.Player2.Id)) lvUserlist.Items.Add(room.Player2.Id, room.Player2.Username, room.Player2.Status);
}
pLoading.Visible = true;
lblLoadStatus.Text = $"Player 1\nIs Selecting\nSymbol...";
});
});
GameHubConnection.On<GameRoom, TicTacToeMove>("UpdateBoard", (room, move) =>
{
CurrentRoom = room;
UpdateBoardUI(move);
});
GameHubConnection.On<GameRoom>("StartTurn", (room) =>
{
CurrentRoom = room;
ToggleBoardControl(true);
AddChatMessage("[SERVER] Your Turn!");
});
GameHubConnection.On<GameRoom>("EndTurn", (room) =>
{
CurrentRoom = room;
ToggleBoardControl(false);
AddChatMessage("[SERVER] Other Players Turn");
});
GameHubConnection.On<string>("ReceiveMessage", (msg) =>
{
AddChatMessage(msg);
if (msg.Split('[')[1] != _apiService.CurrentUser.Username)
_audioService.PlaySoundEffect("sndMessage");
});
try { await GameHubConnection.StartAsync(); }
catch (HttpRequestException ex)
{
MessageBox.Show($"Sorry, We Couldn't Connect To The Game Server Due To An Error. Please Try Again Later.\n\n{ex.Message}", "Oops.", MessageBoxButtons.OK, MessageBoxIcon.Error);
Environment.Exit(0);
}
lblLoadStatus.Text = "Finding\nRoom...";
await GameHubConnection.SendAsync("FindRoom");
}
private async void TicTacToeGame_FormClosed(object sender, FormClosedEventArgs e)
{
if (GameHubConnection != null && GameHubConnection.State == HubConnectionState.Connected)
{
if(JackpotWon)
{
// start a jackpot spin
CurrencyJackpotSpinner currencyJackpotSpinner = new CurrencyJackpotSpinner();
var dialogResult = currencyJackpotSpinner.ShowDialog();
if (dialogResult == DialogResult.OK)
{
await _apiService.AddCurrencyToCurrentUser(currencyJackpotSpinner.TokensWon, false);
_apiService.CurrentUser.CurrencyAmount += currencyJackpotSpinner.TokensWon;
}
}
// stop and dispose connection
await GameHubConnection.StopAsync();
await GameHubConnection.DisposeAsync();
GameHubConnection = null;
}
}
private async void btnXSelect_Click(object sender, EventArgs e) => await SelectSymbol(TicTacToeSymbol.X);
private async void btnOSelect_Click(object sender, EventArgs e) => await SelectSymbol(TicTacToeSymbol.O);
private async void btnPlayAgain_Click(object sender, EventArgs e)
{
if (GameHubConnection != null && GameHubConnection.State == HubConnectionState.Connected && CurrentRoom != null)
{
await GameHubConnection.SendAsync("RestartGame", CurrentRoom.Id);
}
}
private async void btnSquareX_Click(object sender, EventArgs e)
{
// create move
TicTacToeMove move = new TicTacToeMove
{
User = _apiService.CurrentUser
};
if (sender is Button btn)
{
switch (btn.Name)
{
case "btnSquare1":
move.Point = 1;
break;
case "btnSquare2":
move.Point = 2;
break;
case "btnSquare3":
move.Point = 3;
break;
case "btnSquare4":
move.Point = 4;
break;
case "btnSquare5":
move.Point = 5;
break;
case "btnSquare6":
move.Point = 6;
break;
case "btnSquare7":
move.Point = 7;
break;
case "btnSquare8":
move.Point = 8;
break;
case "btnSquare9":
move.Point = 9;
break;
}
}
// send move
if (GameHubConnection != null && GameHubConnection.State == HubConnectionState.Connected && CurrentRoom != null)
await GameHubConnection.SendAsync("MakeMove", CurrentRoom.Id, move);
}
private async void btnSend_Click(object sender, EventArgs e)
{
if ((GameHubConnection != null && GameHubConnection.State == HubConnectionState.Connected && CurrentRoom != null) &&
!string.IsNullOrWhiteSpace(rtxtChatbox.Text))
{
await GameHubConnection.SendAsync("SendRoomMessage", CurrentRoom.Id, $"[{_apiService.CurrentUser.Username}] {rtxtChatbox.Text}");
_audioService.PlaySoundEffect("sndSendClick");
rtxtChatbox.Clear();
}
}
private void rtxtChatbox_KeyDown(object sender, KeyEventArgs e)
{
if(e.KeyCode == Keys.Enter)
btnSend_Click(sender, e);
}
private void ToggleBoardControl(bool toggle)
{
if (IsHandleCreated && !IsDisposed)
{
Invoke(delegate ()
{
pBoard.Enabled = toggle;
});
}
}
private void ToggleAllSquareButtons(bool toggle)
{
if (IsHandleCreated && !IsDisposed)
{
Invoke(delegate ()
{
List<Button> buttons = [btnSquare1, btnSquare2, btnSquare3, btnSquare4, btnSquare5, btnSquare6, btnSquare7, btnSquare8, btnSquare9];
foreach (Button button in buttons)
button.Enabled = toggle;
});
}
}
private async Task SelectSymbol(TicTacToeSymbol symbol)
{
if (GameHubConnection != null && GameHubConnection.State == HubConnectionState.Connected && CurrentRoom != null)
{
await GameHubConnection.SendAsync("SetSymbol", CurrentRoom.Id, symbol);
ToggleAllSquareButtons(true);
}
}
private void UpdateBoardUI(TicTacToeMove move)
{
if (IsHandleCreated && !IsDisposed && CurrentRoom != null)
{
// play move made sound
_audioService.PlaySoundEffect("sndTTTMoveMade");
Invoke(delegate ()
{
switch (CurrentRoom.Board.Square1)
{
case TicTacToeSymbol.X:
btnSquare1.BackgroundImage = Properties.Resources.X;
btnSquare1.Enabled = false;
break;
case TicTacToeSymbol.O:
btnSquare1.BackgroundImage = Properties.Resources.O;
btnSquare1.Enabled = false;
break;
}
switch (CurrentRoom.Board.Square2)
{
case TicTacToeSymbol.X:
btnSquare2.BackgroundImage = Properties.Resources.X;
btnSquare2.Enabled = false;
break;
case TicTacToeSymbol.O:
btnSquare2.BackgroundImage = Properties.Resources.O;
btnSquare2.Enabled = false;
break;
}
switch (CurrentRoom.Board.Square3)
{
case TicTacToeSymbol.X:
btnSquare3.BackgroundImage = Properties.Resources.X;
btnSquare3.Enabled = false;
break;
case TicTacToeSymbol.O:
btnSquare3.BackgroundImage = Properties.Resources.O;
btnSquare3.Enabled = false;
break;
}
switch (CurrentRoom.Board.Square4)
{
case TicTacToeSymbol.X:
btnSquare4.BackgroundImage = Properties.Resources.X;
btnSquare4.Enabled = false;
break;
case TicTacToeSymbol.O:
btnSquare4.BackgroundImage = Properties.Resources.O;
btnSquare4.Enabled = false;
break;
}
switch (CurrentRoom.Board.Square5)
{
case TicTacToeSymbol.X:
btnSquare5.BackgroundImage = Properties.Resources.X;
btnSquare5.Enabled = false;
break;
case TicTacToeSymbol.O:
btnSquare5.BackgroundImage = Properties.Resources.O;
btnSquare5.Enabled = false;
break;
}
switch (CurrentRoom.Board.Square6)
{
case TicTacToeSymbol.X:
btnSquare6.BackgroundImage = Properties.Resources.X;
btnSquare6.Enabled = false;
break;
case TicTacToeSymbol.O:
btnSquare6.BackgroundImage = Properties.Resources.O;
btnSquare6.Enabled = false;
break;
}
switch (CurrentRoom.Board.Square7)
{
case TicTacToeSymbol.X:
btnSquare7.BackgroundImage = Properties.Resources.X;
btnSquare7.Enabled = false;
break;
case TicTacToeSymbol.O:
btnSquare7.BackgroundImage = Properties.Resources.O;
btnSquare7.Enabled = false;
break;
}
switch (CurrentRoom.Board.Square8)
{
case TicTacToeSymbol.X:
btnSquare8.BackgroundImage = Properties.Resources.X;
btnSquare8.Enabled = false;
break;
case TicTacToeSymbol.O:
btnSquare8.BackgroundImage = Properties.Resources.O;
btnSquare8.Enabled = false;
break;
}
switch (CurrentRoom.Board.Square9)
{
case TicTacToeSymbol.X:
btnSquare9.BackgroundImage = Properties.Resources.X;
btnSquare9.Enabled = false;
break;
case TicTacToeSymbol.O:
btnSquare9.BackgroundImage = Properties.Resources.O;
btnSquare9.Enabled = false;
break;
}
});
}
}
private void AddChatMessage(string msg)
{
if (IsHandleCreated && !IsDisposed)
{
Invoke(delegate ()
{
rtxtChat.AppendText($"{msg}\n");
});
}
}
private void ClearBoard()
{
if (IsHandleCreated && !IsDisposed)
{
Invoke(delegate ()
{
List<Button> buttons = [btnSquare1, btnSquare2, btnSquare3, btnSquare4, btnSquare5, btnSquare6, btnSquare7, btnSquare8, btnSquare9];
foreach (Button button in buttons)
button.BackgroundImage = null;
});
}
}
}
}

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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 B

View File

@ -1,4 +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;
@ -14,8 +16,17 @@ namespace qtc_net_client_2
[STAThread]
static async Task Main()
{
// first thing, create a new log
LoggingService loggingService = new();
Config clientConfig = new Config();
if(System.Diagnostics.Debugger.IsAttached)
{
// use localhost
clientConfig.ServerUrl = "http://localhost:5268";
}
// find config file
if(!File.Exists("./config.json"))
{
@ -23,36 +34,51 @@ namespace qtc_net_client_2
string configJson = JsonSerializer.Serialize(clientConfig, options: new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText("./config.json", configJson);
} else
{
try
{
// use config in file
Config? fileConfig = JsonSerializer.Deserialize<Config>(File.ReadAllText("./config.json"));
if (fileConfig != null)
{
clientConfig = fileConfig;
}
} catch (JsonException)
{
// there was a missing property, reset config to default
string configJson = JsonSerializer.Serialize(clientConfig, options: new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText("./config.json", configJson);
}
}
// 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.ApiEndpoint);
IGatewayService gateway = new GatewayService(clientConfig.GatewayEndpoint, api);
IApiService api = new ApiService($"{clientConfig.ServerUrl}/api", loggingService);
IGatewayService gateway = new GatewayService($"{clientConfig.ServerUrl}/chat", api, loggingService);
// ping api
var pingResult = api.PingServerAsync().GetAwaiter().GetResult();
var pingResult = await api.PingServerAsync();
if (!pingResult.Success)
{
MessageBox.Show("The API Specified In The Config Could Not Be Reached.\nCheck The URL Specified, Otherwise Contact The Server Admin.", "Uh Oh.", MessageBoxButtons.OK, MessageBoxIcon.Error);
MessageBox.Show("The Server Specified In The Config Could Not Be Reached.\nCheck The URL Specified, Otherwise Contact The Server Admin.", "Uh Oh.", MessageBoxButtons.OK, MessageBoxIcon.Error);
Environment.Exit(1);
}
// check for updates
UpdateService updateService = new();
await updateService.CheckForUpdatesAsync();
// To customize application configuration such as set high DPI settings or default font,
// see https://aka.ms/applicationconfiguration.
ApplicationConfiguration.Initialize();
Application.Run(new Main(api, gateway));
Application.Run(new Main(api, gateway, clientConfig, loggingService));
// if application loop is exited, dispose everything
await gateway.DisposeAsync();
loggingService.Dispose();
api = null;
gateway = null;
api = null!; // shut up compiler >:(
gateway = null!;
Environment.Exit(0);
}

View File

@ -80,6 +80,15 @@ namespace qtc_net_client_2.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to 6.3.4.
/// </summary>
internal static string AssemblyVersion {
get {
return ResourceManager.GetString("AssemblyVersion", resourceCulture);
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
@ -100,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>
@ -140,6 +159,26 @@ namespace qtc_net_client_2.Properties {
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap dollar_money {
get {
object obj = ResourceManager.GetObject("dollar-money", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <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>
@ -170,6 +209,16 @@ namespace qtc_net_client_2.Properties {
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap O {
get {
object obj = ResourceManager.GetObject("O", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
@ -229,5 +278,35 @@ namespace qtc_net_client_2.Properties {
return ((System.Drawing.Bitmap)(obj));
}
}
/// <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>
internal static System.Drawing.Bitmap Tic_tac_toe {
get {
object obj = ResourceManager.GetObject("Tic-tac-toe", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap X {
get {
object obj = ResourceManager.GetObject("X", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
}
}

View File

@ -124,12 +124,21 @@
<data name="SendIcon" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Icons\SendIcon.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="X" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\X.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="left-horn-animated" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Anims\left-horn-animated.gif;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="OfflineIcon" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Icons\OfflineIcon.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="dollar-money" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Anims\dollar-money.gif;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Tic-tac-toe" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Tic-tac-toe1.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="OnlineIcon" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Icons\OnlineIcon.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
@ -151,22 +160,37 @@
<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>
<data name="MessageIcon" type="System.Resources.ResXFileRef, System.Windows.Forms">
<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>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>
</data>
<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="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="left-horn-animated" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Anims\left-horn-animated.gif;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: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 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

@ -0,0 +1,108 @@
using qtc_net_client_2.ClientModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using System.Diagnostics;
using qtc_net_client_2.Properties;
using System.Reflection;
namespace qtc_net_client_2.Services
{
public class UpdateService
{
public async Task CheckForUpdatesAsync()
{
if (Debugger.IsAttached) return;
// get client update info
HttpClient client = new();
client.BaseAddress = new Uri("https://qtcclient.alanmoon.net");
try
{
ClientUpdateInfo? updateInfo = await client.GetFromJsonAsync<ClientUpdateInfo>("clientinfo.json");
if (updateInfo != null)
{
if (updateInfo.Version != Resources.AssemblyVersion)
{
// inform the user an update is available
if (!updateInfo.IsUpdateMandatory)
{
var result = MessageBox.Show(
$"An Update For QtC.NET Is Available. You Can View The Changelog At {updateInfo.DownloadUrl}. Do You Want To Update Now?",
"Update Available",
MessageBoxButtons.YesNo,
MessageBoxIcon.Information);
if (result == DialogResult.Yes) await UpdateAsync(updateInfo);
}
else
{
var result = MessageBox.Show(
"Your QtC.NET Client Is Not Up To Date. Using This Version Of The Client With The Current Version Of The Server Could Lead To Data Loss.\n" +
$"You Can View The Changelog At {updateInfo.DownloadUrl}. Do You Want To Update Now?",
"Update Required",
MessageBoxButtons.YesNo,
MessageBoxIcon.Error);
if (result == DialogResult.Yes) await UpdateAsync(updateInfo);
else Environment.Exit(1);
}
}
}
} catch (HttpRequestException ex)
{
Debug.WriteLine("Client Update HTTP Request Failed - " + ex.Message);
} catch (JsonException)
{
Debug.WriteLine("Client Update Info From Server Invalid");
}
}
private async Task UpdateAsync(ClientUpdateInfo updateInfo)
{
HttpClient client = new();
client.BaseAddress = new Uri("https://qtcclient.alanmoon.net");
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");
// download new client version
var clientFileStream = await client.GetStreamAsync(updateInfo.ClientFileName);
using(var fs = new FileStream($"./{updateInfo.ClientFileName}", FileMode.Create))
{
clientFileStream.CopyTo(fs);
fs.Dispose();
}
clientFileStream.Dispose();
// restart the process
Process process = new Process();
process.StartInfo = new ProcessStartInfo { FileName = $"./{updateInfo.ClientFileName}", WorkingDirectory = Environment.CurrentDirectory };
process.Start();
Environment.Exit(0);
} catch (HttpRequestException ex)
{
MessageBox.Show($"Update Failed. Please Check Your Internet Connection.\n{ex.Message}",
"Update Failed",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
catch (UnauthorizedAccessException ex)
{
MessageBox.Show($"Update Failed. Permissions In Client Folder Are Wrong.\n{ex.Message}",
"Update Failed",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}
}
}

Binary file not shown.

View File

@ -62,6 +62,9 @@
<None Update="Sounds\sndTokenWin.wav">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Sounds\sndTTTMoveMade.wav">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>