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 | +|:-------------------------:|:-------------------------:|:-------------------------:| +chrispypeaches | Chris Perry | chrispperry12@gmail.com | +iggy808 | Evan Gray | ekgray.make@gmail.com | +JohnParks032 | John Parks | johnparks032@proton.me | +wesleypayton | 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