Version 3.0.312.

This commit is contained in:
Oliver Hauff 2022-06-17 14:17:58 +02:00
parent 310ea37085
commit fd0e63cf10
94 changed files with 3189 additions and 6352 deletions

View file

@ -30,7 +30,7 @@ namespace TINK.Model.Bike.BC
string description = null,
string stationId = null,
Uri operatorUri = null,
TariffDescription tariffDescription = null)
RentalDescription tariffDescription = null)
{
Bike = new Bike(id, lockModel, wheelType, typeOfBike, description);
@ -69,7 +69,7 @@ namespace TINK.Model.Bike.BC
LockModel lockModel,
string stationId,
Uri operatorUri = null,
TariffDescription tariffDescription = null,
RentalDescription tariffDescription = null,
bool? isDemo = DEFAULTVALUEISDEMO,
IEnumerable<string> group = null,
WheelType? wheelType = null,
@ -111,7 +111,7 @@ namespace TINK.Model.Bike.BC
string description,
string stationId,
Uri operatorUri,
TariffDescription tariffDescription,
RentalDescription tariffDescription,
DateTime requestedAt,
string mailAddress,
string code,
@ -157,7 +157,7 @@ namespace TINK.Model.Bike.BC
string description,
string currentStationId,
Uri operatorUri,
TariffDescription tariffDescription,
RentalDescription tariffDescription,
DateTime bookedAt,
string mailAddress,
string code) : this(
@ -190,7 +190,7 @@ namespace TINK.Model.Bike.BC
public string StationId { get; }
/// <summary> Holds description about the tarif. </summary>
public TariffDescription TariffDescription { get; }
public RentalDescription TariffDescription { get; }
/// Holds the rent state of the bike.

View file

@ -39,7 +39,7 @@ namespace TINK.Model.Bike.BC
string stationId = null,
string stationName = null,
Uri operatorUri = null,
TariffDescription tariffDescription = null,
RentalDescription tariffDescription = null,
Func<DateTime> dateTimeProvider = null,
IStateInfo stateInfo = null)
{
@ -82,7 +82,7 @@ namespace TINK.Model.Bike.BC
/// <summary> Holds description about the tarif. </summary>
[DataMember]
public TariffDescription TariffDescription { get; private set; }
public RentalDescription TariffDescription { get; private set; }
/// <summary>
/// Holds the rent state of the bike.

View file

@ -48,7 +48,7 @@ namespace TINK.Model.Bike.BC
Uri OperatorUri { get; }
/// <summary> Holds description about the tarif. </summary>
TariffDescription TariffDescription { get; }
RentalDescription TariffDescription { get; }
/// <summary>
/// Holds the rent state of the bike.

View file

@ -23,7 +23,7 @@ namespace TINK.Model.Bike.BluetoothLock
Guid lockGuid,
string currentStationId,
Uri operatorUri = null,
TariffDescription tariffDescription = null,
RentalDescription tariffDescription = null,
bool? isDemo = DEFAULTVALUEISDEMO,
IEnumerable<string> group = null,
WheelType? wheelType = null,
@ -69,7 +69,7 @@ namespace TINK.Model.Bike.BluetoothLock
string mailAddress,
string currentStationId,
Uri operatorUri,
TariffDescription tariffDescription,
RentalDescription tariffDescription,
Func<DateTime> dateTimeProvider,
bool? isDemo = DEFAULTVALUEISDEMO,
IEnumerable<string> group = null,
@ -118,7 +118,7 @@ namespace TINK.Model.Bike.BluetoothLock
string mailAddress,
string currentStationId,
Uri operatorUri,
TariffDescription tariffDescription = null,
RentalDescription tariffDescription = null,
bool? isDemo = DEFAULTVALUEISDEMO,
IEnumerable<string> group = null,
WheelType? wheelType = null,

View file

@ -22,7 +22,7 @@ namespace TINK.Model.Bike.CopriLock
string currentStationId,
LockInfo lockInfo,
Uri operatorUri = null,
TariffDescription tariffDescription = null,
RentalDescription tariffDescription = null,
bool? isDemo = DEFAULTVALUEISDEMO,
IEnumerable<string> group = null,
WheelType? wheelType = null,
@ -62,7 +62,7 @@ namespace TINK.Model.Bike.CopriLock
string currentStationId,
LockInfo lockInfo,
Uri operatorUri,
TariffDescription tariffDescription,
RentalDescription tariffDescription,
Func<DateTime> dateTimeProvider,
bool? isDemo = DEFAULTVALUEISDEMO,
IEnumerable<string> group = null,
@ -105,7 +105,7 @@ namespace TINK.Model.Bike.CopriLock
string currentStationId,
LockInfo lockInfo,
Uri operatorUri,
TariffDescription tariffDescription = null,
RentalDescription tariffDescription = null,
bool? isDemo = DEFAULTVALUEISDEMO,
IEnumerable<string> group = null,
WheelType? wheelType = null,

View file

@ -0,0 +1,47 @@
using System.Collections.Generic;
namespace TINK.Model.Bikes.Bike
{
/// <summary>
/// Successor of TarifDescription- object.
/// Manages tariff- and rental info.
/// </summary>
public class RentalDescription
{
/// <summary>
/// The different elements of a tariff (example: "Max Gebühr", )
/// </summary>
public class TariffElement
{
/// <summary>
/// Describes the tariff element. To be displayed to user (example of elements: "Gratis Mietzeit", "Mietgebühr", "Max Gebühr").
/// </summary>
public string Description { get; set; } = string.Empty;
/// <summary>
/// Holds the tariff element value. To be displayed to user (example: "9.00 € / Tag").
/// </summary>
public string Value { get; set; } = string.Empty;
}
public class InfoElement
{
public string Key { get; set; }
public string Value { get; set; }
}
/// <summary>
/// Name of the tariff.
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// Number of the tariff.
/// </summary>
public int? Id { get; set; }
public Dictionary<string, TariffElement> TariffEntries { get; set; } = new Dictionary<string, TariffElement>();
public Dictionary<string, InfoElement> InfoEntries { get; set; } = new Dictionary<string, InfoElement>();
}
}

View file

@ -18,6 +18,7 @@ using Xamarin.Forms;
using System.Linq;
using TINK.Model.MiniSurvey;
using TINK.Services.CopriApi;
using TINK.MultilingualResources;
namespace TINK.Model.Connector
{
@ -347,7 +348,7 @@ namespace TINK.Model.Connector
new Bikes.Bike.CopriLock.LockInfo.Builder { State = bikeInfo.GetCopriLockingState()}.Build(),
bikeInfo.GetOperatorUri(),
#if !NOTARIFFDESCRIPTION
Create(bikeInfo.tariff_description),
bikeInfo.rental_description != null ? Create(bikeInfo.rental_description) : Create(bikeInfo.tariff_description),
#else
Create((TINK.Repository.Response.TariffDescription) null),
#endif
@ -365,7 +366,7 @@ namespace TINK.Model.Connector
bikeInfo.station,
bikeInfo.GetOperatorUri(),
#if !NOTARIFFDESCRIPTION
Create(bikeInfo.tariff_description),
bikeInfo.rental_description != null ? Create(bikeInfo.rental_description) : Create(bikeInfo.tariff_description),
#else
Create((TINK.Repository.Response.TariffDescription)null),
#endif
@ -438,9 +439,9 @@ namespace TINK.Model.Connector
bikeInfo.station,
bikeInfo.GetOperatorUri(),
#if !NOTARIFFDESCRIPTION
Create(bikeInfo.tariff_description),
bikeInfo.rental_description != null ? Create(bikeInfo.rental_description) : Create(bikeInfo.tariff_description),
#else
Create((TINK.Repository.Response.TariffDescription)null),
Create((TINK.Repository.Response.TariffDescription)null),
#endif
dateTimeProvider,
bikeInfo.GetIsDemo(),
@ -458,7 +459,7 @@ namespace TINK.Model.Connector
new Bikes.Bike.CopriLock.LockInfo.Builder { State = bikeInfo.GetCopriLockingState() }.Build(),
bikeInfo.GetOperatorUri(),
#if !NOTARIFFDESCRIPTION
Create(bikeInfo.tariff_description),
bikeInfo.rental_description != null ? Create(bikeInfo.rental_description) : Create(bikeInfo.tariff_description),
#else
Create((TINK.Repository.Response.TariffDescription)null),
#endif
@ -497,11 +498,11 @@ namespace TINK.Model.Connector
bikeInfo.station,
bikeInfo.GetOperatorUri(),
#if !NOTARIFFDESCRIPTION
Create(bikeInfo.tariff_description),
bikeInfo.rental_description != null ? Create(bikeInfo.rental_description) : Create(bikeInfo.tariff_description),
#else
Create((TINK.Repository.Response.TariffDescription)null),
Create((TINK.Repository.Response.TariffDescription)null),
#endif
bikeInfo.GetIsDemo(),
bikeInfo.GetIsDemo(),
bikeInfo.GetGroup(),
bikeInfo.GetWheelType(),
bikeInfo.GetTypeOfBike(),
@ -519,11 +520,11 @@ namespace TINK.Model.Connector
bikeInfo.station,
bikeInfo.GetOperatorUri(),
#if !NOTARIFFDESCRIPTION
Create(bikeInfo.tariff_description),
bikeInfo.rental_description != null ? Create(bikeInfo.rental_description) : Create(bikeInfo.tariff_description),
#else
Create((TINK.Repository.Response.TariffDescription)null),
Create((TINK.Repository.Response.TariffDescription)null),
#endif
bikeInfo.GetFrom(),
bikeInfo.GetFrom(),
mailAddress,
bikeInfo.timeCode);
default:
@ -535,9 +536,9 @@ namespace TINK.Model.Connector
new Bikes.Bike.CopriLock.LockInfo.Builder { State = bikeInfo.GetCopriLockingState() }.Build(),
bikeInfo.GetOperatorUri(),
#if !NOTARIFFDESCRIPTION
Create(bikeInfo.tariff_description),
bikeInfo.rental_description != null ? Create(bikeInfo.rental_description) : Create(bikeInfo.tariff_description),
#else
Create((TINK.Repository.Response.TariffDescription)null),
Create((TINK.Repository.Response.TariffDescription)null),
#endif
bikeInfo.GetIsDemo(),
bikeInfo.GetGroup(),
@ -559,28 +560,130 @@ namespace TINK.Model.Connector
}
}
public static Bikes.Bike.TariffDescription Create(this TariffDescription tariffDesciption)
/// <summary>
/// Creates rental description object from JSON- tarif description object.
/// </summary>
/// <param name="tariffDesciption">Source JSON object.</param>
/// <returns>Tariff description object.</returns>
public static Bikes.Bike.RentalDescription Create(this TariffDescription tariffDesciption)
{
return new Bikes.Bike.TariffDescription
var bike = new Bikes.Bike.RentalDescription
{
Name = tariffDesciption?.name,
#if USCSHARP9
Number = int.TryParse(tariffDesciption?.number, out int number) ? number : null,
#else
Number = int.TryParse(tariffDesciption?.number, out int number) ? number : (int?)null,
Id = int.TryParse(tariffDesciption?.number, out int number) ? number : (int?)null,
#endif
FreeTimePerSession = double.TryParse(tariffDesciption?.free_hours, NumberStyles.Any, CultureInfo.InvariantCulture, out double freeHours) ? TimeSpan.FromHours(freeHours) : TimeSpan.Zero,
FeeEuroPerHour = double.TryParse(tariffDesciption?.eur_per_hour, NumberStyles.Any, CultureInfo.InvariantCulture, out double euroPerHour) ? euroPerHour : double.NaN,
AboEuroPerMonth = double.TryParse(tariffDesciption?.abo_eur_per_month, NumberStyles.Any, CultureInfo.InvariantCulture, out double aboEuroPerMonth) ? aboEuroPerMonth : double.NaN,
MaxFeeEuroPerDay = double.TryParse(tariffDesciption?.max_eur_per_day, NumberStyles.Any, CultureInfo.InvariantCulture, out double maxEuroPerDay) ? maxEuroPerDay : double.NaN,
OperatorAgb = tariffDesciption?.operator_agb,
TrackingInfo = tariffDesciption?.track_info
};
if (!string.IsNullOrEmpty(tariffDesciption?.free_hours)
&& double.TryParse(tariffDesciption?.free_hours, NumberStyles.Any, CultureInfo.InvariantCulture, out double freeHours))
{
// Free time. Unit hours,format floating point number.
bike.TariffEntries.Add("1", new Bikes.Bike.RentalDescription.TariffElement {
Description = AppResources.MessageBikesManagementTariffDescriptionFreeTimePerSession,
Value =string.Format("{0} {1}", freeHours.ToString("0.00"), AppResources.MessageBikesManagementTariffDescriptionHour)
});
}
if (!string.IsNullOrEmpty(tariffDesciption?.eur_per_hour)
&& double.TryParse(tariffDesciption?.eur_per_hour, NumberStyles.Any, CultureInfo.InvariantCulture, out double euroPerHour))
{
// Euro per hour. Format floating point.
bike.TariffEntries.Add("2", new Bikes.Bike.RentalDescription.TariffElement {
Description = AppResources.MessageBikesManagementTariffDescriptionFeeEuroPerHour,
Value = string.Format("{0} {1}", euroPerHour.ToString("0.00"), AppResources.MessageBikesManagementTariffDescriptionEuroPerHour)
});
}
if (!string.IsNullOrEmpty(tariffDesciption?.max_eur_per_day)
&& double.TryParse(tariffDesciption.max_eur_per_day, NumberStyles.Any, CultureInfo.InvariantCulture, out double maxEuroPerDay))
{
// Max euro per day. Format floating point.
bike.TariffEntries.Add("3", new Bikes.Bike.RentalDescription.TariffElement {
Description = AppResources.MessageBikesManagementTariffDescriptionMaxFeeEuroPerDay,
Value = string.Format("{0} {1}", maxEuroPerDay.ToString("0.00"), AppResources.MessageBikesManagementMaxFeeEuroPerDay)
});
}
if (!string.IsNullOrEmpty(tariffDesciption?.abo_eur_per_month)
&& double.TryParse(tariffDesciption.abo_eur_per_month, NumberStyles.Any, CultureInfo.InvariantCulture, out double aboEuroPerMonth))
{
// Abo per month
bike.TariffEntries.Add("4", new Bikes.Bike.RentalDescription.TariffElement {
Description = AppResources.MessageBikesManagementTariffDescriptionAboEuroPerMonth,
Value = string.Format("{0} {1}", aboEuroPerMonth.ToString("0.00"), AppResources.MessageBikesManagementTariffDescriptionEuroPerMonth)
});
}
if (!string.IsNullOrEmpty(tariffDesciption?.operator_agb ?? string.Empty))
{
bike.InfoEntries.Add("1", new Bikes.Bike.RentalDescription.InfoElement { Key = "AGB", Value = tariffDesciption.operator_agb });
}
return bike;
}
/// <summary>
/// Creates rental description object from JSON- tarif description object.
/// </summary>
/// <param name="rentalDesciption">Source JSON object.</param>
/// <returns>Tariff description object.</returns>
public static Bikes.Bike.RentalDescription Create(this RentalDescription rentalDesciption)
{
Bikes.Bike.RentalDescription.TariffElement CreateTarifEntry(string[] elementValue)
{
return new Bikes.Bike.RentalDescription.TariffElement
{
Description = elementValue != null && elementValue.Length > 0 ? elementValue[0] : string.Empty,
Value = elementValue != null && elementValue.Length > 1 ? elementValue[1] : string.Empty,
};
}
Bikes.Bike.RentalDescription.InfoElement CreateInfoElement(string[] elementValue)
{
return new Bikes.Bike.RentalDescription.InfoElement
{
Key = elementValue != null && elementValue.Length > 0 ? elementValue[0] : string.Empty,
Value = elementValue != null && elementValue.Length > 1 ? elementValue[1] : string.Empty,
};
}
// Read tariff elements.
var tarifEntries = rentalDesciption?.tarif_elements != null
? rentalDesciption.tarif_elements.Select(x => new
{
Key = x.Key,
Value = CreateTarifEntry(x.Value)
}).ToLookup(x => x.Key, x => x.Value).ToDictionary(x => x.Key, x => x.First())
: new Dictionary<string, Bikes.Bike.RentalDescription.TariffElement>();
// Read info elements.
var InfoEntries = rentalDesciption?.rental_info != null
? rentalDesciption.rental_info.Select(x => new
{
Key = x.Key,
Value = CreateInfoElement(x.Value)
}).ToLookup(x => x.Key, x => x.Value).ToDictionary(x => x.Key, x => x.First())
: new Dictionary<string, Bikes.Bike.RentalDescription.InfoElement>();
var bike = new Bikes.Bike.RentalDescription
{
Name = rentalDesciption?.name ?? string.Empty,
Id = int.TryParse(rentalDesciption?.id ?? string.Empty, out int number) ? number : (int?)null,
TariffEntries = tarifEntries,
InfoEntries = InfoEntries
};
return bike;
}
/// <summary> Creates a booking finished object from response.</summary>
/// <param name="response">Response to create survey object from.</param>
public static BookingFinishedModel Create(this DoReturnResponse response)
public static BookingFinishedModel Create(this DoReturnResponse response)
{
var bookingFinished = new BookingFinishedModel
{

View file

@ -526,7 +526,19 @@ namespace TINK.Model
{
new Version(3, 0, 299),
AppResources.ChangeLog3_0_299
}
},
{
new Version(3, 0, 300),
AppResources.ChangeLog3_0_231 // Minor improvements.
},
{
new Version(3, 0, 311),
AppResources.ChangeLog3_0_301
},
{
new Version(3, 0, 312),
AppResources.ChangeLog3_0_312
},
};
/// <summary> Manges the whats new information.</summary>

View file

@ -151,7 +151,7 @@ namespace TINK.MultilingualResources {
}
/// <summary>
/// Looks up a localized string similar to Open lock &amp; continue renting.
/// Looks up a localized string similar to Open lock.
/// </summary>
public static string ActionOpenAndPause {
get {
@ -1063,6 +1063,28 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Extended tariffs supported..
/// </summary>
public static string ChangeLog3_0_301 {
get {
return ResourceManager.GetString("ChangeLog3_0_301", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to For Lastenrad Bayern users
///- missing titles added to several pages
///- new station markers availabe
///- layout of Wort-Bild-Marke amended
///.
/// </summary>
public static string ChangeLog3_0_312 {
get {
return ResourceManager.GetString("ChangeLog3_0_312", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Lock of rented bike cannot be be connected right now..
/// </summary>
@ -1587,7 +1609,7 @@ namespace TINK.MultilingualResources {
}
/// <summary>
/// Looks up a localized string similar to Please open a bike station page to to contact the bike sharing operator..
/// Looks up a localized string similar to Contact operator?.
/// </summary>
public static string MarkingContactNoStationInfoAvailableNoButton {
get {
@ -1993,6 +2015,15 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to €/month.
/// </summary>
public static string MessageBikesManagementTariffDescriptionEuroPerMonth {
get {
return ResourceManager.GetString("MessageBikesManagementTariffDescriptionEuroPerMonth", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Rental fees.
/// </summary>

View file

@ -31,7 +31,7 @@
<value>Schloss öffnen &amp; Rad mieten</value>
</data>
<data name="ActionOpenAndPause" xml:space="preserve">
<value>Schloss öffnen &amp; Miete fortsetzen</value>
<value>Schloss öffnen</value>
</data>
<data name="ActionRequest" xml:space="preserve">
<value>Rad reservieren</value>
@ -612,7 +612,7 @@ Layout Anzeige Radnamen und nummern verbessert.</value>
<value>Bitte Anmelden um Fahrräder zu reservieren! &lt;font color="blue"&gt;&lt;u&gt;Hier&lt;/u&gt;&lt;/font&gt; tippen, um auf Anmeldeseite zu wechseln.</value>
</data>
<data name="MarkingContactNoStationInfoAvailableNoButton" xml:space="preserve">
<value>Bitte eine Fahrradstationsseite öffen, um den Betreiber zu kontaktieren.</value>
<value>Betreiber kontaktieren?</value>
</data>
<data name="ChangeLog3_0_241" xml:space="preserve">
<value>Auf der Kontaktseite werden Kontaktinformationen betreiberspezifisch angezeigt.</value>
@ -901,6 +901,16 @@ Kleinere Fehlerbehebungen.
<value>Neue Wort-Bild-Marke in das Menü der Lastenrad Bayern App integriert.</value>
</data>
<data name="ChangeLog3_0_299" xml:space="preserve">
<value>Fehlerbehebung: Bluetooth-Schlossanstereuerung funktioniert wieder unter Android 12.</value>
<value>Fehlerbehebung: Bluetooth-Schlossansteuerung funktioniert wieder unter Android 12.</value>
</data>
<data name="ChangeLog3_0_301" xml:space="preserve">
<value>Unterstützung erweiterte Tarife.</value>
</data>
<data name="ChangeLog3_0_312" xml:space="preserve">
<value>Für Lastenrad Bayern
- fehlende Titeltexte werden angezeigt
- neue Stationsmarker verfügbar
- layout der Wort-Bild-Marke verbessert
</value>
</data>
</root>

View file

@ -136,7 +136,7 @@
<value>Open lock &amp; rent bike</value>
</data>
<data name="ActionOpenAndPause" xml:space="preserve">
<value>Open lock &amp; continue renting</value>
<value>Open lock</value>
</data>
<data name="ActionRequest" xml:space="preserve">
<value>Reserve bike</value>
@ -708,7 +708,7 @@ Layout of bike names and id display improved.</value>
<value>Find bike by id functionality added.</value>
</data>
<data name="MarkingContactNoStationInfoAvailableNoButton" xml:space="preserve">
<value>Please open a bike station page to to contact the bike sharing operator.</value>
<value>Contact operator?</value>
</data>
<data name="MarkingContactSupport" xml:space="preserve">
<value>&lt;font color="blue"&gt;&lt;u&gt;Contact&lt;/u&gt;&lt;/font&gt; {0}.</value>
@ -995,4 +995,17 @@ Minor bugfixes.</value>
<data name="ChangeLog3_0_299" xml:space="preserve">
<value>Bugfix: Bluetooth lock control works again on Android 12.</value>
</data>
<data name="ChangeLog3_0_301" xml:space="preserve">
<value>Extended tariffs supported.</value>
</data>
<data name="MessageBikesManagementTariffDescriptionEuroPerMonth" xml:space="preserve">
<value>€/month</value>
</data>
<data name="ChangeLog3_0_312" xml:space="preserve">
<value>For Lastenrad Bayern users
- missing titles added to several pages
- new station markers availabe
- layout of Wort-Bild-Marke amended
</value>
</data>
</root>

View file

@ -31,8 +31,8 @@
<target state="final">Schloss öffnen &amp; Rad mieten</target>
</trans-unit>
<trans-unit id="ActionOpenAndPause" translate="yes" xml:space="preserve">
<source>Open lock &amp; continue renting</source>
<target state="final">Schloss öffnen &amp; Miete fortsetzen</target>
<source>Open lock</source>
<target state="final">Schloss öffnen</target>
</trans-unit>
<trans-unit id="ActionRequest" translate="yes" xml:space="preserve">
<source>Reserve bike</source>
@ -818,8 +818,8 @@ Layout Anzeige Radnamen und nummern verbessert.</target>
<target state="translated">Bitte Anmelden um Fahrräder zu reservieren! <bpt id="1">&lt;font color="blue"&gt;</bpt><bpt id="2">&lt;u&gt;</bpt>Hier<ept id="2">&lt;/u&gt;</ept><ept id="1">&lt;/font&gt;</ept> tippen, um auf Anmeldeseite zu wechseln.</target>
</trans-unit>
<trans-unit id="MarkingContactNoStationInfoAvailableNoButton" translate="yes" xml:space="preserve">
<source>Please open a bike station page to to contact the bike sharing operator.</source>
<target state="translated">Bitte eine Fahrradstationsseite öffen, um den Betreiber zu kontaktieren.</target>
<source>Contact operator?</source>
<target state="translated">Betreiber kontaktieren?</target>
</trans-unit>
<trans-unit id="ChangeLog3_0_241" translate="yes" xml:space="preserve">
<source>Bike sharing system operator specific support info displayed on contact page.</source>
@ -1227,7 +1227,27 @@ Kleinere Fehlerbehebungen.
</trans-unit>
<trans-unit id="ChangeLog3_0_299" translate="yes" xml:space="preserve">
<source>Bugfix: Bluetooth lock control works again on Android 12.</source>
<target state="translated">Fehlerbehebung: Bluetooth-Schlossanstereuerung funktioniert wieder unter Android 12.</target>
<target state="translated">Fehlerbehebung: Bluetooth-Schlossansteuerung funktioniert wieder unter Android 12.</target>
</trans-unit>
<trans-unit id="ChangeLog3_0_301" translate="yes" xml:space="preserve">
<source>Extended tariffs supported.</source>
<target state="translated">Unterstützung erweiterte Tarife.</target>
</trans-unit>
<trans-unit id="MessageBikesManagementTariffDescriptionEuroPerMonth" translate="yes" xml:space="preserve">
<source>€/month</source>
<target state="new">€/month</target>
</trans-unit>
<trans-unit id="ChangeLog3_0_312" translate="yes" xml:space="preserve">
<source>For Lastenrad Bayern users
- missing titles added to several pages
- new station markers availabe
- layout of Wort-Bild-Marke amended
</source>
<target state="translated">Für Lastenrad Bayern
- fehlende Titeltexte werden angezeigt
- neue Stationsmarker verfügbar
- layout der Wort-Bild-Marke verbessert
</target>
</trans-unit>
</group>
</body>

View file

@ -60,8 +60,13 @@ namespace TINK.Repository.Response
#if !NOTARIFFDESCRIPTION
/// <summary> Holds the tariff information for a bike. </summary>
/// <remarks> This member is obsolete. Use <cref="rental_description"> instead.</cref></remarks>
[DataMember]
public TariffDescription tariff_description { get; private set; }
/// <summary> Holds the rental information for a bike. </summary>
[DataMember]
public RentalDescription rental_description { get; private set; }
#endif
/// <summary> Loading state of motor battery in % ]0..100[. </summary>
[DataMember]

View file

@ -0,0 +1,39 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace TINK.Repository.Response
{
/// <summary>
/// Successor of TarifDescription- object.
/// Manages tariff- and rental info.
/// </summary>
[DataContract]
public class RentalDescription
{
/// <summary>
/// Name of the tariff.
/// </summary>
[DataMember]
public string name { get; private set; }
/// <summary>
/// Id of the tariff.
/// </summary>
[DataMember]
public string id { get; private set; }
/// <summary> Holds tariff entires to show to user.</summary>
[DataMember]
public Dictionary<
string /* Key of tariff object for sorting purposes*/,
string[] /* Holds two Elements: first element is the description of the element (example: "Max Gebühr"), second is the value (example: "9.00 € / Tag")*/>
tarif_elements { get; private set; }
/// <summary> Holds tariff entires to show to user.</summary>
[DataMember]
public Dictionary<
string /* Key of info object for sorting purposes*/,
string[] /* Holds two Elements: first element is the key of the element (example: "Tracking"), second is the value (example: "Ich stimme der Speicherung (Tracking) meiner Fahrstrecke ....")*/>
rental_info { get; private set; }
}
}

View file

@ -349,7 +349,6 @@ namespace TINK.ViewModel.Account
m_oViewUpdateManager = new PollingUpdateTaskManager(
() => GetType().Name,
() =>
{
TinkApp.PostAction(

View file

@ -14,7 +14,6 @@ using TINK.MultilingualResources;
using TINK.View;
using TINK.Model.User;
using TINK.Model.Device;
using TINK.Model.User.Account;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{

View file

@ -1,684 +0,0 @@
using System;
using System.Threading.Tasks;
using TINK.Model.Connector;
using TINK.Model.Bike.BluetoothLock;
using TINK.Model.State;
using TINK.View;
using TINK.Services.Geolocation;
using TINK.Services.BluetoothLock;
using Serilog;
using TINK.Repository.Exception;
using TINK.Services.BluetoothLock.Exception;
using Xamarin.Essentials;
using TINK.MultilingualResources;
using TINK.Model.Bikes.Bike.BluetoothLock;
using TINK.Model.User;
using TINK.Repository.Request;
using TINK.Model.Device;
using System.Collections.Generic;
using System.Threading;
using TINK.Model;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
public class BookedOpen : Base, IRequestHandler
{
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
public BookedOpen(
IBikeInfoMutable selectedBike,
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
IGeolocation geolocation,
ILocksService lockService,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
AppResources.ActionCloseAndReturn, // Copri button text: "Schloss schließen & Miete beenden"
true, // Show button to allow user to return bike.
isConnectedDelegate,
connectorFactory,
geolocation,
lockService,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser)
{
LockitButtonText = AppResources.ActionClose; // BT button text "Schließen".
IsLockitButtonVisible = true; // Show button to allow user to lock bike.
}
/// <summary> Gets the bike state. </summary>
public override InUseStateEnum State => InUseStateEnum.Disposable;
/// <summary> Close lock and return bike.</summary>
public async Task<IRequestHandler> HandleRequestOption1() => await CloseLockAndReturnBike();
/// <summary> Close lock in order to pause ride and update COPRI lock state.</summary>
public async Task<IRequestHandler> HandleRequestOption2() => await CloseLock();
/// <summary> Close lock and return bike.</summary>
public async Task<IRequestHandler> CloseLockAndReturnBike()
{
// Prevent concurrent interaction
BikesViewModel.IsIdle = false;
// Start getting geolocation.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationStart;
var ctsLocation = new CancellationTokenSource();
Task<Location> currentLocationTask = null;
var timeStamp = DateTime.Now;
try
{
currentLocationTask = Geolocation.GetAsync(ctsLocation.Token, timeStamp);
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedOpen>().Information("Returning bike {Bike} is not possible. Start query location failed. {Exception}", SelectedBike, ex);
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAlert(
AppResources.MessageErrorQueryLocationStartTitle,
$"{AppResources.MessageErrorQueryLocationMessage}\r\n{ex.Message}",
AppResources.MessageAnswerOk);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Ask whether to really return bike?
var l_oResult = await ViewService.DisplayAlert(
string.Empty,
string.Format(AppResources.QuestionCloseLockAndReturnBike, SelectedBike.GetFullDisplayName()),
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
if (l_oResult == false)
{
// User aborted closing and returning bike process
Log.ForContext<BookedOpen>().Information("User selected booked bike {l_oId} in order to close and return but action was canceled.", SelectedBike.Id);
// Cancel getting geolocation.
ctsLocation.Cancel();
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedOpen>().Information("Canceling query location failed on abort returning opened bike failed. {Exception}", SelectedBike, ex);
}
BikesViewModel.IsIdle = true;
return this;
}
// Unlock bike.
Log.ForContext<BookedOpen>().Information("Request to return bike {bike} detected.", SelectedBike);
// Stop polling before returning bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
// Notify COPRI about start reaturning bike
BikesViewModel.ActionText = AppResources.ActivityTextStartReturningBike;
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.StartReturningBike(
SelectedBike);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<BookedOpen>().Information("User selected booked bike {bike} but returing failed (Copri server not reachable).", SelectedBike);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorReturnBikeNoWebTitle,
string.Format("{0}\r\n{1}", AppResources.ErrorReturnBikeNoWebMessage, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
exception.Message,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<BookedOpen>().Error("User selected booked bike {bike} but returning failed. {@l_oException}", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Close lock
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
// Signal cts to cancel getting geolocation.
ctsLocation.Cancel();
Task updateLockingStateTask = Task.CompletedTask;
try
{
updateLockingStateTask = ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(
SelectedBike);
}
catch (Exception innerExceptionStartUpdateLockingState)
{
// No location information available/ updating state failed.
Log.ForContext<BookedOpen>().Information("Start update locking state failed on lock operating error. {Exception}", SelectedBike, innerExceptionStartUpdateLockingState);
}
// Signal cts to cancel getting geolocation.
ctsLocation.Cancel();
if (exception is OutOfReachException)
{
Log.ForContext<BookedOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockOutOfReachMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CounldntCloseMovingException)
{
Log.ForContext<BookedOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockMovingMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseBoldBlockedException)
{
Log.ForContext<BookedOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockBoldBlockedMessage,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<BookedOpen>().Error("Lock can not be closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
// Wait until cancel getting geolocation has completed.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;
try
{
await Task.WhenAll(new List<Task> { currentLocationTask ?? Task.CompletedTask, updateLockingStateTask });
}
catch (Exception innerExWhenAll)
{
// No location information available/ updating state failed.
Log.ForContext<BookedOpen>().Information("Canceling query location/ updating lock state failed on closing lock error. {Exception}", SelectedBike, innerExWhenAll);
}
// Wait until cancel getting geolocation has completed.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedOpen>().Information("Canceling query location failed on closing lock error. {Exception}", SelectedBike, ex);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
if (SelectedBike.LockInfo.State != LockingState.Closed)
{
Log.ForContext<BookedOpen>().Error($"Lock can not be closed. Invalid locking state state {SelectedBike.LockInfo.State} detected.");
// Signal cts to cancel getting geolocation.
ctsLocation.Cancel();
BikesViewModel.ActionText = string.Empty;
// Signal cts to cancel getting geolocation.
ctsLocation.Cancel();
Task updateLockingStateTask = Task.CompletedTask;
try
{
updateLockingStateTask = ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(
SelectedBike);
}
catch (Exception innerExceptionStartUpdateLockingState)
{
// No location information available/ updating state failed.
Log.ForContext<BookedOpen>().Information("Start update locking state failed on unexpected state. {Exception}", SelectedBike, innerExceptionStartUpdateLockingState);
}
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
SelectedBike.LockInfo.State == LockingState.Open
? AppResources.ErrorCloseLockStillOpenMessage
: string.Format(AppResources.ErrorCloseLockUnexpectedStateMessage, SelectedBike.LockInfo.State),
AppResources.MessageAnswerOk);
// Wait until cancel getting geolocation has completed.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;
try
{
await Task.WhenAll(new List<Task> { currentLocationTask ?? Task.CompletedTask, updateLockingStateTask});
}
catch (Exception innerExWhenAll)
{
// No location information available.
Log.ForContext<BookedOpen>().Information("Canceling query location/ updating lock state failed failed on unexpected lock state failed. {Exception}", SelectedBike, innerExWhenAll);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocation;
Location currentLocation = null;
try
{
var task = await Task.WhenAny(new List<Task> { currentLocationTask });
currentLocation = currentLocationTask.Result;
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedOpen>().Information("Returning bike {Bike} is not possible. Query location failed. {Exception}", SelectedBike, ex);
BikesViewModel.ActionText = string.Empty;
await ViewService.DisplayAdvancedAlert(
AppResources.MessageErrorQueryLocationTitle,
AppResources.MessageErrorQueryLocationMessage,
ex.GetErrorMessage(),
AppResources.MessageAnswerOk);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true; // Unlock GUI
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Lock list to avoid multiple taps while copri action is pending.
BikesViewModel.ActionText = AppResources.ActivityTextReturningBike;
IsConnected = IsConnectedDelegate();
var feedBackUri = SelectedBike?.OperatorUri;
BookingFinishedModel bookingFinished;
try
{
bookingFinished = await ConnectorFactory(IsConnected).Command.DoReturn(
SelectedBike,
currentLocation != null
? new LocationDto.Builder
{
Latitude = currentLocation.Latitude,
Longitude = currentLocation.Longitude,
Accuracy = currentLocation.Accuracy ?? double.NaN,
Age = timeStamp.Subtract(currentLocation.Timestamp.DateTime),
}.Build()
: null,
SmartDevice);
// If canceling bike succedes remove bike because it is not ready to be booked again
IsRemoveBikeRequired = true;
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<BookedOpen>().Information("User selected booked bike {bike} but returing failed (Copri server not reachable).", SelectedBike);
await ViewService.DisplayAdvancedAlert(
AppResources.ErrorReturnBikeNoWebTitle,
string.Format("{0}\r\n{1}", AppResources.ErrorReturnBikeNoWebMessage, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
exception.Message,
AppResources.MessageAnswerOk);
}
else if (exception is NotAtStationException notAtStationException)
{
// COPRI returned an error.
Log.ForContext<BookedOpen>().Information("User selected booked bike {bike} but returing failed. COPRI returned an error.", SelectedBike);
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeTitle,
string.Format(AppResources.ErrorReturnBikeNotAtStationMessage, notAtStationException.StationNr, notAtStationException.Distance),
AppResources.MessageAnswerOk);
}
else if (exception is NoGPSDataException)
{
// COPRI returned an error.
Log.ForContext<BookedOpen>().Information("User selected booked bike {bike} but returing failed. COPRI returned an no GPS- data error.", SelectedBike);
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeTitle,
string.Format(AppResources.ErrorReturnBikeLockOpenNoGPSMessage),
AppResources.MessageAnswerOk);
}
else if (exception is ResponseException copriException)
{
// Copri server is not reachable.
Log.ForContext<BookedOpen>().Information("User selected booked bike {bike} but returing failed. COPRI returned an error.", SelectedBike);
await ViewService.DisplayAdvancedAlert(
"Statusfehler beim Zurückgeben des Rads!",
copriException.Message,
copriException.Response,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<BookedOpen>().Error("User selected booked bike {bike} but returning failed. {@l_oException}", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
Log.ForContext<BookedOpen>().Information("User returned bike {bike} successfully.", SelectedBike);
// Disconnect lock.
BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
try
{
SelectedBike.LockInfo.State = await LockService.DisconnectAsync(SelectedBike.LockInfo.Id, SelectedBike.LockInfo.Guid);
}
catch (Exception exception)
{
Log.ForContext<BookedOpen>().Error("Lock can not be disconnected. {Exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
}
#if !USERFEEDBACKDLG_OFF
// Do get Feedback
var feedback = await ViewService.DisplayUserFeedbackPopup(bookingFinished?.Co2Saving);
try
{
await ConnectorFactory(IsConnected).Command.DoSubmitFeedback(
new UserFeedbackDto { BikeId = SelectedBike.Id, IsBikeBroken = feedback.IsBikeBroken, Message = feedback.Message },
feedBackUri);
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (exception is ResponseException copriException)
{
// Copri server is not reachable.
Log.ForContext<BookedOpen>().Information("Submitting feedback for bike {bike} failed. COPRI returned an error.", SelectedBike);
}
else
{
Log.ForContext<BookedOpen>().Error("Submitting feedback for bike {bike} failed. {@l_oException}", SelectedBike.Id, exception);
}
await ViewService.DisplayAlert(
AppResources.ErrorReturnSubmitFeedbackTitle,
AppResources.ErrorReturnSubmitFeedbackMessage,
AppResources.MessageAnswerOk);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
#endif
if (bookingFinished != null && bookingFinished.MiniSurvey.Questions.Count > 0)
{
await ViewService.PushModalAsync(ViewTypes.MiniSurvey);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary> Close lock in order to pause ride and update COPRI lock state.</summary>
public async Task<IRequestHandler> CloseLock()
{
// Unlock bike.
BikesViewModel.IsIdle = false;
Log.ForContext<BookedOpen>().Information("User request to lock bike {bike} in order to pause ride.", SelectedBike);
// Start getting geolocation.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationStart;
var ctsLocation = new CancellationTokenSource();
Task<Location> currentLocationTask = null;
var timeStamp = DateTime.Now;
try
{
currentLocationTask = Geolocation.GetAsync(ctsLocation.Token, timeStamp);
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedOpen>().Information("Closing lock of bike {Bike} is not possible. Starting query location failed. {Exception}", SelectedBike, ex);
BikesViewModel.ActionText = AppResources.ActivityTextErrorQueryLocationQuery;
}
// Stop polling before returning bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
// Close lock
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
// Signal cts to cancel getting geolocation.
ctsLocation.Cancel();
if (exception is OutOfReachException)
{
Log.ForContext<BookedOpen>().Debug("Lock can not be closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockOutOfReachMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CounldntCloseMovingException)
{
Log.ForContext<BookedOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockMovingMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseBoldBlockedException)
{
Log.ForContext<BookedOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockBoldBlockedMessage,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<BookedOpen>().Error("Lock can not be closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
// Wait until cancel getting geolocation has completed.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedOpen>().Information("Canceling query location failed on closing lock error. {Exception}", SelectedBike, ex);
}
// Wait until cancel getting geolocation has completed.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedOpen>().Information("Canceling query location failed on closing lock error. {Exception}", SelectedBike, ex);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Get geoposition.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocation;
Location currentLocation = null;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask });
currentLocation = currentLocationTask.Result;
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedOpen>().Information("Getting geolocation when closing lock of bike {Bike} failed. {Exception}", SelectedBike, ex);
BikesViewModel.ActionText = AppResources.ActivityTextErrorQueryLocationWhenAny;
}
// Lock list to avoid multiple taps while copri action is pending.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(
SelectedBike,
currentLocation != null
? new LocationDto.Builder
{
Latitude = currentLocation.Latitude,
Longitude = currentLocation.Longitude,
Accuracy = currentLocation.Accuracy ?? double.NaN,
Age = timeStamp.Subtract(currentLocation.Timestamp.DateTime),
}.Build()
: null);
}
catch (Exception exception)
{
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<BookedOpen>().Information("User locked bike {bike} in order to pause ride but updating failed (Copri server not reachable).", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextErrorNoWebUpdateingLockstate;
}
else if (exception is ResponseException copriException)
{
// Copri server is not reachable.
Log.ForContext<BookedOpen>().Information("User locked bike {bike} in order to pause ride but updating failed. Message: {Message} Details: {Details}", SelectedBike, copriException.Message, copriException.Response);
BikesViewModel.ActionText = AppResources.ActivityTextErrorStatusUpdateingLockstate;
}
else
{
Log.ForContext<BookedOpen>().Error("User locked bike {bike} in order to pause ride but updating failed. {@l_oException}", SelectedBike.Id, exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
}
}
Log.ForContext<BookedOpen>().Information("User paused ride using {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
}
}

View file

@ -1,386 +0,0 @@
using Serilog;
using System;
using System.Threading.Tasks;
using TINK.Model.Bike.BluetoothLock;
using TINK.Model.Connector;
using TINK.Model.State;
using TINK.View;
using TINK.Repository.Exception;
using TINK.Services.Geolocation;
using TINK.Services.BluetoothLock;
using TINK.Services.BluetoothLock.Exception;
using TINK.MultilingualResources;
using TINK.Model.Bikes.Bike.BluetoothLock;
using TINK.Model.User;
using Xamarin.Essentials;
using TINK.Repository.Request;
using TINK.Model.Device;
using System.Threading;
using System.Collections.Generic;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
public class BookedUnknown : Base, IRequestHandler
{
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...).</param>
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
public BookedUnknown(
IBikeInfoMutable selectedBike,
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
IGeolocation geolocation,
ILocksService lockService,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
AppResources.ActionOpenAndPause, // Schloss öffnen und Miete fortsetzen.
true, // Show button to enabled returning of bike.
isConnectedDelegate,
connectorFactory,
geolocation,
lockService,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser)
{
LockitButtonText = AppResources.ActionClose; // BT button text "Schließen".;
IsLockitButtonVisible = true; // Show button to enable opening lock in case user took a pause and does not want to return the bike.
}
/// <summary> Gets the bike state. </summary>
public override InUseStateEnum State => InUseStateEnum.Booked;
/// <summary> Open bike and update COPRI lock state. </summary>
public async Task<IRequestHandler> HandleRequestOption1() => await OpenLock();
/// <summary> Close lock in order to pause ride and update COPRI lock state.</summary>
public async Task<IRequestHandler> HandleRequestOption2() => await CloseLock();
/// <summary> Open bike and update COPRI lock state. </summary>
public async Task<IRequestHandler> OpenLock()
{
// Unlock bike.
Log.ForContext<BookedUnknown>().Information("User request to unlock bike {bike}.", SelectedBike);
// Stop polling before returning bike.
BikesViewModel.IsIdle = false;
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
BikesViewModel.ActionText = AppResources.ActivityTextOpeningLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].OpenAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (exception is OutOfReachException)
{
Log.ForContext<BookedUnknown>().Debug("Lock can not be opened. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockOutOfReachMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldIsBlockedException)
{
Log.ForContext<BookedUnknown>().Debug("Lock can not be opened. Bold is blocked. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockBoldBlockedMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldWasBlockedException)
{
Log.ForContext<BookedUnknown>().Debug("Lock can not be opened. Bold was or is blocked. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockStillOpenTitle,
AppResources.ErrorOpenLockBoldWasBlockedMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
&& inconsistentState.State == LockingState.Closed)
{
Log.ForContext<BookedUnknown>().Debug("Lock can not be opened. lock reports state closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStillClosedMessage,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<BookedUnknown>().Error("Lock can not be opened. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
// When bold is blocked lock is still closed even if exception occurres.
// In all other cases state is supposed to be unknown. Example: Lock is out of reach and no more bluetooth connected.
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel;
try
{
SelectedBike.LockInfo.BatteryPercentage = await LockService[SelectedBike.LockInfo.Id].GetBatteryPercentageAsync();
}
catch (Exception exception)
{
if (exception is OutOfReachException)
{
Log.ForContext<BookedUnknown>().Debug("Akkustate can not be read, bike out of range. {Exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelOutOfReach;
}
else
{
Log.ForContext<BookedUnknown>().Error("Akkustate can not be read. {Exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelGeneral;
}
}
// Lock list to avoid multiple taps while copri action is pending.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(SelectedBike);
}
catch (Exception exception)
{
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<BookedUnknown>().Information("User locked bike {bike} in order to pause ride but updating failed (Copri server not reachable).", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextErrorNoWebUpdateingLockstate;
}
else if (exception is ResponseException copriException)
{
// Copri server is not reachable.
Log.ForContext<BookedUnknown>().Information("User locked bike {bike} in order to pause ride but updating failed. {response}.", SelectedBike, copriException.Response);
BikesViewModel.ActionText = AppResources.ActivityTextErrorStatusUpdateingLockstate;
}
else
{
Log.ForContext<BookedUnknown>().Error("User locked bike {bike} in order to pause ride but updating failed . {@l_oException}", SelectedBike.Id, exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
}
}
Log.ForContext<BookedUnknown>().Information("User paused ride using {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary> Close lock in order to pause ride and update COPRI lock state.</summary>
public async Task<IRequestHandler> CloseLock()
{
// Unlock bike.
BikesViewModel.IsIdle = false;
Log.ForContext<BookedUnknown>().Information("User request to lock bike {bike} in order to pause ride.", SelectedBike);
// Start getting geolocation.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationStart;
var ctsLocation = new CancellationTokenSource();
Task<Location> currentLocationTask = null;
var timeStamp = DateTime.Now;
try
{
currentLocationTask = Geolocation.GetAsync(ctsLocation.Token, timeStamp);
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedUnknown>().Information("Returning bike {Bike} is not possible. Start query location failed. {Exception}", SelectedBike, ex);
BikesViewModel.ActionText = AppResources.ActivityTextErrorQueryLocationQuery;
}
// Stop polling before returning bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
// Close lock
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
// Signal cts to cancel getting geolocation.
ctsLocation.Cancel();
if (exception is OutOfReachException)
{
Log.ForContext<BookedUnknown>().Debug("Lock can not be closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockOutOfReachMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CounldntCloseMovingException)
{
Log.ForContext<BookedUnknown>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockMovingMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseBoldBlockedException)
{
Log.ForContext<BookedUnknown>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockBoldBlockedMessage,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<BookedUnknown>().Error("Lock can not be closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
// Wait until cancel getting geolocation has completed.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedUnknown>().Information("Canceling query location failed on unexpected lock state failed. {Exception}", SelectedBike, ex);
}
// Wait until cancel getting geolocation has completed.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedUnknown>().Information("Canceling query location failed on unexpected lock state failed. {Exception}", SelectedBike, ex);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Get geoposition.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocation;
Location currentLocation = null;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
currentLocation = currentLocationTask?.Result ?? null;
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<BookedUnknown>().Information("Get geolocation failed when closing lock of bike {Bike} with unknown state. {Exception}", SelectedBike, ex);
BikesViewModel.ActionText = AppResources.ActivityTextErrorQueryLocationWhenAny;
}
// Lock list to avoid multiple taps while copri action is pending.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(
SelectedBike,
currentLocation != null
? new LocationDto.Builder
{
Latitude = currentLocation.Latitude,
Longitude = currentLocation.Longitude,
Accuracy = currentLocation.Accuracy ?? double.NaN,
Age = timeStamp.Subtract(currentLocation.Timestamp.DateTime),
}.Build()
: null);
}
catch (Exception exception)
{
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<BookedUnknown>().Information("User locked bike {bike} in order to pause ride but updating failed (Copri server not reachable).", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextErrorNoWebUpdateingLockstate;
}
else if (exception is ResponseException copriException)
{
// Copri server is not reachable.
Log.ForContext<BookedUnknown>().Information("User locked bike {bike} in order to pause ride but updating failed. Message: {Message} Details: {Details}", SelectedBike, copriException.Message, copriException.Response);
BikesViewModel.ActionText = AppResources.ActivityTextErrorStatusUpdateingLockstate;
}
else
{
Log.ForContext<BookedUnknown>().Error("User locked bike {bike} in order to pause ride but updating failed. {@l_oException}", SelectedBike.Id, exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
}
}
Log.ForContext<BookedUnknown>().Information("User paused ride using {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
}
}

View file

@ -1,386 +0,0 @@
using Serilog;
using System;
using System.Threading.Tasks;
using TINK.Model.Bike.BluetoothLock;
using TINK.Model.Connector;
using TINK.Model.State;
using TINK.View;
using TINK.Repository.Exception;
using TINK.Services.Geolocation;
using TINK.Services.BluetoothLock;
using TINK.Services.BluetoothLock.Exception;
using TINK.MultilingualResources;
using TINK.Model.Bikes.Bike.BluetoothLock;
using TINK.Model.User;
using Xamarin.Essentials;
using TINK.Repository.Request;
using TINK.Model.Device;
using System.Threading;
using System.Collections.Generic;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
public class ReservedUnknown : Base, IRequestHandler
{
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...)</param>
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
public ReservedUnknown(
IBikeInfoMutable selectedBike,
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
IGeolocation geolocation,
ILocksService lockService,
Func<IPollingUpdateTaskManager> viewUpdateManager,
ISmartDevice smartDevice,
IViewService viewService,
IBikesViewModel bikesViewModel,
IUser activeUser) : base(
selectedBike,
AppResources.ActionOpenAndBook, // BT button text "Schloss öffnen und Rad mieten."
false, // Show button to enabled returning of bike.
isConnectedDelegate,
connectorFactory,
geolocation,
lockService,
viewUpdateManager,
smartDevice,
viewService,
bikesViewModel,
activeUser)
{
LockitButtonText = AppResources.ActionClose; // BT button text "Schließen"
IsLockitButtonVisible = true; // Show button to enable opening lock in case user took a pause and does not want to return the bike.
}
/// <summary> Gets the bike state. </summary>
public override InUseStateEnum State => InUseStateEnum.Reserved;
/// <summary> Open bike and update COPRI lock state. </summary>
public async Task<IRequestHandler> HandleRequestOption1() => await OpenLock();
/// <summary> Open bike and update COPRI lock state. </summary>
public async Task<IRequestHandler> OpenLock()
{
// Unlock bike.
Log.ForContext<ReservedUnknown>().Information("User request to unlock bike {bike}.", SelectedBike);
// Stop polling before returning bike.
BikesViewModel.IsIdle = false;
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
BikesViewModel.ActionText = AppResources.ActivityTextOpeningLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].OpenAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (exception is OutOfReachException)
{
Log.ForContext<ReservedUnknown>().Debug("Lock can not be opened. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockOutOfReachMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldIsBlockedException)
{
Log.ForContext<ReservedUnknown>().Debug("Lock can not be opened. Bold is blocked. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockBoldBlockedMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldWasBlockedException)
{
Log.ForContext<ReservedUnknown>().Debug("Lock can not be opened. Bold was or is blocked. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockStillOpenTitle,
AppResources.ErrorOpenLockBoldWasBlockedMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
&& inconsistentState.State == LockingState.Closed)
{
Log.ForContext<ReservedUnknown>().Debug("Lock can not be opened. lock reports state closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStillClosedMessage,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<ReservedUnknown>().Error("Lock can not be opened. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
// When bold is blocked lock is still closed even if exception occurres.
// In all other cases state is supposed to be unknown. Example: Lock is out of reach and no more bluetooth connected.
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel;
try
{
SelectedBike.LockInfo.BatteryPercentage = await LockService[SelectedBike.LockInfo.Id].GetBatteryPercentageAsync();
}
catch (Exception exception)
{
if (exception is OutOfReachException)
{
Log.ForContext<ReservedUnknown>().Debug("Akkustate can not be read, bike out of range. {Exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelOutOfReach;
}
else
{
Log.ForContext<ReservedUnknown>().Error("Akkustate can not be read. {Exception}", exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelGeneral;
}
}
// Lock list to avoid multiple taps while copri action is pending.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(SelectedBike);
}
catch (Exception exception)
{
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<ReservedUnknown>().Information("User locked bike {bike} in order to pause ride but updating failed (Copri server not reachable).", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextErrorNoWebUpdateingLockstate;
}
else if (exception is ResponseException copriException)
{
// Copri server is not reachable.
Log.ForContext<ReservedUnknown>().Information("User locked bike {bike} in order to pause ride but updating failed. {response}.", SelectedBike, copriException.Response);
BikesViewModel.ActionText = AppResources.ActivityTextErrorStatusUpdateingLockstate;
}
else
{
Log.ForContext<ReservedUnknown>().Error("User locked bike {bike} in order to pause ride but updating failed . {@l_oException}", SelectedBike.Id, exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
}
}
Log.ForContext<ReservedUnknown>().Information("User paused ride using {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
/// <summary> Close lock in order to pause ride and update COPRI lock state.</summary>
public async Task<IRequestHandler> HandleRequestOption2() => await CloseLock();
/// <summary> Close lock in order to pause ride and update COPRI lock state.</summary>
public async Task<IRequestHandler> CloseLock()
{
// Unlock bike.
BikesViewModel.IsIdle = false;
Log.ForContext<ReservedUnknown>().Information("User request to lock bike {bike} in order to pause ride.", SelectedBike);
// Start getting geolocation.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationStart;
var ctsLocation = new CancellationTokenSource();
Task<Location> currentLocationTask = null;
var timeStamp = DateTime.Now;
try
{
currentLocationTask = Geolocation.GetAsync(ctsLocation.Token, timeStamp);
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<ReservedUnknown>().Information("Returning bike {Bike} is not possible. Start query location failed. {Exception}", SelectedBike, ex);
BikesViewModel.ActionText = AppResources.ActivityTextErrorQueryLocationQuery;
}
// Stop polling before returning bike.
BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
await ViewUpdateManager().StopUpdatePeridically();
// Close lock
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
// Signal cts to cancel getting geolocation.
ctsLocation.Cancel();
if (exception is OutOfReachException)
{
Log.ForContext<ReservedUnknown>().Debug("Lock can not be closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockOutOfReachMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CounldntCloseMovingException)
{
Log.ForContext<ReservedUnknown>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockMovingMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseBoldBlockedException)
{
Log.ForContext<ReservedUnknown>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockBoldBlockedMessage,
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<ReservedUnknown>().Error("Lock can not be closed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
// Wait until cancel getting geolocation has completed.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<ReservedUnknown>().Information("Canceling query location failed on closing lock error. {Exception}", SelectedBike, ex);
}
// Wait until cancel getting geolocation has completed.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<ReservedUnknown>().Information("Canceling query location failed on closing lock error. {Exception}", SelectedBike, ex);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
// Get geoposition.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocation;
Location currentLocation = null;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
currentLocation = currentLocationTask?.Result ?? null;
}
catch (Exception ex)
{
// No location information available.
Log.ForContext<ReservedUnknown>().Information("Returning bike {Bike} is not possible. {Exception}", SelectedBike, ex);
BikesViewModel.ActionText = AppResources.ActivityTextErrorQueryLocationWhenAny;
}
// Lock list to avoid multiple taps while copri action is pending.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState;
IsConnected = IsConnectedDelegate();
try
{
await ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(
SelectedBike,
currentLocation != null
? new LocationDto.Builder
{
Latitude = currentLocation.Latitude,
Longitude = currentLocation.Longitude,
Accuracy = currentLocation.Accuracy ?? double.NaN,
Age = timeStamp.Subtract(currentLocation.Timestamp.DateTime),
}.Build()
: null);
}
catch (Exception exception)
{
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<ReservedUnknown>().Information("User locked bike {bike} in order to pause ride but updating failed (Copri server not reachable).", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextErrorNoWebUpdateingLockstate;
}
else if (exception is ResponseException copriException)
{
// Copri server is not reachable.
Log.ForContext<ReservedUnknown>().Information("User locked bike {bike} in order to pause ride but updating failed. Message: {Message} Details: {Details}", SelectedBike, copriException.Message, copriException.Response);
BikesViewModel.ActionText = AppResources.ActivityTextErrorStatusUpdateingLockstate;
}
else
{
Log.ForContext<ReservedUnknown>().Error("User locked bike {bike} in order to pause ride but updating failed. {@l_oException}", SelectedBike.Id, exception);
BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate;
}
}
Log.ForContext<ReservedUnknown>().Information("User paused ride using {bike} successfully.", SelectedBike);
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
BikesViewModel.ActionText = string.Empty;
BikesViewModel.IsIdle = true;
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
}
}

View file

@ -1,6 +1,6 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using TINK.Model.Bikes.Bike;
using TINK.MultilingualResources;
namespace TINK.ViewModel.Bikes.Bike
{
@ -9,64 +9,59 @@ namespace TINK.ViewModel.Bikes.Bike
/// </summary>
public class TariffDescriptionViewModel
{
private TariffDescription Tariff { get; }
private const string AGBKEY = "AGB";
public TariffDescriptionViewModel(TariffDescription tariff)
public TariffDescriptionViewModel(RentalDescription tariff)
{
Tariff = tariff;
}
Name = tariff?.Name ?? string.Empty;
public string Header
{
get
{
if (string.IsNullOrEmpty(FeeEuroPerHour)
&& string.IsNullOrEmpty(AboEuroPerMonth)
&& string.IsNullOrEmpty(FreeTimePerSession)
&& string.IsNullOrEmpty(MaxFeeEuroPerDay))
// No tariff description details available.
return string.Empty;
TariffEntries = tariff != null && tariff?.TariffEntries != null
? new ObservableCollection<RentalDescription.TariffElement>(tariff.TariffEntries.OrderBy(x => x.Key).Select(x => x.Value))
: new ObservableCollection<RentalDescription.TariffElement>();
// Up to version MessageBikesManagementTariffDescriptionTariffHeaderNameId
return Tariff?.Name ?? "-";
}
InfoEntries = tariff != null && tariff?.InfoEntries != null
? new ObservableCollection<string>(tariff.InfoEntries.OrderBy(x => x.Key).Where(x => x.Value.Key != AGBKEY).Select(x => x.Value.Value))
: new ObservableCollection<string>();
OperatorAgb = tariff?.InfoEntries != null
&& tariff.InfoEntries.Select(x => x.Value.Key).Contains(AGBKEY)
? tariff?.InfoEntries.FirstOrDefault(x => x.Value.Key == AGBKEY).Value.Value
: string.Empty;
}
/// <summary>
/// Costs per hour in euro.
/// Holds the name of the tariff.
/// </summary>
public string FeeEuroPerHour
=> !double.IsNaN(Tariff.FeeEuroPerHour)
? string.Format("{0} {1}", Tariff.FeeEuroPerHour.ToString("0.00"), AppResources.MessageBikesManagementTariffDescriptionEuroPerHour)
: string.Empty;
public string Name { get; set; }
/// <summary>
/// Costs of the abo per month.
/// Holds the tariff entries to display.
/// </summary>
public string AboEuroPerMonth
=> !double.IsNaN(Tariff.AboEuroPerMonth)
? string.Format("{0} {1}", Tariff.AboEuroPerMonth.ToString("0.00"), AppResources.MessageBikesManagementTariffDescriptionEuroPerHour)
: string.Empty;
public ObservableCollection<RentalDescription.TariffElement> TariffEntries { get; private set; }
/// <summary>
/// Free use time.
/// Holds the info entries to display.
/// </summary>
public string FreeTimePerSession
=> Tariff.FreeTimePerSession != TimeSpan.Zero
? string.Format("{0} {1}", Tariff.FreeTimePerSession.TotalHours, AppResources.MessageBikesManagementTariffDescriptionHour)
: string.Empty;
/// <summary>
/// Max costs per day in euro.
/// </summary>
public string MaxFeeEuroPerDay
=> !double.IsNaN(Tariff.FeeEuroPerHour)
? string.Format("{0} {1}", Tariff.MaxFeeEuroPerDay.ToString("0.00"), AppResources.MessageBikesManagementMaxFeeEuroPerDay)
: string.Empty;
public ObservableCollection<string> InfoEntries { get; private set; }
/// <summary> Info about operator agb as HTML (i.g. text and hyperlink). </summary>
public string OperatorAgb => !string.IsNullOrEmpty(Tariff?.OperatorAgb)
? Tariff.OperatorAgb
: string.Empty;
public string OperatorAgb { get; set; }
public RentalDescription.TariffElement TarifEntry1 => TariffEntries.Count > 0 ? TariffEntries[0] : new RentalDescription.TariffElement();
public RentalDescription.TariffElement TarifEntry2 => TariffEntries.Count > 1 ? TariffEntries[1] : new RentalDescription.TariffElement();
public RentalDescription.TariffElement TarifEntry3 => TariffEntries.Count > 2 ? TariffEntries[2] : new RentalDescription.TariffElement();
public RentalDescription.TariffElement TarifEntry4 => TariffEntries.Count > 3 ? TariffEntries[3] : new RentalDescription.TariffElement();
public RentalDescription.TariffElement TarifEntry5 => TariffEntries.Count > 4 ? TariffEntries[4] : new RentalDescription.TariffElement();
public RentalDescription.TariffElement TarifEntry6 => TariffEntries.Count > 5 ? TariffEntries[5] : new RentalDescription.TariffElement();
public RentalDescription.TariffElement TarifEntry7 => TariffEntries.Count > 6 ? TariffEntries[6] : new RentalDescription.TariffElement();
public RentalDescription.TariffElement TarifEntry8 => TariffEntries.Count > 7 ? TariffEntries[7] : new RentalDescription.TariffElement();
public RentalDescription.TariffElement TarifEntry9 => TariffEntries.Count > 8 ? TariffEntries[8] : new RentalDescription.TariffElement();
public string InfoEntry1 => InfoEntries.Count > 0 ? InfoEntries[0] : string.Empty;
public string InfoEntry2 => InfoEntries.Count > 1 ? InfoEntries[1] : string.Empty;
public string InfoEntry3 => InfoEntries.Count > 2 ? InfoEntries[2] : string.Empty;
public string InfoEntry4 => InfoEntries.Count > 3 ? InfoEntries[3] : string.Empty;
public string InfoEntry5 => InfoEntries.Count > 4 ? InfoEntries[4] : string.Empty;
}
}

View file

@ -449,7 +449,7 @@ namespace TINK.ViewModel.Bikes
/// <param name="updateAction"> Update fuction passed as argument by child class.</param>
protected async Task OnAppearing(Action updateAction)
{
m_oViewUpdateManager = new PollingUpdateTaskManager(() => GetType().Name, updateAction);
m_oViewUpdateManager = new PollingUpdateTaskManager(updateAction);
try
{

View file

@ -556,7 +556,6 @@ namespace TINK.ViewModel.Map
{
// Start task which periodically updates pins.
return new PollingUpdateTaskManager(
() => GetType().Name,
() =>
{
try

View file

@ -7,64 +7,59 @@ using TINK.Repository.Exception;
namespace TINK.ViewModel
{
/// <summary>
/// Object to periodically actuate an action.
/// </summary>
public class PollingUpdateTask
{
/// <summary>Name of child class provider. </summary>
private readonly Func<String> m_oGetChildName;
/// <summary> Object to control canelling. </summary>
private readonly CancellationTokenSource m_oCanellationTokenSource;
private CancellationTokenSource CanellationTokenSource { get; }
/// <summary> Task to perform update. </summary>
private readonly Task m_oUpdateTask;
private Task UpdateTask { get; }
/// <summary> Action which performs an update.</summary>
private readonly Action m_oUpdateAction;
private Action UpdateAction { get; }
/// <summary> Obects which does a periodic update. </summary>
/// <param name="p_oGetChildName">Name of the child class. For logging purposes.</param>
/// <param name="p_oUpdateAction">Update action to perform.</param>
/// <param name="p_oPolling"> Holds whether to poll or not and the periode leght is polling is on. </param>
/// <param name="updateAction">Update action to perform.</param>
/// <param name="polling"> Holds whether to poll or not and the periode leght is polling is on. </param>
public PollingUpdateTask(
Func<String> p_oGetChildName,
Action p_oUpdateAction,
TimeSpan? p_oPolling)
Action updateAction,
TimeSpan? polling)
{
m_oGetChildName = p_oGetChildName
?? throw new ArgumentException($"Can not construct {GetType().Name}- obect. Argument {nameof(p_oGetChildName)} must not be null.");
UpdateAction = updateAction
?? throw new ArgumentException($"Can not construct {GetType().Name}- obect. Argument {nameof(updateAction)} must not be null.");
m_oUpdateAction = p_oUpdateAction
?? throw new ArgumentException($"Can not construct {GetType().Name}- obect. Argument {nameof(p_oUpdateAction)} must not be null.");
if (!p_oPolling.HasValue)
if (!polling.HasValue)
{
// Automatic update is switched off.
Log.ForContext<PollingUpdateTask>().Debug($"Automatic update is off, context {GetType().Name} at {DateTime.Now}.");
return;
}
var l_iUpdatePeriodeSet = p_oPolling.Value;
var updatePeriodeSet = polling.Value;
m_oCanellationTokenSource = new CancellationTokenSource();
CanellationTokenSource = new CancellationTokenSource();
int l_iCycleIndex = 2;
m_oUpdateTask = Task.Run(
int cycleIndex = 2;
UpdateTask = Task.Run(
async () =>
{
while (!m_oCanellationTokenSource.IsCancellationRequested)
while (!CanellationTokenSource.IsCancellationRequested)
{
await Task.Delay(l_iUpdatePeriodeSet, m_oCanellationTokenSource.Token);
await Task.Delay(updatePeriodeSet, CanellationTokenSource.Token);
{
// N. update cycle
Log.ForContext<PollingUpdateTask>().Information($"Actuating {l_iCycleIndex} update cycle, context {GetType().Name} at {DateTime.Now}.");
Log.ForContext<PollingUpdateTask>().Information($"Actuating {cycleIndex} update cycle, context {GetType().Name} at {DateTime.Now}.");
m_oUpdateAction();
UpdateAction();
l_iCycleIndex = l_iCycleIndex < int.MaxValue ? ++l_iCycleIndex : 0;
cycleIndex = cycleIndex < int.MaxValue ? ++cycleIndex : 0;
}
}
},
m_oCanellationTokenSource.Token);
CanellationTokenSource.Token);
}
/// <summary>
@ -73,51 +68,57 @@ namespace TINK.ViewModel
/// </summary>
public async Task Terminate()
{
if (UpdateTask == null)
{
// Nothing to do if no task was created.
return;
}
// Cancel update task;
if (m_oCanellationTokenSource == null)
if (CanellationTokenSource == null)
{
throw new Exception($"Can not terminate periodical update task, context {GetType().Name} at {DateTime.Now}. No task running.");
}
Log.Information($"Request to terminate update cycle, context {GetType().Name} at {DateTime.Now}.");
m_oCanellationTokenSource.Cancel();
CanellationTokenSource.Cancel();
try
{
await m_oUpdateTask;
await UpdateTask;
}
catch (TaskCanceledException)
{
// Polling update was canceled.
// Nothing to notice/ worry about.
}
catch (Exception l_oException)
catch (Exception exception)
{
// An error occurred updating pin.
var l_oAggregateException = l_oException as AggregateException;
if (l_oAggregateException == null)
var aggregateException = exception as AggregateException;
if (aggregateException == null)
{
// Unexpected exception detected. Exception should alyways be of type AggregateException
Log.Error("An/ several errors occurred on update task. {@Exceptions}.", l_oException);
Log.Error("An/ several errors occurred on update task. {@Exceptions}.", exception);
}
else
{
if (l_oAggregateException.InnerExceptions.Count == 1
&& l_oAggregateException.InnerExceptions[0].GetType() == typeof(TaskCanceledException))
if (aggregateException.InnerExceptions.Count == 1
&& aggregateException.InnerExceptions[0].GetType() == typeof(TaskCanceledException))
{
// Polling update was canceled.
// Nothing to notice/ worry about.
}
else if (l_oAggregateException.InnerExceptions.Count() > 0 &&
l_oAggregateException.InnerExceptions.First(x => !x.GetIsConnectFailureException()) == null) // There is no exception which is not of type connect failure.
else if (aggregateException.InnerExceptions.Count() > 0 &&
aggregateException.InnerExceptions.First(x => !x.GetIsConnectFailureException()) == null) // There is no exception which is not of type connect failure.
{
// All exceptions were caused by communication error
Log.Information("An/ several web related connect failure exceptions occurred on update task. {@Exceptions}.", l_oException);
Log.Information("An/ several web related connect failure exceptions occurred on update task. {@Exceptions}.", exception);
}
else
{
// All exceptions were caused by communication errors.
Log.Error("An/ several exceptions occurred on update task. {@Exception}.", l_oException);
Log.Error("An/ several exceptions occurred on update task. {@Exception}.", exception);
}
}
}

View file

@ -10,14 +10,11 @@ namespace TINK.ViewModel
/// </summary>
public class PollingUpdateTaskManager : IPollingUpdateTaskManager
{
/// <summary>Name of child class provider. </summary>
private readonly Func<string> m_oGetChildName;
/// <summary> Action which performs an update.</summary>
private Action m_oUpdateAction;
private Action UpdateAction { get; }
/// <summary> Reference on the current polling task</summary>
private PollingUpdateTask m_oPollingUpdateTask;
private PollingUpdateTask UpdateTask { get; set; }
/// <summary>
/// Gets or sets polling parameters.
@ -30,16 +27,12 @@ namespace TINK.ViewModel
/// <summary>Constructs a update manager object. </summary>
/// <param name="p_oGetChildName">Name of the child class. For logging purposes.</param>
/// <param name="p_oUpdateAction"></param>
/// <param name="updateAction"></param>
public PollingUpdateTaskManager(
Func<String> p_oGetChildName,
Action p_oUpdateAction)
Action updateAction)
{
m_oGetChildName = p_oGetChildName ?? throw new ArgumentException(
$"Can not construct {GetType().Name}- obect. Argument {nameof(p_oGetChildName)} must not be null.");
m_oUpdateAction = p_oUpdateAction ?? throw new ArgumentException(
$"Can not construct {GetType().Name}- obect. Argument {nameof(p_oUpdateAction)} must not be null.");
UpdateAction = updateAction ?? throw new ArgumentException(
$"Can not construct {GetType().Name}- obect. Argument {nameof(updateAction)} must not be null.");
}
/// <summary>
@ -49,13 +42,13 @@ namespace TINK.ViewModel
/// <param name="pollingParameters">Parametes holding polling periode, if null last parameters are used if available.</param>
public async Task StartUpdateAyncPeridically(PollingParameters pollingParameters = null)
{
if (m_oPollingUpdateTask != null)
if (UpdateTask != null)
{
Log.Error($"Call to stop periodic update task missed in context {GetType().Name} at {DateTime.Now}.");
// Call of StopUpdatePeriodically missed.
// Terminate running polling update task before starting a new one
await m_oPollingUpdateTask.Terminate();
await UpdateTask.Terminate();
}
if (pollingParameters != null)
@ -79,9 +72,8 @@ namespace TINK.ViewModel
return;
}
m_oPollingUpdateTask = new PollingUpdateTask(
m_oGetChildName,
m_oUpdateAction,
UpdateTask = new PollingUpdateTask(
UpdateAction,
PollingParameters.Periode);
}
@ -91,15 +83,15 @@ namespace TINK.ViewModel
/// </summary>
public async Task StopUpdatePeridically()
{
if (m_oPollingUpdateTask == null)
if (UpdateTask == null)
{
// Nothing to do if there is no update task pending.
return;
}
await m_oPollingUpdateTask.Terminate();
await UpdateTask.Terminate();
m_oPollingUpdateTask = null;
UpdateTask = null;
}
}
}