| @@ -54,11 +54,23 @@ | |||
| </div> | |||
| </div> | |||
| @if (string.IsNullOrEmpty(FromRoute)) { | |||
| <div class="row no-gutters align-items-end justify-content-center w-100" style="padding-bottom:2em"> | |||
| <MatButton Class="w-100" Raised="true" @onclick="SaveUserData">@i18n["Save"]</MatButton> | |||
| @if (!string.IsNullOrEmpty(FromRoute)) { | |||
| <div class="row no-gutters justify-content-end w-100"> | |||
| <MatRipple class="inputfile-mat-ripple" Color="@MatRippleColor.Default" @onclick="SaveUserData" Style="background: lightgrey; width: 64px; height: 64px; border-radius: 32px; align-items: flex-end; justify-content: center; display: inline-flex;"> | |||
| <label> | |||
| <svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 20 20" height="48px" viewBox="0 0 20 20" width="48px" fill="#000000"> | |||
| <g><rect fill="none" height="20" width="20" /></g> | |||
| <g> | |||
| <g> | |||
| <path d="M8,8.5c0.83,0,1.5-0.67,1.5-1.5S8.83,5.5,8,5.5S6.5,6.17,6.5,7S7.17,8.5,8,8.5z M12.28,13.66 C10.98,12.9,9.5,12.5,8,12.5s-2.98,0.4-4.28,1.16c-0.14,0.08-0.22,0.25-0.22,0.42v0.41h9v-0.41 C12.5,13.91,12.41,13.74,12.28,13.66z" opacity=".3" /> | |||
| <path d="M8,10c1.66,0,3-1.34,3-3S9.66,4,8,4S5,5.34,5,7S6.34,10,8,10z M8,5.5c0.83,0,1.5,0.67,1.5,1.5S8.83,8.5,8,8.5 S6.5,7.83,6.5,7S7.17,5.5,8,5.5z M13.03,12.37C11.56,11.5,9.84,11,8,11s-3.56,0.5-5.03,1.37C2.36,12.72,2,13.39,2,14.09V16h12 v-1.91C14,13.39,13.64,12.72,13.03,12.37z M12.5,14.5h-9v-0.41c0-0.18,0.09-0.34,0.22-0.42C5.02,12.9,6.5,12.5,8,12.5 s2.98,0.4,4.28,1.16c0.14,0.08,0.22,0.25,0.22,0.42V14.5z" /> | |||
| </g> | |||
| <polygon points="18,7.75 16.25,7.75 16.25,6 14.75,6 14.75,7.75 13,7.75 13,9.25 14.75,9.25 14.75,11 16.25,11 16.25,9.25 18,9.25" /> | |||
| </g> | |||
| </svg> | |||
| </label> | |||
| </MatRipple> | |||
| </div> | |||
| } else { | |||
| <div class="row no-gutters align-items-end justify-content-center w-100" style="padding-bottom:2em"> | |||
| <div class="col" style="padding-right:0.5em"> | |||
| <MatButton Class="w-100" Outlined="true" @onclick="Cancel">@i18n["Cancel"]</MatButton> | |||
| @@ -67,6 +79,10 @@ | |||
| <MatButton Class="w-100" Raised="true" @onclick="Next">@i18n["Send"]</MatButton> | |||
| </div> | |||
| </div> | |||
| } else { | |||
| <div class="row no-gutters align-items-end justify-content-center w-100" style="padding-bottom:2em"> | |||
| <MatButton Class="w-100" Raised="true" @onclick="SaveUserDataAndClose">@i18n["Save"]</MatButton> | |||
| </div> | |||
| } | |||
| </div> | |||
| @@ -83,7 +99,7 @@ | |||
| PageHistoryManager.OnBeforeNavigateBack = new EventCallback(this, (Action)OnBeforeNavigateBack); | |||
| PageHistoryManager.AddPageToHistory(NavigationManager.Uri); | |||
| Account = await GetUserData(); | |||
| if (ReportDataProvider.Report != null) { | |||
| if (!string.IsNullOrEmpty(FromRoute) && ReportDataProvider.Report != null) { | |||
| UserDataProvider.mapUserData(Account, ReportDataProvider.Report); | |||
| } | |||
| StateHasChanged(); | |||
| @@ -95,11 +111,16 @@ | |||
| } | |||
| } | |||
| private async void SaveUserData() { | |||
| private async Task SaveUserData() { | |||
| await UserDataProvider.Save(Account); | |||
| } | |||
| private async void SaveUserDataAndClose() { | |||
| await SaveUserData(); | |||
| NavigationManager.NavigateTo("caritas_services"); | |||
| } | |||
| private async Task<UserData> GetUserData() { | |||
| return await UserDataProvider.Get(); | |||
| } | |||
| @@ -92,6 +92,7 @@ | |||
| Toaster.ShowError(response.Message, response.GetDataAsFormattedList()); | |||
| } else { | |||
| Toaster.ShowWarning(i18n.GetString("Warning.NoConnection.Title"), i18n.GetString("Warning.NoConnection.Msg")); | |||
| PageHistoryManager.Reset(); | |||
| } | |||
| StateHasChanged(); | |||
| AppState.NotifyChanged(); | |||
| @@ -45,6 +45,7 @@ | |||
| protected override void OnInitialized() { | |||
| PageHistoryManager.AddPageToHistory(NavigationManager.Uri); | |||
| ReportDataProvider.Report = null; | |||
| ReportDataProvider.ReportRepositoryItem = null; | |||
| base.OnInitialized(); | |||
| } | |||
| @@ -25,7 +25,7 @@ | |||
| <h6 style="font-style:italic;padding-bottom:1em">@i18n["Info.Report.Transmitting"]</h6> | |||
| </div> | |||
| } else { | |||
| if (responseOk) { | |||
| if (ResposeStatus.OK == responseStatus) { | |||
| <MatHeadline5 Style="font-family:Ubuntu; text-align:center">@i18n["FinishedTextMissing"]</MatHeadline5> | |||
| <MatSubtitle1 Class="w-100" Style="font-family:Ubuntu; text-align:left; font-weight:800">@i18n["FinishedTextMissing_Heading1"]</MatSubtitle1> | |||
| <ul class="w-100"> | |||
| @@ -87,7 +87,13 @@ | |||
| @code { | |||
| private bool responseOk = false; | |||
| enum ResposeStatus : ushort { | |||
| OK = 1, | |||
| Error = 10, | |||
| NoConnection = 20 | |||
| } | |||
| private ResposeStatus responseStatus = ResposeStatus.NoConnection; | |||
| private bool running = true; | |||
| ReportResponse response; | |||
| @@ -96,18 +102,21 @@ | |||
| PageHistoryManager.AddPageToHistory(NavigationManager.Uri); | |||
| try { | |||
| response = await IBicycleRestService.SendMissingReport(ReportDataProvider.Report); | |||
| responseOk = System.Net.HttpStatusCode.OK == response.StatusCode ? true : false; | |||
| responseStatus = response == null ? ResposeStatus.NoConnection : System.Net.HttpStatusCode.OK == response.StatusCode ? ResposeStatus.OK : ResposeStatus.Error; | |||
| } catch (HttpRequestException ex) { | |||
| response = new(i18n.GetString("MissingBike"), new string[] { ex.Message }); | |||
| responseOk = false; | |||
| responseStatus = ResposeStatus.Error; | |||
| } | |||
| running = false; | |||
| if (responseOk) { | |||
| if (ResposeStatus.OK == responseStatus) { | |||
| Toaster.ShowSuccess(i18n.GetString("MissingBike"), response.Message); | |||
| PageHistoryManager.Reset(); | |||
| } else { | |||
| } else if (ResposeStatus.Error == responseStatus) { | |||
| Toaster.ShowError(response.Message, response.GetDataAsFormattedList()); | |||
| } else { | |||
| Toaster.ShowWarning(i18n.GetString("Warning.NoConnection.Title"), i18n.GetString("Warning.NoConnection.Msg")); | |||
| PageHistoryManager.Reset(); | |||
| } | |||
| StateHasChanged(); | |||
| AppState.NotifyChanged(); | |||
| @@ -10,8 +10,7 @@ | |||
| @inject MasterDataService MasterDataService; | |||
| @inject Toaster Toaster; | |||
| @inject IConfiguration Configuration; | |||
| @inject IBicycleRestService IBicycleRestService; | |||
| @inject OnlineStatusProvider OnlineStatusProvider; | |||
| @inject IBicycleRestService BicycleRestService; | |||
| <div class="row h-100 justify-content-center"> | |||
| @@ -22,9 +21,6 @@ | |||
| <div class="row align-items-end vw-100 h-50"> | |||
| <div class="col text-center"> | |||
| <h3 style="font-style:italic;padding-bottom:1em">@i18n["Welcome"]</h3> | |||
| @*<div> | |||
| <MatButton Disabled="@btnDisabled" Raised="true" Style="width:50%" @onclick="@((e) => NavigateToNext())">@i18n["Login"]</MatButton> | |||
| </div>*@ | |||
| </div> | |||
| </div> | |||
| <div class="row align-items-center justify-content-center vw-100 h-25"> | |||
| @@ -52,8 +48,9 @@ | |||
| showProgressCircle = true; | |||
| StateHasChanged(); | |||
| try { | |||
| IBicycleRestService.Initialize(Configuration, OnlineStatusProvider); | |||
| BicycleRestService.Initialize(Configuration); | |||
| await MasterDataService.SynchronizeMasterdata(); | |||
| await BicycleRestService.TrySendPendingReports(); | |||
| } catch (Exception) { | |||
| Toaster.ShowWarning(i18n.GetString("Warning.Masterdata.Title"), i18n.GetString("Warning.Masterdata.Msg")); | |||
| } finally { | |||
| @@ -23,6 +23,7 @@ namespace CaritasPWA { | |||
| builder.Services.AddSingleton<PageHistoryManager>(); | |||
| builder.Services.AddSingleton<ReportDataProvider>(); | |||
| builder.Services.AddSingleton<OnlineStatusProvider>(); | |||
| builder.Services.AddSingleton<ReportRepositoryService>(); | |||
| builder.Services.AddScoped<Toaster>(); | |||
| builder.Services.AddScoped<UserDataProvider>(); | |||
| builder.Services.AddScoped<MasterDataService>(); | |||
| @@ -0,0 +1,11 @@ | |||
| namespace cwebplusApp.Shared.Models { | |||
| public class FoundReportRepositoryItem : ReportRepositoryItem { | |||
| private FoundReport report; | |||
| public FoundReport Report { get => report; set => report = value; } | |||
| public FoundReportRepositoryItem(FoundReport _report, long identifier) : base(Type.FOUND, identifier) { | |||
| this.report = _report; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,11 @@ | |||
| namespace cwebplusApp.Shared.Models { | |||
| public class MissingReportRepositoryItem : ReportRepositoryItem { | |||
| private MissingReport report; | |||
| public MissingReport Report { get => report; set => report = value; } | |||
| public MissingReportRepositoryItem(MissingReport _report, long identifier) : base(Type.MISSING, identifier) { | |||
| this.report = _report; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,33 @@ | |||
| using System; | |||
| namespace cwebplusApp.Shared.Models { | |||
| public abstract class ReportRepositoryItem { | |||
| public enum Type { FOUND, MISSING } | |||
| private long id; | |||
| private string serverRefNbr; | |||
| private bool transmitted; | |||
| private Type reportType; | |||
| public long ID { get => id; set => id = value; } | |||
| public string ServerRefNbr { get => serverRefNbr; set => serverRefNbr = value; } | |||
| public bool Transmitted { get => transmitted; set => transmitted = value; } | |||
| public Type ReportType { get => reportType; set => reportType = value; } | |||
| public ReportRepositoryItem(Type _reportType, long identifier) { | |||
| this.id = identifier; | |||
| this.reportType = _reportType; | |||
| this.transmitted = false; | |||
| } | |||
| public override bool Equals(Object obj) { | |||
| if ((obj == null) || !this.GetType().Equals(obj.GetType())) { | |||
| return false; | |||
| } else { | |||
| ReportRepositoryItem rri = (ReportRepositoryItem)obj; | |||
| return (ID == rri.ID); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -119,21 +119,23 @@ | |||
| private void LocationChanged(object sender, LocationChangedEventArgs e) { | |||
| locUrl = i18n.GetString(e.Location.Replace(NavigationManager.BaseUri, "")); | |||
| if (IsInServicesUrl(e)) { | |||
| if (IsInServicesUrl(e.Location)) { | |||
| Index = 1; | |||
| } else if (e.Location.Contains("account")) { | |||
| Index = 2; | |||
| } else if (e.Location.Contains("extras")) { | |||
| Index = 3; | |||
| } else if (e.Location.Contains("info")) { | |||
| Index = 4; | |||
| } else { | |||
| Index = 3; | |||
| Index = 0; | |||
| } | |||
| StateHasChanged(); | |||
| } | |||
| private bool IsInServicesUrl(LocationChangedEventArgs e) { | |||
| return (e.Location.Contains("caritas_services") || e.Location.Contains("lost_found") || e.Location.Contains("keydata") | |||
| || e.Location.Contains("account/") || e.Location.Contains("conclusion_")); | |||
| private bool IsInServicesUrl(string location) { | |||
| return (location.Contains("caritas_services") || location.Contains("lost_found") || location.Contains("keydata") | |||
| || location.Contains("account/") || location.Contains("conclusion_")); | |||
| } | |||
| private bool HandleAppBarContainer() { | |||
| @@ -141,16 +143,22 @@ | |||
| string baseUri = NavigationManager.BaseUri; | |||
| string delta = uri.Replace(baseUri, ""); | |||
| if (delta.Equals("caritas_services")) { | |||
| Index = 1; | |||
| } else if (delta.Equals("account")) { | |||
| Index = 2; | |||
| } else if (delta.Equals("info")) { | |||
| Index = 4; | |||
| if (delta == null || delta.Equals("")) { | |||
| return false; | |||
| } else { | |||
| Index = 3; | |||
| if (IsInServicesUrl(delta)) { | |||
| Index = 1; | |||
| } else if (delta.Equals("account")) { | |||
| Index = 2; | |||
| } else if (delta.Equals("extras")) { | |||
| Index = 3; | |||
| } else if (delta.Equals("info")) { | |||
| Index = 4; | |||
| } else { | |||
| Index = 0; | |||
| } | |||
| return true; | |||
| } | |||
| return true; | |||
| } | |||
| private bool BackButtonDisabled() { | |||
| @@ -4,6 +4,7 @@ using Newtonsoft.Json; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Globalization; | |||
| using System.Net; | |||
| using System.Net.Http; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| @@ -16,13 +17,22 @@ namespace cwebplusApp.Shared.Services { | |||
| private static readonly string VERSION = "v1"; | |||
| private HttpClient httpClient; | |||
| public IConfiguration Configuration { get; set; } | |||
| public OnlineStatusProvider OnlineStatusProvider { get; set; } | |||
| private IConfiguration configuration; | |||
| private OnlineStatusProvider onlineStatusProvider; | |||
| private ReportRepositoryService reportRepositoryService; | |||
| private ReportDataProvider reportDataProvider; | |||
| public void Initialize(IConfiguration configuration, OnlineStatusProvider onlineStatusProvider) { | |||
| this.Configuration = configuration; | |||
| this.OnlineStatusProvider = onlineStatusProvider; | |||
| string hostBaseUrl = Configuration.GetValue<string>("host_base_url"); | |||
| public BicycleRestService(ReportRepositoryService _reportRepositoryService, OnlineStatusProvider _onlineStatusProvider, | |||
| ReportDataProvider _reportDataProvider) { | |||
| this.reportRepositoryService = _reportRepositoryService; | |||
| this.onlineStatusProvider = _onlineStatusProvider; | |||
| this.reportDataProvider = _reportDataProvider; | |||
| } | |||
| public void Initialize(IConfiguration _configuration) { | |||
| this.configuration = _configuration; | |||
| string hostBaseUrl = configuration.GetValue<string>("host_base_url"); | |||
| if (!String.IsNullOrEmpty(hostBaseUrl)) { | |||
| this.httpClient = new HttpClient { BaseAddress = new Uri(hostBaseUrl) }; | |||
| } | |||
| @@ -30,7 +40,7 @@ namespace cwebplusApp.Shared.Services { | |||
| public async Task<List<ColorItem>> GetColors() { | |||
| if (httpClient != null) { | |||
| string subResourceUrl = Configuration.GetValue<string>("subresource_url_colors"); | |||
| string subResourceUrl = configuration.GetValue<string>("subresource_url_colors"); | |||
| if (!String.IsNullOrEmpty(subResourceUrl)) { | |||
| HttpResponseMessage httpResult = await httpClient.GetAsync(string.Format(subResourceUrl, VERSION, CultureInfo.CurrentCulture.TwoLetterISOLanguageName)); | |||
| @@ -46,7 +56,7 @@ namespace cwebplusApp.Shared.Services { | |||
| public async Task<List<BicycleType>> GetBicycleTypes() { | |||
| if (httpClient != null) { | |||
| string subResourceUrl = Configuration.GetValue<string>("subresource_url_types"); | |||
| string subResourceUrl = configuration.GetValue<string>("subresource_url_types"); | |||
| if (!String.IsNullOrEmpty(subResourceUrl)) { | |||
| HttpResponseMessage httpResult = await httpClient.GetAsync(string.Format(subResourceUrl, VERSION, CultureInfo.CurrentCulture.TwoLetterISOLanguageName)); | |||
| @@ -62,7 +72,7 @@ namespace cwebplusApp.Shared.Services { | |||
| public async Task<List<Brand>> GetBrands() { | |||
| if (httpClient != null) { | |||
| string subResourceUrl = Configuration.GetValue<string>("subresource_url_brands"); | |||
| string subResourceUrl = configuration.GetValue<string>("subresource_url_brands"); | |||
| if (!String.IsNullOrEmpty(subResourceUrl)) { | |||
| HttpResponseMessage httpResult = await httpClient.GetAsync(string.Format(subResourceUrl, VERSION, CultureInfo.CurrentCulture.TwoLetterISOLanguageName)); | |||
| @@ -77,33 +87,79 @@ namespace cwebplusApp.Shared.Services { | |||
| } | |||
| public async Task<ReportResponse> SendFoundReport(Report report) { | |||
| string subResourceUrl = Configuration.GetValue<string>("subresource_url_foundreport"); | |||
| return await SendReport(report, subResourceUrl); | |||
| string subResourceUrl = configuration.GetValue<string>("subresource_url_foundreport"); | |||
| return await SendReport(report, subResourceUrl, new FoundReportRepositoryItem((FoundReport)report, GetCurrentTimeInMillis())); | |||
| } | |||
| private async Task<ReportResponse> SendFoundReport(FoundReportRepositoryItem reportRepositoryItem) { | |||
| string subResourceUrl = configuration.GetValue<string>("subresource_url_foundreport"); | |||
| return await SendReport(reportRepositoryItem.Report, subResourceUrl, reportRepositoryItem); | |||
| } | |||
| public async Task<ReportResponse> SendMissingReport(Report report) { | |||
| string subResourceUrl = Configuration.GetValue<string>("subresource_url_missingreport"); | |||
| return await SendReport(report, subResourceUrl); | |||
| string subResourceUrl = configuration.GetValue<string>("subresource_url_missingreport"); | |||
| return await SendReport(report, subResourceUrl, new MissingReportRepositoryItem((MissingReport)report, GetCurrentTimeInMillis())); | |||
| } | |||
| protected async Task<ReportResponse> SendReport(Report report, string subResourceUrl) { | |||
| if (OnlineStatusProvider.Online) { | |||
| public async Task<ReportResponse> SendMissingReport(MissingReportRepositoryItem reportRepositoryItem) { | |||
| string subResourceUrl = configuration.GetValue<string>("subresource_url_missingreport"); | |||
| return await SendReport(reportRepositoryItem.Report, subResourceUrl, reportRepositoryItem); | |||
| } | |||
| public async Task TrySendPendingReports() { | |||
| await TrySendFoundPendingReports(); | |||
| await TrySendMissingPendingReports(); | |||
| } | |||
| public async Task TrySendFoundPendingReports() { | |||
| List<FoundReportRepositoryItem> pendingFoundReports = await reportRepositoryService.GetPendingFoundReports(); | |||
| foreach (FoundReportRepositoryItem item in pendingFoundReports) { | |||
| try { | |||
| ReportResponse response = await SendFoundReport(item); | |||
| } catch (Exception) { | |||
| continue; | |||
| } | |||
| } | |||
| } | |||
| public async Task TrySendMissingPendingReports() { | |||
| List<MissingReportRepositoryItem> pendingMissingReports = await reportRepositoryService.GetPendingMissingReports(); | |||
| foreach (MissingReportRepositoryItem item in pendingMissingReports) { | |||
| try { | |||
| ReportResponse response = await SendMissingReport(item); | |||
| } catch (Exception) { | |||
| continue; | |||
| } | |||
| } | |||
| } | |||
| protected async Task<ReportResponse> SendReport(Report report, string subResourceUrl, ReportRepositoryItem reportRepositoryItem) { | |||
| ReportResponse response = null; | |||
| if (onlineStatusProvider.Online && !reportRepositoryItem.Transmitted) { | |||
| if (httpClient != null) { | |||
| if (!String.IsNullOrEmpty(subResourceUrl)) { | |||
| string reportJson = JsonConvert.SerializeObject(report); | |||
| HttpContent content = new StringContent(reportJson, Encoding.UTF8, "application/json"); | |||
| HttpResponseMessage httpResult = await httpClient.PostAsync(string.Format(subResourceUrl, VERSION, CultureInfo.CurrentCulture.TwoLetterISOLanguageName), content); | |||
| string msg = await httpResult.Content.ReadAsStringAsync(); | |||
| ReportResponse response = JsonConvert.DeserializeObject<ReportResponse>(msg); | |||
| response = JsonConvert.DeserializeObject<ReportResponse>(msg); | |||
| response.StatusCode = httpResult.StatusCode; | |||
| if (HttpStatusCode.OK == response.StatusCode) { | |||
| reportRepositoryItem.ServerRefNbr = response.Data[0]; | |||
| reportRepositoryItem.Transmitted = true; | |||
| await reportRepositoryService.SaveReport(reportRepositoryItem); | |||
| } | |||
| return response; | |||
| } | |||
| } | |||
| throw new HttpRequestException("HTTP client not initialized!"); | |||
| } else { | |||
| //TODO: Save to app storage | |||
| return null; | |||
| } | |||
| await reportRepositoryService.SaveReport(reportRepositoryItem); | |||
| return response; | |||
| } | |||
| private long GetCurrentTimeInMillis() { | |||
| return DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; | |||
| } | |||
| } | |||
| } | |||
| @@ -6,7 +6,7 @@ using System.Threading.Tasks; | |||
| namespace cwebplusApp.Shared.Services { | |||
| public interface IBicycleRestService { | |||
| void Initialize(IConfiguration configuration, OnlineStatusProvider onlineStatusProvider); | |||
| void Initialize(IConfiguration configuration); | |||
| Task<List<ColorItem>> GetColors(); | |||
| @@ -17,5 +17,11 @@ namespace cwebplusApp.Shared.Services { | |||
| Task<ReportResponse> SendFoundReport(Report report); | |||
| Task<ReportResponse> SendMissingReport(Report report); | |||
| Task TrySendPendingReports(); | |||
| Task TrySendFoundPendingReports(); | |||
| Task TrySendMissingPendingReports(); | |||
| } | |||
| } | |||
| @@ -1,21 +0,0 @@ | |||
| using cwebplusApp.Shared.Models; | |||
| using Microsoft.Extensions.Configuration; | |||
| using System.Collections.Generic; | |||
| using System.Threading.Tasks; | |||
| namespace cwebplusApp.Shared.Services { | |||
| public interface ILFBicycleRest { | |||
| void Initialize(IConfiguration configuration); | |||
| Task<List<ColorItem>> GetColors(); | |||
| Task<List<BicycleType>> GetBicycleTypes(); | |||
| Task<List<Brand>> GetBrands(); | |||
| Task<ReportResponse> SendFoundReport(Report report); | |||
| Task<ReportResponse> SendMissingReport(Report report); | |||
| } | |||
| } | |||
| @@ -1,104 +0,0 @@ | |||
| using cwebplusApp.Shared.Models; | |||
| using Microsoft.AspNetCore.Components; | |||
| using Microsoft.Extensions.Configuration; | |||
| using Newtonsoft.Json; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Globalization; | |||
| using System.Net.Http; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace cwebplusApp.Shared.Services { | |||
| // REST interface responsible to submit lost or found reports and get the available master data. | |||
| public class LFBicycleRest : ILFBicycleRest { | |||
| private static readonly string VERSION = "v1"; | |||
| private HttpClient httpClient; | |||
| [Inject] | |||
| public IConfiguration Configuration { get; set; } | |||
| public void Initialize(IConfiguration configuration) { | |||
| this.Configuration = configuration; | |||
| string hostBaseUrl = Configuration.GetValue<string>("host_base_url"); | |||
| if (!String.IsNullOrEmpty(hostBaseUrl)) { | |||
| this.httpClient = new HttpClient { BaseAddress = new Uri(hostBaseUrl) }; | |||
| } | |||
| } | |||
| public async Task<List<ColorItem>> GetColors() { | |||
| if (httpClient != null) { | |||
| string subResourceUrl = Configuration.GetValue<string>("subresource_url_colors"); | |||
| if (!String.IsNullOrEmpty(subResourceUrl)) { | |||
| HttpResponseMessage httpResult = await httpClient.GetAsync(string.Format(subResourceUrl, VERSION, CultureInfo.CurrentCulture.TwoLetterISOLanguageName)); | |||
| if (httpResult.StatusCode == System.Net.HttpStatusCode.OK) { | |||
| ColorItem[] colors = JsonConvert.DeserializeObject<ColorItem[]>(await httpResult.Content.ReadAsStringAsync()); | |||
| return new List<ColorItem>(colors); | |||
| } | |||
| throw new HttpRequestException("HTTP error " + httpResult.StatusCode); | |||
| } | |||
| } | |||
| throw new HttpRequestException("HTTP client not initialized!"); | |||
| } | |||
| public async Task<List<BicycleType>> GetBicycleTypes() { | |||
| if (httpClient != null) { | |||
| string subResourceUrl = Configuration.GetValue<string>("subresource_url_types"); | |||
| if (!String.IsNullOrEmpty(subResourceUrl)) { | |||
| HttpResponseMessage httpResult = await httpClient.GetAsync(string.Format(subResourceUrl, VERSION, CultureInfo.CurrentCulture.TwoLetterISOLanguageName)); | |||
| if (httpResult.StatusCode == System.Net.HttpStatusCode.OK) { | |||
| BicycleType[] bicycleTypes = JsonConvert.DeserializeObject<BicycleType[]>(await httpResult.Content.ReadAsStringAsync()); | |||
| return new List<BicycleType>(bicycleTypes); | |||
| } | |||
| throw new HttpRequestException("HTTP error " + httpResult.StatusCode); | |||
| } | |||
| } | |||
| throw new HttpRequestException("HTTP client not initialized!"); | |||
| } | |||
| public async Task<List<Brand>> GetBrands() { | |||
| if (httpClient != null) { | |||
| string subResourceUrl = Configuration.GetValue<string>("subresource_url_brands"); | |||
| if (!String.IsNullOrEmpty(subResourceUrl)) { | |||
| HttpResponseMessage httpResult = await httpClient.GetAsync(string.Format(subResourceUrl, VERSION, CultureInfo.CurrentCulture.TwoLetterISOLanguageName)); | |||
| if (httpResult.StatusCode == System.Net.HttpStatusCode.OK) { | |||
| Brand[] brands = JsonConvert.DeserializeObject<Brand[]>(await httpResult.Content.ReadAsStringAsync()); | |||
| return new List<Brand>(brands); | |||
| } | |||
| throw new HttpRequestException("HTTP error " + httpResult.StatusCode); | |||
| } | |||
| } | |||
| throw new HttpRequestException("HTTP client not initialized!"); | |||
| } | |||
| public async Task<ReportResponse> SendFoundReport(Report report) { | |||
| string subResourceUrl = Configuration.GetValue<string>("subresource_url_foundreport"); | |||
| return await SendReport(report, subResourceUrl); | |||
| } | |||
| public async Task<ReportResponse> SendMissingReport(Report report) { | |||
| string subResourceUrl = Configuration.GetValue<string>("subresource_url_missingreport"); | |||
| return await SendReport(report, subResourceUrl); | |||
| } | |||
| protected async Task<ReportResponse> SendReport(Report report, string subResourceUrl) { | |||
| if (httpClient != null) { | |||
| if (!String.IsNullOrEmpty(subResourceUrl)) { | |||
| string reportJson = JsonConvert.SerializeObject(report); | |||
| HttpContent content = new StringContent(reportJson, Encoding.UTF8, "application/json"); | |||
| HttpResponseMessage httpResult = await httpClient.PostAsync(string.Format(subResourceUrl, VERSION, CultureInfo.CurrentCulture.TwoLetterISOLanguageName), content); | |||
| string msg = await httpResult.Content.ReadAsStringAsync(); | |||
| ReportResponse response = JsonConvert.DeserializeObject<ReportResponse>(msg); | |||
| response.StatusCode = httpResult.StatusCode; | |||
| return response; | |||
| } | |||
| } | |||
| throw new HttpRequestException("HTTP client not initialized!"); | |||
| } | |||
| } | |||
| } | |||
| @@ -127,6 +127,9 @@ namespace cwebplusApp.Shared.Services { | |||
| } else if (key == KeyNameBcTypes) { | |||
| _bicycleTypes = null; | |||
| Changed?.Invoke(this, EventArgs.Empty); | |||
| } else if (key == KeyNameBrands) { | |||
| _brands = null; | |||
| Changed?.Invoke(this, EventArgs.Empty); | |||
| } | |||
| } | |||
| @@ -4,6 +4,7 @@ namespace cwebplusApp.Shared.Services { | |||
| public class ReportDataProvider { | |||
| public Report Report { get; set; } | |||
| public ReportRepositoryItem ReportRepositoryItem { get; set; } | |||
| public FoundReport GetFoundReport() { | |||
| @@ -0,0 +1,119 @@ | |||
| using cwebplusApp.Shared.Models; | |||
| using Microsoft.JSInterop; | |||
| using Newtonsoft.Json; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Threading.Tasks; | |||
| namespace cwebplusApp.Shared.Services { | |||
| public sealed class ReportRepositoryService { | |||
| private const string KeyNameFoundReports = "foundReportRepository"; | |||
| private const string KeyNameMissingReports = "missingReportRepository"; | |||
| private readonly IJSRuntime jsRuntime; | |||
| private bool initialized; | |||
| public event EventHandler Changed; | |||
| public ReportRepositoryService(IJSRuntime jsRuntime) { | |||
| this.jsRuntime = jsRuntime; | |||
| } | |||
| public async Task SaveReport(ReportRepositoryItem reportRepositoryItem) { | |||
| if (reportRepositoryItem is FoundReportRepositoryItem fItem) { | |||
| await SaveFoundReport(fItem); | |||
| } else if (reportRepositoryItem is MissingReportRepositoryItem mItem) { | |||
| await SaveMissingReport(mItem); | |||
| } | |||
| } | |||
| public async ValueTask<List<FoundReportRepositoryItem>> GetFoundReports() { | |||
| // Register the Storage event handler. This handler calls OnStorageUpdated when the storage changed. | |||
| // This way, you can reload the settings when another instance of the application (tab / window) save the settings | |||
| if (!initialized) { | |||
| // Create a reference to the current object, so the JS function can call the public method "OnStorageUpdated" | |||
| var reference = DotNetObjectReference.Create(this); | |||
| await this.jsRuntime.InvokeVoidAsync("BlazorRegisterStorageEvent", reference); | |||
| this.initialized = true; | |||
| } | |||
| // Read the JSON string that contains the data from the local storage | |||
| List<FoundReportRepositoryItem> result; | |||
| var str = await this.jsRuntime.InvokeAsync<string>("BlazorGetLocalStorage", KeyNameFoundReports); | |||
| if (str != null) { | |||
| result = JsonConvert.DeserializeObject<List<FoundReportRepositoryItem>>(str) ?? new(); | |||
| } else { | |||
| result = new(); | |||
| } | |||
| return result; | |||
| } | |||
| public async ValueTask<List<FoundReportRepositoryItem>> GetPendingFoundReports() { | |||
| List<FoundReportRepositoryItem> pendingFoundReps = new(); | |||
| List<FoundReportRepositoryItem> foundReps = await GetFoundReports(); | |||
| foreach (FoundReportRepositoryItem report in foundReps) { | |||
| if (!report.Transmitted) { | |||
| pendingFoundReps.Add(report); | |||
| } | |||
| } | |||
| return pendingFoundReps; | |||
| } | |||
| public async ValueTask<List<MissingReportRepositoryItem>> GetMissingReports() { | |||
| // Register the Storage event handler. This handler calls OnStorageUpdated when the storage changed. | |||
| // This way, you can reload the settings when another instance of the application (tab / window) save the settings | |||
| if (!initialized) { | |||
| // Create a reference to the current object, so the JS function can call the public method "OnStorageUpdated" | |||
| var reference = DotNetObjectReference.Create(this); | |||
| await this.jsRuntime.InvokeVoidAsync("BlazorRegisterStorageEvent", reference); | |||
| this.initialized = true; | |||
| } | |||
| // Read the JSON string that contains the data from the local storage | |||
| List<MissingReportRepositoryItem> result; | |||
| var str = await this.jsRuntime.InvokeAsync<string>("BlazorGetLocalStorage", KeyNameMissingReports); | |||
| if (str != null) { | |||
| result = JsonConvert.DeserializeObject<List<MissingReportRepositoryItem>>(str) ?? new(); | |||
| } else { | |||
| result = new(); | |||
| } | |||
| return result; | |||
| } | |||
| public async ValueTask<List<MissingReportRepositoryItem>> GetPendingMissingReports() { | |||
| List<MissingReportRepositoryItem> pendingMissingReps = new(); | |||
| List<MissingReportRepositoryItem> missingReps = await GetMissingReports(); | |||
| foreach (MissingReportRepositoryItem report in missingReps) { | |||
| if (!report.Transmitted) { | |||
| pendingMissingReps.Add(report); | |||
| } | |||
| } | |||
| return pendingMissingReps; | |||
| } | |||
| public async Task SaveFoundReport(FoundReportRepositoryItem foundReportRepositoryItem) { | |||
| List<FoundReportRepositoryItem> foundReps = await GetFoundReports(); | |||
| foundReps.Remove(foundReportRepositoryItem); | |||
| foundReps.Add(foundReportRepositoryItem); | |||
| var json = JsonConvert.SerializeObject(foundReps); | |||
| await jsRuntime.InvokeVoidAsync("BlazorSetLocalStorage", KeyNameFoundReports, json); | |||
| } | |||
| public async Task SaveMissingReport(MissingReportRepositoryItem missingReportRepositoryItem) { | |||
| List<MissingReportRepositoryItem> missingReps = await GetMissingReports(); | |||
| missingReps.Remove(missingReportRepositoryItem); | |||
| missingReps.Add(missingReportRepositoryItem); | |||
| var json = JsonConvert.SerializeObject(missingReps); | |||
| await jsRuntime.InvokeVoidAsync("BlazorSetLocalStorage", KeyNameMissingReports, json); | |||
| } | |||
| // This method is called from BlazorRegisterStorageEvent when the storage changed | |||
| [JSInvokable] | |||
| public void OnStorageUpdated(string key) { | |||
| if (key == KeyNameMissingReports || key == KeyNameFoundReports) { | |||
| // Reset the settings. The next call to Get will reload the data | |||
| Changed?.Invoke(this, EventArgs.Empty); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -6,15 +6,13 @@ using System.Threading.Tasks; | |||
| namespace cwebplusApp.Shared.Services { | |||
| public sealed class UserDataProvider { | |||
| private const string KeyName = "account"; | |||
| private const string KeyName = "account"; | |||
| private readonly IJSRuntime _jsRuntime; | |||
| private bool _initialized; | |||
| public event EventHandler Changed; | |||
| public bool AutoSave { get; set; } = true; | |||
| public UserDataProvider(IJSRuntime jsRuntime) { | |||
| _jsRuntime = jsRuntime; | |||
| } | |||
| @@ -18,9 +18,9 @@ | |||
| <PackageReference Include="BlazorGeolocation" Version="0.1.1" /> | |||
| <PackageReference Include="FisSst.BlazorMaps" Version="1.0.2" /> | |||
| <PackageReference Include="MatBlazor" Version="2.9.0-develop-042" /> | |||
| <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="5.0.8" /> | |||
| <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="5.0.8" PrivateAssets="all" /> | |||
| <PackageReference Include="Microsoft.Extensions.Localization" Version="5.0.8" /> | |||
| <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="5.0.9" /> | |||
| <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="5.0.9" PrivateAssets="all" /> | |||
| <PackageReference Include="Microsoft.Extensions.Localization" Version="5.0.9" /> | |||
| <PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> | |||
| <PackageReference Include="System.Configuration.ConfigurationManager" Version="5.0.0" /> | |||
| </ItemGroup> | |||
| @@ -20,6 +20,7 @@ const assets = [ | |||
| '/conclusion_missing', | |||
| '/doneimage', | |||
| '/failureimage', | |||
| '/warningimage', | |||
| 'favicon.ico', | |||
| 'images/batch_found.png', | |||
| 'images/batch_fundvelo.png', | |||
| @@ -45,7 +46,7 @@ const assets = [ | |||
| '_content/blazoranimate/aos.css', | |||
| '_framework/blazor.webassembly.js', | |||
| '_framework/blazor.boot.json', | |||
| '_framework/dotnet.5.0.8.js', | |||
| '_framework/dotnet.5.0.9.js', | |||
| 'https://fonts.googleapis.com/css?family=Roboto:300,400,500', | |||
| 'https://fonts.googleapis.com/icon?family=Material+Icons', | |||
| 'https://fonts.googleapis.com/css2?family=Ubuntu:wght@400;700&display=swap', | |||
| @@ -86,23 +87,39 @@ self.addEventListener('activate', event => { | |||
| }); | |||
| // fetch events (appsettings are always first fetched from network) | |||
| self.addEventListener('fetch', function (event) { | |||
| event.respondWith(networkOrCache(event.request).catch(function () { | |||
| })); | |||
| }) | |||
| self.addEventListener('fetch', event => { | |||
| if (event.request.url.endsWith(appsettings_url)) { | |||
| fetch(event.request).then(function (response) { | |||
| return caches.open(staticCacheName).then(function (cache) { | |||
| console.log('update cache'); | |||
| cache.put(event.request, response.clone()); | |||
| return response; | |||
| }); | |||
| }).catch(function () { | |||
| return useFallback(); | |||
| }); | |||
| } else { | |||
| event.respondWith( | |||
| caches.match(event.request).then(cacheRes => { | |||
| return cacheRes || fetch(event.request); | |||
| }) | |||
| ); | |||
| } | |||
| }); | |||
| function networkOrCache(request) { | |||
| return fetch(request).then(function (response) { | |||
| return response.ok ? response : fromCache(request); | |||
| }).catch(function () { | |||
| return fromCache(request); | |||
| }); | |||
| function useFallback() { | |||
| return Promise.resolve(new Response(Appsettings_Fallback, { | |||
| headers: { | |||
| 'Content-Type': 'application/json' | |||
| } | |||
| })); | |||
| } | |||
| function fromCache(request) { | |||
| return caches.open(staticCacheName).then(function (cache) { | |||
| return cache.match(request).then(function (matching) { | |||
| return matching || Promise.reject('request-not-in-cache'); | |||
| }); | |||
| }); | |||
| var Appsettings_Fallback = { | |||
| "host_base_url": "https://integrate.dynalias.net:9443/Fundvelo/", | |||
| "subresource_url_colors": "api/{0}/{1}/fundvelo/colors", | |||
| "subresource_url_brands": "api/{0}/{1}/fundvelo/brands", | |||
| "subresource_url_types": "api/{0}/{1}/fundvelo/types", | |||
| "subresource_url_foundreport": "api/{0}/{1}/fundvelo/fundmeldung", | |||
| "subresource_url_missingreport": "api/{0}/{1}/fundvelo/suchauftrag" | |||
| } | |||