diff --git a/README.md b/README.md
index c2662ba..c7b72f4 100644
--- a/README.md
+++ b/README.md
@@ -10,3 +10,11 @@ Focus Friends is a mobile app that uses a timer to track and record user focus s
[Visual Mockup](https://www.figma.com/file/HG8eqMzI47otQWYFIv5iK7/Focus-Timer-App-MockUp?type=design&node-id=0%3A1&mode=design&t=gdzpRvpkRlAWQJPb-1)
+### Contributors
+| Github Link | Name | Email |
+|:-------------------------:|:-------------------------:|:-------------------------:|
+ | Chris Perry | chrispperry12@gmail.com |
+ | Evan Gray | ekgray.make@gmail.com |
+ | John Parks | johnparks032@proton.me |
+ | Elijah Payton | elipayton1@gmail.com |
+
diff --git a/src/FocusApp.Client/Helpers/AuthenticationService.cs b/src/FocusApp.Client/Helpers/AuthenticationService.cs
index fa478dd..ff1c3b7 100644
--- a/src/FocusApp.Client/Helpers/AuthenticationService.cs
+++ b/src/FocusApp.Client/Helpers/AuthenticationService.cs
@@ -8,17 +8,22 @@ namespace FocusApp.Client.Helpers;
internal interface IAuthenticationService
{
+ bool IsLoggedIn { get; }
+ Guid? Id { get; set; }
string? Auth0Id { get; set; }
string? Email { get; set; }
- User? CurrentUser { get; set; }
+ string? UserName { get; set; }
+ string? Pronouns { get; set; }
+ int Balance { get; set; }
+ DateTime? DateCreated { get; set; }
+ byte[]? ProfilePicture { get; set; }
Island? SelectedIsland { get; set; }
Pet? SelectedPet { get; set; }
Badge? SelectedBadge { get; set; }
Decor? SelectedDecor { get; set; }
- int Balance { get; set; }
+ Task? StartupSyncTask { get; set; }
event PropertyChangedEventHandler? PropertyChanged;
-
void ClearUser();
Task Logout(IAuth0Client auth0Client);
void PopulateWithUserData(User user);
@@ -26,16 +31,16 @@ internal interface IAuthenticationService
public class AuthenticationService : INotifyPropertyChanged, IAuthenticationService
{
+ public bool IsLoggedIn => Auth0Id is not null;
public event PropertyChangedEventHandler? PropertyChanged;
+ public Guid? Id { get; set; }
public string? Auth0Id { get; set; } = "";
public string? Email { get; set; } = "";
-
- private User? _currentUser;
- public User? CurrentUser
- {
- get => _currentUser;
- set => SetProperty(ref _currentUser, value);
- }
+ public string? UserName { get; set; } = "";
+ public string? Pronouns { get; set; } = "";
+ public DateTime? DateCreated { get; set; }
+ public byte[]? ProfilePicture { get; set; }
+ public Task? StartupSyncTask { get; set; }
private int? _balance;
public int Balance
@@ -99,26 +104,33 @@ public async Task Logout(IAuth0Client auth0Client)
///
public void ClearUser()
{
- Auth0Id = string.Empty;
- Email = string.Empty;
+ Id = null;
+ Auth0Id = null;
+ Email = null;
+ UserName = null;
+ Pronouns = null;
Balance = 0;
- CurrentUser = null;
+ DateCreated = DateTime.MinValue;
+ ProfilePicture = null;
SelectedIsland = null;
- SelectedPet = null;
SelectedBadge = null;
SelectedDecor = null;
+ SelectedPet = null;
}
public void PopulateWithUserData(User user)
{
- CurrentUser = user;
-
+ Id = user.Id;
Auth0Id = user.Auth0Id;
Email = user.Email;
+ UserName = user.UserName;
+ Pronouns = user.Pronouns;
Balance = user.Balance;
+ DateCreated = user.DateCreated;
+ ProfilePicture = user.ProfilePicture;
+ SelectedIsland = user.SelectedIsland;
SelectedBadge = user.SelectedBadge;
SelectedDecor = user.SelectedDecor;
- SelectedIsland = user.SelectedIsland;
SelectedPet = user.SelectedPet;
}
diff --git a/src/FocusApp.Client/Helpers/SyncService.cs b/src/FocusApp.Client/Helpers/SyncService.cs
index 670a1be..7df8cca 100644
--- a/src/FocusApp.Client/Helpers/SyncService.cs
+++ b/src/FocusApp.Client/Helpers/SyncService.cs
@@ -1,5 +1,8 @@
-using FocusApp.Shared.Data;
+using FocusApp.Client.Methods.Sync;
+using FocusApp.Shared.Data;
using FocusApp.Shared.Models;
+using MediatR;
+using Microsoft.Extensions.Logging;
namespace FocusApp.Client.Helpers;
@@ -7,15 +10,42 @@ public interface ISyncService
{
IQueryable GetInitialIslandQuery();
IQueryable GetInitialPetQuery();
+
+ ///
+ /// Ensure the database is created and migrations are applied, then run the startup logic.
+ ///
+ Task RunStartupLogic();
+
+ ///
+ /// Populates the database with initial data requested from the API for any of
+ /// the island, pets, or decor tables if they don't have any entries.
+ ///
+ Task GatherEssentialDatabaseData();
+
+ ///
+ /// Syncs all item types in the enum between the API and mobile database
+ /// if the last sync happened over a week ago or this is the first time starting the app.
+ ///
+ ///
+ /// If there's an unexpected error, the critical data for app functionality will be retrieved.
+ ///
+ Task StartupSync();
}
public class SyncService : ISyncService
{
private readonly FocusAppContext _context;
+ private readonly IServiceProvider _serviceProvider;
+ private readonly ILogger _logger;
- public SyncService(FocusAppContext context)
+ public SyncService(
+ FocusAppContext context,
+ IServiceProvider serviceProvider,
+ ILogger logger)
{
_context = context;
+ _serviceProvider = serviceProvider;
+ _logger = logger;
}
public IQueryable GetInitialIslandQuery()
@@ -29,4 +59,89 @@ public IQueryable GetInitialPetQuery()
return _context.Pets
.Where(pet => pet.Name == FocusCore.Consts.NameOfInitialPet);
}
+
+ ///
+ /// Ensure the database is created and migrations are applied, then run the startup logic.
+ ///
+ public async Task RunStartupLogic()
+ {
+
+ }
+
+ ///
+ /// Syncs all item types in the enum between the API and mobile database
+ /// if the last sync happened over a week ago or this is the first time starting the app.
+ ///
+ ///
+ /// If there's an unexpected error, the critical data for app functionality will be retrieved.
+ ///
+ public async Task StartupSync()
+ {
+ try
+ {
+
+ // If not in debug and a sync has been done in the past week, don't sync
+#if !DEBUG
+ string lastSyncTimeString = PreferencesHelper.Get(PreferencesHelper.PreferenceNames.last_sync_time);
+ if (!string.IsNullOrEmpty(lastSyncTimeString))
+ {
+ DateTimeOffset lastSyncTime = DateTimeOffset.Parse(lastSyncTimeString);
+
+ if (DateTimeOffset.UtcNow < lastSyncTime.AddDays(7))
+ return;
+ }
+#endif
+
+ IList tasks = new List();
+ var scopedServiceProvider = _serviceProvider
+ .CreateScope()
+ .ServiceProvider;
+ _ = scopedServiceProvider.GetRequiredService();
+
+ foreach (SyncItems.SyncItemType syncType in Enum.GetValues(typeof(SyncItems.SyncItemType)))
+ {
+ IMediator mediator = _serviceProvider
+ .CreateScope()
+ .ServiceProvider
+ .GetRequiredService();
+
+ tasks.Add(
+ Task.Run(() => mediator.Send(
+ new SyncItems.Query() { ItemType = syncType }
+ ))
+ );
+ }
+
+ await Task.WhenAll(tasks);
+
+ PreferencesHelper.Set(PreferencesHelper.PreferenceNames.last_sync_time, DateTimeOffset.UtcNow.ToString("O"));
+ }
+ catch (Exception ex)
+ {
+ // If there's an error with island or pet sync, ensure the essential database information is gathered
+ _logger.LogError(ex, "Error occurred when syncing selectable items and mindfulness tips. Running essential database population.");
+ await GatherEssentialDatabaseData();
+ }
+ }
+
+ ///
+ /// Populates the database with initial data requested from the API for any of
+ /// the island, pets, or decor tables if they don't have any entries.
+ ///
+ public async Task GatherEssentialDatabaseData()
+ {
+ try
+ {
+ var scopedServiceProvider = _serviceProvider
+ .CreateScope()
+ .ServiceProvider;
+ IMediator mediator = scopedServiceProvider.GetRequiredService();
+
+ await mediator.Send(new SyncInitialData.Query());
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error when running essential database population.");
+ }
+ }
}
\ No newline at end of file
diff --git a/src/FocusApp.Client/MauiProgram.cs b/src/FocusApp.Client/MauiProgram.cs
index 0ab3fe5..6cfb0e8 100644
--- a/src/FocusApp.Client/MauiProgram.cs
+++ b/src/FocusApp.Client/MauiProgram.cs
@@ -50,18 +50,19 @@ public static MauiApp CreateMauiApp()
builder.Logging.AddDebug();
IdentityModelEventSource.ShowPII = true;
#endif
-
- Task.Run(() => RunStartupLogic(builder.Services));
-
return builder.Build();
}
public static IServiceCollection RegisterAuthenticationServices(this IServiceCollection services)
{
+#if DEBUG
const string domain = "dev-7c8vyxbx5myhzmji.us.auth0.com";
const string clientId = "PR3eHq0ehapDGtpYyLl5XFhd1mOQX9uD";
-
+#else
+ const string domain = "zenpxl.us.auth0.com";
+ const string clientId = "tQ6cctnvL3AoyXNEEy7YGe5eYtJIewaC";
+#endif
services.AddSingleton(new Auth0Client(new()
{
Domain = domain,
@@ -99,9 +100,15 @@ private static IServiceCollection RegisterDatabaseContext(this IServiceCollectio
private static IServiceCollection RegisterRefitClient(this IServiceCollection services)
{
+#if DEBUG
+ string apiDomain = "http://10.0.2.2:5223";
+#else
+ string apiDomain = "http://prod.zenpxl.com:25565";
+#endif
+
services
.AddRefitClient()
- .ConfigureHttpClient(c => c.BaseAddress = new Uri("http://prod.zenpxl.com:25565"));
+ .ConfigureHttpClient(c => c.BaseAddress = new Uri(apiDomain));
return services;
}
@@ -155,105 +162,5 @@ private static IServiceCollection RegisterPopups(this IServiceCollection service
return services;
}
-
- ///
- /// Ensure the database is created and migrations are applied, then run the startup logic.
- ///
- private static async Task RunStartupLogic(IServiceCollection services)
- {
- try
- {
- var scopedServiceProvider = services
- .BuildServiceProvider()
- .CreateScope()
- .ServiceProvider;
- _ = scopedServiceProvider.GetRequiredService();
-
- await Task.Run(() => StartupSync(services, default), default);
- }
- catch (Exception ex)
- {
- Console.WriteLine("Error running startup logic");
- Console.Write(ex);
- }
-
- }
-
- ///
- /// Syncs all item types in the enum between the API and mobile database
- /// if the last sync happened over a week ago or this is the first time starting the app.
- ///
- ///
- /// If there's an unexpected error, the critical data for app functionality will be retrieved.
- ///
- private static async Task StartupSync(IServiceCollection services, CancellationToken cancellationToken)
- {
- try
- {
-
- // If not in debug and a sync has been done in the past week, don't sync
-#if !DEBUG
- string lastSyncTimeString = PreferencesHelper.Get(PreferenceNames.last_sync_time);
- if (!string.IsNullOrEmpty(lastSyncTimeString))
- {
- DateTimeOffset lastSyncTime = DateTimeOffset.Parse(lastSyncTimeString);
-
- if (DateTimeOffset.UtcNow < lastSyncTime.AddDays(7))
- return;
- }
-#endif
-
- IList tasks = new List();
- foreach (SyncItems.SyncItemType syncType in Enum.GetValues(typeof(SyncItems.SyncItemType)))
- {
- IMediator mediator = services
- .BuildServiceProvider()
- .CreateScope()
- .ServiceProvider
- .GetRequiredService();
-
- tasks.Add(
- Task.Run(() => mediator.Send(
- new SyncItems.Query() { ItemType = syncType },
- cancellationToken),
- cancellationToken)
- );
- }
-
- await Task.WhenAll(tasks);
-
- PreferencesHelper.Set(PreferenceNames.last_sync_time, DateTimeOffset.UtcNow.ToString("O"));
- }
- catch (Exception ex)
- {
- // If there's an error, ensure the essential database information is gathered
- Console.WriteLine("Error occurred when syncing selectable items and mindfulness tips. Running essential database population.");
- Console.Write(ex);
- await GatherEssentialDatabaseData(services);
- }
- }
-
- ///
- /// Populates the database with initial data requested from the API for any of
- /// the island, pets, or decor tables if they don't have any entries.
- ///
- private static async Task GatherEssentialDatabaseData(IServiceCollection services)
- {
- try
- {
- var scopedServiceProvider = services
- .BuildServiceProvider()
- .CreateScope()
- .ServiceProvider;
- IMediator mediator = scopedServiceProvider.GetRequiredService();
-
- await mediator.Send(new SyncInitialData.Query());
- }
- catch (Exception ex)
- {
- Console.WriteLine("Error when running essential database population.");
- Console.Write(ex);
- }
- }
}
}
\ No newline at end of file
diff --git a/src/FocusApp.Client/Methods/Badges/CheckBreakSessionBadgeEligibility.cs b/src/FocusApp.Client/Methods/Badges/CheckBreakSessionBadgeEligibility.cs
index a20ee88..0fd1f8e 100644
--- a/src/FocusApp.Client/Methods/Badges/CheckBreakSessionBadgeEligibility.cs
+++ b/src/FocusApp.Client/Methods/Badges/CheckBreakSessionBadgeEligibility.cs
@@ -34,7 +34,7 @@ public async Task Handle(Query query, CancellationToken
IsEligible = false
};
- if (_authenticationService.CurrentUser is null)
+ if (!_authenticationService.IsLoggedIn)
throw new InvalidOperationException("User is not logged in.");
string? badgeName = "Downtime";
@@ -50,7 +50,7 @@ private async Task AddBadgeToUser(string? badgeName, BadgeEligibilityResult resu
{
bool hasBadge = await _localContext.UserBadges
.Where(userBadge =>
- userBadge.UserId == _authenticationService.CurrentUser.Id &&
+ userBadge.UserId == _authenticationService.Id.Value &&
userBadge.Badge.Name == badgeName)
.AnyAsync(cancellationToken);
@@ -62,7 +62,7 @@ private async Task AddBadgeToUser(string? badgeName, BadgeEligibilityResult resu
_localContext.UserBadges.Add(new UserBadge()
{
BadgeId = result.EarnedBadge.Id,
- UserId = _authenticationService.CurrentUser.Id,
+ UserId = _authenticationService.Id.Value,
DateAcquired = DateTime.UtcNow
});
@@ -72,7 +72,7 @@ private async Task AddBadgeToUser(string? badgeName, BadgeEligibilityResult resu
await _client.AddUserBadge(new AddUserBadgeCommand()
{
BadgeId = result.EarnedBadge.Id,
- UserId = _authenticationService.CurrentUser.Id
+ UserId = _authenticationService.Id.Value
},
cancellationToken);
diff --git a/src/FocusApp.Client/Methods/Badges/CheckDecorPurchaseBadgeEligibility.cs b/src/FocusApp.Client/Methods/Badges/CheckDecorPurchaseBadgeEligibility.cs
index a95e5d2..862d769 100644
--- a/src/FocusApp.Client/Methods/Badges/CheckDecorPurchaseBadgeEligibility.cs
+++ b/src/FocusApp.Client/Methods/Badges/CheckDecorPurchaseBadgeEligibility.cs
@@ -30,7 +30,7 @@ public async Task Handle(Query query, CancellationToken
{
Shared.Models.User? user = await _localContext.Users
.Include(u => u.Decor)
- .SingleOrDefaultAsync(u => u.Id == _authenticationService.CurrentUser.Id, cancellationToken);
+ .SingleOrDefaultAsync(u => u.Id == _authenticationService.Id.Value, cancellationToken);
if (user == null)
throw new InvalidOperationException("User not found in local database.");
@@ -62,7 +62,7 @@ public async Task Handle(Query query, CancellationToken
// Save new user badge to server database
try
{
- await _client.AddUserBadge(new AddUserBadgeCommand { UserId = _authenticationService.CurrentUser.Id, BadgeId = result.EarnedBadge.Id });
+ await _client.AddUserBadge(new AddUserBadgeCommand { UserId = _authenticationService.Id.Value, BadgeId = result.EarnedBadge.Id });
}
catch (Exception ex)
{
diff --git a/src/FocusApp.Client/Methods/Badges/CheckFocusSessionBadgeEligibility.cs b/src/FocusApp.Client/Methods/Badges/CheckFocusSessionBadgeEligibility.cs
index 6266fca..59b8529 100644
--- a/src/FocusApp.Client/Methods/Badges/CheckFocusSessionBadgeEligibility.cs
+++ b/src/FocusApp.Client/Methods/Badges/CheckFocusSessionBadgeEligibility.cs
@@ -34,13 +34,13 @@ public async Task Handle(Query query, CancellationToken
IsEligible = false
};
- if (_authenticationService.CurrentUser is null)
+ if (!_authenticationService.IsLoggedIn)
{
throw new InvalidOperationException("User is not logged in.");
}
int sessionCount = await _localContext.UserSessionHistory
- .CountAsync(u => u.UserId == _authenticationService.CurrentUser.Id, cancellationToken);
+ .CountAsync(u => u.UserId == _authenticationService.Id.Value, cancellationToken);
string? badgeName = sessionCount switch
{
@@ -61,7 +61,7 @@ private async Task AddBadgeToUser(string? badgeName, BadgeEligibilityResult resu
{
bool hasBadge = await _localContext.UserBadges
.Where(userBadge =>
- userBadge.UserId == _authenticationService.CurrentUser.Id &&
+ userBadge.UserId == _authenticationService.Id.Value &&
userBadge.Badge.Name == badgeName)
.AnyAsync(cancellationToken);
@@ -73,7 +73,7 @@ private async Task AddBadgeToUser(string? badgeName, BadgeEligibilityResult resu
_localContext.UserBadges.Add(new UserBadge()
{
BadgeId = result.EarnedBadge.Id,
- UserId = _authenticationService.CurrentUser.Id,
+ UserId = _authenticationService.Id.Value,
DateAcquired = DateTime.UtcNow
});
@@ -83,7 +83,7 @@ private async Task AddBadgeToUser(string? badgeName, BadgeEligibilityResult resu
await _client.AddUserBadge(new AddUserBadgeCommand()
{
BadgeId = result.EarnedBadge.Id,
- UserId = _authenticationService.CurrentUser.Id
+ UserId = _authenticationService.Id.Value
},
cancellationToken);
diff --git a/src/FocusApp.Client/Methods/Badges/CheckIslandPurchaseBadgeEligibility.cs b/src/FocusApp.Client/Methods/Badges/CheckIslandPurchaseBadgeEligibility.cs
index d9ee60c..081f2bc 100644
--- a/src/FocusApp.Client/Methods/Badges/CheckIslandPurchaseBadgeEligibility.cs
+++ b/src/FocusApp.Client/Methods/Badges/CheckIslandPurchaseBadgeEligibility.cs
@@ -30,7 +30,7 @@ public async Task Handle(Query query, CancellationToken
{
Shared.Models.User? user = await _localContext.Users
.Include(u => u.Islands)
- .SingleOrDefaultAsync(u => u.Id == _authenticationService.CurrentUser.Id, cancellationToken);
+ .SingleOrDefaultAsync(u => u.Id == _authenticationService.Id.Value, cancellationToken);
if (user == null)
throw new InvalidOperationException("User not found in local database.");
@@ -63,7 +63,7 @@ public async Task Handle(Query query, CancellationToken
// Save new user badge to server database
try
{
- await _client.AddUserBadge(new AddUserBadgeCommand { UserId = _authenticationService.CurrentUser.Id, BadgeId = result.EarnedBadge.Id });
+ await _client.AddUserBadge(new AddUserBadgeCommand { UserId = _authenticationService.Id.Value, BadgeId = result.EarnedBadge.Id });
}
catch (Exception ex)
{
diff --git a/src/FocusApp.Client/Methods/Badges/CheckPetPurchaseBadgeEligbility.cs b/src/FocusApp.Client/Methods/Badges/CheckPetPurchaseBadgeEligbility.cs
index d3c6507..5d7fcec 100644
--- a/src/FocusApp.Client/Methods/Badges/CheckPetPurchaseBadgeEligbility.cs
+++ b/src/FocusApp.Client/Methods/Badges/CheckPetPurchaseBadgeEligbility.cs
@@ -30,7 +30,7 @@ public async Task Handle(Query query, CancellationToken
{
Shared.Models.User? user = await _localContext.Users
.Include(u => u.Pets)
- .SingleOrDefaultAsync(u => u.Id == _authenticationService.CurrentUser.Id, cancellationToken);
+ .SingleOrDefaultAsync(u => u.Id == _authenticationService.Id.Value, cancellationToken);
if (user == null)
throw new InvalidOperationException("User not found in local database.");
@@ -63,7 +63,7 @@ public async Task Handle(Query query, CancellationToken
// Save new user badge to server database
try
{
- await _client.AddUserBadge(new AddUserBadgeCommand { UserId = _authenticationService.CurrentUser.Id, BadgeId = result.EarnedBadge.Id });
+ await _client.AddUserBadge(new AddUserBadgeCommand { UserId = _authenticationService.Id.Value, BadgeId = result.EarnedBadge.Id });
}
catch (Exception ex)
{
diff --git a/src/FocusApp.Client/Methods/Badges/CheckSocialBadgeEligibility.cs b/src/FocusApp.Client/Methods/Badges/CheckSocialBadgeEligibility.cs
index 33e4b92..6224154 100644
--- a/src/FocusApp.Client/Methods/Badges/CheckSocialBadgeEligibility.cs
+++ b/src/FocusApp.Client/Methods/Badges/CheckSocialBadgeEligibility.cs
@@ -33,7 +33,7 @@ public async Task Handle(Query query, CancellationToken
var friendResult = await _client
.GetAllFriends(new GetAllFriendsQuery()
{
- UserId = _authenticationService.CurrentUser.Id
+ UserId = _authenticationService.Id.Value
},
cancellationToken);
@@ -54,7 +54,7 @@ public async Task Handle(Query query, CancellationToken
{
bool hasBadge = await _localContext.UserBadges
.Where(userBadge =>
- userBadge.UserId == _authenticationService.CurrentUser.Id &&
+ userBadge.UserId == _authenticationService.Id.Value &&
userBadge.Badge.Name == badgeName)
.AnyAsync(cancellationToken);
@@ -80,7 +80,7 @@ private async Task AddBadgeToUser(
_localContext.UserBadges.Add(new UserBadge()
{
BadgeId = result.EarnedBadge.Id,
- UserId = _authenticationService.CurrentUser.Id,
+ UserId = _authenticationService.Id.Value,
DateAcquired = DateTime.UtcNow
});
@@ -90,7 +90,7 @@ private async Task AddBadgeToUser(
await _client.AddUserBadge(new AddUserBadgeCommand()
{
BadgeId = result.EarnedBadge.Id,
- UserId = _authenticationService.CurrentUser.Id
+ UserId = _authenticationService.Id.Value
},
cancellationToken);
}
diff --git a/src/FocusApp.Client/Methods/Shop/PurchaseItem.cs b/src/FocusApp.Client/Methods/Shop/PurchaseItem.cs
index 6ae4659..f862457 100644
--- a/src/FocusApp.Client/Methods/Shop/PurchaseItem.cs
+++ b/src/FocusApp.Client/Methods/Shop/PurchaseItem.cs
@@ -39,7 +39,7 @@ public async Task Handle(Command command, CancellationToken cancellationTo
try
{
// Add the user's new pet to the local database
- Shared.Models.User user = await _localContext.Users.FirstAsync(u => u.Id == _authenticationService.CurrentUser.Id, cancellationToken);
+ Shared.Models.User user = await _localContext.Users.FirstAsync(u => u.Id == _authenticationService.Id.Value, cancellationToken);
user.Pets?.Add(new UserPet
{
Pet = await _localContext.Pets.FirstAsync(p => p.Id == command.Item.Id, cancellationToken)
@@ -59,7 +59,7 @@ public async Task Handle(Command command, CancellationToken cancellationTo
{
await _client.AddUserPet(new AddUserPetCommand
{
- UserId = _authenticationService.CurrentUser.Id,
+ UserId = _authenticationService.Id.Value,
PetId = command.Item.Id,
UpdatedBalance = _authenticationService.Balance,
});
@@ -75,7 +75,7 @@ await _client.AddUserPet(new AddUserPetCommand
// Add the user's new decor to the local database
try
{
- Shared.Models.User user = await _localContext.Users.FirstAsync(u => u.Id == _authenticationService.CurrentUser.Id, cancellationToken);
+ Shared.Models.User user = await _localContext.Users.FirstAsync(u => u.Id == _authenticationService.Id.Value, cancellationToken);
user.Decor?.Add(new UserDecor
{
Decor = await _localContext.Decor.FirstAsync(d => d.Id == command.Item.Id, cancellationToken)
@@ -95,7 +95,7 @@ await _client.AddUserPet(new AddUserPetCommand
{
await _client.AddUserDecor(new AddUserDecorCommand
{
- UserId = _authenticationService.CurrentUser.Id,
+ UserId = _authenticationService.Id.Value,
DecorId = command.Item.Id,
UpdatedBalance = _authenticationService.Balance,
});
@@ -110,7 +110,7 @@ await _client.AddUserDecor(new AddUserDecorCommand
try
{
// Add the user's new island to the local database
- Shared.Models.User user = await _localContext.Users.FirstAsync(u => u.Id == _authenticationService.CurrentUser.Id, cancellationToken);
+ Shared.Models.User user = await _localContext.Users.FirstAsync(u => u.Id == _authenticationService.Id.Value, cancellationToken);
user.Islands?.Add(new UserIsland
{
Island = await _localContext.Islands.FirstAsync(i => i.Id == command.Item.Id, cancellationToken)
@@ -129,7 +129,7 @@ await _client.AddUserDecor(new AddUserDecorCommand
{
await _client.AddUserIsland(new AddUserIslandCommand
{
- UserId = _authenticationService.CurrentUser.Id,
+ UserId = _authenticationService.Id.Value,
IslandId = command.Item.Id,
UpdatedBalance = _authenticationService.Balance,
});
diff --git a/src/FocusApp.Client/Methods/Sync/SyncItems.cs b/src/FocusApp.Client/Methods/Sync/SyncItems.cs
index 4a44b86..5f826a3 100644
--- a/src/FocusApp.Client/Methods/Sync/SyncItems.cs
+++ b/src/FocusApp.Client/Methods/Sync/SyncItems.cs
@@ -5,6 +5,7 @@
using FocusApp.Shared.Data;
using FocusApp.Shared.Models;
using FocusCore.Models;
+using Microsoft.Extensions.Logging;
namespace FocusApp.Client.Methods.Sync;
@@ -27,51 +28,65 @@ public class Handler : IRequestHandler
{
private readonly FocusAppContext _context;
private readonly IAPIClient _client;
+ private readonly ILogger _logger;
- public Handler(FocusAppContext context, IAPIClient client)
+ public Handler(FocusAppContext context, IAPIClient client, ILogger logger)
{
_context = context;
_client = client;
+ _logger = logger;
}
public async Task Handle(
Query query,
CancellationToken cancellationToken)
{
- List mobileIds;
- switch (query.ItemType)
- {
- case SyncItemType.MindfulnessTips:
- mobileIds = await GetMobileDbMindfulnessTipIds(cancellationToken);
- var tipsToAdd = await GetMissingTipsFromServer(mobileIds, cancellationToken);
- if (tipsToAdd.Count > 0)
- await AddNewItemsToMobileDb(tipsToAdd, cancellationToken);
- break;
- case SyncItemType.Badges:
- mobileIds = await GetMobileDbMindfulnessTipIds(cancellationToken);
- var badgesToAdd = await GetMissingBadgesFromServer(mobileIds, cancellationToken);
- if (badgesToAdd.Count > 0)
- await AddNewItemsToMobileDb(badgesToAdd, cancellationToken);
- break;
- case SyncItemType.Pets:
- mobileIds = await GetMobileDbMindfulnessTipIds(cancellationToken);
- var petsToAdd = await GetMissingPetsFromServer(mobileIds, cancellationToken);
- if (petsToAdd.Count > 0)
- await AddNewItemsToMobileDb(petsToAdd, cancellationToken);
- break;
- case SyncItemType.Decor:
- mobileIds = await GetMobileDbMindfulnessTipIds(cancellationToken);
- var decorToAdd = await GetMissingDecorFromServer(mobileIds, cancellationToken);
- if (decorToAdd.Count > 0)
- await AddNewItemsToMobileDb(decorToAdd, cancellationToken);
- break;
- case SyncItemType.Islands:
- mobileIds = await GetMobileDbMindfulnessTipIds(cancellationToken);
- var islandsToAdd = await GetMissingIslandsFromServer(mobileIds, cancellationToken);
- if (islandsToAdd.Count > 0)
- await AddNewItemsToMobileDb(islandsToAdd, cancellationToken);
- break;
+ try
+ {
+ List mobileIds;
+ switch (query.ItemType)
+ {
+ case SyncItemType.MindfulnessTips:
+ mobileIds = await GetMobileDbMindfulnessTipIds(cancellationToken);
+ var tipsToAdd = await GetMissingTipsFromServer(mobileIds, cancellationToken);
+ if (tipsToAdd.Count > 0)
+ await AddNewItemsToMobileDb(tipsToAdd, cancellationToken);
+ break;
+ case SyncItemType.Badges:
+ mobileIds = await GetMobileDbMindfulnessTipIds(cancellationToken);
+ var badgesToAdd = await GetMissingBadgesFromServer(mobileIds, cancellationToken);
+ if (badgesToAdd.Count > 0)
+ await AddNewItemsToMobileDb(badgesToAdd, cancellationToken);
+ break;
+ case SyncItemType.Pets:
+ mobileIds = await GetMobileDbMindfulnessTipIds(cancellationToken);
+ var petsToAdd = await GetMissingPetsFromServer(mobileIds, cancellationToken);
+ if (petsToAdd.Count > 0)
+ await AddNewItemsToMobileDb(petsToAdd, cancellationToken);
+ break;
+ case SyncItemType.Decor:
+ mobileIds = await GetMobileDbMindfulnessTipIds(cancellationToken);
+ var decorToAdd = await GetMissingDecorFromServer(mobileIds, cancellationToken);
+ if (decorToAdd.Count > 0)
+ await AddNewItemsToMobileDb(decorToAdd, cancellationToken);
+ break;
+ case SyncItemType.Islands:
+ mobileIds = await GetMobileDbMindfulnessTipIds(cancellationToken);
+ var islandsToAdd = await GetMissingIslandsFromServer(mobileIds, cancellationToken);
+ if (islandsToAdd.Count > 0)
+ await AddNewItemsToMobileDb(islandsToAdd, cancellationToken);
+ break;
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, $"Error occurred when syncing {query.ItemType}");
+ if (query.ItemType == SyncItemType.Islands || query.ItemType == SyncItemType.Pets)
+ {
+ throw new InvalidOperationException($"{query.ItemType} is needed for app functionality.");
+ }
}
+
}
private async Task> GetMobileDbMindfulnessTipIds(CancellationToken cancellationToken)
diff --git a/src/FocusApp.Client/Methods/User/EditUserProfile.cs b/src/FocusApp.Client/Methods/User/EditUserProfile.cs
index bbfb8aa..235f8a9 100644
--- a/src/FocusApp.Client/Methods/User/EditUserProfile.cs
+++ b/src/FocusApp.Client/Methods/User/EditUserProfile.cs
@@ -46,9 +46,9 @@ public async Task Handle(EditUserProfileCommand command, Cancella
user.ProfilePicture = command.ProfilePicture == null ? user.ProfilePicture : command.ProfilePicture;
// Update the authentication service to reflect the new changes in the current session
- _authenticationService.CurrentUser.UserName = user.UserName;
- _authenticationService.CurrentUser.Pronouns = user.Pronouns;
- _authenticationService.CurrentUser.ProfilePicture = user.ProfilePicture;
+ _authenticationService.UserName = user.UserName;
+ _authenticationService.Pronouns = user.Pronouns;
+ _authenticationService.ProfilePicture = user.ProfilePicture;
await _localContext.SaveChangesAsync(cancellationToken);
}
diff --git a/src/FocusApp.Client/Methods/User/GetUserLogin.cs b/src/FocusApp.Client/Methods/User/GetUserLogin.cs
index 6a370e9..dcfeb9b 100644
--- a/src/FocusApp.Client/Methods/User/GetUserLogin.cs
+++ b/src/FocusApp.Client/Methods/User/GetUserLogin.cs
@@ -4,6 +4,7 @@
using FocusApp.Client.Clients;
using FocusApp.Client.Helpers;
using FocusApp.Client.Methods.Sync;
+using FocusApp.Client.Views;
using FocusApp.Shared.Data;
using FocusApp.Shared.Models;
using FocusCore.Commands.User;
@@ -17,7 +18,7 @@
namespace FocusApp.Client.Methods.User
{
- public class GetUserLogin
+ internal class GetUserLogin
{
public class Query : IRequest { }
@@ -36,18 +37,25 @@ public class Handler : IRequestHandler
FocusAppContext _localContext;
ILogger _logger;
IMediator _mediator;
+ private readonly IAuthenticationService _authService;
+ private readonly PopupService _popupService;
+
public Handler(
Auth0Client auth0Client,
IAPIClient client,
FocusAppContext localContext,
ILogger logger,
- IMediator mediator)
+ IMediator mediator,
+ IAuthenticationService authService,
+ PopupService popupService)
{
_auth0Client = auth0Client;
_client = client;
_localContext = localContext;
_logger = logger;
_mediator = mediator;
+ _authService = authService;
+ _popupService = popupService;
}
public async Task Handle(
@@ -167,6 +175,11 @@ public async Task Handle(
string userName,
CancellationToken cancellationToken = default)
{
+ if (createUserResponse.User == null)
+ {
+ return null;
+ }
+
Shared.Models.User user = new()
{
Id = createUserResponse.User.Id,
@@ -222,6 +235,21 @@ public async Task Handle(
{
user = ProjectionHelper.ProjectFromBaseUser(getUserResponse.User);
+ bool userExistsLocally = await _localContext.Users
+ .AnyAsync(u => u.Auth0Id == getUserResponse.User.Auth0Id || getUserResponse.User.Id == u.Id, cancellationToken);
+
+ // If the user doesn't exist locally and they have content that isnt't in the DB,
+ // wait for all content to be downloaded to ensure the app has all of their items downloaded
+ if (!userExistsLocally &&
+ _authService.StartupSyncTask != null &&
+ !_authService.StartupSyncTask.IsCompleted &&
+ await DoesUserHaveUnsyncedData(getUserResponse))
+ {
+ await _popupService.ShowPopupAsync();
+ await _authService.StartupSyncTask.WaitAsync(cancellationToken);
+ await _popupService.HidePopupAsync();
+ }
+
// Gather the user's selected island and pet or get the defaults if one isn't selected
user.SelectedIsland = user.SelectedIslandId == null ?
// If the user does not have a selected island id, default to tropical
@@ -249,9 +277,7 @@ await GetSelectedBadgeQuery(user.SelectedBadgeId.Value)
await GetSelectedDecorQuery(user.SelectedDecorId.Value)
.FirstOrDefaultAsync(cancellationToken);
- bool userExistsLocally = await _localContext.Users
- .AnyAsync(u => u.Auth0Id == getUserResponse.User.Auth0Id || getUserResponse.User.Id == u.Id, cancellationToken);
-
+
// Add user to the local database if the user doesn't exist locally
if (!userExistsLocally)
{
@@ -281,6 +307,31 @@ await _mediator.Send(new SyncUserData.Query
return user;
}
+ ///
+ /// Check if the user owns any items that aren't in the mobile DB
+ ///
+ private async Task DoesUserHaveUnsyncedData(GetUserResponse response)
+ {
+ var mobileDbHasAllOwnedIslands = (await _localContext.Islands.Where(i => response.UserIslandIds.Contains(i.Id)).CountAsync()) ==
+ response.UserIslandIds.Count;
+ if (!mobileDbHasAllOwnedIslands) return true;
+
+ var mobileDbHasAllOwnedPets = (await _localContext.Pets.Where(p => response.UserPetIds.Contains(p.Id)).CountAsync()) ==
+ response.UserPetIds.Count;
+ if (!mobileDbHasAllOwnedPets) return true;
+
+ var mobileDbHasAllOwnedDecor = (await _localContext.Decor.Where(d => response.UserDecorIds.Contains(d.Id)).CountAsync()) ==
+ response.UserDecorIds.Count;
+ if (!mobileDbHasAllOwnedDecor) return true;
+
+
+ var mobileDbHasAllOwnedBadges = (await _localContext.Badges.Where(b => response.UserBadgeIds.Contains(b.Id)).CountAsync()) ==
+ response.UserBadgeIds.Count;
+ if (!mobileDbHasAllOwnedBadges) return true;
+
+ return false;
+ }
+
private IQueryable GetInitialIslandQuery()
{
return _localContext.Islands
diff --git a/src/FocusApp.Client/Views/LoginPage.cs b/src/FocusApp.Client/Views/LoginPage.cs
index a28ebe6..25a5f29 100644
--- a/src/FocusApp.Client/Views/LoginPage.cs
+++ b/src/FocusApp.Client/Views/LoginPage.cs
@@ -17,11 +17,13 @@ internal class LoginPage : BasePage
ILogger _logger;
IMediator _mediator;
PopupService _popupService;
+ private readonly ISyncService _syncService;
public LoginPage(
IAuthenticationService authenticationService,
ILogger logger,
IMediator mediator,
+ ISyncService syncService,
PopupService popupService
)
{
@@ -29,6 +31,7 @@ PopupService popupService
_logger = logger;
_mediator = mediator;
_popupService = popupService;
+ _syncService = syncService;
var pets = new List { "pet_cool_cat.png", "pet_cool_cat.png", "pet_cool_cat.png", "pet_cooler_cat.png", };
var rnd = new Random();
@@ -114,6 +117,8 @@ PopupService popupService
private async Task OnTapSkipButton()
{
+
+ await _popupService.ShowPopupAsync();
// If user skips login, initialize empty user and set selected pet and island to defaults
try
{
@@ -124,8 +129,7 @@ private async Task OnTapSkipButton()
_logger.LogError(ex, "Error initializing empty user.");
}
-
- // Todo: If user skips login, set selected pet and island to defaults
+ await _popupService.HidePopupAsync();
await Shell.Current.GoToAsync("///" + nameof(TimerPage));
}
@@ -138,7 +142,8 @@ private async Task OnTapLoginSignup()
{
try
{
- // Handle login process on non-UI thread
+ await _popupService.ShowPopupAsync();
+ // Handle login process
GetUserLogin.Result loginResult = await Task.Run(() => _mediator.Send(new GetUserLogin.Query()));
if (loginResult.IsSuccessful && loginResult.CurrentUser is not null)
@@ -147,6 +152,7 @@ private async Task OnTapLoginSignup()
}
else
{
+ await _popupService.HidePopupAsync();
await DisplayAlert("Error", loginResult.ErrorDescription, "OK");
}
}
@@ -165,6 +171,7 @@ private async Task OnTapLoginSignup()
_logger.LogError(ex, "Error initializing empty user.");
}
+ await _popupService.HidePopupAsync();
await Shell.Current.GoToAsync($"///" + nameof(TimerPage));
}
@@ -179,14 +186,6 @@ private async Task InitializeEmptyUser()
_authenticationService.ClearUser();
}
- _authenticationService.CurrentUser ??= new User()
- {
- Auth0Id = "",
- Email = "",
- UserName = "",
- Balance = 0
- };
-
// Add the initial/default island and pet if they don't exist
if (_authenticationService.SelectedIsland is null || _authenticationService.SelectedPet is null)
{
@@ -203,44 +202,56 @@ protected override async void OnAppearing()
}
///
- /// Run persistent login when page is created (on app startup), but not after
+ /// Run startup logic on app startup, but not after
///
private async void LoginPage_Loaded(object sender, EventArgs e)
{
Loaded -= LoginPage_Loaded;
- Task.Run(TryLoginFromStoredToken);
+ _ = Task.Run(async () =>
+ {
+ // Only show the content downloading popup for getting the essential data
+ await _popupService.ShowPopupAsync();
+ try
+ {
+ await _syncService.GatherEssentialDatabaseData();
+ }
+ catch (Exception ex)
+ {
+ await DisplayAlert(
+ "Error",
+ "There was an issue downloading content. Please ensure you have a stable internet connection and restart the app.",
+ "OK");
+ }
+
+ await _popupService.HidePopupAsync();
+
+ await TryLoginFromStoredToken();
+ _authenticationService.StartupSyncTask = _syncService.StartupSync();
+ });
}
- private async Task TryLoginFromStoredToken()
+ private async Task TryLoginFromStoredToken()
{
- _popupService.ShowPopup();
+ await _popupService.ShowPopupAsync();
try
{
- var result = await _mediator.Send(new GetPersistentUserLogin.Query(), default);
-
+ var result = await _mediator.Send(new GetPersistentUserLogin.Query());
+
if (result.IsSuccessful)
{
- await MainThread.InvokeOnMainThreadAsync(() => Shell.Current.GoToAsync($"///" + nameof(TimerPage)));
- }
- else
- {
- _logger.LogInformation(result.Message);
+ await _popupService.HidePopupAsync();
+ await MainThread.InvokeOnMainThreadAsync(async () => await Shell.Current.GoToAsync($"///" + nameof(TimerPage)));
+ return;
}
+ await _popupService.HidePopupAsync();
- _popupService.HidePopup();
- return result;
+ _logger.LogInformation(result.Message);
}
catch (Exception ex)
{
_logger.LogError(ex, "User was not found in database, or the user had no identity token in secure storage. Please manually log in as the user.");
}
-
- _popupService.HidePopup();
- return new GetPersistentUserLogin.Result
- {
- IsSuccessful = false
- };
}
}
diff --git a/src/FocusApp.Client/Views/Settings/SettingsAboutPopupInterface.cs b/src/FocusApp.Client/Views/Settings/SettingsAboutPopupInterface.cs
index ceaecdb..f7aaaca 100644
--- a/src/FocusApp.Client/Views/Settings/SettingsAboutPopupInterface.cs
+++ b/src/FocusApp.Client/Views/Settings/SettingsAboutPopupInterface.cs
@@ -31,7 +31,7 @@ public SettingsAboutPopupInterface(Helpers.PopupService popupService)
StrokeShape = new RoundRectangle() { CornerRadius = new CornerRadius(20, 20, 20, 20) },
BackgroundColor = AppStyles.Palette.LightMauve,
WidthRequest = 360,
- HeightRequest = 460,
+ HeightRequest = 190,
Content = new Grid()
{
RowDefinitions = GridRowsColumns.Rows.Define(
@@ -68,16 +68,19 @@ public SettingsAboutPopupInterface(Helpers.PopupService popupService)
new Label()
{
Text = "Contact us",
- FontSize = 20,
+ FontSize = 22,
+ TextColor = Colors.Black
}.Margins(bottom: 2),
new Label()
{
Text = "zenpxldev@gmail.com",
- FontSize = 17
+ TextColor = Colors.Black,
+ FontSize = 19
},
}
}
.Row(Row.Body)
+ .CenterVertical()
}
}
}
diff --git a/src/FocusApp.Client/Views/Settings/SettingsPage.cs b/src/FocusApp.Client/Views/Settings/SettingsPage.cs
index 3534359..9c9aa73 100644
--- a/src/FocusApp.Client/Views/Settings/SettingsPage.cs
+++ b/src/FocusApp.Client/Views/Settings/SettingsPage.cs
@@ -22,6 +22,20 @@ private bool IsLoggedIn
set => SetProperty(ref _isLoggedIn, value);
}
+ private bool _isStartupTipsEnabled;
+ private bool IsStartupTipsEnabled
+ {
+ get => _isStartupTipsEnabled;
+ set => SetProperty(ref _isStartupTipsEnabled, value);
+ }
+
+ private bool _isSessionRatingEnabled;
+ private bool IsSessionRatingEnabled
+ {
+ get => _isSessionRatingEnabled;
+ set => SetProperty(ref _isSessionRatingEnabled, value);
+ }
+
private enum Row { Header, StartupMindfulnessTipSetting, ShowSessionRatingSetting, AboutButton, LoginLogoutButton, Whitespace1, Logo, Whitespace2 }
public SettingsPage(
@@ -33,9 +47,7 @@ public SettingsPage(
_authService = authService;
_auth0Client = auth0Client;
- // Default values for preferences
- bool isStartupTipsEnabled = PreferencesHelper.Get(PreferencesHelper.PreferenceNames.startup_tips_enabled);
- bool isSessionRatingEnabled = PreferencesHelper.Get(PreferencesHelper.PreferenceNames.session_rating_enabled);
+ GetPreferenceValues();
// Using grids
Content = new Grid
@@ -116,12 +128,15 @@ public SettingsPage(
{
ThumbColor = Colors.SlateGrey,
OnColor = Colors.Green,
- IsToggled = isSessionRatingEnabled
+ IsToggled = IsStartupTipsEnabled
}
.Row(Row.StartupMindfulnessTipSetting)
.Column(5)
.Left()
.CenterVertical()
+ .Bind(Switch.IsToggledProperty,
+ getter: static (settingsPage) => settingsPage.IsStartupTipsEnabled,
+ source: this)
.Invoke(sw => sw.Toggled += (sender, e) =>
PreferencesHelper.Set(PreferencesHelper.PreferenceNames.startup_tips_enabled, e.Value)),
@@ -144,12 +159,15 @@ public SettingsPage(
{
ThumbColor = Colors.SlateGrey,
OnColor = Colors.Green,
- IsToggled = isStartupTipsEnabled
+ IsToggled = IsSessionRatingEnabled
}
.Row(Row.ShowSessionRatingSetting)
.Column(5)
.Left()
.CenterVertical()
+ .Bind(Switch.IsToggledProperty,
+ getter: static (settingsPage) => settingsPage.IsSessionRatingEnabled,
+ source: this)
.Invoke(sw => sw.Toggled += (sender, e) =>
PreferencesHelper.Set(PreferencesHelper.PreferenceNames.session_rating_enabled, e.Value)),
@@ -225,10 +243,17 @@ private async Task OnTapLoginLogoutButton()
}
}
+ private void GetPreferenceValues()
+ {
+ IsStartupTipsEnabled = PreferencesHelper.Get(PreferencesHelper.PreferenceNames.startup_tips_enabled);
+ IsSessionRatingEnabled = PreferencesHelper.Get(PreferencesHelper.PreferenceNames.session_rating_enabled);
+ }
+
protected override void OnAppearing()
{
base.OnAppearing();
+ GetPreferenceValues();
IsLoggedIn = !string.IsNullOrEmpty(_authService.Auth0Id);
}
}
diff --git a/src/FocusApp.Client/Views/Shop/ShopItemPopupInterface.cs b/src/FocusApp.Client/Views/Shop/ShopItemPopupInterface.cs
index 50070e1..73015c1 100644
--- a/src/FocusApp.Client/Views/Shop/ShopItemPopupInterface.cs
+++ b/src/FocusApp.Client/Views/Shop/ShopItemPopupInterface.cs
@@ -186,16 +186,16 @@ private async Task UserOwnsItem()
case ShopItemType.Pets:
return await _localContext.UserPets.AnyAsync(p =>
p.PetId == _currentItem.Id
- && p.UserId == _authenticationService.CurrentUser.Id);
+ && p.UserId == _authenticationService.Id.Value);
case ShopItemType.Decor:
return await _localContext.UserDecor.AnyAsync(d =>
d.DecorId == _currentItem.Id
- && d.UserId == _authenticationService.CurrentUser.Id);
+ && d.UserId == _authenticationService.Id.Value);
case ShopItemType.Islands:
return await _localContext.UserIslands.AnyAsync(i =>
i.IslandId == _currentItem.Id
- && i.UserId == _authenticationService.CurrentUser.Id);
+ && i.UserId == _authenticationService.Id.Value);
default:
return false;
diff --git a/src/FocusApp.Client/Views/Shop/ShopPage.cs b/src/FocusApp.Client/Views/Shop/ShopPage.cs
index 166bc66..6eb5bbb 100644
--- a/src/FocusApp.Client/Views/Shop/ShopPage.cs
+++ b/src/FocusApp.Client/Views/Shop/ShopPage.cs
@@ -39,7 +39,7 @@ public ShopPage(IAuthenticationService authenticationService, PopupService popup
// Currency text
_balanceLabel = new Label
{
- Text = _authenticationService.CurrentUser?.Balance.ToString(),
+ Text = _authenticationService.Balance.ToString(),
FontSize = 20,
HorizontalOptions = LayoutOptions.Start,
VerticalOptions = LayoutOptions.Center,
@@ -244,11 +244,34 @@ protected override async void OnAppearing()
// Update user balance upon showing shop page
_balanceLabel.Text = _authenticationService.Balance.ToString();
- List shopItems = await _mediator.Send(new GetLocalShopItems.Query(), default);
+ // If the startup sync task is not completed, show the loading popup and wait for it to complete
+ if (_authenticationService.StartupSyncTask != null && !_authenticationService.StartupSyncTask.IsCompleted)
+ {
+ await _popupService.ShowPopupAsync();
+
+ _authenticationService.StartupSyncTask.ContinueWith(async (_) =>
+ {
+ await Task.Run(PopulateShopCarousels);
+
+ await _popupService.HidePopupAsync();
+ });
+ }
+ else
+ {
+ Task.Run(PopulateShopCarousels);
+ }
+ }
+
+ private async Task PopulateShopCarousels()
+ {
+ List shopItems = await _mediator.Send(new GetLocalShopItems.Query());
- _petsCarouselView.ItemsSource = shopItems.Where(p => p.Type == ShopItemType.Pets);
- _islandsCarouselView.ItemsSource = shopItems.Where(p => p.Type == ShopItemType.Islands);
- _decorCarouselView.ItemsSource = shopItems.Where(p => p.Type == ShopItemType.Decor);
+ await MainThread.InvokeOnMainThreadAsync(() =>
+ {
+ _petsCarouselView.ItemsSource = shopItems.Where(p => p.Type == ShopItemType.Pets);
+ _islandsCarouselView.ItemsSource = shopItems.Where(p => p.Type == ShopItemType.Islands);
+ _decorCarouselView.ItemsSource = shopItems.Where(p => p.Type == ShopItemType.Decor);
+ });
}
async Task OnImageButtonClicked(object sender, EventArgs eventArgs)
diff --git a/src/FocusApp.Client/Views/Social/AddFriendPopupInterface.cs b/src/FocusApp.Client/Views/Social/AddFriendPopupInterface.cs
index c369b32..34d500e 100644
--- a/src/FocusApp.Client/Views/Social/AddFriendPopupInterface.cs
+++ b/src/FocusApp.Client/Views/Social/AddFriendPopupInterface.cs
@@ -318,7 +318,7 @@ private async Task PopulatePopup()
// Fetch all pending friend requests
var query = new GetAllFriendRequestsQuery
{
- UserId = _authenticationService.CurrentUser.Id
+ UserId = _authenticationService.Id.Value
};
try
@@ -408,7 +408,7 @@ private async void OnClickAcceptFriendRequest(object sender, EventArgs e)
var acceptCommand = new AcceptFriendRequestCommand
{
- UserId = _authenticationService.CurrentUser.Id,
+ UserId = _authenticationService.Id.Value,
FriendId = friendId
};
@@ -450,7 +450,7 @@ private async void OnClickRejectFriendRequest(object sender, EventArgs e)
var cancelCommand = new CancelFriendRequestCommand
{
UserId = friendId,
- FriendId = _authenticationService.CurrentUser.Id
+ FriendId = _authenticationService.Id.Value
};
// Reject Friend Request
@@ -468,7 +468,7 @@ private async void OnClickCancelFriendRequest(object sender, EventArgs e)
var cancelCommand = new CancelFriendRequestCommand
{
- UserId = _authenticationService.CurrentUser.Id,
+ UserId = _authenticationService.Id.Value,
FriendId = friendId
};
diff --git a/src/FocusApp.Client/Views/Social/BadgesPage.cs b/src/FocusApp.Client/Views/Social/BadgesPage.cs
index 4c19e0c..85032b6 100644
--- a/src/FocusApp.Client/Views/Social/BadgesPage.cs
+++ b/src/FocusApp.Client/Views/Social/BadgesPage.cs
@@ -142,7 +142,7 @@ private async Task> FetchUserBadgesFromLocalDb()
try
{
userBadgeIds = await _localContext.UserBadges?
- .Where(ub => ub.UserId == _authenticationService.CurrentUser.Id)
+ .Where(ub => ub.UserId == _authenticationService.Id.Value)
.Select(ub => ub.BadgeId).ToListAsync();
}
catch (Exception ex)
diff --git a/src/FocusApp.Client/Views/Social/BadgesPagePopupInterface.cs b/src/FocusApp.Client/Views/Social/BadgesPagePopupInterface.cs
index 3dfae17..088ca7a 100644
--- a/src/FocusApp.Client/Views/Social/BadgesPagePopupInterface.cs
+++ b/src/FocusApp.Client/Views/Social/BadgesPagePopupInterface.cs
@@ -197,7 +197,7 @@ private async void SelectBadgeButton(object sender, EventArgs e)
EditUserSelectedBadgeCommand command = new EditUserSelectedBadgeCommand
{
- UserId = _authenticationService.CurrentUser?.Id,
+ UserId = _authenticationService.Id.Value,
BadgeId = badge.Id
};
@@ -216,7 +216,7 @@ private async Task UserOwnsItem()
{
itemOwned = await _localContext.UserBadges.AnyAsync(b =>
b.BadgeId == _currentBadge.Id
- && b.UserId == _authenticationService.CurrentUser.Id);
+ && b.UserId == _authenticationService.Id.Value);
}
catch(Exception ex)
{
@@ -228,7 +228,7 @@ private async Task UserOwnsItem()
private async Task GetBadgeDate()
{
return (await _localContext.UserBadges.FirstAsync(ub =>
- ub.UserId == _authenticationService.CurrentUser.Id &&
+ ub.UserId == _authenticationService.Id.Value &&
ub.BadgeId == _currentBadge.Id)).DateAcquired;
}
}
diff --git a/src/FocusApp.Client/Views/Social/DecorPage.cs b/src/FocusApp.Client/Views/Social/DecorPage.cs
index 2f0c112..a90de0a 100644
--- a/src/FocusApp.Client/Views/Social/DecorPage.cs
+++ b/src/FocusApp.Client/Views/Social/DecorPage.cs
@@ -141,7 +141,7 @@ protected override async void OnAppearing()
base.OnAppearing();
// Check if the user is logged in
- if (_authenticationService.CurrentUser == null)
+ if (!_authenticationService.IsLoggedIn)
{
var loginPopup = (EnsureLoginPopupInterface)_popupService.ShowAndGetPopup();
loginPopup.OriginPage = nameof(ShopPage);
@@ -165,7 +165,7 @@ private async Task> FetchDecorFromLocalDb()
// Fetch decor from the local database using Mediatr feature
GetUserDecor.Result result = await _mediator.Send(new GetUserDecor.Query()
{
- UserId = _authenticationService.CurrentUser.Id,
+ UserId = _authenticationService.Id.Value,
selectedDecorId = _authenticationService.SelectedDecor != null ? _authenticationService.SelectedDecor.Id : null
},
default);
@@ -406,7 +406,7 @@ private async void SendSelectCommand(Guid? decorId, Image checkmark)
{
EditUserSelectedDecorCommand command = new EditUserSelectedDecorCommand
{
- UserId = _authenticationService.CurrentUser?.Id,
+ UserId = _authenticationService.Id.Value,
DecorId = decorId
};
diff --git a/src/FocusApp.Client/Views/Social/IslandsPage.cs b/src/FocusApp.Client/Views/Social/IslandsPage.cs
index e4d0623..abd9eb8 100644
--- a/src/FocusApp.Client/Views/Social/IslandsPage.cs
+++ b/src/FocusApp.Client/Views/Social/IslandsPage.cs
@@ -141,7 +141,7 @@ protected override async void OnAppearing()
base.OnAppearing();
// Check if the user is logged in
- if (_authenticationService.CurrentUser == null)
+ if (!_authenticationService.IsLoggedIn)
{
var loginPopup = (EnsureLoginPopupInterface)_popupService.ShowAndGetPopup();
loginPopup.OriginPage = nameof(ShopPage);
@@ -165,7 +165,7 @@ private async Task> FetchIslandsFromLocalDb()
// Fetch islands from the local database using Mediatr feature
GetUserIslands.Result result = await _mediator.Send(new GetUserIslands.Query()
{
- UserId = _authenticationService.CurrentUser.Id,
+ UserId = _authenticationService.Id.Value,
selectedIslandId = _authenticationService.SelectedIsland != null ? _authenticationService.SelectedIsland.Id : null
},
default);
@@ -292,7 +292,7 @@ private async void OnImageButtonClicked(object sender, EventArgs eventArgs)
EditUserSelectedIslandCommand command = new EditUserSelectedIslandCommand
{
- UserId = _authenticationService.CurrentUser?.Id,
+ UserId = _authenticationService.Id.Value,
IslandId = islandId
};
diff --git a/src/FocusApp.Client/Views/Social/LeaderboardsPage.cs b/src/FocusApp.Client/Views/Social/LeaderboardsPage.cs
index 5c644bb..5339dfd 100644
--- a/src/FocusApp.Client/Views/Social/LeaderboardsPage.cs
+++ b/src/FocusApp.Client/Views/Social/LeaderboardsPage.cs
@@ -495,11 +495,11 @@ private async Task FetchAndDisplayLeaderboards(LeaderboardType leaderboardType)
LeaderboardResponse leaderboardResponse = null;
if (leaderboardType == LeaderboardType.Daily)
{
- leaderboardResponse = await _client.GetDailyLeaderboard(new GetDailyLeaderboardQuery { UserId = _authenticationService.CurrentUser.Id }, default);
+ leaderboardResponse = await _client.GetDailyLeaderboard(new GetDailyLeaderboardQuery { UserId = _authenticationService.Id.Value }, default);
}
else
{
- leaderboardResponse = await _client.GetWeeklyLeaderboard(new GetWeeklyLeaderboardQuery { UserId = _authenticationService.CurrentUser.Id }, default);
+ leaderboardResponse = await _client.GetWeeklyLeaderboard(new GetWeeklyLeaderboardQuery { UserId = _authenticationService.Id.Value }, default);
}
await MainThread.InvokeOnMainThreadAsync(() =>
diff --git a/src/FocusApp.Client/Views/Social/PetsPage.cs b/src/FocusApp.Client/Views/Social/PetsPage.cs
index a09d2e0..dd9deb5 100644
--- a/src/FocusApp.Client/Views/Social/PetsPage.cs
+++ b/src/FocusApp.Client/Views/Social/PetsPage.cs
@@ -141,7 +141,7 @@ protected override async void OnAppearing()
base.OnAppearing();
// Check if the user is logged in
- if (_authenticationService.CurrentUser == null)
+ if (!_authenticationService.IsLoggedIn)
{
var loginPopup = (EnsureLoginPopupInterface)_popupService.ShowAndGetPopup();
loginPopup.OriginPage = nameof(ShopPage);
@@ -165,7 +165,7 @@ private async Task> FetchPetsFromLocalDb()
// Fetch pets from the local database using Mediatr feature
GetUserPets.Result result = await _mediator.Send(new GetUserPets.Query()
{
- UserId = _authenticationService.CurrentUser.Id,
+ UserId = _authenticationService.Id.Value,
selectedPetId = _authenticationService.SelectedPet.Id
},
default);
@@ -292,7 +292,7 @@ private async void OnImageButtonClicked(object sender, EventArgs eventArgs)
EditUserSelectedPetCommand command = new EditUserSelectedPetCommand
{
- UserId = _authenticationService.CurrentUser?.Id,
+ UserId = _authenticationService.Id.Value,
PetId = petId
};
diff --git a/src/FocusApp.Client/Views/Social/ProfilePage.cs b/src/FocusApp.Client/Views/Social/ProfilePage.cs
index 4b5fc5b..e81962e 100644
--- a/src/FocusApp.Client/Views/Social/ProfilePage.cs
+++ b/src/FocusApp.Client/Views/Social/ProfilePage.cs
@@ -319,7 +319,7 @@ public ProfilePage(IAPIClient client, IAuthenticationService authenticationServi
// Date Joined
new Label
{
- Text = $"Member Since: {_authenticationService.CurrentUser?.DateCreated.ToLocalTime().ToShortDateString()}",
+ Text = $"Member Since: {_authenticationService.DateCreated?.ToLocalTime().ToShortDateString()}",
TextColor = Colors.Black,
FontSize = 20
}
@@ -339,10 +339,10 @@ protected override async void OnAppearing()
ByteArrayToImageSourceConverter byteArrayConverter = new ByteArrayToImageSourceConverter();
// Set user details bindings
- _profilePicture.ImageSource = byteArrayConverter.ConvertFrom(_authenticationService.CurrentUser?.ProfilePicture);
- _userName.Text = _authenticationService.CurrentUser?.UserName;
- _pronouns.Text = $"Pronouns: {_authenticationService.CurrentUser?.Pronouns}";
- _email.Text = $"Friend Id: #{_authenticationService.CurrentUser?.Email}";
+ _profilePicture.ImageSource = byteArrayConverter.ConvertFrom(_authenticationService.ProfilePicture);
+ _userName.Text = _authenticationService.UserName;
+ _pronouns.Text = $"Pronouns: {_authenticationService.Pronouns}";
+ _email.Text = $"Friend Id: #{_authenticationService.Email}";
// Set user selected items bindings
_selectedPet.Source = byteArrayConverter.ConvertFrom(_authenticationService.SelectedPet?.Image);
@@ -386,23 +386,23 @@ private void CreateUserDataElements()
.Bind(AvatarView.ImageSourceProperty,
"ProfilePicture",
converter: new ByteArrayToImageSourceConverter());
- _profilePicture.BindingContext = _authenticationService.CurrentUser;
+ _profilePicture.BindingContext = _authenticationService;
_userName = new Label
{
- Text = $"{_authenticationService.CurrentUser?.UserName}",
+ Text = $"{_authenticationService.UserName}",
FontSize = 18
};
_pronouns = new Label
{
- Text = $"Pronouns: {_authenticationService.CurrentUser?.Pronouns}",
+ Text = $"Pronouns: {_authenticationService.Pronouns}",
FontSize = 15
};
_email = new Label
{
- Text = $"Friend Id: #{_authenticationService.CurrentUser?.Email}",
+ Text = $"Friend Id: #{_authenticationService.Email}",
FontSize = 15
};
}
@@ -412,33 +412,33 @@ private void CreateSelectedItemElements()
// Selected item labels
_selectedPetLabel = new Label
{
- Text = $"{(_authenticationService.CurrentUser?.SelectedPet?.Name == null
+ Text = $"{(_authenticationService.SelectedPet?.Name == null
? "Select a pet!"
- : _authenticationService.CurrentUser?.SelectedPet?.Name)}",
+ : _authenticationService.SelectedPet?.Name)}",
FontSize = 15
};
_selectedIslandLabel = new Label
{
- Text = $"{(_authenticationService.CurrentUser?.SelectedIsland?.Name == null
+ Text = $"{(_authenticationService.SelectedIsland?.Name == null
? "Select an island!"
- : _authenticationService.CurrentUser?.SelectedIsland?.Name)}",
+ : _authenticationService.SelectedIsland?.Name)}",
FontSize = 15
};
_selectedDecorLabel = new Label
{
- Text = $"{(_authenticationService.CurrentUser?.SelectedDecor?.Name == null
+ Text = $"{(_authenticationService.SelectedDecor?.Name == null
? "Select decor!"
- : _authenticationService.CurrentUser?.SelectedDecor?.Name)}",
+ : _authenticationService.SelectedDecor?.Name)}",
FontSize = 15
};
_selectedBadgeLabel = new Label
{
- Text = $"{(_authenticationService.CurrentUser?.SelectedBadge?.Name == null
+ Text = $"{(_authenticationService.SelectedBadge?.Name == null
? "Select a badge!"
- : _authenticationService.CurrentUser?.SelectedBadge?.Name)}",
+ : _authenticationService.SelectedBadge?.Name)}",
FontSize = 15
};
@@ -485,7 +485,7 @@ private void CreateSelectedItemElements()
#region Backend
private async void CopyEmailClicked(object sender, EventArgs e)
{
- await Clipboard.Default.SetTextAsync(_authenticationService.CurrentUser?.Email);
+ await Clipboard.Default.SetTextAsync(_authenticationService.Email);
}
private async void EditButtonClicked(object sender, EventArgs eventArgs)
diff --git a/src/FocusApp.Client/Views/Social/ProfilePageEdit.cs b/src/FocusApp.Client/Views/Social/ProfilePageEdit.cs
index 50eb482..4cdb77d 100644
--- a/src/FocusApp.Client/Views/Social/ProfilePageEdit.cs
+++ b/src/FocusApp.Client/Views/Social/ProfilePageEdit.cs
@@ -62,7 +62,7 @@ public ProfilePageEdit(IAuthenticationService authenticationService, IMediator m
WidthRequest = 126
}
.Bind(AvatarView.ImageSourceProperty, "ProfilePicture", converter: new ByteArrayToImageSourceConverter());
- _profilePicture.BindingContext = _authenticationService.CurrentUser;
+ _profilePicture.BindingContext = _authenticationService;
Content = new Grid
{
@@ -256,13 +256,13 @@ private void CreateFormElements()
_userNameField = new Entry
{
Placeholder = "Enter username here",
- Text = _authenticationService.CurrentUser?.UserName
+ Text = _authenticationService.UserName
};
_pronounsField = new Entry
{
Placeholder = "Enter pronouns here",
- Text = _authenticationService.CurrentUser?.Pronouns
+ Text = _authenticationService.Pronouns
};
_userNameField.Behaviors.Add(_userNameValidationBehavior);
@@ -274,7 +274,7 @@ protected override async void OnAppearing()
await AppShell.Current.SetTabBarIsVisible(false);
// Refresh profile picture on page load in case it was wiped via navigating to settings
- _profilePicture.ImageSource = new ByteArrayToImageSourceConverter().ConvertFrom(_authenticationService.CurrentUser?.ProfilePicture);
+ _profilePicture.ImageSource = new ByteArrayToImageSourceConverter().ConvertFrom(_authenticationService.ProfilePicture);
}
#endregion
@@ -349,18 +349,18 @@ private async void SaveChangesButtonClicked(object sender, EventArgs e)
{
EditUserProfileCommand command = new EditUserProfileCommand
{
- UserId = _authenticationService.CurrentUser?.Id,
+ UserId = _authenticationService.Id.Value,
};
- if (_userNameField.Text != _authenticationService.CurrentUser?.UserName)
+ if (_userNameField.Text != _authenticationService.UserName)
command.UserName = _userNameField.Text;
- if (_pronounsField.Text != _authenticationService.CurrentUser?.Pronouns)
+ if (_pronounsField.Text != _authenticationService.Pronouns)
command.Pronouns = _pronounsField.Text;
- else if (string.IsNullOrEmpty(_pronounsField.Text) && _pronounsField.Text != _authenticationService.CurrentUser?.Pronouns)
+ else if (string.IsNullOrEmpty(_pronounsField.Text) && _pronounsField.Text != _authenticationService.Pronouns)
command.Pronouns = _pronounsField.Text;
- if (_newPhoto != _authenticationService.CurrentUser?.ProfilePicture)
+ if (_newPhoto != _authenticationService.ProfilePicture)
command.ProfilePicture = _newPhoto;
if (command.Pronouns is not null ||
diff --git a/src/FocusApp.Client/Views/Social/ProfilePopupInterface.cs b/src/FocusApp.Client/Views/Social/ProfilePopupInterface.cs
index 2c72247..4c648c4 100644
--- a/src/FocusApp.Client/Views/Social/ProfilePopupInterface.cs
+++ b/src/FocusApp.Client/Views/Social/ProfilePopupInterface.cs
@@ -19,7 +19,7 @@ public ProfilePopupInterface(IAuthenticationService authenticationService, Helpe
_popupService = popupService;
// Fetch current user's username
- string username = _authenticationService.CurrentUser.UserName;
+ string username = _authenticationService.UserName;
// Set popup location
HorizontalOptions = Microsoft.Maui.Primitives.LayoutAlignment.End;
diff --git a/src/FocusApp.Client/Views/Social/SocialPage.cs b/src/FocusApp.Client/Views/Social/SocialPage.cs
index f5be44e..8d35464 100644
--- a/src/FocusApp.Client/Views/Social/SocialPage.cs
+++ b/src/FocusApp.Client/Views/Social/SocialPage.cs
@@ -50,11 +50,9 @@ IBadgeService badgeService
_friendsListView = BuildFriendsListView();
_profilePictureNavMenuButton = new AvatarView()
{
- ImageSource = new ByteArrayToImageSourceConverter().ConvertFrom(_authenticationService.CurrentUser?.ProfilePicture)
+ ImageSource = new ByteArrayToImageSourceConverter().ConvertFrom(_authenticationService.ProfilePicture)
};
- Appearing += CheckForSocialBadgeEarned;
-
Content = new Grid
{
// Define rows and columns (Star means that row/column will take up the remaining space)
@@ -107,7 +105,7 @@ IBadgeService badgeService
.Aspect(Aspect.AspectFit)
.Right()
.FillVertical()
- .Margin(1)
+ .Margins(1,1,5,1)
.Column(1)
// Set the corner radius to be half of the height to make it a circle
.Bind(
@@ -132,7 +130,7 @@ IBadgeService badgeService
private AvatarView GetProfilePictureNavMenuButton() =>
new AvatarView()
{
- ImageSource = new ByteArrayToImageSourceConverter().ConvertFrom(_authenticationService.CurrentUser?.ProfilePicture)
+ ImageSource = new ByteArrayToImageSourceConverter().ConvertFrom(_authenticationService.ProfilePicture)
};
private ListView BuildFriendsListView()
@@ -208,7 +206,7 @@ protected override async void OnAppearing()
{
base.OnAppearing();
- _profilePictureNavMenuButton.ImageSource = new ByteArrayToImageSourceConverter().ConvertFrom(_authenticationService?.CurrentUser?.ProfilePicture);
+ _profilePictureNavMenuButton.ImageSource = new ByteArrayToImageSourceConverter().ConvertFrom(_authenticationService?.ProfilePicture);
// If not logged in display popup, otherwise populate friends list
if (string.IsNullOrEmpty(_authenticationService.Auth0Id))
@@ -222,26 +220,20 @@ protected override async void OnAppearing()
}
}
- private async void CheckForSocialBadgeEarned(object? sender, EventArgs eventArgs)
+ private async Task CheckForSocialBadgeEarned()
{
- Appearing -= CheckForSocialBadgeEarned;
-
- Task.Run(async () =>
+ try
{
- try
- {
- BadgeEligibilityResult result = await _badgeService.CheckSocialBadgeEligibility();
- if (result is { IsEligible: true, EarnedBadge: not null })
- {
- _popupService.TriggerBadgeEvent(result.EarnedBadge);
- }
- }
- catch (Exception ex)
+ BadgeEligibilityResult result = await _badgeService.CheckSocialBadgeEligibility();
+ if (result is { IsEligible: true, EarnedBadge: not null })
{
- _logger.LogError(ex, "Error occurred while checking for badge eligibility.");
+ _popupService.TriggerBadgeEvent(result.EarnedBadge);
}
- });
-
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error occurred while checking for badge eligibility.");
+ }
}
// We call this function to populate FriendsList and from friends popup to refresh list
@@ -252,7 +244,7 @@ public async Task PopulateFriendsList()
var query = new GetAllFriendsQuery
{
- UserId = _authenticationService.CurrentUser.Id
+ UserId = _authenticationService.Id.Value
};
try
@@ -268,6 +260,10 @@ public async Task PopulateFriendsList()
{
_friendsListView.ItemsSource = friendsList;
});
+
+
+
+ await CheckForSocialBadgeEarned();
}
public RelayCommand TapProfilePictureShowNavigationCommand => new(OnClickShowProfileInterfacePopup);
diff --git a/src/FocusApp.Client/Views/SyncDataLoadingPopupInterface.cs b/src/FocusApp.Client/Views/SyncDataLoadingPopupInterface.cs
new file mode 100644
index 0000000..e3bf53c
--- /dev/null
+++ b/src/FocusApp.Client/Views/SyncDataLoadingPopupInterface.cs
@@ -0,0 +1,46 @@
+using CommunityToolkit.Maui.Markup;
+using FocusApp.Client.Resources;
+
+namespace FocusApp.Client.Views;
+internal class SyncDataLoadingPopupInterface : BasePopup
+{
+ public SyncDataLoadingPopupInterface()
+ {
+ CanBeDismissedByTappingOutsideOfPopup = false;
+ Content = new Border
+ {
+ StrokeThickness = 1,
+ BackgroundColor = Colors.White,
+ WidthRequest = 180,
+ HeightRequest = 200,
+
+ Content = new Grid()
+ {
+ RowDefinitions = GridRowsColumns.Rows.Define(
+ 40,
+ GridRowsColumns.Star
+ ),
+ Children =
+ {
+ new Label()
+ {
+ Text = "Downloading Content",
+ FontSize = 16,
+ TextColor = Colors.Black
+ }
+ .Center()
+ .Row(0)
+ ,
+ new ActivityIndicator
+ {
+ Color = AppStyles.Palette.OrchidPink,
+ IsRunning = true
+ }
+ .Center()
+ .Row(1)
+ }
+
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/FocusCore/Responses/User/GetUserResponse.cs b/src/FocusCore/Responses/User/GetUserResponse.cs
index df0451d..20e6955 100644
--- a/src/FocusCore/Responses/User/GetUserResponse.cs
+++ b/src/FocusCore/Responses/User/GetUserResponse.cs
@@ -5,8 +5,8 @@ namespace FocusCore.Responses.User;
public class GetUserResponse
{
public BaseUser? User { get; set; }
- public List? UserIslandIds { get; set; }
- public List? UserPetIds { get; set; }
- public List? UserDecorIds { get; set; }
- public List? UserBadgeIds { get; set; }
+ public List UserIslandIds { get; set; } = new List();
+ public List UserPetIds { get; set; } = new List();
+ public List UserDecorIds { get; set; } = new List();
+ public List UserBadgeIds { get; set; } = new List();
}
\ No newline at end of file