This commit is contained in:
Oliver Hauff 2022-01-22 18:28:01 +01:00
parent f38b516d25
commit 578fcee611
70 changed files with 6828 additions and 9625 deletions

View file

@ -90,7 +90,8 @@ namespace TINK.Model.Connector
response.merchant_message,
response.TryGetCopriVersion(out Version copriVersion)
? new Version(0,0)
: copriVersion);
: copriVersion,
new ResourceUrls(response.tariff_info_html, response.bike_info_html, response.agb_html, response.privacy_html, response.impress_html));
/// <summary> Gets account object from login response.</summary>
/// <param name="merchantId">Needed to extract cookie from autorization response.</param>

View file

@ -100,6 +100,22 @@ namespace TINK.Model
string ExternalFolder { get; }
/// <summary> Holds the stations availalbe. </summary>
IEnumerable<IStation> Stations {get; set;}
IEnumerable<IStation> Stations { get; set; }
IResourceUrls ResourceUrls { get; set; }
}
public interface IResourceUrls
{
string FeesResourcePath { get; }
string BikesResourcePath { get; }
string AgbResourcePath { get; }
string PrivacyResourcePath { get; }
string ImpressResourcePath { get; }
}
}

View file

@ -0,0 +1,29 @@
namespace TINK.Model
{
public class ResourceUrls : IResourceUrls
{
public ResourceUrls(
string feesResourcePath = null,
string bikesResourcePath = null,
string agbResourcePath = null,
string privacyResourcePath = null,
string impressResourcePath = null)
{
FeesResourcePath = !string.IsNullOrEmpty(feesResourcePath) ? feesResourcePath : "site/tariff_info_1.html";
BikesResourcePath = !string.IsNullOrEmpty(bikesResourcePath) ? bikesResourcePath : "site/bike_info.html";
AgbResourcePath = !string.IsNullOrEmpty(agbResourcePath) ? agbResourcePath : "site/agb.html";
PrivacyResourcePath = !string.IsNullOrEmpty(privacyResourcePath) ? privacyResourcePath : "site/privacy.html";
ImpressResourcePath = !string.IsNullOrEmpty(impressResourcePath) ? impressResourcePath : "site/impress.html";
}
public string FeesResourcePath { get; }
public string BikesResourcePath { get; }
public string AgbResourcePath { get; }
public string PrivacyResourcePath { get; }
public string ImpressResourcePath { get; }
}
}

View file

@ -344,6 +344,8 @@ namespace TINK.Model
/// <summary> Holds the stations availalbe. </summary>
public IEnumerable<IStation> Stations { get; set; } = new List<Station.Station>();
public IResourceUrls ResourceUrls { get; set; } = new ResourceUrls();
/// <summary> Action to post to GUI thread.</summary>
public Action<SendOrPostCallback, object> PostAction { get; }

View file

@ -476,7 +476,7 @@ namespace TINK.Model
AppResources.ChangeLog3_0_266
},
{
new Version(3, 0, 271),
new Version(3, 0, 274),
AppResources.ChangeLog3_0_231 // Minor improvements.
}
};

View file

@ -733,18 +733,18 @@ namespace TINK.Repository
}
/// <summary> http- post request.</summary>
/// <param name="p_strCommand">Command to send.</param>
/// <param name="p_oDisplayCommand">Command to display/ log used for error handling.</param>
/// <param name="command">Command to send.</param>
/// <param name="displayCommand">Command to display/ log used for error handling.</param>
/// <param name="uRL">Address of server to communicate with.</param>
/// <returns>Response as text.</returns>
/// <changelog> An unused member PostAsyncHttpClient using HttpClient for posting was removed 2020-04-02.</changelog>
private static async Task<string> PostAsync(
string uRL,
string p_strCommand,
string command,
string userAgent = null,
Func<string> p_oDisplayCommand = null)
Func<string> displayCommand = null)
{
if (string.IsNullOrEmpty(p_strCommand))
if (string.IsNullOrEmpty(command))
{
Log.ForContext<CopriCallsHttps>().Fatal("Can not post command. Command must not be null or empty.");
@ -759,7 +759,7 @@ namespace TINK.Repository
}
// Get display version of command to used for display/ logging (password should never be included in output)
Func<string> displayCommandFunc = p_oDisplayCommand ?? delegate () { return p_strCommand; };
Func<string> displayCommandFunc = displayCommand ?? delegate () { return command; };
try
{
@ -769,7 +769,6 @@ namespace TINK.Repository
// Returns a http request.
var request = WebRequest.CreateHttp(l_strHost);
request.Timeout = 5000; // Default value for HttpWebRequest is 100 secs. According to doc this has no impact on async call but when debugging it has.
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.UserAgent = userAgent;
@ -778,38 +777,38 @@ namespace TINK.Repository
// If not KeepAlive is set to true Stream.Write leads arbitrarily to an object disposed exception.
request.KeepAlive = true;
byte[] l_oPostData = Encoding.UTF8.GetBytes(p_strCommand);
byte[] postData = Encoding.UTF8.GetBytes(command);
request.ContentLength = l_oPostData.Length;
request.ContentLength = postData.Length;
// Get the request stream.
using (Stream l_oDataStream = await request.GetRequestStreamAsync())
using (Stream dataStream = await request.GetRequestStreamAsync())
{
// Write the data to the request stream.
await l_oDataStream.WriteAsync(l_oPostData, 0, l_oPostData.Length);
await dataStream.WriteAsync(postData, 0, postData.Length);
}
// Get the response.
var l_oResponse = await request.GetResponseAsync() as HttpWebResponse;
var webResponse = await request.GetResponseAsync() as HttpWebResponse;
if (l_oResponse == null)
if (webResponse == null)
{
throw new System.Exception(string.Format("Reserve request failed. Response form from server was not of expected type."));
}
if (l_oResponse.StatusCode != HttpStatusCode.OK)
if (webResponse.StatusCode != HttpStatusCode.OK)
{
throw new CommunicationException(string.Format(
"Posting request {0} failed. Expected status code is {1} but was {2}.",
displayCommandFunc(),
HttpStatusCode.OK,
l_oResponse.StatusCode));
webResponse.StatusCode));
}
string response = string.Empty;
// Get the request stream.
using (Stream l_oDataStream = l_oResponse.GetResponseStream())
using (Stream l_oDataStream = webResponse.GetResponseStream())
using (StreamReader l_oReader = new StreamReader(l_oDataStream))
{
// Read the content.
@ -819,7 +818,7 @@ namespace TINK.Repository
Console.WriteLine(response);
// Clean up the streams.
l_oResponse.Close();
webResponse.Close();
}
Log.ForContext<CopriCallsHttps>().Verbose("Post command {DisplayCommand} to host {URL} received {ResponseText:j}.", displayCommandFunc(), uRL, response);

View file

@ -1,7 +1,5 @@

using System.Runtime.Serialization;
namespace TINK.Repository.Response
{
/// <summary>

View file

@ -25,6 +25,26 @@ namespace TINK.Repository.Response
[DataMember]
public MapSpan init_map { get; private set; }
/// <summary> Url of page holding agb info. </summary>
[DataMember]
public string agb_html { get; private set; }
/// <summary> Url of page holding instructions how to rent bikes. </summary>
[DataMember]
public string bike_info_html { get; private set; }
/// <summary> Url of page holding privacy info. </summary>
[DataMember]
public string privacy_html { get; private set; }
/// <summary> Url of page holding impress info. </summary>
[DataMember]
public string impress_html { get; private set; }
/// <summary> Url of page holding tariff info. </summary>
[DataMember]
public string tariff_info_html { get; private set; }
/// <summary> Textual description of response. </summary>
public new string ToString()
{

View file

@ -1,4 +1,5 @@
using System;
using TINK.Model;
using TINK.Model.Map;
namespace TINK.Services.CopriApi
@ -7,16 +8,18 @@ namespace TINK.Services.CopriApi
public class GeneralData
{
/// <summary> Constructs an empty general data object. </summary>
public GeneralData() : this(null, null, null) { }
public GeneralData() : this(null, null, null, null) { }
public GeneralData(
IMapSpan initialMapSpan,
string merachantMessage,
Version apiVersion)
Version apiVersion,
ResourceUrls resourceUrls)
{
InitialMapSpan = initialMapSpan ?? MapSpanFactory.Create();
MerchantMessage = merachantMessage ?? string.Empty;
ApiVersion = apiVersion ?? new Version(0, 0);
ResourceUrls = resourceUrls ?? new ResourceUrls();
}
/// <summary> Initial map display area.</summary>
@ -27,5 +30,8 @@ namespace TINK.Services.CopriApi
/// <summary> Version of COPRI api. 0.0 if version is not set</summary>
public Version ApiVersion { get; private set; }
/// <summary> Resources (html pages) to be displayed provided by COPRI.</summary>
public IResourceUrls ResourceUrls { get; private set; }
}
}

View file

@ -22,24 +22,5 @@ namespace TINK.Services.CopriApi.ServerUris
? "tinkapp" /* resource tree is more complex for TINK/ konrad*/
: "app";
/// <summary> Get folder name for a static site depending on host name. </summary>
/// <param name="hostName">Host name.</param>
/// <returns>Folder name.</returns>
public static string GetSiteFolderName(this string hostName)
=> hostName.GetIsCopri()
? "tinkapp" /* resource tree is more complex for TINK/ konrad*/
: SHAREE_SILTEFOLDERNAME;
/// <summary> Get the agb resource name name depending on host name. </summary>
/// <param name="hostName">Host name.</param>
/// <returns>AGB resource.</returns>
public static string GetAGBResource(this string hostName)
=> $"{hostName.GetSiteFolderName()}/{(hostName.GetIsCopri()? "konrad-TINK-AGB" : "agb.html")}";
/// <summary> Get the privacy resource name name depending on host name. </summary>
/// <param name="hostName">Host name.</param>
/// <returns>Privacy resource.</returns>
public static string GetPrivacyResource(this string hostName)
=> $"{hostName.GetSiteFolderName()}/{(hostName.GetIsCopri() ? "Datenschutz" : "privacy.html")}";
}
}

View file

@ -53,7 +53,6 @@
<Folder Include="Services\Permissions\Essentials\" />
<Folder Include="Services\Permissions\Plugin\" />
<Folder Include="ViewModel\Info\BikeInfo\" />
<Folder Include="ViewModel\FeesAndBikes\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LockItBLE\LockItBLE.csproj" />

View file

@ -301,122 +301,12 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Close lock
Log.ForContext<ReservedOpen>().Information("User selected disposable bike {bike} in order to manage sound/ alarm settings.", SelectedBike);
// Check current state.
BikesViewModel.ActionText = "Schlosseinstellung abfragen...";
bool isAlarmOff;
try
{
isAlarmOff = await LockService[SelectedBike.LockInfo.Id].GetIsAlarmOffAsync();
}
catch (OutOfReachException exception)
{
Log.ForContext<ReservedOpen>().Debug("Can not get lock alarm settings. {Exception}", exception);
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
"Fehler beim Abfragen der Alarmeinstellungen!",
"Schloss kann erst geschlossen werden, wenn Rad in der Nähe ist.",
"OK");
return this;
}
catch (Exception exception)
{
Log.ForContext<ReservedOpen>().Error("Can not get lock alarm settings. {Exception}", exception);
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
"Fehler beim Abfragen der Alarmeinstellungen!",
exception.Message,
"OK");
return this;
}
if (isAlarmOff)
{
// Switch on sound.
BikesViewModel.ActionText = "Anschalten von Sounds...";
try
{
await LockService[SelectedBike.LockInfo.Id].SetSoundAsync(SoundSettings.AllOn);
}
catch (OutOfReachException exception)
{
Log.ForContext<ReservedOpen>().Debug("Can not turn on sounds. {Exception}", exception);
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
"Fehler beim Anschalten der Sounds!",
"Sounds können erst angeschalten werden, wenn Rad in der Nähe ist.",
"OK");
return this;
}
catch (Exception exception)
{
Log.ForContext<ReservedOpen>().Error("Can not turn on sounds. {Exception}", exception);
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
"Fehler beim Anschalten der Sounds!",
exception.Message,
"OK");
return this;
}
// Switch off alarm.
BikesViewModel.ActionText = "Anschalten von Alarm...";
try
{
await LockService[SelectedBike.LockInfo.Id].SetIsAlarmOffAsync(true);
}
catch (OutOfReachException exception)
{
Log.ForContext<ReservedOpen>().Debug("Can not turn on alarm settings. {Exception}", exception);
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
"Fehler beim Anschalten des Alarms!",
"Alarm kann erst angeschalten werden, wenn Rad in der Nähe ist.",
"OK");
return this;
}
catch (Exception exception)
{
Log.ForContext<ReservedOpen>().Error("Can not turn on alarm. {Exception}", exception);
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
"Fehler beim Anschalten des Alarms!",
exception.Message,
"OK");
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
finally
{
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
}
await ViewService.DisplayAlert(
"Hinweis",
"Alarm und Sounds erfolgreich aktiviert",
"OK");
return this;
}
// Alarm and sounds are on, toggle to off.
// Switch off sound.
BikesViewModel.ActionText = "Abschalten der Sounds...";
try
{
await LockService[SelectedBike.LockInfo.Id].SetSoundAsync(SoundSettings.AllOff);
await LockService[SelectedBike.LockInfo.Id].SetSoundAsync(SoundSettings.Warn);
}
catch (OutOfReachException exception)
{
@ -443,11 +333,42 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
return this;
}
// Lower alarm sensivity.
BikesViewModel.ActionText = "Setzen Alarm-Einstellungen...";
try
{
await LockService[SelectedBike.LockInfo.Id].SetAlarmSettingsAsync(AlarmSettings.SmallSensivitySilent);
}
catch (OutOfReachException exception)
{
Log.ForContext<ReservedOpen>().Debug("Can not set alarm settings. {Exception}", exception);
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
"Fehler beim Setzen der Alarm-Einstellungen!",
"Alarm kann erst eingestellt werden, wenn Rad in der Nähe ist.",
"OK");
return this;
}
catch (Exception exception)
{
Log.ForContext<ReservedOpen>().Error("Can not set alarm settings. {Exception}", exception);
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
"Fehler beim Setzen der Alarms-Einstellungen!",
exception.Message,
"OK");
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Switch off alarm.
BikesViewModel.ActionText = "Abschalten von Alarm...";
try
{
await LockService[SelectedBike.LockInfo.Id].SetIsAlarmOffAsync(false);
await LockService[SelectedBike.LockInfo.Id].SetIsAlarmOffAsync(true);
}
catch (OutOfReachException exception)
{

View file

@ -331,6 +331,7 @@ namespace TINK.ViewModel.Contact
var resultStationsAndBikes = await TinkApp.GetConnector(IsConnected).Query.GetBikesAndStationsAsync();
TinkApp.Stations = resultStationsAndBikes.Response.StationsAll;
TinkApp.ResourceUrls = resultStationsAndBikes.GeneralData.ResourceUrls;
if (Pins.Count > 0 && Pins.Count != resultStationsAndBikes.Response.StationsAll.Count)
{

View file

@ -1,34 +1,35 @@
using System.ComponentModel;
using Xamarin.Forms;
using TINK.Services.CopriApi.ServerUris;
using System;
using System.Threading.Tasks;
namespace TINK.ViewModel.Contact
{
public class HelpContactViewModel : INotifyPropertyChanged
public class FeesAndBikesPageViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
/// <summary> Gets the platfrom specific prefix. </summary>
private Func<string, string> ResourceProvider { get; set; }
/// <summary> Holds value wether site caching is on or off.</summary>
bool IsSiteCachingOn { get; }
private string FeesResourcePath { get; }
private string BikesResourcePath { get; }
/// <summary> Constructs view model.</summary>
/// <param name="isSiteCachingOn">Set of user permissions</param>
/// <param name="resourceProvider">Delegate to get an an embedded html ressource. Used as fallback if download from web page does not work and cache is empty.</param>
public HelpContactViewModel(
public FeesAndBikesPageViewModel(
string hostName,
bool isSiteCachingOn,
Func<string, string> resourceProvider)
string feesResourcePath,
string bikesResourcePath,
bool isSiteCachingOn)
{
HostName = hostName;
FeesResourcePath = feesResourcePath;
BikesResourcePath = bikesResourcePath;
IsSiteCachingOn = isSiteCachingOn;
ResourceProvider = resourceProvider
?? throw new ArgumentException($"Can not instantiate {typeof(HelpContactViewModel)}-object. No ressource provider availalbe.");
}
/// <summary> Holds the name of the host.</summary>
@ -39,16 +40,16 @@ namespace TINK.ViewModel.Contact
{
RentBikeText = new HtmlWebViewSource
{
Html = HostName.GetIsCopri()
? ResourceProvider("HtmlResouces.V02.InfoRentBike.html")
: await ViewModelHelper.GetSource($"https://{HostName}/{CopriHelper.SHAREE_SILTEFOLDERNAME}/tariff_info_1.html", IsSiteCachingOn)
Html = !string.IsNullOrEmpty(FeesResourcePath)
? await ViewModelHelper.GetSource($"https://{HostName}/{FeesResourcePath}" /* "site/tariff_info_1.html" */, IsSiteCachingOn)
: await Task.FromResult(ViewModelHelper.FromBody("No fees resource available. Resource path is null or empty."))
};
TypesOfBikesText = new HtmlWebViewSource
{
Html = HostName.GetIsCopri()
? ResourceProvider("HtmlResouces.V02.InfoTypesOfBikes.html")
: await ViewModelHelper.GetSource($"https://{HostName}/{CopriHelper.SHAREE_SILTEFOLDERNAME}/bike_info.html", IsSiteCachingOn)
Html = !string.IsNullOrEmpty(BikesResourcePath)
? await ViewModelHelper.GetSource($"https://{HostName}/{BikesResourcePath}" /*"site/bike_info.html"*/, IsSiteCachingOn)
: await Task.FromResult(ViewModelHelper.FromBody("No bikes instruction resource available. Resource path is null or empty."))
};
}

View file

@ -9,7 +9,7 @@ using Xamarin.Forms;
namespace TINK.ViewModel.Info
{
/// <summary> Manges the tabbed info page. </summary>
public class InfoViewModel : INotifyPropertyChanged
public class InfoPageViewModel : INotifyPropertyChanged
{
/// <summary> Fired whenever a property changed.</summary>
public event PropertyChangedEventHandler PropertyChanged;
@ -20,42 +20,56 @@ namespace TINK.ViewModel.Info
/// <summary> Holds value wether site caching is on or off.</summary>
bool IsSiteCachingOn { get; }
private string AgbResourcePath { get; }
private string PrivacyResourcePath { get; }
private string ImpressResourcePath { get; }
/// <summary> Constructs Info view model</summary>
/// <param name="isSiteCachingOn">Holds value wether site caching is on or off.</param>
/// <param name="resourceProvider">Delegate to get an an embedded html ressource. Used as fallback if download from web page does not work and cache is empty.</param>
public InfoViewModel(
public InfoPageViewModel(
string hostName,
bool isSiteCachingOn,
string agbResourcePath,
string privacyResourcePath,
string impressResourcePath,
bool isSiteCachingOn,
Func<string, string> resourceProvider)
{
HostName = hostName;
AgbResourcePath = agbResourcePath;
PrivacyResourcePath = privacyResourcePath;
ImpressResourcePath = impressResourcePath;
IsSiteCachingOn = isSiteCachingOn;
InfoAgb = new HtmlWebViewSource { Html = "<html>Loading...</html>" };
ResourceProvider = resourceProvider
?? throw new ArgumentException($"Can not instantiate {typeof(InfoViewModel)}-object. No ressource provider availalbe.");
?? throw new ArgumentException($"Can not instantiate {typeof(InfoPageViewModel)}-object. No ressource provider availalbe.");
}
/// <summary> Called when page is shown. </summary>
public async void OnAppearing()
{
InfoAgb = await GetAgb(HostName, IsSiteCachingOn, ResourceProvider);
InfoAgb = await GetAgb(HostName, AgbResourcePath, IsSiteCachingOn);
InfoPrivacy = new HtmlWebViewSource
{
Html = await ViewModelHelper.GetSource(
$"https://{HostName}/{HostName.GetPrivacyResource()}",
IsSiteCachingOn,
HostName.GetIsCopri() ? () => ResourceProvider("HtmlResouces.V02.InfoDatenschutz.html") : (Func<string>) null) /* offline resources only available for TINK */,
Html = !string.IsNullOrEmpty(PrivacyResourcePath)
? await ViewModelHelper.GetSource(
$"https://{HostName}/{PrivacyResourcePath}", // "site/privacy.html"
IsSiteCachingOn)
: await Task.FromResult(ViewModelHelper.FromBody("No privacy resource available. Resource path is null or empty."))
};
InfoImpressum = new HtmlWebViewSource
{
Html = HostName.GetIsCopri()
? ResourceProvider("HtmlResouces.V02.InfoImpressum.html")
: await ViewModelHelper.GetSource($"https://{HostName}/{CopriHelper.SHAREE_SILTEFOLDERNAME}/impress.html", IsSiteCachingOn)
Html = !string.IsNullOrEmpty(ImpressResourcePath)
? await ViewModelHelper.GetSource(
$"https://{HostName}/{ImpressResourcePath}", // "site/impress.html"
IsSiteCachingOn)
: await Task.FromResult(ViewModelHelper.FromBody("No impress resource available. Resource path is null or empty."))
};
}
@ -64,15 +78,16 @@ namespace TINK.ViewModel.Info
/// <returns> AGBs</returns>
public static async Task<HtmlWebViewSource> GetAgb(
string hostName,
bool isSiteCachingOn,
Func<string, string> resourceProvider)
string agbResourcePath,
bool isSiteCachingOn)
{
return new HtmlWebViewSource
{
Html = await ViewModelHelper.GetSource(
$"https://{hostName}/{hostName.GetAGBResource()}",
isSiteCachingOn,
hostName.GetIsCopri() ? () => resourceProvider("HtmlResouces.V02.InfoAGB.html") : (Func<string>) null) /* offline resources only available for TINK */,
Html = !string.IsNullOrEmpty(agbResourcePath)
? await ViewModelHelper.GetSource(
$"https://{hostName}/{agbResourcePath}", // "agb.html"
isSiteCachingOn)
: await Task.FromResult(ViewModelHelper.FromBody("No terms resource available. Resource path is null or empty."))
};
}

View file

@ -325,6 +325,7 @@ namespace TINK.ViewModel.Map
var resultStationsAndBikes = await TinkApp.GetConnector(IsConnected).Query.GetBikesAndStationsAsync();
TinkApp.Stations = resultStationsAndBikes.Response.StationsAll;
TinkApp.ResourceUrls = resultStationsAndBikes.GeneralData.ResourceUrls;
if (!string.IsNullOrEmpty(resultStationsAndBikes?.GeneralData?.MerchantMessage)
&& !WasMerchantMessageAlreadyShown)

View file

@ -286,8 +286,9 @@ namespace TINK.ViewModel
break;
}
return htmlContent ?? string.Format("<!DOCTYPE html><html lang=\"de\"><body>An error occurred loading html- ressource.</body>");
return htmlContent ?? FromBody("An error occurred loading html- ressource.");
}
public static string FromBody(string message) => $"<!DOCTYPE html><html lang=\"de\"><head><title>Error Information</title></head><body>{message}</body></html>";
}
}

View file

@ -61,7 +61,7 @@ namespace TINK.ViewModel.WhatsNew.Agb
/// <summary> Called when page is shown. </summary>
public async Task OnAppearing()
{
InfoAgb = await InfoViewModel.GetAgb(HostName, IsSiteCachingOn, ResourceProvider);
InfoAgb = await InfoPageViewModel.GetAgb(HostName, "agbResourcePath", IsSiteCachingOn);
}
/// <summary> User clicks OK button.</summary>