Skip to content

Commit

Permalink
Merge pull request #123 from ChrispyPeaches/add-loading-on-sync
Browse files Browse the repository at this point in the history
Add loading on sync
  • Loading branch information
JohnParks032 authored May 10, 2024
2 parents 4d6b606 + 33b7f25 commit 10e57f6
Show file tree
Hide file tree
Showing 32 changed files with 523 additions and 311 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
|:-------------------------:|:-------------------------:|:-------------------------:|
<a href="https://github.com/chrispypeaches"><img src="https://avatars.githubusercontent.com/u/26045099?v=4" alt="chrispypeaches" width="50" height="50" style="border-radius: 50%;"></a> | Chris Perry | [email protected] |
<a href="https://github.com/iggy808"><img src="https://avatars.githubusercontent.com/u/63073663?v=4" alt="iggy808" width="50" height="50" style="border-radius: 50%;"></a> | Evan Gray | [email protected] |
<a href="https://github.com/JohnParks032"><img src="https://avatars.githubusercontent.com/u/80427077?v=4" alt="JohnParks032" width="50" height="50" style="border-radius: 50%;"></a> | John Parks | [email protected] |
<a href="https://github.com/wesleypayton"><img src="https://avatars.githubusercontent.com/u/80854811?v=4" alt="wesleypayton" width="50" height="50" style="border-radius: 50%;"></a> | Elijah Payton | [email protected] |

46 changes: 29 additions & 17 deletions src/FocusApp.Client/Helpers/AuthenticationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,39 @@ 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);
}

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
Expand Down Expand Up @@ -99,26 +104,33 @@ public async Task Logout(IAuth0Client auth0Client)
/// </summary>
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;
}

Expand Down
119 changes: 117 additions & 2 deletions src/FocusApp.Client/Helpers/SyncService.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,51 @@
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;

public interface ISyncService
{
IQueryable<Island> GetInitialIslandQuery();
IQueryable<Pet> GetInitialPetQuery();

/// <summary>
/// Ensure the database is created and migrations are applied, then run the startup logic.
/// </summary>
Task RunStartupLogic();

/// <summary>
/// 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.
/// </summary>
Task GatherEssentialDatabaseData();

/// <summary>
/// Syncs all item types in the <see cref="SyncItems.SyncItemType"/> 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.
/// </summary>
/// <remarks>
/// If there's an unexpected error, the critical data for app functionality will be retrieved.
/// </remarks>
Task StartupSync();
}

public class SyncService : ISyncService
{
private readonly FocusAppContext _context;
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<ISyncService> _logger;

public SyncService(FocusAppContext context)
public SyncService(
FocusAppContext context,
IServiceProvider serviceProvider,
ILogger<ISyncService> logger)
{
_context = context;
_serviceProvider = serviceProvider;
_logger = logger;
}

public IQueryable<Island> GetInitialIslandQuery()
Expand All @@ -29,4 +59,89 @@ public IQueryable<Pet> GetInitialPetQuery()
return _context.Pets
.Where(pet => pet.Name == FocusCore.Consts.NameOfInitialPet);
}

/// <summary>
/// Ensure the database is created and migrations are applied, then run the startup logic.
/// </summary>
public async Task RunStartupLogic()
{

}

/// <summary>
/// Syncs all item types in the <see cref="SyncItems.SyncItemType"/> 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.
/// </summary>
/// <remarks>
/// If there's an unexpected error, the critical data for app functionality will be retrieved.
/// </remarks>
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<string>(PreferencesHelper.PreferenceNames.last_sync_time);
if (!string.IsNullOrEmpty(lastSyncTimeString))
{
DateTimeOffset lastSyncTime = DateTimeOffset.Parse(lastSyncTimeString);

if (DateTimeOffset.UtcNow < lastSyncTime.AddDays(7))
return;
}
#endif

IList<Task> tasks = new List<Task>();
var scopedServiceProvider = _serviceProvider
.CreateScope()
.ServiceProvider;
_ = scopedServiceProvider.GetRequiredService<FocusAppContext>();

foreach (SyncItems.SyncItemType syncType in Enum.GetValues(typeof(SyncItems.SyncItemType)))
{
IMediator mediator = _serviceProvider
.CreateScope()
.ServiceProvider
.GetRequiredService<IMediator>();

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();
}
}

/// <summary>
/// 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.
/// </summary>
public async Task GatherEssentialDatabaseData()
{
try
{
var scopedServiceProvider = _serviceProvider
.CreateScope()
.ServiceProvider;
IMediator mediator = scopedServiceProvider.GetRequiredService<IMediator>();

await mediator.Send(new SyncInitialData.Query());
}
catch (Exception ex)
{
_logger.LogError(ex, "Error when running essential database population.");
}
}
}
117 changes: 12 additions & 105 deletions src/FocusApp.Client/MauiProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<IAPIClient>()
.ConfigureHttpClient(c => c.BaseAddress = new Uri("http://prod.zenpxl.com:25565"));
.ConfigureHttpClient(c => c.BaseAddress = new Uri(apiDomain));

return services;
}
Expand Down Expand Up @@ -155,105 +162,5 @@ private static IServiceCollection RegisterPopups(this IServiceCollection service

return services;
}

/// <summary>
/// Ensure the database is created and migrations are applied, then run the startup logic.
/// </summary>
private static async Task RunStartupLogic(IServiceCollection services)
{
try
{
var scopedServiceProvider = services
.BuildServiceProvider()
.CreateScope()
.ServiceProvider;
_ = scopedServiceProvider.GetRequiredService<FocusAppContext>();

await Task.Run(() => StartupSync(services, default), default);
}
catch (Exception ex)
{
Console.WriteLine("Error running startup logic");
Console.Write(ex);
}

}

/// <summary>
/// Syncs all item types in the <see cref="SyncItems.SyncItemType"/> 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.
/// </summary>
/// <remarks>
/// If there's an unexpected error, the critical data for app functionality will be retrieved.
/// </remarks>
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<string>(PreferenceNames.last_sync_time);
if (!string.IsNullOrEmpty(lastSyncTimeString))
{
DateTimeOffset lastSyncTime = DateTimeOffset.Parse(lastSyncTimeString);

if (DateTimeOffset.UtcNow < lastSyncTime.AddDays(7))
return;
}
#endif

IList<Task> tasks = new List<Task>();
foreach (SyncItems.SyncItemType syncType in Enum.GetValues(typeof(SyncItems.SyncItemType)))
{
IMediator mediator = services
.BuildServiceProvider()
.CreateScope()
.ServiceProvider
.GetRequiredService<IMediator>();

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);
}
}

/// <summary>
/// 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.
/// </summary>
private static async Task GatherEssentialDatabaseData(IServiceCollection services)
{
try
{
var scopedServiceProvider = services
.BuildServiceProvider()
.CreateScope()
.ServiceProvider;
IMediator mediator = scopedServiceProvider.GetRequiredService<IMediator>();

await mediator.Send(new SyncInitialData.Query());
}
catch (Exception ex)
{
Console.WriteLine("Error when running essential database population.");
Console.Write(ex);
}
}
}
}
Loading

0 comments on commit 10e57f6

Please sign in to comment.