Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add loading on sync #123

Merged
merged 18 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading