| | | 1 | | using Blazored.LocalStorage; |
| | | 2 | | using Chronicis.Shared.DTOs; |
| | | 3 | | |
| | | 4 | | namespace Chronicis.Client.Services; |
| | | 5 | | |
| | | 6 | | /// <summary> |
| | | 7 | | /// Service for managing application context (current World/Campaign) |
| | | 8 | | /// </summary> |
| | | 9 | | public class AppContextService : IAppContextService |
| | | 10 | | { |
| | | 11 | | private readonly IWorldApiService _worldApi; |
| | | 12 | | private readonly ILocalStorageService _localStorage; |
| | | 13 | | private readonly ILogger<AppContextService> _logger; |
| | | 14 | | |
| | | 15 | | private const string WorldIdKey = "chronicis_current_world_id"; |
| | | 16 | | private const string CampaignIdKey = "chronicis_current_campaign_id"; |
| | | 17 | | |
| | 29 | 18 | | public Guid? CurrentWorldId { get; private set; } |
| | 27 | 19 | | public Guid? CurrentCampaignId { get; private set; } |
| | 35 | 20 | | public WorldDetailDto? CurrentWorld { get; private set; } |
| | 22 | 21 | | public CampaignDto? CurrentCampaign { get; private set; } |
| | 40 | 22 | | public List<WorldDto> Worlds { get; private set; } = new(); |
| | 15 | 23 | | public bool IsInitialized { get; private set; } |
| | | 24 | | |
| | | 25 | | public event Action? OnContextChanged; |
| | | 26 | | |
| | 18 | 27 | | public AppContextService( |
| | 18 | 28 | | IWorldApiService worldApi, |
| | 18 | 29 | | ILocalStorageService localStorage, |
| | 18 | 30 | | ILogger<AppContextService> logger) |
| | | 31 | | { |
| | 18 | 32 | | _worldApi = worldApi; |
| | 18 | 33 | | _localStorage = localStorage; |
| | 18 | 34 | | _logger = logger; |
| | 18 | 35 | | } |
| | | 36 | | |
| | | 37 | | public async Task InitializeAsync() |
| | | 38 | | { |
| | 7 | 39 | | if (IsInitialized) |
| | 1 | 40 | | return; |
| | | 41 | | |
| | 6 | 42 | | _logger.LogDebug("Initializing app context"); |
| | | 43 | | |
| | | 44 | | // Load all worlds |
| | 6 | 45 | | Worlds = await _worldApi.GetWorldsAsync(); |
| | | 46 | | |
| | 6 | 47 | | if (Worlds.Count == 0) |
| | | 48 | | { |
| | 1 | 49 | | _logger.LogWarning("No worlds found for user"); |
| | 1 | 50 | | IsInitialized = true; |
| | 1 | 51 | | return; |
| | | 52 | | } |
| | | 53 | | |
| | | 54 | | // Try to restore saved selection |
| | 5 | 55 | | Guid? savedWorldId = null; |
| | 5 | 56 | | Guid? savedCampaignId = null; |
| | | 57 | | |
| | | 58 | | try |
| | | 59 | | { |
| | 5 | 60 | | var savedWorldIdStr = await _localStorage.GetItemAsStringAsync(WorldIdKey); |
| | 5 | 61 | | var savedCampaignIdStr = await _localStorage.GetItemAsStringAsync(CampaignIdKey); |
| | | 62 | | |
| | 5 | 63 | | if (!string.IsNullOrEmpty(savedWorldIdStr) && Guid.TryParse(savedWorldIdStr.Trim('"'), out var parsedWorldId |
| | 2 | 64 | | savedWorldId = parsedWorldId; |
| | | 65 | | |
| | 5 | 66 | | if (!string.IsNullOrEmpty(savedCampaignIdStr) && Guid.TryParse(savedCampaignIdStr.Trim('"'), out var parsedC |
| | 0 | 67 | | savedCampaignId = parsedCampaignId; |
| | 5 | 68 | | } |
| | 0 | 69 | | catch (Exception ex) |
| | | 70 | | { |
| | 0 | 71 | | _logger.LogWarning(ex, "Failed to read saved context from localStorage"); |
| | 0 | 72 | | } |
| | | 73 | | |
| | | 74 | | // Validate saved world exists |
| | 8 | 75 | | if (savedWorldId.HasValue && Worlds.Any(w => w.Id == savedWorldId.Value)) |
| | | 76 | | { |
| | 1 | 77 | | await SelectWorldAsync(savedWorldId.Value, savedCampaignId); |
| | | 78 | | } |
| | | 79 | | else |
| | | 80 | | { |
| | | 81 | | // Default to first world |
| | 4 | 82 | | await SelectWorldAsync(Worlds[0].Id); |
| | | 83 | | } |
| | | 84 | | |
| | 5 | 85 | | IsInitialized = true; |
| | 5 | 86 | | _logger.LogDebug("App context initialized. World: {WorldId}, Campaign: {CampaignId}", |
| | 5 | 87 | | CurrentWorldId, CurrentCampaignId); |
| | 7 | 88 | | } |
| | | 89 | | |
| | | 90 | | public async Task SelectWorldAsync(Guid worldId, Guid? campaignId = null) |
| | | 91 | | { |
| | 15 | 92 | | _logger.LogDebug("Selecting world {WorldId}", worldId); |
| | | 93 | | |
| | | 94 | | // Load world details |
| | 15 | 95 | | var world = await _worldApi.GetWorldAsync(worldId); |
| | 15 | 96 | | if (world == null) |
| | | 97 | | { |
| | 2 | 98 | | _logger.LogWarning("Failed to load world {WorldId}", worldId); |
| | 2 | 99 | | return; |
| | | 100 | | } |
| | | 101 | | |
| | 13 | 102 | | CurrentWorldId = worldId; |
| | 13 | 103 | | CurrentWorld = world; |
| | | 104 | | |
| | | 105 | | // Save to localStorage |
| | 13 | 106 | | await _localStorage.SetItemAsStringAsync(WorldIdKey, worldId.ToString()); |
| | | 107 | | |
| | | 108 | | // Select campaign |
| | 17 | 109 | | if (campaignId.HasValue && world.Campaigns.Any(c => c.Id == campaignId.Value)) |
| | | 110 | | { |
| | 3 | 111 | | await SelectCampaignAsync(campaignId.Value); |
| | | 112 | | } |
| | 10 | 113 | | else if (world.Campaigns.Count > 0) |
| | | 114 | | { |
| | | 115 | | // Default to first campaign if available |
| | 3 | 116 | | await SelectCampaignAsync(world.Campaigns[0].Id); |
| | | 117 | | } |
| | | 118 | | else |
| | | 119 | | { |
| | 7 | 120 | | await SelectCampaignAsync(null); |
| | | 121 | | } |
| | | 122 | | |
| | 13 | 123 | | OnContextChanged?.Invoke(); |
| | 15 | 124 | | } |
| | | 125 | | |
| | | 126 | | public async Task SelectCampaignAsync(Guid? campaignId) |
| | | 127 | | { |
| | 16 | 128 | | _logger.LogDebug("Selecting campaign {CampaignId}", campaignId); |
| | | 129 | | |
| | 16 | 130 | | CurrentCampaignId = campaignId; |
| | | 131 | | |
| | 16 | 132 | | if (campaignId.HasValue && CurrentWorld != null) |
| | | 133 | | { |
| | 17 | 134 | | CurrentCampaign = CurrentWorld.Campaigns.FirstOrDefault(c => c.Id == campaignId.Value); |
| | 8 | 135 | | await _localStorage.SetItemAsStringAsync(CampaignIdKey, campaignId.Value.ToString()); |
| | | 136 | | } |
| | | 137 | | else |
| | | 138 | | { |
| | 8 | 139 | | CurrentCampaign = null; |
| | 8 | 140 | | await _localStorage.RemoveItemAsync(CampaignIdKey); |
| | | 141 | | } |
| | | 142 | | |
| | 16 | 143 | | OnContextChanged?.Invoke(); |
| | 16 | 144 | | } |
| | | 145 | | |
| | | 146 | | public async Task RefreshCurrentWorldAsync() |
| | | 147 | | { |
| | 3 | 148 | | if (!CurrentWorldId.HasValue) |
| | 1 | 149 | | return; |
| | | 150 | | |
| | 2 | 151 | | var world = await _worldApi.GetWorldAsync(CurrentWorldId.Value); |
| | 2 | 152 | | if (world != null) |
| | | 153 | | { |
| | 2 | 154 | | CurrentWorld = world; |
| | | 155 | | |
| | | 156 | | // Update campaign reference if still valid |
| | 2 | 157 | | if (CurrentCampaignId.HasValue) |
| | | 158 | | { |
| | 1 | 159 | | CurrentCampaign = world.Campaigns.FirstOrDefault(c => c.Id == CurrentCampaignId.Value); |
| | | 160 | | } |
| | | 161 | | |
| | 2 | 162 | | OnContextChanged?.Invoke(); |
| | | 163 | | } |
| | 3 | 164 | | } |
| | | 165 | | |
| | | 166 | | public async Task RefreshWorldsAsync() |
| | | 167 | | { |
| | 2 | 168 | | Worlds = await _worldApi.GetWorldsAsync(); |
| | 2 | 169 | | OnContextChanged?.Invoke(); |
| | 2 | 170 | | } |
| | | 171 | | } |