Version 3.0.290

This commit is contained in:
Oliver Hauff 2022-04-10 17:38:34 +02:00
parent af3c20ea1c
commit ad3cdbcadf
231 changed files with 14555 additions and 7798 deletions

View file

@ -111,6 +111,16 @@ namespace TINK.Model.Connector
/// <param name="bike">Bike to update locking state for.</param>
/// <param name="location">Location where lock was opened/ changed.</param>
/// <returns>Response on updating locking state.</returns>
public async Task StartReturningBike(Bikes.Bike.BluetoothLock.IBikeInfoMutable bike)
{
Log.ForContext<Command>().Error("Unexpected request to notify about start of returning bike. No user logged in.");
await Task.CompletedTask;
}
/// <summary> Notifies COPRI about start of returning sequence. </summary>
/// <remarks> Operator specific call.</remarks>
/// <param name="bike">Bike to return.</param>
/// <returns>Response on notification about start of returning sequence.</returns>
public async Task UpdateLockingStateAsync(Bikes.Bike.BluetoothLock.IBikeInfoMutable bike, LocationDto location)
{
Log.ForContext<Command>().Error("Unexpected request to update locking state detected. No user logged in.");

View file

@ -159,15 +159,42 @@ namespace TINK.Model.Connector
Bikes.Bike.BC.NotifyPropertyChangedLevel.None);
}
/// <summary> Updates COPRI lock state for a booked bike. </summary>
/// <param name="bike">Bike to update locking state for.</param>
/// <returns>Response on updating locking state.</returns>
public async Task UpdateLockingStateAsync(
Bikes.Bike.BluetoothLock.IBikeInfoMutable bike, LocationDto location)
/// <summary> Notifies COPRI about start of returning sequence. </summary>
/// <remarks> Operator specific call.</remarks>
/// <param name="bike">Bike to return.</param>
/// <returns>Response on notification about start of returning sequence.</returns>
public async Task StartReturningBike(Bikes.Bike.BluetoothLock.IBikeInfoMutable bike)
{
if (bike == null)
{
throw new ArgumentNullException("Can not book bike. No bike object available.");
throw new ArgumentNullException("Can not notify about start returning bike. No bike object available.");
}
try
{
(await CopriServer.StartReturningBike(
bike.Id,
bike.OperatorUri)).GetIsResponseOk("Start returning bike");
}
catch (Exception)
{
// Exception was not expected or too many subsequent excepitons detected.
throw;
}
}
/// <summary> Updates COPRI lock state for a booked bike. </summary>
/// <param name="bike">Bike to update locking state for.</param>
/// <param name="location">Location of the bike.</param>
/// <returns>Response on updating locking state.</returns>
public async Task UpdateLockingStateAsync(
Bikes.Bike.BluetoothLock.IBikeInfoMutable bike,
LocationDto location)
{
if (bike == null)
{
throw new ArgumentNullException("Can not update locking state of bike. No bike object available.");
}
if (bike.State.Value != State.InUseStateEnum.Booked)

View file

@ -25,19 +25,25 @@ namespace TINK.Model.Connector
Task DoLogout();
/// <summary> Request to reserve a bike.</summary>
/// <param name="p_oBike">Bike to book.</param>
Task DoReserve(Bikes.Bike.BC.IBikeInfoMutable p_oBike);
/// <param name="bike">Bike to book.</param>
Task DoReserve(Bikes.Bike.BC.IBikeInfoMutable bike);
/// <summary> Request to cancel a reservation.</summary>
/// <param name="p_oBike">Bike to book.</param>
Task DoCancelReservation(Bikes.Bike.BC.IBikeInfoMutable p_oBike);
/// <param name="bike">Bike to book.</param>
Task DoCancelReservation(Bikes.Bike.BC.IBikeInfoMutable bike);
/// <summary> Get authentication keys to connect to lock.</summary>
/// <param name="bike">Bike to book.</param>
Task CalculateAuthKeys(Bikes.Bike.BluetoothLock.IBikeInfoMutable bike);
/// <summary> Notifies COPRI about start of returning sequence. </summary>
/// <remarks> Operator specific call.</remarks>
/// <param name="bike">Bike to return.</param>
/// <returns>Response on notification about start of returning sequence.</returns>
Task StartReturningBike(Bikes.Bike.BluetoothLock.IBikeInfoMutable bike);
/// <summary> Updates COPRI lock state for a booked bike. </summary>
/// <param name="bikeId">Id of the bike to update locking state for.</param>
/// <param name="bike">Bike to update locking state for.</param>
/// <param name="location">Geolocation of lock when returning bike.</param>
/// <returns>Response on updating locking state.</returns>
Task UpdateLockingStateAsync(Bikes.Bike.BluetoothLock.IBikeInfoMutable bike, LocationDto location = null);
@ -90,8 +96,8 @@ namespace TINK.Model.Connector
}
/// <summary>Defines delegate to be raised whenever login state changes.</summary>
/// <param name="p_oEventArgs">Holds session cookie and mail address if user logged in successfully.</param>
public delegate void LoginStateChangedEventHandler(object p_oSender, LoginStateChangedEventArgs p_oEventArgs);
/// <param name="eventArgs">Holds session cookie and mail address if user logged in successfully.</param>
public delegate void LoginStateChangedEventHandler(object sender, LoginStateChangedEventArgs eventArgs);
#if !USCSHARP9
/// <summary>
@ -123,10 +129,10 @@ namespace TINK.Model.Connector
public LoginStateChangedEventArgs() : this(string.Empty, string.Empty)
{ }
public LoginStateChangedEventArgs(string p_strSessionCookie, string p_strMail)
public LoginStateChangedEventArgs(string sessionCookie, string mail)
{
SessionCookie = p_strSessionCookie;
Mail = p_strMail;
SessionCookie = sessionCookie;
Mail = mail;
}
public string SessionCookie { get; }

View file

@ -4,10 +4,9 @@ using Serilog.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using TINK.Model.Connector;
using TINK.Services.BluetoothLock;
using TINK.Model.Services.CopriApi.ServerUris;
using TINK.Model.Services.Geolocation;
using TINK.Services.Geolocation;
using TINK.Settings;
using TINK.ViewModel.Map;
using TINK.ViewModel.Settings;
@ -155,14 +154,14 @@ namespace TINK.Model.Settings
/// <summary> Sets the version of the app. </summary>
/// <param name="settingsJSON">Dictionary holding parameters from JSON.</param>
public static Dictionary<string, string> SetAppVersion(this IDictionary<string, string> p_oTargetDictionary, Version p_strAppVersion)
public static Dictionary<string, string> SetAppVersion(this IDictionary<string, string> targetDictionary, Version appVersion)
{
if (p_oTargetDictionary == null)
if (targetDictionary == null)
throw new Exception("Writing copri host uri to dictionary failed. Dictionary must not be null.");
return p_oTargetDictionary.Union(new Dictionary<string, string>
return targetDictionary.Union(new Dictionary<string, string>
{
{APPVERIONKEY , JsonConvert.SerializeObject(p_strAppVersion, new VersionConverter()) },
{APPVERIONKEY , JsonConvert.SerializeObject(appVersion, new VersionConverter()) },
}).ToDictionary(key => key.Key, value => value.Value);
}
@ -172,16 +171,16 @@ namespace TINK.Model.Settings
public static Version GetAppVersion(this IDictionary<string, string> settingsJSON)
{
// Get the version of the app which wrote the settings file.
if (!settingsJSON.TryGetValue(APPVERIONKEY, out string l_oAppVersion)
|| string.IsNullOrEmpty(l_oAppVersion))
if (!settingsJSON.TryGetValue(APPVERIONKEY, out string appVersion)
|| string.IsNullOrEmpty(appVersion))
{
// File holds no entry.
return null;
}
return l_oAppVersion.TrimStart().StartsWith("\"")
? JsonConvert.DeserializeObject<Version>(l_oAppVersion, new VersionConverter())
: Version.Parse(l_oAppVersion); // Format used up to version 3.0.0.115
return appVersion.TrimStart().StartsWith("\"")
? JsonConvert.DeserializeObject<Version>(appVersion, new VersionConverter())
: Version.Parse(appVersion); // Format used up to version 3.0.0.115
}
@ -220,21 +219,21 @@ namespace TINK.Model.Settings
}
/// <summary> Saves object to file. </summary>
/// <param name="p_SettingsList">Settings to save.</param>
public static void Serialize(string p_strSettingsFileFolder, IDictionary<string, string> p_SettingsList)
/// <param name="settingsList">Settings to save.</param>
public static void Serialize(string settingsFileFolder, IDictionary<string, string> settingsList)
{
// Save settings to file.
var l_oText = JsonConvert.SerializeObject(p_SettingsList, Formatting.Indented);
var l_oFolder = p_strSettingsFileFolder;
var l_oText = JsonConvert.SerializeObject(settingsList, Formatting.Indented);
var l_oFolder = settingsFileFolder;
System.IO.File.WriteAllText($"{l_oFolder}{System.IO.Path.DirectorySeparatorChar}{SETTINGSFILETITLE}", l_oText);
}
/// <summary> Gets TINK app settings form xml- file. </summary>
/// <param name="p_strSettingsDirectory">Directory to read settings from.</param>
/// <param name="settingsDirectory">Directory to read settings from.</param>
/// <returns>Dictionary of settings.</returns>
public static Dictionary<string, string> Deserialize(string p_strSettingsDirectory)
public static Dictionary<string, string> Deserialize(string settingsDirectory)
{
var l_oFileName = $"{p_strSettingsDirectory}{System.IO.Path.DirectorySeparatorChar}{SETTINGSFILETITLE}";
var l_oFileName = $"{settingsDirectory}{System.IO.Path.DirectorySeparatorChar}{SETTINGSFILETITLE}";
if (!System.IO.File.Exists(l_oFileName))
{

View file

@ -1,6 +1,6 @@
using Serilog.Events;
using System;
using TINK.Model.Services.Geolocation;
using TINK.Services.Geolocation;
using TINK.Services.BluetoothLock;
using TINK.Services.CopriApi.ServerUris;
using TINK.Settings;
@ -18,7 +18,8 @@ namespace TINK.Model.Settings
public const bool DEFAULTREPOTLEVEL = false;
/// <summary> Gets the type of the default geolocation service. </summary>
public static Type DefaultLocationService => typeof(GeolocationService);
/// <remarks> Swtiched from GeolocationService (GeolocationAccuracyMediumService) to GeolocationAccuracyHighService in app version 3.0.290.</remarks>
public static Type DefaultLocationService => typeof(GeolocationAccuracyHighService);
// Default value of the expires after entry. Controls the expiration time of the cache values.
private TimeSpan DEFAULTEXPIRESAFTER = TimeSpan.FromSeconds(1);

View file

@ -13,9 +13,8 @@ using Serilog;
using Plugin.Connectivity;
using System.Threading;
using TINK.Services.BluetoothLock;
using TINK.Model.Services.Geolocation;
using TINK.Services.Geolocation;
using TINK.Model.Services.CopriApi.ServerUris;
using Plugin.Permissions.Abstractions;
using TINK.Services.BluetoothLock.Crypto;
using TINK.ViewModel.Map;
using TINK.ViewModel.Settings;
@ -23,12 +22,16 @@ using TINK.Services;
using TINK.Services.BluetoothLock.BLE;
using Xamarin.Forms;
using TINK.Model.Station;
using TINK.Services.Permissions;
using Plugin.BLE.Abstractions.Contracts;
namespace TINK.Model
{
[DataContract]
public class TinkApp : ITinkApp
{
private const string PLATFORMANDROID = "ANDROID";
/// <summary> Delegate used by login view to commit user name and password. </summary>
/// <param name="p_strMailAddress">Mail address used as id login.</param>
/// <param name="p_strPassword">Password for login.</param>
@ -159,16 +162,17 @@ namespace TINK.Model
public TinkApp(
Settings.Settings settings,
IStore accountStore,
Func<bool> isConnectedFunc,
Func<bool, Uri, string /* session cookie*/, string /* mail address*/, TimeSpan, IConnector> connectorFactory,
string merchantId,
IServicesContainer<IGeolocation> geolocationServicesContainer,
IBluetoothLE bluetoothService,
ILocationPermission locationPermissionsService,
IServicesContainer<IGeolocation> locationServicesContainer,
ILocksService locksService,
ISmartDevice device,
ISpecialFolder specialFolder,
ICipher cipher,
IPermissions permissions = null,
object arendiCentral = null,
Func<bool> isConnectedFunc = null,
Action<SendOrPostCallback, object> postAction = null,
Version currentVersion = null,
Version lastVersion = null,
@ -185,11 +189,21 @@ namespace TINK.Model
Cipher = cipher ?? new Cipher();
bool GetIsAndroid(string platformText) => platformText.ToUpper().Contains(PLATFORMANDROID);
var locksServices = locksService != null
? new HashSet<ILocksService> { locksService }
: new HashSet<ILocksService> {
new LockItByScanServiceEventBased(Cipher),
new LockItByScanServicePolling(Cipher),
new LockItByScanServiceEventBased(
Cipher,
bluetoothService,
async () => GetIsAndroid(device.PlatformText) && await locationPermissionsService.CheckStatusAsync() != Status.Granted,
() => GetIsAndroid(device.PlatformText) && !locationServicesContainer.Active.IsGeolcationEnabled),
new LockItByScanServicePolling(
Cipher,
bluetoothService,
async () => GetIsAndroid(device.PlatformText) && await locationPermissionsService.CheckStatusAsync() != Status.Granted,
() => GetIsAndroid(device.PlatformText) && !locationServicesContainer.Active.IsGeolcationEnabled),
new LockItByGuidService(Cipher),
#if BLUETOOTHLE // Requires LockItBluetoothle library.
new Bluetoothle.LockItByGuidService(Cipher),
@ -216,7 +230,7 @@ namespace TINK.Model
},
settings.ActiveTheme);
GeolocationServices = geolocationServicesContainer
GeolocationServices = locationServicesContainer
?? throw new ArgumentException($"Can not instantiate {nameof(TinkApp)}- object. No geolocation services container object available.");
FilterGroupSetting = settings.GroupFilterSettings;

View file

@ -477,11 +477,43 @@ namespace TINK.Model
},
{
new Version(3, 0, 276),
AppResources.ChangeLog3_0_276
AppResources.ChangeLog3_0_276
},
{
new Version(3, 0, 277),
AppResources.ChangeLog3_0_277
},
{
new Version(3, 0, 279),
AppResources.ChangeLog3_0_278 // Addition spelling corrected and missing translation added.
},
{
new Version(3, 0, 280),
AppResources.ChangeLog3_0_280
},
{
new Version(3, 0, 281),
AppResources.ChangeLog3_0_280
},
{
new Version(3, 0, 283),
AppResources.ChangeLog3_0_282
},
{
new Version(3, 0, 284),
AppResources.ChangeLog3_0_284
},
{
new Version(3, 0, 285),
AppResources.ChangeLog3_0_285
},
{
new Version(3, 0, 289),
AppResources.ChangeLog3_0_289
},
{
new Version(3, 0, 290),
AppResources.ChangeLog3_0_290
}
};

View file

@ -195,6 +195,15 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Auth. expired.
/// </summary>
public static string ActivityTextAuthcookieNotDefinedException {
get {
return ResourceManager.GetString("ActivityTextAuthcookieNotDefinedException", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Loading bikes located at station....
/// </summary>
@ -411,6 +420,24 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Lock out of reach.
/// </summary>
public static string ActivityTextLockIsOutOfReach {
get {
return ResourceManager.GetString("ActivityTextLockIsOutOfReach", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Lock not found.
/// </summary>
public static string ActivityTextLockNotFound {
get {
return ResourceManager.GetString("ActivityTextLockNotFound", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Loading Stations and Bikes....
/// </summary>
@ -564,6 +591,15 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Starting bike return....
/// </summary>
public static string ActivityTextStartReturningBike {
get {
return ResourceManager.GetString("ActivityTextStartReturningBike", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Updateing.....
/// </summary>
@ -930,7 +966,75 @@ namespace TINK.MultilingualResources {
}
/// <summary>
/// Looks up a localized string similar to Lock of rented bike can not be found..
/// Looks up a localized string similar to Hints added to ease closing of lock in case closing does not succeed on first try..
/// </summary>
public static string ChangeLog3_0_278 {
get {
return ResourceManager.GetString("ChangeLog3_0_278", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Errormessages improved for szenarios when
///- bluetooth is off,
///- location permission is not granted or
///- location is off..
/// </summary>
public static string ChangeLog3_0_280 {
get {
return ResourceManager.GetString("ChangeLog3_0_280", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Permission requests messages on iOS formulated in more detail.
///Hints to ease connecting to lock in case connecting does not succeed on first try improved..
/// </summary>
public static string ChangeLog3_0_282 {
get {
return ResourceManager.GetString("ChangeLog3_0_282", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to User friendly message is shown if authentication expired..
/// </summary>
public static string ChangeLog3_0_284 {
get {
return ResourceManager.GetString("ChangeLog3_0_284", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Framework updated.
///Activity indicator added account management pages and logging extended..
/// </summary>
public static string ChangeLog3_0_285 {
get {
return ResourceManager.GetString("ChangeLog3_0_285", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Flyout menu header improved..
/// </summary>
public static string ChangeLog3_0_289 {
get {
return ResourceManager.GetString("ChangeLog3_0_289", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Geolocation is queried with higher accuracy..
/// </summary>
public static string ChangeLog3_0_290 {
get {
return ResourceManager.GetString("ChangeLog3_0_290", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Lock of rented bike cannot be be connected right now..
/// </summary>
public static string ErrorBookedSearchMessage {
get {
@ -938,6 +1042,40 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Lock of rented bike cannot be be connected right now.
///If the bike is nearby:
///- restart app and repeat action
///- restart phone and repeat action
///to connect the lock..
/// </summary>
public static string ErrorBookedSearchMessageEscalationLevel1 {
get {
return ResourceManager.GetString("ErrorBookedSearchMessageEscalationLevel1", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Lock of rented bike cannot be be connected right now.
///If the bike is nearby:
///- restart app and repeat action
///- restart phone and repeat action
///to connect the lock.
///
///Return bike manually.
///Steps 1: Close lock manually if lock is still open
///- close app
///- press on button at the top of the lock until it starts blinking
///- wait until lock is closed completely
///Step 2:
///- contact support regarding manual locking please to terminate rent.
/// </summary>
public static string ErrorBookedSearchMessageEscalationLevel2 {
get {
return ResourceManager.GetString("ErrorBookedSearchMessageEscalationLevel2", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Lock is blocked. Please ensure that no spoke or any other obstacle prevents the lock from closing and try again..
/// </summary>
@ -1012,6 +1150,87 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Communication error during lock search..
/// </summary>
public static string ErrorConnectLockGeneralErrorMessage {
get {
return ResourceManager.GetString("ErrorConnectLockGeneralErrorMessage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Internet must be reachable to connect to lock of rented bike..
/// </summary>
public static string ErrorConnectLockRentedBikeNoWebMessage {
get {
return ResourceManager.GetString("ErrorConnectLockRentedBikeNoWebMessage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Internet must be reachable to connect to lock of reserved bike..
/// </summary>
public static string ErrorConnectLockReservedBikeNoWebMessage {
get {
return ResourceManager.GetString("ErrorConnectLockReservedBikeNoWebMessage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Please turn Bluetooth on, otherwise lock cannot be connected..
/// </summary>
public static string ErrorFindLockBluetoothNotOn {
get {
return ResourceManager.GetString("ErrorFindLockBluetoothNotOn", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Please turn Location on, otherwise lock cannot be connected..
/// </summary>
public static string ErrorFindLockLocationOff {
get {
return ResourceManager.GetString("ErrorFindLockLocationOff", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Please grant location permissions, otherwise lock cannot be connected..
/// </summary>
public static string ErrorFindLockLocationPermissionMissing {
get {
return ResourceManager.GetString("ErrorFindLockLocationPermissionMissing", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Lock can only be found when rented bike is nearby..
/// </summary>
public static string ErrorFindLockRentedBikeOutOfReachMessage {
get {
return ResourceManager.GetString("ErrorFindLockRentedBikeOutOfReachMessage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Lock status of the reserved bike could not be determined..
/// </summary>
public static string ErrorFindLockReservedBikeNoStausMessage {
get {
return ResourceManager.GetString("ErrorFindLockReservedBikeNoStausMessage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Lock can only be found when reserved bike is nearby..
/// </summary>
public static string ErrorFindLockReservedBikeOutOfReachMessage {
get {
return ResourceManager.GetString("ErrorFindLockReservedBikeOutOfReachMessage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Lock is blocked. Please ensure that no obstacle prevents lock from opening and try again..
/// </summary>
@ -1033,9 +1252,9 @@ namespace TINK.MultilingualResources {
/// <summary>
/// Looks up a localized string similar to Lock cannot be opened until bike is near..
/// </summary>
public static string ErrorOpenLockOutOfReadMessage {
public static string ErrorOpenLockOutOfReachMessage {
get {
return ResourceManager.GetString("ErrorOpenLockOutOfReadMessage", resourceCulture);
return ResourceManager.GetString("ErrorOpenLockOutOfReachMessage", resourceCulture);
}
}
@ -1075,6 +1294,19 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Lock of reserved bike cannot be be connected right now.
///If the bike is nearby:
///- restart app and repeat action
///- restart phone and repeat action
///to connect the lock.
/// </summary>
public static string ErrorReservedSearchMessageEscalationLevel1 {
get {
return ResourceManager.GetString("ErrorReservedSearchMessageEscalationLevel1", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Returning bike at an unknown location is not possible.
///Bike can be returned if
@ -1106,12 +1338,30 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Internet must be available when returning the bike..
/// </summary>
public static string ErrorReturnBikeNoWebMessage {
get {
return ResourceManager.GetString("ErrorReturnBikeNoWebMessage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Connection error when returning the bike!.
/// </summary>
public static string ErrorReturnBikeNoWebTitle {
get {
return ResourceManager.GetString("ErrorReturnBikeNoWebTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Error returning bike!.
/// </summary>
public static string ErrorReturnBikeNotAtStationTitle {
public static string ErrorReturnBikeTitle {
get {
return ResourceManager.GetString("ErrorReturnBikeNotAtStationTitle", resourceCulture);
return ResourceManager.GetString("ErrorReturnBikeTitle", resourceCulture);
}
}
@ -1214,6 +1464,24 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to The session has expired. Please register again..
/// </summary>
public static string ExceptionTextSessionExpired {
get {
return ResourceManager.GetString("ExceptionTextSessionExpired", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Is WIFI available/ mobile networt available and mobile data activated / ... ?.
/// </summary>
public static string ExceptionTextWebConnectFailureException {
get {
return ResourceManager.GetString("ExceptionTextWebConnectFailureException", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Legal Information.
/// </summary>
@ -1514,6 +1782,33 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Log in.
/// </summary>
public static string MessageAccountPageManageLogin {
get {
return ResourceManager.GetString("MessageAccountPageManageLogin", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Manage personal data.
/// </summary>
public static string MessageAccountPageManagePersonalData {
get {
return ResourceManager.GetString("MessageAccountPageManagePersonalData", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Cancel.
/// </summary>
public static string MessageAnswerCancel {
get {
return ResourceManager.GetString("MessageAnswerCancel", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to No.
/// </summary>
@ -1532,6 +1827,15 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Retry.
/// </summary>
public static string MessageAnswerRetry {
get {
return ResourceManager.GetString("MessageAnswerRetry", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Yes.
/// </summary>
@ -1651,7 +1955,7 @@ namespace TINK.MultilingualResources {
}
/// <summary>
/// Looks up a localized string similar to Tariff {0}.
/// Looks up a localized string similar to Tariff.
/// </summary>
public static string MessageBikesManagementTariffDescriptionTariffHeader {
get {
@ -1678,6 +1982,15 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Error when connecting with lock!.
/// </summary>
public static string MessageConnectLockErrorTitle {
get {
return ResourceManager.GetString("MessageConnectLockErrorTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Questions? Remarks? Criticism?.
/// </summary>
@ -1697,6 +2010,15 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Error when connecting to lock!.
/// </summary>
public static string MessageErrorConnectTitle {
get {
return ResourceManager.GetString("MessageErrorConnectTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Attention: Lock is closed!
///{0}
@ -1846,9 +2168,7 @@ namespace TINK.MultilingualResources {
/// <summary>
/// Looks up a localized string similar to Session has expired.
///Either there are more than 8 devices in use or the user&apos;s account is no longer valid.
///Use of app is restricted to maximu 8 devices per account.
///Please login to app once again. In case this fails please check on website if the account is still valid..
///Please login to app once again..
/// </summary>
public static string MessageMapPageErrorAuthcookieUndefined {
get {

View file

@ -106,7 +106,7 @@
<value>Schloss kann erst geschlossen werden, wenn Rad nicht mehr bewegt wird. Bitte Rad abstellen und Vorgang wiederholen.</value>
</data>
<data name="ErrorBookedSearchMessage" xml:space="preserve">
<value>Schloss des gemieteten Rads kann nicht gefunden werden.</value>
<value>Das Schloss des gemieteten Fahrrads kann im Moment nicht verbunden werden.</value>
</data>
<data name="ErrorReservedSearchMessage" xml:space="preserve">
<value>Schloss des reservierten Rads kann nicht gefunden werden.</value>
@ -129,7 +129,7 @@
<data name="ErrorReturnBikeNotAtStationMessage" xml:space="preserve">
<value>Rückgabe ausserhalb von Station nicht möglich. Entfernung zur Station {0} ist {1} m.</value>
</data>
<data name="ErrorReturnBikeNotAtStationTitle" xml:space="preserve">
<data name="ErrorReturnBikeTitle" xml:space="preserve">
<value>Fehler bei Radrückgabe!</value>
</data>
<data name="ErrorReturnBikeLockClosedNoGPSMessage" xml:space="preserve">
@ -177,9 +177,7 @@ Eine Radrückgabe ist nur möglich, wenn das Rad in Reichweite ist und Standorti
</data>
<data name="MessageMapPageErrorAuthcookieUndefined" xml:space="preserve">
<value>Sitzung ist abgelaufen.
Entweder es sind mehr als 8 Geräte in Benutzung oder das Konto ist nicht mehr gültig.
Die Nutzung der App ist auf maximal 8 Geräten pro Konto möglich.
Bitte erneut in App anmelden. Sollte dies fehlschlagen bitte auf Website prüfen, ob das Konto noch gültig ist.</value>
Bitte erneut in App anmelden.</value>
</data>
<data name="StatusTextReservationExpiredCodeMaxReservationTime" xml:space="preserve">
<value>Code ist {0}, max. Reservierungszeit von {1} Min. abgelaufen.
@ -281,7 +279,7 @@ Bitte erneut in App anmelden. Sollte dies fehlschlagen bitte auf Website prüfen
<data name="ErrorOpenLockStillClosedMessage" xml:space="preserve">
<value>Nach Versuch Schloss zu öffnen wird Status geschlossen zurückgemeldet.</value>
</data>
<data name="ErrorOpenLockOutOfReadMessage" xml:space="preserve">
<data name="ErrorOpenLockOutOfReachMessage" xml:space="preserve">
<value>Schloss kann erst geöffnet werden, wenn Rad in der Nähe ist.</value>
</data>
<data name="ErrorCloseLockOutOfReachMessage" xml:space="preserve">
@ -484,7 +482,7 @@ Bitte App neu starten um Rad Infos zu bekommen.</value>
<value>Std./Tag</value>
</data>
<data name="MessageBikesManagementTariffDescriptionTariffHeader" xml:space="preserve">
<value>Tarif {0}</value>
<value>Tarif</value>
</data>
<data name="ActivityTextQuerryServer" xml:space="preserve">
<value>Anfrage Server...</value>
@ -751,4 +749,124 @@ Fehlerbehebung: Supportmails können wieder verschickt werden.</value>
<data name="ChangeLog3_0_277" xml:space="preserve">
<value>Fehlerbehebung: App schließt sich nicht mehr auf Fahrrad Wählen-Seite.</value>
</data>
<data name="ErrorBookedSearchMessageEscalationLevel1" xml:space="preserve">
<value>Das Schloss des gemieteten Fahrrads kann im Moment nicht verbunden werden.
Wenn das Fahrrad in der Nähe ist:
- App neu starten und Vorgang wiederholen
- Telefon neu starten und Vorgang wiederholen
um das Schloss zu verbinden.</value>
</data>
<data name="MessageAnswerCancel" xml:space="preserve">
<value>Abbrechen</value>
</data>
<data name="MessageAnswerRetry" xml:space="preserve">
<value>Wiederholen</value>
</data>
<data name="MessageConnectLockErrorTitle" xml:space="preserve">
<value>Fehler beim Verbinden mit Schloss!</value>
</data>
<data name="ErrorBookedSearchMessageEscalationLevel2" xml:space="preserve">
<value>Das Schloss des gemieteten Fahrrads kann im Moment nicht verbunden werden.
Wenn das Fahrrad in der Nähe ist:
- App neu starten und Vorgang wiederholen
- Telefon neu starten und Vorgang wiederholen
um das Schloss zu verbinden.
Manuelle Fahrradrückgabe.
Schitt 1: Schloss manuell schließen wenn nötig. Dazu
- App schließen
- auf den Knopf oben am Schloss drücken, bis dieser zu blinken beginnt
- warten, bis das Schloss vollständig geschlossen ist
Schritt 2:
- Support bitte bezüglich der manuellen Schließung kontaktieren um Miete abzuschließen</value>
</data>
<data name="ChangeLog3_0_278" xml:space="preserve">
<value>Hinweise hinzugefügt, um das Schließen des Schlosses zu erleichtern, falls dies nicht beim ersten Versuch gelingt.</value>
</data>
<data name="ActivityTextLockIsOutOfReach" xml:space="preserve">
<value>Schloss außerhalb Reichweite</value>
</data>
<data name="ActivityTextLockNotFound" xml:space="preserve">
<value>Schloss nicht gefunden</value>
</data>
<data name="ErrorFindLockRentedBikeOutOfReachMessage" xml:space="preserve">
<value>Schloss kann erst gefunden werden, wenn gemietetes Rad in der Nähe ist.</value>
</data>
<data name="ErrorFindLockReservedBikeOutOfReachMessage" xml:space="preserve">
<value>Schloss kann erst gefunden werden, wenn reserviertes Rad in der Nähe ist.</value>
</data>
<data name="MessageErrorConnectTitle" xml:space="preserve">
<value>Fehler bei Verbinden mit Schloss!</value>
</data>
<data name="ErrorConnectLockGeneralErrorMessage" xml:space="preserve">
<value>Kommunikationsfehler bei Schlosssuche.</value>
</data>
<data name="ErrorConnectLockRentedBikeNoWebMessage" xml:space="preserve">
<value>Internet muss erreichbar sein um Verbindung mit Schloss für gemietetes Rad herzustellen.</value>
</data>
<data name="ErrorConnectLockReservedBikeNoWebMessage" xml:space="preserve">
<value>Internet muss erreichbar sein um Verbindung mit Schloss für reserviertes Rad herzustellen.</value>
</data>
<data name="ErrorFindLockReservedBikeNoStausMessage" xml:space="preserve">
<value>Schlossstatus des reservierten Rads konnte nicht ermittelt werden.</value>
</data>
<data name="ErrorFindLockBluetoothNotOn" xml:space="preserve">
<value>Bitte Bluetooth anschalten, anderenfalls ist eine Verbindung mit dem Schloss nicht möglich.</value>
</data>
<data name="ErrorFindLockLocationPermissionMissing" xml:space="preserve">
<value>Bitte Standort-Zugriffsfreigabe erteilen, anderenfalls ist eine Verbindung mit dem Schloss nicht möglich.
</value>
</data>
<data name="ErrorFindLockLocationOff" xml:space="preserve">
<value>Bitte Standortbestimmung aktivieren, anderenfalls ist eine Verbindung mit dem Schloss nicht möglich.</value>
</data>
<data name="ChangeLog3_0_280" xml:space="preserve">
<value>Fehlermeldungen verbessert für die Fälle, dass
- Bluetooth deaktiviert ist,
- die Standortfreigabe nicht erteilt wurde oder
- die Standorterkennung deaktiviert ist.</value>
</data>
<data name="ErrorReservedSearchMessageEscalationLevel1" xml:space="preserve">
<value>Das Schloss des reservierten Fahrrads kann im Moment nicht verbunden werden.
Wenn das Fahrrad in der Nähe ist:
- App neu starten und Vorgang wiederholen
- Telefon neu starten und Vorgang wiederholen
um das Schloss zu verbinden.</value>
</data>
<data name="ChangeLog3_0_282" xml:space="preserve">
<value>Freigabe-Erlaubnisanfragen unter iOS ausführlicher formuliert.
Hinweise, um das Verbinden Schlosses zu erleichtern, falls dies nicht beim ersten Versuch gelingt, erweitert.</value>
</data>
<data name="ExceptionTextSessionExpired" xml:space="preserve">
<value>Die Sitzung ist abgelaufen. Bitte neu anmelden.</value>
</data>
<data name="ActivityTextAuthcookieNotDefinedException" xml:space="preserve">
<value>Auth. abgelaufen</value>
</data>
<data name="ChangeLog3_0_284" xml:space="preserve">
<value>Anzeige einer benutzerfreundlichen Meldung, wenn die Authentifizierung abgelaufen ist.</value>
</data>
<data name="MessageAccountPageManageLogin" xml:space="preserve">
<value>Anmelden</value>
</data>
<data name="MessageAccountPageManagePersonalData" xml:space="preserve">
<value>Persönliche Daten Verwalten</value>
</data>
<data name="ChangeLog3_0_285" xml:space="preserve">
<value>Framework aktualisiert.
Activity Indicator zu Konto-Verwaltungsseiten hinzugefügt und Logging erweitert.</value>
</data>
<data name="ActivityTextStartReturningBike" xml:space="preserve">
<value>Starte Rückgabe...</value>
</data>
<data name="ExceptionTextWebConnectFailureException" xml:space="preserve">
<value>Ist WLAN verfügbar/ Mobilfunknetz vefügbar und mobile Daten aktiviert / ... ?</value>
</data>
<data name="ChangeLog3_0_289" xml:space="preserve">
<value>Flyout-Menü Überschift verschönert.</value>
</data>
<data name="ChangeLog3_0_290" xml:space="preserve">
<value>Der aktuelle Standort wird mit höherer Genauigkeit abgefragt.</value>
</data>
</root>

View file

@ -148,7 +148,7 @@
<value>Loading Stations and Bikes...</value>
</data>
<data name="ErrorBookedSearchMessage" xml:space="preserve">
<value>Lock of rented bike can not be found.</value>
<value>Lock of rented bike cannot be be connected right now.</value>
</data>
<data name="ErrorCloseLockBoldBlockedMessage" xml:space="preserve">
<value>Lock is blocked. Please ensure that no spoke or any other obstacle prevents the lock from closing and try again.</value>
@ -172,7 +172,7 @@ Bike can only be returned if bike is in reach and location information is avail
<data name="ErrorReturnBikeNotAtStationMessage" xml:space="preserve">
<value>Returning bike outside of station is not possible. Distance to station {0} is {1} m.</value>
</data>
<data name="ErrorReturnBikeNotAtStationTitle" xml:space="preserve">
<data name="ErrorReturnBikeTitle" xml:space="preserve">
<value>Error returning bike!</value>
</data>
<data name="ErrorSupportmailCreateAttachment" xml:space="preserve">
@ -243,9 +243,7 @@ Bike can only be returned if bike is in reach and location information is avail
</data>
<data name="MessageMapPageErrorAuthcookieUndefined" xml:space="preserve">
<value>Session has expired.
Either there are more than 8 devices in use or the user's account is no longer valid.
Use of app is restricted to maximu 8 devices per account.
Please login to app once again. In case this fails please check on website if the account is still valid.</value>
Please login to app once again.</value>
</data>
<data name="MessagePhoneMail" xml:space="preserve">
<value>Urgent questions?</value>
@ -383,7 +381,7 @@ Please login to app once again. In case this fails please check on website if th
<data name="ErrorOpenLockStillClosedMessage" xml:space="preserve">
<value>After try to open lock state closed is reported.</value>
</data>
<data name="ErrorOpenLockOutOfReadMessage" xml:space="preserve">
<data name="ErrorOpenLockOutOfReachMessage" xml:space="preserve">
<value>Lock cannot be opened until bike is near.</value>
</data>
<data name="ErrorOpenLockTitle" xml:space="preserve">
@ -593,7 +591,7 @@ Please restart app in order to get bike info.</value>
<value>Max. fee</value>
</data>
<data name="MessageBikesManagementTariffDescriptionTariffHeader" xml:space="preserve">
<value>Tariff {0}</value>
<value>Tariff</value>
</data>
<data name="ActivityTextQuerryServer" xml:space="preserve">
<value>Request server...</value>
@ -846,4 +844,122 @@ Bugfix: Sending support mails works again. </value>
<data name="ChangeLog3_0_277" xml:space="preserve">
<value>Bugfix: No more closing of app on Select Bike page.</value>
</data>
<data name="ErrorBookedSearchMessageEscalationLevel1" xml:space="preserve">
<value>Lock of rented bike cannot be be connected right now.
If the bike is nearby:
- restart app and repeat action
- restart phone and repeat action
to connect the lock.</value>
</data>
<data name="MessageAnswerCancel" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="MessageAnswerRetry" xml:space="preserve">
<value>Retry</value>
</data>
<data name="MessageConnectLockErrorTitle" xml:space="preserve">
<value>Error when connecting with lock!</value>
</data>
<data name="ErrorBookedSearchMessageEscalationLevel2" xml:space="preserve">
<value>Lock of rented bike cannot be be connected right now.
If the bike is nearby:
- restart app and repeat action
- restart phone and repeat action
to connect the lock.
Return bike manually.
Steps 1: Close lock manually if lock is still open
- close app
- press on button at the top of the lock until it starts blinking
- wait until lock is closed completely
Step 2:
- contact support regarding manual locking please to terminate rent</value>
</data>
<data name="ChangeLog3_0_278" xml:space="preserve">
<value>Hints added to ease closing of lock in case closing does not succeed on first try.</value>
</data>
<data name="ActivityTextLockIsOutOfReach" xml:space="preserve">
<value>Lock out of reach</value>
</data>
<data name="ActivityTextLockNotFound" xml:space="preserve">
<value>Lock not found</value>
</data>
<data name="ErrorFindLockRentedBikeOutOfReachMessage" xml:space="preserve">
<value>Lock can only be found when rented bike is nearby.</value>
</data>
<data name="ErrorFindLockReservedBikeOutOfReachMessage" xml:space="preserve">
<value>Lock can only be found when reserved bike is nearby.</value>
</data>
<data name="MessageErrorConnectTitle" xml:space="preserve">
<value>Error when connecting to lock!</value>
</data>
<data name="ErrorConnectLockGeneralErrorMessage" xml:space="preserve">
<value>Communication error during lock search.</value>
</data>
<data name="ErrorConnectLockRentedBikeNoWebMessage" xml:space="preserve">
<value>Internet must be reachable to connect to lock of rented bike.</value>
</data>
<data name="ErrorConnectLockReservedBikeNoWebMessage" xml:space="preserve">
<value>Internet must be reachable to connect to lock of reserved bike.</value>
</data>
<data name="ErrorFindLockReservedBikeNoStausMessage" xml:space="preserve">
<value>Lock status of the reserved bike could not be determined.</value>
</data>
<data name="ErrorFindLockBluetoothNotOn" xml:space="preserve">
<value>Please turn Bluetooth on, otherwise lock cannot be connected.</value>
</data>
<data name="ErrorFindLockLocationPermissionMissing" xml:space="preserve">
<value>Please grant location permissions, otherwise lock cannot be connected.</value>
</data>
<data name="ErrorFindLockLocationOff" xml:space="preserve">
<value>Please turn Location on, otherwise lock cannot be connected.</value>
</data>
<data name="ChangeLog3_0_280" xml:space="preserve">
<value>Errormessages improved for szenarios when
- bluetooth is off,
- location permission is not granted or
- location is off.</value>
</data>
<data name="ErrorReservedSearchMessageEscalationLevel1" xml:space="preserve">
<value>Lock of reserved bike cannot be be connected right now.
If the bike is nearby:
- restart app and repeat action
- restart phone and repeat action
to connect the lock</value>
</data>
<data name="ChangeLog3_0_282" xml:space="preserve">
<value>Permission requests messages on iOS formulated in more detail.
Hints to ease connecting to lock in case connecting does not succeed on first try improved.</value>
</data>
<data name="ExceptionTextSessionExpired" xml:space="preserve">
<value>The session has expired. Please register again.</value>
</data>
<data name="ActivityTextAuthcookieNotDefinedException" xml:space="preserve">
<value>Auth. expired</value>
</data>
<data name="ChangeLog3_0_284" xml:space="preserve">
<value>User friendly message is shown if authentication expired.</value>
</data>
<data name="MessageAccountPageManageLogin" xml:space="preserve">
<value>Log in</value>
</data>
<data name="MessageAccountPageManagePersonalData" xml:space="preserve">
<value>Manage personal data</value>
</data>
<data name="ChangeLog3_0_285" xml:space="preserve">
<value>Framework updated.
Activity indicator added account management pages and logging extended.</value>
</data>
<data name="ActivityTextStartReturningBike" xml:space="preserve">
<value>Starting bike return...</value>
</data>
<data name="ExceptionTextWebConnectFailureException" xml:space="preserve">
<value>Is WIFI available/ mobile networt available and mobile data activated / ... ?</value>
</data>
<data name="ChangeLog3_0_289" xml:space="preserve">
<value>Flyout menu header improved.</value>
</data>
<data name="ChangeLog3_0_290" xml:space="preserve">
<value>Geolocation is queried with higher accuracy.</value>
</data>
</root>

View file

@ -131,8 +131,8 @@
<target state="final">Schloss kann erst geschlossen werden, wenn Rad nicht mehr bewegt wird. Bitte Rad abstellen und Vorgang wiederholen.</target>
</trans-unit>
<trans-unit id="ErrorBookedSearchMessage" translate="yes" xml:space="preserve">
<source>Lock of rented bike can not be found.</source>
<target state="final">Schloss des gemieteten Rads kann nicht gefunden werden.</target>
<source>Lock of rented bike cannot be be connected right now.</source>
<target state="translated">Das Schloss des gemieteten Fahrrads kann im Moment nicht verbunden werden.</target>
</trans-unit>
<trans-unit id="ErrorReservedSearchMessage" translate="yes" xml:space="preserve">
<source>Lock of reserved bike can not be found.</source>
@ -162,7 +162,7 @@
<source>Returning bike outside of station is not possible. Distance to station {0} is {1} m.</source>
<target state="translated">Rückgabe ausserhalb von Station nicht möglich. Entfernung zur Station {0} ist {1} m.</target>
</trans-unit>
<trans-unit id="ErrorReturnBikeNotAtStationTitle" translate="yes" xml:space="preserve">
<trans-unit id="ErrorReturnBikeTitle" translate="yes" xml:space="preserve">
<source>Error returning bike!</source>
<target state="translated">Fehler bei Radrückgabe!</target>
</trans-unit>
@ -228,13 +228,9 @@ Eine Radrückgabe ist nur möglich, wenn das Rad in Reichweite ist und Standorti
</trans-unit>
<trans-unit id="MessageMapPageErrorAuthcookieUndefined" translate="yes" xml:space="preserve">
<source>Session has expired.
Either there are more than 8 devices in use or the user's account is no longer valid.
Use of app is restricted to maximu 8 devices per account.
Please login to app once again. In case this fails please check on website if the account is still valid.</source>
Please login to app once again.</source>
<target state="translated">Sitzung ist abgelaufen.
Entweder es sind mehr als 8 Geräte in Benutzung oder das Konto ist nicht mehr gültig.
Die Nutzung der App ist auf maximal 8 Geräten pro Konto möglich.
Bitte erneut in App anmelden. Sollte dies fehlschlagen bitte auf Website prüfen, ob das Konto noch gültig ist.</target>
Bitte erneut in App anmelden.</target>
</trans-unit>
<trans-unit id="StatusTextReservationExpiredCodeMaxReservationTime" translate="yes" xml:space="preserve">
<source>Code {0}, max. reservation time of {1} minutes expired.</source>
@ -368,7 +364,7 @@ Bitte erneut in App anmelden. Sollte dies fehlschlagen bitte auf Website prüfen
<source>After try to open lock state closed is reported.</source>
<target state="translated">Nach Versuch Schloss zu öffnen wird Status geschlossen zurückgemeldet.</target>
</trans-unit>
<trans-unit id="ErrorOpenLockOutOfReadMessage" translate="yes" xml:space="preserve">
<trans-unit id="ErrorOpenLockOutOfReachMessage" translate="yes" xml:space="preserve">
<source>Lock cannot be opened until bike is near.</source>
<target state="translated">Schloss kann erst geöffnet werden, wenn Rad in der Nähe ist.</target>
</trans-unit>
@ -645,8 +641,8 @@ Bitte App neu starten um Rad Infos zu bekommen.</target>
<target state="translated">Std./Tag</target>
</trans-unit>
<trans-unit id="MessageBikesManagementTariffDescriptionTariffHeader" translate="yes" xml:space="preserve">
<source>Tariff {0}</source>
<target state="translated">Tarif {0}</target>
<source>Tariff</source>
<target state="translated">Tarif</target>
</trans-unit>
<trans-unit id="ActivityTextQuerryServer" translate="yes" xml:space="preserve">
<source>Request server...</source>
@ -1010,6 +1006,182 @@ Fehlerbehebung: Supportmails können wieder verschickt werden.</target>
<source>Bugfix: No more closing of app on Select Bike page.</source>
<target state="translated">Fehlerbehebung: App schließt sich nicht mehr auf Fahrrad Wählen-Seite.</target>
</trans-unit>
<trans-unit id="ErrorBookedSearchMessageEscalationLevel1" translate="yes" xml:space="preserve">
<source>Lock of rented bike cannot be be connected right now.
If the bike is nearby:
- restart app and repeat action
- restart phone and repeat action
to connect the lock.</source>
<target state="translated">Das Schloss des gemieteten Fahrrads kann im Moment nicht verbunden werden.
Wenn das Fahrrad in der Nähe ist:
- App neu starten und Vorgang wiederholen
- Telefon neu starten und Vorgang wiederholen
um das Schloss zu verbinden.</target>
</trans-unit>
<trans-unit id="MessageAnswerCancel" translate="yes" xml:space="preserve">
<source>Cancel</source>
<target state="translated">Abbrechen</target>
</trans-unit>
<trans-unit id="MessageAnswerRetry" translate="yes" xml:space="preserve">
<source>Retry</source>
<target state="translated">Wiederholen</target>
</trans-unit>
<trans-unit id="MessageConnectLockErrorTitle" translate="yes" xml:space="preserve">
<source>Error when connecting with lock!</source>
<target state="translated">Fehler beim Verbinden mit Schloss!</target>
</trans-unit>
<trans-unit id="ErrorBookedSearchMessageEscalationLevel2" translate="yes" xml:space="preserve">
<source>Lock of rented bike cannot be be connected right now.
If the bike is nearby:
- restart app and repeat action
- restart phone and repeat action
to connect the lock.
Return bike manually.
Steps 1: Close lock manually if lock is still open
- close app
- press on button at the top of the lock until it starts blinking
- wait until lock is closed completely
Step 2:
- contact support regarding manual locking please to terminate rent</source>
<target state="translated">Das Schloss des gemieteten Fahrrads kann im Moment nicht verbunden werden.
Wenn das Fahrrad in der Nähe ist:
- App neu starten und Vorgang wiederholen
- Telefon neu starten und Vorgang wiederholen
um das Schloss zu verbinden.
Manuelle Fahrradrückgabe.
Schitt 1: Schloss manuell schließen wenn nötig. Dazu
- App schließen
- auf den Knopf oben am Schloss drücken, bis dieser zu blinken beginnt
- warten, bis das Schloss vollständig geschlossen ist
Schritt 2:
- Support bitte bezüglich der manuellen Schließung kontaktieren um Miete abzuschließen</target>
</trans-unit>
<trans-unit id="ChangeLog3_0_278" translate="yes" xml:space="preserve">
<source>Hints added to ease closing of lock in case closing does not succeed on first try.</source>
<target state="translated">Hinweise hinzugefügt, um das Schließen des Schlosses zu erleichtern, falls dies nicht beim ersten Versuch gelingt.</target>
</trans-unit>
<trans-unit id="ActivityTextLockIsOutOfReach" translate="yes" xml:space="preserve">
<source>Lock out of reach</source>
<target state="translated">Schloss außerhalb Reichweite</target>
</trans-unit>
<trans-unit id="ActivityTextLockNotFound" translate="yes" xml:space="preserve">
<source>Lock not found</source>
<target state="translated">Schloss nicht gefunden</target>
</trans-unit>
<trans-unit id="ErrorFindLockRentedBikeOutOfReachMessage" translate="yes" xml:space="preserve">
<source>Lock can only be found when rented bike is nearby.</source>
<target state="translated">Schloss kann erst gefunden werden, wenn gemietetes Rad in der Nähe ist.</target>
</trans-unit>
<trans-unit id="ErrorFindLockReservedBikeOutOfReachMessage" translate="yes" xml:space="preserve">
<source>Lock can only be found when reserved bike is nearby.</source>
<target state="translated">Schloss kann erst gefunden werden, wenn reserviertes Rad in der Nähe ist.</target>
</trans-unit>
<trans-unit id="MessageErrorConnectTitle" translate="yes" xml:space="preserve">
<source>Error when connecting to lock!</source>
<target state="translated">Fehler bei Verbinden mit Schloss!</target>
</trans-unit>
<trans-unit id="ErrorConnectLockGeneralErrorMessage" translate="yes" xml:space="preserve">
<source>Communication error during lock search.</source>
<target state="translated">Kommunikationsfehler bei Schlosssuche.</target>
</trans-unit>
<trans-unit id="ErrorConnectLockRentedBikeNoWebMessage" translate="yes" xml:space="preserve">
<source>Internet must be reachable to connect to lock of rented bike.</source>
<target state="translated">Internet muss erreichbar sein um Verbindung mit Schloss für gemietetes Rad herzustellen.</target>
</trans-unit>
<trans-unit id="ErrorConnectLockReservedBikeNoWebMessage" translate="yes" xml:space="preserve">
<source>Internet must be reachable to connect to lock of reserved bike.</source>
<target state="translated">Internet muss erreichbar sein um Verbindung mit Schloss für reserviertes Rad herzustellen.</target>
</trans-unit>
<trans-unit id="ErrorFindLockReservedBikeNoStausMessage" translate="yes" xml:space="preserve">
<source>Lock status of the reserved bike could not be determined.</source>
<target state="translated">Schlossstatus des reservierten Rads konnte nicht ermittelt werden.</target>
</trans-unit>
<trans-unit id="ErrorFindLockBluetoothNotOn" translate="yes" xml:space="preserve">
<source>Please turn Bluetooth on, otherwise lock cannot be connected.</source>
<target state="translated">Bitte Bluetooth anschalten, anderenfalls ist eine Verbindung mit dem Schloss nicht möglich.</target>
</trans-unit>
<trans-unit id="ErrorFindLockLocationPermissionMissing" translate="yes" xml:space="preserve">
<source>Please grant location permissions, otherwise lock cannot be connected.</source>
<target state="translated">Bitte Standort-Zugriffsfreigabe erteilen, anderenfalls ist eine Verbindung mit dem Schloss nicht möglich.
</target>
</trans-unit>
<trans-unit id="ErrorFindLockLocationOff" translate="yes" xml:space="preserve">
<source>Please turn Location on, otherwise lock cannot be connected.</source>
<target state="translated">Bitte Standortbestimmung aktivieren, anderenfalls ist eine Verbindung mit dem Schloss nicht möglich.</target>
</trans-unit>
<trans-unit id="ChangeLog3_0_280" translate="yes" xml:space="preserve">
<source>Errormessages improved for szenarios when
- bluetooth is off,
- location permission is not granted or
- location is off.</source>
<target state="translated">Fehlermeldungen verbessert für die Fälle, dass
- Bluetooth deaktiviert ist,
- die Standortfreigabe nicht erteilt wurde oder
- die Standorterkennung deaktiviert ist.</target>
</trans-unit>
<trans-unit id="ErrorReservedSearchMessageEscalationLevel1" translate="yes" xml:space="preserve">
<source>Lock of reserved bike cannot be be connected right now.
If the bike is nearby:
- restart app and repeat action
- restart phone and repeat action
to connect the lock</source>
<target state="translated">Das Schloss des reservierten Fahrrads kann im Moment nicht verbunden werden.
Wenn das Fahrrad in der Nähe ist:
- App neu starten und Vorgang wiederholen
- Telefon neu starten und Vorgang wiederholen
um das Schloss zu verbinden.</target>
</trans-unit>
<trans-unit id="ChangeLog3_0_282" translate="yes" xml:space="preserve">
<source>Permission requests messages on iOS formulated in more detail.
Hints to ease connecting to lock in case connecting does not succeed on first try improved.</source>
<target state="translated">Freigabe-Erlaubnisanfragen unter iOS ausführlicher formuliert.
Hinweise, um das Verbinden Schlosses zu erleichtern, falls dies nicht beim ersten Versuch gelingt, erweitert.</target>
</trans-unit>
<trans-unit id="ExceptionTextSessionExpired" translate="yes" xml:space="preserve">
<source>The session has expired. Please register again.</source>
<target state="translated">Die Sitzung ist abgelaufen. Bitte neu anmelden.</target>
</trans-unit>
<trans-unit id="ActivityTextAuthcookieNotDefinedException" translate="yes" xml:space="preserve">
<source>Auth. expired</source>
<target state="translated">Auth. abgelaufen</target>
</trans-unit>
<trans-unit id="ChangeLog3_0_284" translate="yes" xml:space="preserve">
<source>User friendly message is shown if authentication expired.</source>
<target state="translated">Anzeige einer benutzerfreundlichen Meldung, wenn die Authentifizierung abgelaufen ist.</target>
</trans-unit>
<trans-unit id="MessageAccountPageManageLogin" translate="yes" xml:space="preserve">
<source>Log in</source>
<target state="translated">Anmelden</target>
</trans-unit>
<trans-unit id="MessageAccountPageManagePersonalData" translate="yes" xml:space="preserve">
<source>Manage personal data</source>
<target state="translated">Persönliche Daten Verwalten</target>
</trans-unit>
<trans-unit id="ChangeLog3_0_285" translate="yes" xml:space="preserve">
<source>Framework updated.
Activity indicator added account management pages and logging extended.</source>
<target state="translated">Framework aktualisiert.
Activity Indicator zu Konto-Verwaltungsseiten hinzugefügt und Logging erweitert.</target>
</trans-unit>
<trans-unit id="ActivityTextStartReturningBike" translate="yes" xml:space="preserve">
<source>Starting bike return...</source>
<target state="translated">Starte Rückgabe...</target>
</trans-unit>
<trans-unit id="ExceptionTextWebConnectFailureException" translate="yes" xml:space="preserve">
<source>Is WIFI available/ mobile networt available and mobile data activated / ... ?</source>
<target state="translated">Ist WLAN verfügbar/ Mobilfunknetz vefügbar und mobile Daten aktiviert / ... ?</target>
</trans-unit>
<trans-unit id="ChangeLog3_0_289" translate="yes" xml:space="preserve">
<source>Flyout menu header improved.</source>
<target state="translated">Flyout-Menü Überschift verschönert.</target>
</trans-unit>
<trans-unit id="ChangeLog3_0_290" translate="yes" xml:space="preserve">
<source>Geolocation is queried with higher accuracy.</source>
<target state="translated">Der aktuelle Standort wird mit höherer Genauigkeit abgefragt.</target>
</trans-unit>
</group>
</body>
</file>

View file

@ -158,6 +158,19 @@ namespace TINK.Repository
requestBuilder.CalculateAuthParameters(bikeId),
UserAgent);
/// <summary> Notifies COPRI about start of returning sequence. </summary>
/// <remarks> Operator specific call.</remarks>
/// <param name="bikeId">Id of the bike to return.</param>+
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
/// <returns>Response on notification about start of returning sequence.</returns>
public async Task<ResponseBase> StartReturningBike(
string bikeId,
Uri operatorUri)
=> await DoStartReturningBike(
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
requestBuilder.StartReturningBike(bikeId),
UserAgent);
/// <summary> Updates lock state for a booked bike. </summary>
/// <param name="bikeId">Id of the bike to update locking state for.</param>
/// <param name="location">Geolocation of lock.</param>
@ -170,13 +183,11 @@ namespace TINK.Repository
LocationDto location,
lock_state state,
double batteryLevel,
Uri operatorUri)
{
return await DoUpdateLockingStateAsync(
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
requestBuilder.UpateLockingState(bikeId, location, state, batteryLevel),
UserAgent);
}
Uri operatorUri)=>
await DoUpdateLockingStateAsync(
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
requestBuilder.UpateLockingState(bikeId, location, state, batteryLevel),
UserAgent);
/// <summary> Gets booking request request. </summary>
/// <param name="bikeId">Id of the bike to book.</param>
@ -547,34 +558,67 @@ namespace TINK.Repository
#endif
}
public static async Task<ReservationBookingResponse> DoUpdateLockingStateAsync(
public static async Task<ResponseBase> DoStartReturningBike(
string copriHost,
string command,
string agent = null)
{
#if !WINDOWS_UWP
string l_oBikesAvaialbeResponse;
string response;
try
{
l_oBikesAvaialbeResponse = await PostAsync(copriHost, command, agent);
response = await PostAsync(copriHost, command, agent);
}
catch (System.Exception l_oException)
catch (System.Exception exception)
{
if (l_oException.GetIsConnectFailureException())
if (exception.GetIsConnectFailureException())
{
throw new WebConnectFailureException("Aktualisierung des Schlossstatuses wegen Netzwerkfehler fehlgeschlagen.", l_oException);
throw new WebConnectFailureException("Benachrichtigung von Start der Rückgabe wegen Netzwerkfehler fehlgeschlagen.", exception);
}
if (l_oException.GetIsForbiddenException())
if (exception.GetIsForbiddenException())
{
throw new WebForbiddenException("Aktualisierung des Schlossstatuses wegen Netzwerkfehler fehlgeschlagen.", l_oException);
throw new WebForbiddenException("Benachrichtigung von Start der Rückgabe wegen Netzwerkfehler fehlgeschlagen.", exception);
}
throw;
}
// Extract bikes from response.
return JsonConvertRethrow.DeserializeObject<ResponseContainer<ReservationBookingResponse>>(l_oBikesAvaialbeResponse)?.shareejson;
return JsonConvertRethrow.DeserializeObject<ResponseContainer<ResponseBase>>(response)?.shareejson;
#else
return null;
#endif
}
public static async Task<ReservationBookingResponse> DoUpdateLockingStateAsync(
string copriHost,
string command,
string agent = null)
{
#if !WINDOWS_UWP
string bikesAvaialbeResponse;
try
{
bikesAvaialbeResponse = await PostAsync(copriHost, command, agent);
}
catch (System.Exception exception)
{
if (exception.GetIsConnectFailureException())
{
throw new WebConnectFailureException("Aktualisierung des Schlossstatuses wegen Netzwerkfehler fehlgeschlagen.", exception);
}
if (exception.GetIsForbiddenException())
{
throw new WebForbiddenException("Aktualisierung des Schlossstatuses wegen Netzwerkfehler fehlgeschlagen.", exception);
}
throw;
}
// Extract bikes from response.
return JsonConvertRethrow.DeserializeObject<ResponseContainer<ReservationBookingResponse>>(bikesAvaialbeResponse)?.shareejson;
#else
return null;
#endif
@ -591,16 +635,16 @@ namespace TINK.Repository
{
l_oBikesAvaialbeResponse = await PostAsync(copriHost, command, agent);
}
catch (System.Exception l_oException)
catch (System.Exception exception)
{
if (l_oException.GetIsConnectFailureException())
if (exception.GetIsConnectFailureException())
{
throw new WebConnectFailureException("Buchung des Fahrrads wegen Netzwerkfehler fehlgeschlagen.", l_oException);
throw new WebConnectFailureException("Buchung des Fahrrads wegen Netzwerkfehler fehlgeschlagen.", exception);
}
if (l_oException.GetIsForbiddenException())
if (exception.GetIsForbiddenException())
{
throw new WebForbiddenException("Buchung des Fahrrads wegen Netzwerkfehler fehlgeschlagen.", l_oException);
throw new WebForbiddenException("Buchung des Fahrrads wegen Netzwerkfehler fehlgeschlagen.", exception);
}
throw;

View file

@ -1613,6 +1613,10 @@ namespace TINK.Repository
public Task<ReservationBookingResponse> CalculateAuthKeysAsync(string bikeId, Uri operatorUri) => null;
public Task<ResponseBase> StartReturningBike(
string bikeId,
Uri operatorUri) => null;
public Task<ReservationBookingResponse> UpdateLockingStateAsync(
string bikeId,
LocationDto geolocation,

View file

@ -159,6 +159,11 @@ namespace TINK.Repository
public Task<ReservationBookingResponse> CalculateAuthKeysAsync(string bikeId, Uri operatorUri)
=> throw new System.Exception("Schlosssuche im Offlinemodus nicht möglich!");
public Task<ResponseBase> StartReturningBike(
string bikeId,
Uri operatorUri)
=> throw new System.Exception("Benachrichtigung von start der Rückgabe im Offlinemodus nicht möglich!");
public Task<ReservationBookingResponse> UpdateLockingStateAsync(
string bikeId,
LocationDto geolocation,

View file

@ -1,4 +1,6 @@
namespace TINK.Repository.Exception
using TINK.MultilingualResources;
namespace TINK.Repository.Exception
{
public class WebConnectFailureException : CommunicationException
{
@ -6,13 +8,9 @@
/// Returns a hint to fix communication problem.
/// </summary>
public static string GetHintToPossibleExceptionsReasons
{
get
{
return "Ist WLAN verfügbar/ Mobilfunknetz vefügbar und mobile Daten aktiviert / ... ?";
}
}
/// <summary>
=> AppResources.ExceptionTextWebConnectFailureException;
/// <summary>
/// Constructs a communication exeption object.
/// </summary>
/// <param name="p_strMessage"></param>

View file

@ -48,6 +48,15 @@ namespace TINK.Repository
string bikeId,
Uri operatorUri);
/// <summary> Notifies COPRI about start of returning sequence. </summary>
/// <remarks> Operator specific call.</remarks>
/// <param name="bikeId">Id of the bike to return.</param>+
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
/// <returns>Response on notification about start of returning sequence.</returns>
Task<ResponseBase> StartReturningBike(
string bikeId,
Uri operatorUri);
/// <summary> Updates COPRI lock state for a booked bike. </summary>
/// <param name="bikeId">Id of the bike to update locking state for.</param>
/// <param name="location">Geolocation of lock.</param>

View file

@ -55,6 +55,12 @@ namespace TINK.Repository.Request
/// <returns>Request to get keys.</returns>
string CalculateAuthParameters(string bikeId);
/// <summary> Gets the request for notifying about start of returning sequence. </summary>
/// <remarks> Operator specific call.</remarks>
/// <param name="bikeId">Id of the bike to return.</param>
/// <returns>Request to notify about start of returning sequence.</returns>
string StartReturningBike(string bikeId);
/// <summary> Gets the request for updating lock state for a booked bike. </summary>
/// <param name="bikeId">Id of the bike to update locking state for.</param>
/// <param name="location">Geolocation of lock when state change occurred.</param>

View file

@ -96,6 +96,13 @@ namespace TINK.Repository.Request
/// <returns>Response on request.</returns>
public string CalculateAuthParameters(string bikeId) => throw new NotSupportedException();
/// <summary> Gets the request for notifying about start of returning sequence. </summary>
/// <remarks> Operator specific call.</remarks>
/// <param name="bikeId">Id of the bike to return.</param>
/// <returns>Request to notify about start of returning sequence.</returns>
public string StartReturningBike(string bikeId)
=> throw new NotSupportedException();
public string UpateLockingState(string bikeId, LocationDto geolocation, lock_state state, double batteryPercentage)
=> throw new NotSupportedException();

View file

@ -95,15 +95,20 @@ namespace TINK.Repository.Request
public string CalculateAuthParameters(string bikeId)
=> $"request=booking_update&bike={bikeId}&authcookie={SessionCookie}{MerchantId}&genkey=1";
/// <summary> Gets the request for notifying about start of returning sequence. </summary>
/// <remarks> Operator specific call.</remarks>
/// <param name="bikeId">Id of the bike to return.</param>
/// <returns>Request to notify about start of returning sequence.</returns>
public string StartReturningBike(string bikeId)
=> $"request=booking_update&bike={bikeId}&lock_state=locking&authcookie={SessionCookie}{MerchantId}";
/// <summary> Gets the request for updating lock state for a booked bike. </summary>
/// <remarks> Operator specific call.</remarks>
/// <param name="bikeId">Id of the bike to update locking state for.</param>
/// <param name="state">New locking state.</param>
/// <returns>Request to update locking state.</returns>
public string UpateLockingState(string bikeId, LocationDto geolocation, lock_state state, double batteryPercentage)
{
return $"request=booking_update&bike={bikeId}{GetLocationParameters(geolocation)}&lock_state={state}{GetBatteryPercentageParameters(batteryPercentage)}&authcookie={SessionCookie}{MerchantId}";
}
=> $"request=booking_update&bike={bikeId}{GetLocationParameters(geolocation)}&lock_state={state}{GetBatteryPercentageParameters(batteryPercentage)}&authcookie={SessionCookie}{MerchantId}";
/// <summary> Gets booking request request (synonym: booking == renting == mieten). </summary>
/// <remarks> Operator specific call.</remarks>

View file

@ -8,6 +8,9 @@ namespace TINK.Repository.Response
{
public const string RESPONSE_OK = "OK";
/// <summary> Holds the description of the action return bike. </summary>
public const string RESPONSE_AUTHCOOKIE_EXPRIED = "Failure 1001:";
/// <summary> Holds the description of the action logout. </summary>
public const string BIKES_LOGOUT_ACTIONTEXT = "Abmeldung fehlgeschlagen.";
@ -127,6 +130,34 @@ namespace TINK.Repository.Response
return bikeInfoRequestedOccupied;
}
/// <summary> Gets if request is ok.</summary>
/// <param name="response">Response to verify.</param>
/// <param name="textOfAction">Text describing request which is shown if validation fails.</param>
/// <returns>Verified response.</returns>
public static BikesReservedOccupiedResponse GetIsResponseOk(this BikesReservedOccupiedResponse response, string textOfAction)
{
if (response == null || response.response_state == null)
{
throw new InvalidResponseException<BikesReservedOccupiedResponse>(textOfAction, null);
}
if (AuthcookieNotDefinedException.IsAuthcookieNotDefined(response, textOfAction, out AuthcookieNotDefinedException exception))
{
throw exception;
}
if (response.response_state.Trim().ToUpper().StartsWith(RESPONSE_AUTHCOOKIE_EXPRIED.ToUpper()))
{
throw new AuthcookieNotDefinedException(
$"{textOfAction}\r\n{AppResources.ExceptionTextSessionExpired}",
response);
}
GetIsResponseOk((ResponseBase)response, textOfAction);
return response;
}
/// <summary> Gets if request is ok.</summary>
/// <param name="response">Response to verify.</param>
/// <param name="textOfAction">Text describing request which is shown if validation fails.</param>

View file

@ -48,8 +48,8 @@ namespace TINK.Services.BluetoothLock
switch (lockInfo.State )
{
case LockingState.Open:
case LockingState.Disconnected:
case LockingState.Unknown:
case LockingState.UnknownDisconnected:
case LockingState.UnknownFromHardwareError:
// Open bikes are never disposable because as soon as a they are open they get booked.
if (LocksInfo.FirstOrDefault(x => x.Id == lockInfo.Id) != null)
{
@ -64,8 +64,8 @@ namespace TINK.Services.BluetoothLock
switch (lockInfo.State)
{
case LockingState.Open:
case LockingState.Disconnected:
case LockingState.Unknown:
case LockingState.UnknownDisconnected:
case LockingState.UnknownFromHardwareError:
// Closed bikes are never reserved because as soon as they are open they get booked.
if (LocksInfo.FirstOrDefault(x => x.Id == lockInfo.Id) != null)
{
@ -79,8 +79,8 @@ namespace TINK.Services.BluetoothLock
case InUseStateEnum.Booked:
switch (lockInfo.State)
{
case LockingState.Disconnected:
case LockingState.Unknown:
case LockingState.UnknownDisconnected:
case LockingState.UnknownFromHardwareError:
if (LocksInfo.FirstOrDefault(x => x.Id == lockInfo.Id) != null)
{
continue; // Lock was already added.
@ -152,6 +152,6 @@ namespace TINK.Services.BluetoothLock
/// <summary> Disconnects lock.</summary>
/// <param name="bikeId"> Id of lock to disconnect.</param>
/// <param name="bikeGuid"> Guid of lock to disconnect.</param>
public async Task<LockingState> DisconnectAsync(int bikeId, Guid bikeGuid) => await Task.FromResult(LockingState.Disconnected);
public async Task<LockingState> DisconnectAsync(int bikeId, Guid bikeGuid) => await Task.FromResult(LockingState.UnknownDisconnected);
}
}

View file

@ -60,6 +60,6 @@ namespace TINK.Services.BluetoothLock
/// <summary> Disconnects lock.</summary>
/// <param name="bikeId"> Id of lock to disconnect.</param>
/// <param name="bikeGuid"> Guid of lock to disconnect.</param>
public async Task<LockingState> DisconnectAsync(int bikeId, Guid bikeGuid) => await Task.FromResult(LockingState.Disconnected);
public async Task<LockingState> DisconnectAsync(int bikeId, Guid bikeGuid) => await Task.FromResult(LockingState.UnknownDisconnected);
}
}

View file

@ -1,41 +0,0 @@
using Plugin.BLE.Abstractions.Contracts;
using System;
using System.Threading.Tasks;
namespace TINK.Services.BluetoothLock
{
public static class StateChecker
{
/// <summary>
/// Get current bluetooth state
/// </summary>
/// <remarks>See https://github.com/xabre/xamarin-bluetooth-le/issues/112#issuecomment-380994887.</remarks>
/// <param name="ble">Crossplatform bluetooth implementation object</param>
/// <returns>BluetoothState</returns>
public static Task<BluetoothState> GetBluetoothState(this IBluetoothLE ble)
{
var tcs = new TaskCompletionSource<BluetoothState>();
if (ble.State != BluetoothState.Unknown)
{
// If we can detect state out of box just returning in
tcs.SetResult(ble.State);
}
else
{
// Otherwise let's setup dynamic event handler and wait for first state update
EventHandler<Plugin.BLE.Abstractions.EventArgs.BluetoothStateChangedArgs> handler = null;
handler = (o, e) =>
{
ble.StateChanged -= handler;
// and return it as our state
// we can have an 'Unknown' check here, but in normal situation it should never occur
tcs.SetResult(e.NewState);
};
ble.StateChanged += handler;
}
return tcs.Task;
}
}
}

View file

@ -227,6 +227,11 @@ namespace TINK.Model.Services.CopriApi
return await HttpsServer.CalculateAuthKeysAsync(bikeId, operatorUri);
}
public async Task<ResponseBase> StartReturningBike(
string bikeId,
Uri operatorUri)
=> await HttpsServer.StartReturningBike(bikeId, operatorUri);
public async Task<ReservationBookingResponse> UpdateLockingStateAsync(
string bikeId,
LocationDto location,

View file

@ -40,6 +40,12 @@ namespace TINK.Model.Services.CopriApi
public Task<ReservationBookingResponse> CalculateAuthKeysAsync(string bikeId, Uri operatorUri)
=> throw new NotSupportedException($"{nameof(CalculateAuthKeysAsync)} is not cachable.");
public async Task<ResponseBase> StartReturningBike(
string bikeId,
Uri operatorUri)
=> await monkeyStore.StartReturningBike(bikeId, operatorUri);
public async Task<ReservationBookingResponse> UpdateLockingStateAsync(
string bikeId,
LocationDto geolocation,

View file

@ -0,0 +1,13 @@
using TINK.Model.Device;
using Xamarin.Essentials;
namespace TINK.Services.Geolocation
{
public class GeolocationAccuracyBestService : GeolocationService
{
public GeolocationAccuracyBestService(IGeolodationDependent dependent) : base(
dependent, GeolocationAccuracy.Best)
{
}
}
}

View file

@ -0,0 +1,13 @@
using TINK.Model.Device;
using Xamarin.Essentials;
namespace TINK.Services.Geolocation
{
public class GeolocationAccuracyHighService : GeolocationService
{
public GeolocationAccuracyHighService(IGeolodationDependent dependent) : base(
dependent, GeolocationAccuracy.High)
{
}
}
}

View file

@ -0,0 +1,13 @@
using TINK.Model.Device;
using Xamarin.Essentials;
namespace TINK.Services.Geolocation
{
public class GeolocationAccuracyMediumService : GeolocationService
{
public GeolocationAccuracyMediumService(IGeolodationDependent dependent) : base(
dependent, GeolocationAccuracy.Medium)
{
}
}
}

View file

@ -5,18 +5,23 @@ using System.Threading.Tasks;
using TINK.Model.Device;
using Xamarin.Essentials;
namespace TINK.Model.Services.Geolocation
namespace TINK.Services.Geolocation
{
public class GeolocationService : IGeolocation
public abstract class GeolocationService : IGeolocation
{
/// <summary> Timeout for geolocation request operations.</summary>
private const int GEOLOCATIONREQUEST_TIMEOUT_MS = 5000;
private IGeolodationDependent Dependent { get; }
public GeolocationService(IGeolodationDependent dependent)
private GeolocationAccuracy Accuracy { get; }
public GeolocationService(
IGeolodationDependent dependent,
GeolocationAccuracy accuracy = GeolocationAccuracy.Default)
{
Dependent = dependent;
Accuracy = accuracy;
}
public bool IsSimulation => false;
@ -30,7 +35,7 @@ namespace TINK.Model.Services.Geolocation
{
try
{
var request = new GeolocationRequest(GeolocationAccuracy.Medium, TimeSpan.FromMilliseconds(GEOLOCATIONREQUEST_TIMEOUT_MS));
var request = new GeolocationRequest(Accuracy, TimeSpan.FromMilliseconds(GEOLOCATIONREQUEST_TIMEOUT_MS));
return cancellationToken.HasValue
? await Xamarin.Essentials.Geolocation.GetLocationAsync(request, cancellationToken.Value)
: await Xamarin.Essentials.Geolocation.GetLocationAsync(request);

View file

@ -4,7 +4,7 @@ using System.Threading.Tasks;
using TINK.Model.Device;
using Xamarin.Essentials;
namespace TINK.Model.Services.Geolocation
namespace TINK.Services.Geolocation
{
/// <summary> Query geolocation. </summary>
public interface IGeolocation : IGeolodationDependent

View file

@ -5,7 +5,7 @@ using System.Threading.Tasks;
using TINK.Model.Device;
using Xamarin.Essentials;
namespace TINK.Model.Services.Geolocation
namespace TINK.Services.Geolocation
{
public class LastKnownGeolocationService : IGeolocation
{
@ -58,7 +58,7 @@ namespace TINK.Model.Services.Geolocation
return location;
}
return await new GeolocationService(Dependent).GetAsync(cancelationToken, timeStamp);
return await new GeolocationAccuracyMediumService(Dependent).GetAsync(cancelationToken, timeStamp);
}
/// <summary> If true location data returned is simulated.</summary>

View file

@ -4,7 +4,7 @@ using System.Threading.Tasks;
using TINK.Model.Device;
using Xamarin.Essentials;
namespace TINK.Model.Services.Geolocation
namespace TINK.Services.Geolocation
{
public class SimulatedGeolocationService : IGeolocation
{

View file

@ -5,6 +5,7 @@
<MultilingualFallbackLanguage>en-GB</MultilingualFallbackLanguage>
<TranslationReport Condition="'$(Configuration)' == 'Release'">true</TranslationReport>
<SuppressPseudoWarning Condition="'$(Configuration)' == 'Debug'">true</SuppressPseudoWarning>
<Configurations>Debug;Release</Configurations>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
@ -13,10 +14,10 @@
<NeutralLanguage>en-GB</NeutralLanguage>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>TRACE;USEFLYOUT</DefineConstants>
<DefineConstants>TRACE;</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DefineConstants>TRACE;USEFLYOUT</DefineConstants>
<DefineConstants>TRACE;</DefineConstants>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\Microsoft\Multilingual App Toolkit\Microsoft.Multilingual.ResxResources.targets" Label="MultilingualAppToolkit" Condition="Exists('$(MSBuildExtensionsPath)\Microsoft\Multilingual App Toolkit\v$(MultilingualAppToolkitVersion)\Microsoft.Multilingual.ResxResources.targets')" />
<Target Name="MATPrerequisite" BeforeTargets="PrepareForBuild" Condition="!Exists('$(MSBuildExtensionsPath)\Microsoft\Multilingual App Toolkit\Microsoft.Multilingual.ResxResources.targets')" Label="MultilingualAppToolkit">

View file

@ -2,7 +2,7 @@
<ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TINK.Themes.LastenradBayern">
<!-- Pallete -->
<!-- Pallete v0: #009BDB v1: 7fd8ff kind of light blue-->
<Color x:Key="primary-back-title-color">#009BDB</Color>
<!-- Pallete-end -->
<Style ApplyToDerivedTypes="true" TargetType="NavigationPage">

View file

@ -323,24 +323,13 @@ namespace TINK.ViewModel.Bikes.Bike
/// <summary> Command object to bind login page redirect link to view model.</summary>
public System.Windows.Input.ICommand ShowAgbTappedCommand
#if USEFLYOUT
=> new Xamarin.Forms.Command(() => ShowAgbPageAsync());
#else
=> new Xamarin.Forms.Command(async () => await ShowAgbPageAsync());
#endif
/// <summary> Opens login page. </summary>
#if USEFLYOUT
public void ShowAgbPageAsync()
#else
public async Task ShowAgbPageAsync()
#endif
{
try
{
// Switch to map page
#if USEFLYOUT
var url = GetUrlFirstOrDefault(TariffDescription.OperatorAgb);
if (string.IsNullOrEmpty(url))
{
@ -349,7 +338,7 @@ namespace TINK.ViewModel.Bikes.Bike
}
OpenUrlInBrowser(url);
#endif
}
catch (Exception p_oException)
{

View file

@ -1,7 +1,7 @@
using System;
using TINK.Model.Connector;
using TINK.Services.BluetoothLock;
using TINK.Model.Services.Geolocation;
using TINK.Services.Geolocation;
using TINK.Model.User;
using TINK.View;
using TINK.Model.Device;

View file

@ -3,7 +3,7 @@ using System.ComponentModel;
using System.Runtime.CompilerServices;
using TINK.Model.Connector;
using TINK.Services.BluetoothLock;
using TINK.Model.Services.Geolocation;
using TINK.Services.Geolocation;
using TINK.Model.User;
using TINK.View;
using BikeInfoMutable = TINK.Model.Bike.BluetoothLock.BikeInfoMutable;

View file

@ -1,7 +1,7 @@
using System;
using TINK.Model.Connector;
using TINK.Services.BluetoothLock;
using TINK.Model.Services.Geolocation;
using TINK.Services.Geolocation;
using TINK.Model.State;
using TINK.View;
using TINK.Model.User;

View file

@ -4,7 +4,7 @@ using TINK.Model.Connector;
using TINK.Model.Bike.BluetoothLock;
using TINK.Model.State;
using TINK.View;
using TINK.Model.Services.Geolocation;
using TINK.Services.Geolocation;
using TINK.Services.BluetoothLock;
using Serilog;
using TINK.Repository.Exception;
@ -155,7 +155,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
else
{
// Bluetooth out of reach. Lock state is no more known.
SelectedBike.LockInfo.State = LockingState.Disconnected;
SelectedBike.LockInfo.State = LockingState.UnknownDisconnected;
}
BikesViewModel.ActionText = "Returning bike...";
@ -193,7 +193,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<BookedClosed>().Information("User selected booked bike {bike} but returning failed. COPRI returned an not at station error.", SelectedBike);
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeNotAtStationTitle,
AppResources.ErrorReturnBikeTitle,
string.Format(AppResources.ErrorReturnBikeNotAtStationMessage, notAtStationException.StationNr, notAtStationException.Distance),
AppResources.MessageAnswerOk);
}
@ -203,7 +203,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<BookedClosed>().Information("User selected booked bike {bike} but returing failed. COPRI returned an no GPS- data error.", SelectedBike);
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeNotAtStationTitle,
AppResources.ErrorReturnBikeTitle,
string.Format(AppResources.ErrorReturnBikeLockClosedNoGPSMessage),
AppResources.MessageAnswerOk);
}
@ -220,12 +220,12 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else
{
Log.ForContext<BookedClosed>().Error("User selected availalbe bike {bike} but reserving failed. {@l_oException}", SelectedBike.Id, exception);
Log.ForContext<BookedClosed>().Error("User selected booked bike {bike} but returning failed. {@l_oException}", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
"Fehler beim Zurückgeben des Rads!",
AppResources.ErrorReturnBikeTitle,
exception.Message,
"OK");
AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
@ -316,7 +316,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = AppResources.ActivityTextOpeningLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].OpenAsync())?.GetLockingState() ?? LockingState.Disconnected;
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].OpenAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
catch (Exception exception)
{
@ -328,8 +328,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockOutOfReadMessage,
"OK");
AppResources.ErrorOpenLockOutOfReachMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldIsBlockedException)
{
@ -338,7 +338,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockBoldBlockedMessage,
"OK");
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldWasBlockedException)
{
@ -373,7 +373,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// 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.Disconnected;
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();

View file

@ -8,12 +8,13 @@ using TINK.Repository.Exception;
using TINK.Services.BluetoothLock;
using TINK.Services.BluetoothLock.Exception;
using TINK.Services.BluetoothLock.Tdo;
using TINK.Model.Services.Geolocation;
using TINK.Services.Geolocation;
using TINK.Model.State;
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
{
@ -86,28 +87,28 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
// Repeat booking to get a new seed/ k_user value.
await ConnectorFactory(IsConnected).Command.CalculateAuthKeys(SelectedBike);
}
catch (Exception l_oException)
catch (Exception exception)
{
BikesViewModel.ActionText = string.Empty;
if (l_oException is WebConnectFailureException)
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<BookedDisconnected>().Information("User selected booked bike {l_oId} to connect to lock. (Copri server not reachable).", SelectedBike.Id);
await ViewService.DisplayAlert(
"Fehler bei Verbinden mit Schloss!",
$"Internet muss erreichbar sein um Verbindung mit Schloss für gemietetes Rad herzustellen.\r\n{l_oException.Message}\r\n{WebConnectFailureException.GetHintToPossibleExceptionsReasons}",
"OK");
AppResources.MessageConnectLockErrorTitle,
$"{AppResources.ErrorConnectLockRentedBikeNoWebMessage}\r\n{exception.Message}\r\n{WebConnectFailureException.GetHintToPossibleExceptionsReasons}",
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<BookedDisconnected>().Error("User selected booked bike {l_oId} to connect to lock. {@l_oException}", SelectedBike.Id, l_oException);
Log.ForContext<BookedDisconnected>().Error("User selected booked bike {l_oId} to connect to lock. {@l_oException}", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
"Fehler bei Verbinden mit Schloss!",
$"Kommunikationsfehler bei Schlosssuche.\r\n{l_oException.Message}",
"OK");
AppResources.MessageConnectLockErrorTitle,
$"{AppResources.ErrorConnectLockGeneralErrorMessage}\r\n{exception.Message}",
AppResources.MessageAnswerOk);
}
// Restart polling again.
@ -135,26 +136,68 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
BikesViewModel.ActionText = string.Empty;
if (exception is OutOfReachException)
if (exception is ConnectBluetoothNotOnException)
{
continueConnect = false;
await ViewService.DisplayAlert(
AppResources.MessageConnectLockErrorTitle,
AppResources.ErrorFindLockBluetoothNotOn,
AppResources.MessageAnswerOk);
}
else if (exception is ConnectLocationPermissionMissingException)
{
continueConnect = false;
await ViewService.DisplayAlert(
AppResources.MessageConnectLockErrorTitle,
AppResources.ErrorFindLockLocationPermissionMissing,
AppResources.MessageAnswerOk);
}
else if (exception is ConnectLocationOffException)
{
continueConnect = false;
await ViewService.DisplayAlert(
AppResources.MessageConnectLockErrorTitle,
AppResources.ErrorFindLockLocationOff,
AppResources.MessageAnswerOk);
}
else if (exception is OutOfReachException)
{
Log.ForContext<BookedDisconnected>().Debug("Lock can not be found. {Exception}", exception);
continueConnect = await ViewService.DisplayAlert(
"Fehler bei Verbinden mit Schloss!",
"Schloss kann erst gefunden werden, wenn gemietetes Rad in der Nähe ist.",
"Wiederholen",
"Abbrechen");
AppResources.MessageConnectLockErrorTitle,
AppResources.ErrorFindLockRentedBikeOutOfReachMessage,
AppResources.MessageAnswerRetry,
AppResources.MessageAnswerCancel);
}
else
{
Log.ForContext<BookedDisconnected>().Error("Lock can not be found. {Exception}", exception);
string message;
if (retryCount < 2)
{
message = AppResources.ErrorBookedSearchMessage;
}
else if (retryCount < 3)
{
message = AppResources.ErrorBookedSearchMessageEscalationLevel1;
}
else
{
message = AppResources.ErrorBookedSearchMessageEscalationLevel2;
}
continueConnect = await ViewService.DisplayAdvancedAlert(
"Fehler bei Verbinden mit Schloss!",
AppResources.ErrorBookedSearchMessage,
exception.Message,
"Wiederholen",
"Abbrechen");
AppResources.MessageConnectLockErrorTitle,
message,
"", // bool IsReportLevelVerbose ? exception.Message : string.Empty, // or use ActiveUser.DebugLevel.HasFlag(Permissions.ReportLevel) instead?
AppResources.MessageAnswerRetry,
AppResources.MessageAnswerCancel);
}
if (continueConnect)
@ -178,9 +221,9 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = "";
await ViewService.DisplayAlert(
"Fehler bei Verbinden mit Schloss!",
AppResources.MessageConnectLockErrorTitle,
$"Schlossstatus des gemieteten Rads konnte nicht ermittelt werden.",
"OK");
AppResources.MessageAnswerOk);
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;

View file

@ -4,7 +4,7 @@ using TINK.Model.Connector;
using TINK.Model.Bike.BluetoothLock;
using TINK.Model.State;
using TINK.View;
using TINK.Model.Services.Geolocation;
using TINK.Services.Geolocation;
using TINK.Services.BluetoothLock;
using Serilog;
using TINK.Repository.Exception;
@ -131,18 +131,74 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
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.Disconnected;
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);
}
if (exception is OutOfReachException)
{
@ -181,20 +237,16 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
AppResources.MessageAnswerOk);
}
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.Disconnected;
// Wait until cancel getting geolocation has completed.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
await Task.WhenAll(new List<Task> { currentLocationTask ?? Task.CompletedTask, updateLockingStateTask });
}
catch (Exception ex)
catch (Exception innerExWhenAll)
{
// No location information available.
Log.ForContext<BookedOpen>().Information("Canceling query location failed on closing lock error. {Exception}", SelectedBike, ex);
// 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);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
@ -208,11 +260,24 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
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
@ -224,12 +289,12 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;
try
{
await Task.WhenAny(new List<Task> { currentLocationTask ?? Task.CompletedTask });
await Task.WhenAll(new List<Task> { currentLocationTask ?? Task.CompletedTask, updateLockingStateTask});
}
catch (Exception ex)
catch (Exception innerExWhenAll)
{
// No location information available.
Log.ForContext<BookedOpen>().Information("Canceling query location failed on unexpected lock state failed. {Exception}", SelectedBike, ex);
Log.ForContext<BookedOpen>().Information("Canceling query location/ updating lock state failed failed on unexpected lock state failed. {Exception}", SelectedBike, innerExWhenAll);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
@ -311,7 +376,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<BookedOpen>().Information("User selected booked bike {bike} but returing failed. COPRI returned an error.", SelectedBike);
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeNotAtStationTitle,
AppResources.ErrorReturnBikeTitle,
string.Format(AppResources.ErrorReturnBikeNotAtStationMessage, notAtStationException.StationNr, notAtStationException.Distance),
AppResources.MessageAnswerOk);
}
@ -321,7 +386,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<BookedOpen>().Information("User selected booked bike {bike} but returing failed. COPRI returned an no GPS- data error.", SelectedBike);
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeNotAtStationTitle,
AppResources.ErrorReturnBikeTitle,
string.Format(AppResources.ErrorReturnBikeLockOpenNoGPSMessage),
AppResources.MessageAnswerOk);
}
@ -338,9 +403,12 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
else
{
Log.ForContext<BookedOpen>().Error("User selected availalbe bike {bike} but reserving failed. {@l_oException}", SelectedBike.Id, exception);
Log.ForContext<BookedOpen>().Error("User selected booked bike {bike} but returning failed. {@l_oException}", SelectedBike.Id, exception);
await ViewService.DisplayAlert("Fehler beim Zurückgeben des Rads!", exception.Message, "OK");
await ViewService.DisplayAlert(
AppResources.ErrorReturnBikeTitle,
exception.Message,
AppResources.MessageAnswerOk);
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
@ -448,7 +516,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.Disconnected;
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
catch (Exception exception)
{
@ -494,7 +562,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.Disconnected;
: LockingState.UnknownDisconnected;
// Wait until cancel getting geolocation has completed.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;

View file

@ -0,0 +1,684 @@
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

@ -6,7 +6,7 @@ using TINK.Model.Connector;
using TINK.Model.State;
using TINK.View;
using TINK.Repository.Exception;
using TINK.Model.Services.Geolocation;
using TINK.Services.Geolocation;
using TINK.Services.BluetoothLock;
using TINK.Services.BluetoothLock.Exception;
using TINK.MultilingualResources;
@ -76,7 +76,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = AppResources.ActivityTextOpeningLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].OpenAsync())?.GetLockingState() ?? LockingState.Disconnected;
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].OpenAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
catch (Exception exception)
{
@ -88,8 +88,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockOutOfReadMessage,
"OK");
AppResources.ErrorOpenLockOutOfReachMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldIsBlockedException)
{
@ -98,7 +98,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockBoldBlockedMessage,
"OK");
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldWasBlockedException)
{
@ -107,7 +107,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockStillOpenTitle,
AppResources.ErrorOpenLockBoldWasBlockedMessage,
"OK");
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
&& inconsistentState.State == LockingState.Closed)
@ -117,7 +117,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStillClosedMessage,
"OK");
AppResources.MessageAnswerOk);
}
else
{
@ -126,14 +126,14 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
exception.Message,
"OK");
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.Disconnected;
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
@ -236,7 +236,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.Disconnected;
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
catch (Exception exception)
{
@ -251,7 +251,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockOutOfReachMessage,
"OK");
AppResources.MessageAnswerOk);
}
else if (exception is CounldntCloseMovingException)
{
@ -260,7 +260,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockMovingMessage,
"OK");
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseBoldBlockedException)
{
@ -269,7 +269,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockBoldBlockedMessage,
"OK");
AppResources.MessageAnswerOk);
}
else
{
@ -277,12 +277,12 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
exception.Message,
"OK");
AppResources.MessageAnswerOk);
}
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.Disconnected;
: LockingState.UnknownDisconnected;
// Wait until cancel getting geolocation has completed.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;

View file

@ -0,0 +1,386 @@
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

@ -6,7 +6,7 @@ using TINK.Model.Connector;
using TINK.Model.State;
using TINK.View;
using TINK.Repository.Exception;
using TINK.Model.Services.Geolocation;
using TINK.Services.Geolocation;
using TINK.Services.BluetoothLock;
using TINK.Services.BluetoothLock.Tdo;
using TINK.MultilingualResources;
@ -149,12 +149,12 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
Log.ForContext<DisposableDisconnected>().Debug("Lock state can not be retrieved, lock is out of reach. {Exception}", exception);
BikesViewModel.ActionText = "Schloss außerhalb Reichweite";
BikesViewModel.ActionText = AppResources.ActivityTextLockIsOutOfReach;
}
else
{
Log.ForContext<DisposableDisconnected>().Error("Lock state can not be retrieved. {Exception}", exception);
BikesViewModel.ActionText = "Schloss nicht gefunden";
BikesViewModel.ActionText = AppResources.ActivityTextLockNotFound;
}
// Restart polling again.
@ -165,8 +165,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
SelectedBike.LockInfo.State = result?.State?.GetLockingState() ?? LockingState.Disconnected;
if (SelectedBike.LockInfo.State == LockingState.Disconnected)
SelectedBike.LockInfo.State = result?.State?.GetLockingState() ?? LockingState.UnknownDisconnected;
if (SelectedBike.LockInfo.State == LockingState.UnknownDisconnected)
{
// Do not display any messages here, because search is implicit.
Log.ForContext<DisposableDisconnected>().Information("Lock for bike {bike} not found.", SelectedBike);
@ -262,7 +262,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = AppResources.ActivityTextOpeningLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].OpenAsync())?.GetLockingState() ?? LockingState.Disconnected;
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].OpenAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
catch (Exception exception)
{
@ -273,8 +273,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockOutOfReadMessage,
"OK");
AppResources.ErrorOpenLockOutOfReachMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldIsBlockedException)
{
@ -283,7 +283,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockBoldBlockedMessage,
"OK");
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldWasBlockedException)
{
@ -292,7 +292,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockStillOpenTitle,
AppResources.ErrorOpenLockBoldWasBlockedMessage,
"OK");
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
&& inconsistentState.State == LockingState.Closed)
@ -302,7 +302,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStillClosedMessage,
"OK");
AppResources.MessageAnswerOk);
}
else
{
@ -310,12 +310,12 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
exception.Message,
"OK");
AppResources.MessageAnswerOk);
}
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.Disconnected;
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.

View file

@ -6,7 +6,7 @@ using TINK.Model.Connector;
using TINK.Model.State;
using TINK.View;
using TINK.Repository.Exception;
using TINK.Model.Services.Geolocation;
using TINK.Services.Geolocation;
using TINK.Services.BluetoothLock;
using TINK.Services.BluetoothLock.Exception;
using TINK.MultilingualResources;
@ -97,7 +97,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.Disconnected;
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
catch (Exception exception)
{
@ -109,7 +109,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockOutOfReachMessage,
"OK");
AppResources.MessageAnswerOk);
}
else if (exception is CounldntCloseMovingException)
{
@ -118,7 +118,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockMovingMessage,
"OK");
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseBoldBlockedException)
{
@ -127,7 +127,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockBoldBlockedMessage,
"OK");
AppResources.MessageAnswerOk);
}
else
{
@ -136,12 +136,12 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
exception.Message,
"OK");
AppResources.MessageAnswerOk);
}
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.Disconnected;
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
@ -228,7 +228,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = "Verschließe Schloss...";
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.Disconnected;
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
catch (Exception exception)
{
@ -236,7 +236,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.Disconnected;
: LockingState.UnknownDisconnected;
}
// Disconnect lock.

View file

@ -8,7 +8,7 @@ using TINK.Model.State;
using TINK.View;
using IBikeInfoMutable = TINK.Model.Bikes.Bike.BluetoothLock.IBikeInfoMutable;
using TINK.Model.Services.Geolocation;
using TINK.Services.Geolocation;
using TINK.Services.BluetoothLock;
using TINK.Services.BluetoothLock.Exception;
using TINK.MultilingualResources;
@ -228,7 +228,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = AppResources.ActivityTextOpeningLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].OpenAsync())?.GetLockingState() ?? LockingState.Disconnected;
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].OpenAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
catch (Exception exception)
{
@ -239,8 +239,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockOutOfReadMessage,
"OK");
AppResources.ErrorOpenLockOutOfReachMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldIsBlockedException)
{
@ -249,7 +249,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockBoldBlockedMessage,
"OK");
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldWasBlockedException)
{
@ -258,7 +258,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockStillOpenTitle,
AppResources.ErrorOpenLockBoldWasBlockedMessage,
"OK");
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
&& inconsistentState.State == LockingState.Closed)
@ -268,7 +268,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStillClosedMessage,
"OK");
AppResources.MessageAnswerOk);
}
else
{
@ -276,12 +276,12 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
exception.Message,
"OK");
AppResources.MessageAnswerOk);
}
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.Disconnected;
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.

View file

@ -6,7 +6,7 @@ using TINK.Repository.Exception;
using TINK.Model.Bike.BluetoothLock;
using TINK.Model.State;
using TINK.View;
using TINK.Model.Services.Geolocation;
using TINK.Services.Geolocation;
using TINK.Services.BluetoothLock;
using TINK.Services.BluetoothLock.Exception;
using TINK.Services.BluetoothLock.Tdo;
@ -161,18 +161,18 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
Log.ForContext<ReservedDisconnected>().Information("User selected requested bike {l_oId} to connect to lock. (Copri server not reachable).", SelectedBike.Id);
await ViewService.DisplayAlert(
"Fehler bei Verbinden mit Schloss!",
$"Internet muss erreichbar sein um Verbindung mit Schloss für reserviertes Rad herzustellen.\r\n{exception.Message}\r\n{WebConnectFailureException.GetHintToPossibleExceptionsReasons}",
"OK");
AppResources.MessageErrorConnectTitle,
$"{AppResources.ErrorConnectLockReservedBikeNoWebMessage}\r\n{exception.Message}\r\n{WebConnectFailureException.GetHintToPossibleExceptionsReasons}",
AppResources.MessageAnswerOk);
}
else
{
Log.ForContext<ReservedDisconnected>().Error("User selected requested bike {l_oId} to scan for lock. {@l_oException}", SelectedBike.Id, exception);
await ViewService.DisplayAlert(
"Fehler bei Verbinden mit Schloss!",
$"Kommunikationsfehler bei Schlosssuche.\r\n{exception.Message}",
"OK");
AppResources.MessageErrorConnectTitle,
$"{AppResources.ErrorConnectLockGeneralErrorMessage}\r\n{exception.Message}",
AppResources.MessageAnswerOk);
}
// Restart polling again.
@ -206,25 +206,64 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
BikesViewModel.ActionText = string.Empty;
if (exception is OutOfReachException)
if (exception is ConnectBluetoothNotOnException)
{
continueConnect = false;
await ViewService.DisplayAlert(
AppResources.MessageErrorConnectTitle,
AppResources.ErrorFindLockBluetoothNotOn,
AppResources.MessageAnswerOk);
}
else if (exception is ConnectLocationPermissionMissingException)
{
continueConnect = false;
await ViewService.DisplayAlert(
AppResources.MessageConnectLockErrorTitle,
AppResources.ErrorFindLockLocationPermissionMissing,
AppResources.MessageAnswerOk);
}
else if (exception is ConnectLocationOffException)
{
continueConnect = false;
await ViewService.DisplayAlert(
AppResources.MessageConnectLockErrorTitle,
AppResources.ErrorFindLockLocationOff,
AppResources.MessageAnswerOk);
}
else if (exception is OutOfReachException)
{
Log.ForContext<ReservedDisconnected>().Debug("Lock state can not be retrieved. {Exception}", exception);
continueConnect = await ViewService.DisplayAlert(
"Fehler bei Verbinden mit Schloss!",
"Schloss kann erst gefunden werden, wenn reserviertes Rad in der Nähe ist.",
"Wiederholen",
"Abbrechen");
AppResources.MessageErrorConnectTitle,
AppResources.ErrorFindLockReservedBikeOutOfReachMessage,
AppResources.MessageAnswerRetry,
AppResources.MessageAnswerCancel);
}
else
{
Log.ForContext<BookedDisconnected>().Error("Lock can not be found. {Exception}", exception);
string message;
if (retryCount < 2)
{
message = AppResources.ErrorReservedSearchMessage;
}
else
{
message = AppResources.ErrorReservedSearchMessageEscalationLevel1;
}
Log.ForContext<ReservedDisconnected>().Error("Lock state can not be retrieved. {Exception}", exception);
continueConnect = await ViewService.DisplayAdvancedAlert(
"Fehler bei Verbinden mit Schloss!",
AppResources.ErrorReservedSearchMessage,
exception.Message,
"Wiederholen",
"Abbrechen");
AppResources.MessageErrorConnectTitle,
message,
"", // bool IsReportLevelVerbose ? exception.Message : string.Empty, // or use ActiveUser.DebugLevel.HasFlag(Permissions.ReportLevel) instead?
AppResources.MessageAnswerRetry,
AppResources.MessageAnswerCancel );
}
if (continueConnect)
@ -247,9 +286,9 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = "";
await ViewService.DisplayAlert(
"Fehler bei Verbinden mit Schloss!",
$"Schlossstatus des reservierten Rads konnte nicht ermittelt werden.",
"OK");
AppResources.MessageErrorConnectTitle,
AppResources.ErrorFindLockReservedBikeNoStausMessage,
AppResources.MessageAnswerOk);
// Restart polling again.
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
@ -345,7 +384,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = AppResources.ActivityTextOpeningLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].OpenAsync())?.GetLockingState() ?? LockingState.Disconnected;
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].OpenAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
catch (Exception exception)
{
@ -356,8 +395,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockOutOfReadMessage,
"OK");
AppResources.ErrorOpenLockOutOfReachMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldIsBlockedException)
{
@ -366,7 +405,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockBoldBlockedMessage,
"OK");
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldWasBlockedException)
{
@ -375,7 +414,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockStillOpenTitle,
AppResources.ErrorOpenLockBoldWasBlockedMessage,
"OK");
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
&& inconsistentState.State == LockingState.Closed)
@ -385,7 +424,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStillClosedMessage,
"OK");
AppResources.MessageAnswerOk);
}
else
{
@ -393,12 +432,12 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
exception.Message,
"OK");
AppResources.MessageAnswerOk);
}
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.Disconnected;
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.

View file

@ -6,7 +6,7 @@ using TINK.Repository.Exception;
using TINK.Model.Bike.BluetoothLock;
using TINK.Model.State;
using TINK.View;
using TINK.Model.Services.Geolocation;
using TINK.Services.Geolocation;
using TINK.Services.BluetoothLock;
using TINK.Services.BluetoothLock.Exception;
using TINK.Model.Bikes.Bike.BluetoothLock;
@ -140,7 +140,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = "Wiederverschließe Schloss...";
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.Disconnected;
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
catch (Exception exception)
{
@ -148,7 +148,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.Disconnected;
: LockingState.UnknownDisconnected;
}
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
@ -174,7 +174,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.Disconnected;
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
catch (Exception exception)
{
@ -218,7 +218,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.Disconnected;
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically(); // Restart polling again.

View file

@ -6,7 +6,7 @@ using TINK.Model.Connector;
using TINK.Model.State;
using TINK.View;
using TINK.Repository.Exception;
using TINK.Model.Services.Geolocation;
using TINK.Services.Geolocation;
using TINK.Services.BluetoothLock;
using TINK.Services.BluetoothLock.Exception;
using TINK.MultilingualResources;
@ -72,7 +72,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = AppResources.ActivityTextOpeningLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].OpenAsync())?.GetLockingState() ?? LockingState.Disconnected;
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].OpenAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
catch (Exception exception)
{
@ -84,8 +84,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockOutOfReadMessage,
"OK");
AppResources.ErrorOpenLockOutOfReachMessage,
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldIsBlockedException)
{
@ -94,7 +94,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockBoldBlockedMessage,
"OK");
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenBoldWasBlockedException)
{
@ -103,7 +103,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockStillOpenTitle,
AppResources.ErrorOpenLockBoldWasBlockedMessage,
"OK");
AppResources.MessageAnswerOk);
}
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
&& inconsistentState.State == LockingState.Closed)
@ -113,7 +113,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
AppResources.ErrorOpenLockStillClosedMessage,
"OK");
AppResources.MessageAnswerOk);
}
else
{
@ -122,14 +122,14 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorOpenLockTitle,
exception.Message,
"OK");
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.Disconnected;
: LockingState.UnknownDisconnected;
BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
await ViewUpdateManager().StartUpdateAyncPeridically();
@ -236,7 +236,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
try
{
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.Disconnected;
SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.UnknownDisconnected;
}
catch (Exception exception)
{
@ -251,7 +251,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockOutOfReachMessage,
"OK");
AppResources.MessageAnswerOk);
}
else if (exception is CounldntCloseMovingException)
{
@ -260,7 +260,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockMovingMessage,
"OK");
AppResources.MessageAnswerOk);
}
else if (exception is CouldntCloseBoldBlockedException)
{
@ -269,7 +269,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
AppResources.ErrorCloseLockBoldBlockedMessage,
"OK");
AppResources.MessageAnswerOk);
}
else
{
@ -277,12 +277,12 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
await ViewService.DisplayAlert(
AppResources.ErrorCloseLockTitle,
exception.Message,
"OK");
AppResources.MessageAnswerOk);
}
SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.Disconnected;
: LockingState.UnknownDisconnected;
// Wait until cancel getting geolocation has completed.
BikesViewModel.ActionText = AppResources.ActivityTextQueryLocationCancelWait;

View file

@ -0,0 +1,386 @@
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

@ -2,7 +2,7 @@
using TINK.Model.Bike.BluetoothLock;
using TINK.Model.Connector;
using TINK.Services.BluetoothLock;
using TINK.Model.Services.Geolocation;
using TINK.Services.Geolocation;
using TINK.View;
using TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler;
using TINK.Model.User;
@ -59,7 +59,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
string.Format(AppResources.MarkingBikeInfoErrorStateDisposableClosedDetected, selectedBluetoothLockBike.Description));
case LockingState.Open:
case LockingState.Unknown:
case LockingState.UnknownFromHardwareError:
// Unexepected state detected.
/// This state is unexpected because
/// - app does not allow to return bike/ cancel reservation when lock is closed
@ -117,7 +117,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
bikesViewModel,
activeUser);
case LockingState.Disconnected:
case LockingState.UnknownDisconnected:
return new ReservedDisconnected(
selectedBluetoothLockBike,
isConnectedDelegate,
@ -146,7 +146,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
bikesViewModel,
activeUser);
case LockingState.Unknown:
case LockingState.UnknownFromHardwareError:
// User wants to return bike/ pause ride.
return new ReservedUnknown(
selectedBluetoothLockBike,
@ -198,7 +198,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
bikesViewModel,
activeUser);
case LockingState.Unknown:
case LockingState.UnknownFromHardwareError:
// User wants to return bike/ pause ride.
return new BookedUnknown(
selectedBluetoothLockBike,

View file

@ -28,13 +28,9 @@ namespace TINK.ViewModel.Bikes.Bike
return string.Empty;
// Up to version MessageBikesManagementTariffDescriptionTariffHeaderNameId
#if USCSHARP9
return string.Format(AppResources.MessageBikesManagementTariffDescriptionTariffHeader, Tariff?.Name ?? "-");
#else
return string.Format(AppResources.MessageBikesManagementTariffDescriptionTariffHeader, Tariff?.Name ?? "-");
#endif
}
return Tariff?.Name ?? "-";
}
}
/// <summary>
/// Costs per hour in euro.

View file

@ -7,7 +7,7 @@ using System.Threading.Tasks;
using TINK.Model.Bike;
using TINK.Model.Connector;
using TINK.Services.BluetoothLock;
using TINK.Model.Services.Geolocation;
using TINK.Services.Geolocation;
using TINK.Model.User;
using TINK.View;
using TINK.ViewModel.Bikes.Bike;
@ -381,9 +381,7 @@ namespace TINK.ViewModel.Bikes
if (Exception != null)
{
// An error occurred getting data from copri.
return IsReportLevelVerbose
? Exception.GetShortErrorInfoText()
: AppResources.ActivityTextException;
return Exception.GetShortErrorInfoText(IsReportLevelVerbose);
}
if (!IsConnected)

View file

@ -15,7 +15,7 @@ using System.Linq;
using TINK.Model.Bike.BluetoothLock;
using System.Collections.Generic;
using TINK.Services.BluetoothLock;
using TINK.Model.Services.Geolocation;
using TINK.Services.Geolocation;
using TINK.ViewModel.Bikes;
using TINK.Services.BluetoothLock.Tdo;
using Plugin.BLE.Abstractions.Contracts;
@ -145,7 +145,7 @@ namespace TINK.ViewModel.BikesAtStation
#if USEFLYOUT
=> new Xamarin.Forms.Command(() => OpenSupportPageAsync());
#else
=> new Xamarin.Forms.Command(async () => await OpenLoginPageAsync());
=> new Xamarin.Forms.Command(async () => await OpenSupportPageAsync());
#endif
/// <summary> Command object to bind login page redirect link to view model.</summary>
@ -194,7 +194,7 @@ namespace TINK.ViewModel.BikesAtStation
#if USEFLYOUT
ViewService.ShowPage(ViewTypes.ContactPage, AppResources.MarkingFeedbackAndContact);
#else
await ViewService.ShowPage("//LoginPage");
await ViewService.ShowPage("//ContactPage");
#endif
}
catch (Exception p_oException)

View file

@ -2,6 +2,7 @@
using Serilog;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows.Input;
using TINK.Model.Services.CopriApi.ServerUris;
@ -14,27 +15,33 @@ using Xamarin.Forms;
namespace TINK.ViewModel.Info
{
/// <summary> View model for contact page.</summary>
public class ContactPageViewModel
public class ContactPageViewModel : INotifyPropertyChanged
{
/// <summary> Reference on view service to show modal notifications and to perform navigation. </summary>
private IViewService ViewService { get; }
/// <summary> Station selected by user. </summary>
private IStation SelectedStation;
private IStation SelectedStation { get; set; } = new NullStation();
Uri ActiveUri { get; }
/// <summary> Holds the name of the app (sharee.bike, Mein konrad, ...)</summary>
string AppName { get; }
/// <summary> Holds a reference to the external trigger service. </summary>
private Action OpenUrlInExternalBrowser { get; }
Func<string> CreateAttachment { get; }
/// <summary> Notifies view about changes. </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary> Constructs a contact page view model. </summary>
/// <param name="openUrlInExternalBrowser">Action to open an external browser.</param>
/// <param name="viewService">View service to notify user.</param>
public ContactPageViewModel(
IStation selectedStation,
Uri activeUri,
string appName,
Func<string> createAttachment,
Action openUrlInExternalBrowser,
IViewService viewService)
@ -42,6 +49,10 @@ namespace TINK.ViewModel.Info
ActiveUri = activeUri
?? throw new ArgumentException("Can not instantiate contact page view model- object. No active uri available.");
AppName = !string.IsNullOrEmpty(appName)
? appName
: throw new ArgumentException("Can not instantiate contact page view model- object. No app name availalbe.");
CreateAttachment = createAttachment
?? throw new ArgumentException("Can not instantiate contact page view model- object. No create attachment provider available.");
@ -50,9 +61,27 @@ namespace TINK.ViewModel.Info
OpenUrlInExternalBrowser = openUrlInExternalBrowser
?? throw new ArgumentException("Can not instantiate contact page view model- object. No user external browse service available.");
}
/// <summary> Is invoked when page is shown. </summary>
public async Task OnAppearing(
IStation selectedStation)
{
if (SelectedStation?.Id == selectedStation?.Id)
{
// Nothing to do because either both are null or of same id.
return;
}
// Station might be null-station, if no station info is avialable.
SelectedStation = selectedStation;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(MailAddressText)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(OfficeHoursText)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(PhoneNumberText)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ProviderNameText)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsOperatorInfoAvaliable)));
await Task.CompletedTask;
}
/// <summary> Command object to bind login button to view model. </summary>
@ -186,7 +215,7 @@ namespace TINK.ViewModel.Info
#if USEFLYOUT
ViewService.PushAsync(ViewTypes.SelectStationPage);
#else
await ViewService.ShowPage("//SelectStationPage");
await ViewService.PushAsync(ViewTypes.SelectStationPage);
#endif
}
catch (Exception p_oException)
@ -249,7 +278,7 @@ namespace TINK.ViewModel.Info
get
{
var l_oHint = new FormattedString();
l_oHint.Spans.Add(new Span { Text = string.Format(AppResources.MessageRateMail, GetAppName(ActiveUri)) });
l_oHint.Spans.Add(new Span { Text = string.Format(AppResources.MessageRateMail, AppName) });
return l_oHint;
}
}

View file

@ -21,7 +21,7 @@ using System.Threading;
using TINK.MultilingualResources;
using TINK.ViewModel.Info;
using TINK.Repository;
using TINK.Model.Services.Geolocation;
using TINK.Services.Geolocation;
namespace TINK.ViewModel.Contact
{
@ -85,7 +85,7 @@ namespace TINK.ViewModel.Contact
/// <summary> False if user tabed on station marker to show bikes at a given station.</summary>
private bool isMapPageEnabled = false;
Model.Services.Geolocation.IGeolocation GeolocationService { get; }
IGeolocation GeolocationService { get; }
/// <summary> False if user tabed on station marker to show bikes at a given station.</summary>
public bool IsMapPageEnabled
@ -628,9 +628,8 @@ namespace TINK.ViewModel.Contact
if (Exception != null)
{
// An error occurred getting data from copri.
return TinkApp.IsReportLevelVerbose
? Exception.GetShortErrorInfoText()
: AppResources.ActivityTextException;
return Exception.GetShortErrorInfoText(TinkApp.IsReportLevelVerbose);
}
if (!IsConnected)

View file

@ -13,7 +13,7 @@ using TINK.Settings;
using TINK.Model.Bike.BluetoothLock;
using System.Collections.Generic;
using TINK.Services.BluetoothLock;
using TINK.Model.Services.Geolocation;
using TINK.Services.Geolocation;
using System.Linq;
using TINK.Model;
using Xamarin.Forms;

View file

@ -25,7 +25,7 @@ using TINK.MultilingualResources;
using TINK.Services.BluetoothLock;
using TINK.ViewModel.Info;
using TINK.Repository;
using TINK.Model.Services.Geolocation;
using TINK.Services.Geolocation;
#if !TRYNOTBACKSTYLE
#endif
@ -60,7 +60,6 @@ namespace TINK.ViewModel.Map
/// </summary>
private Plugin.BLE.Abstractions.Contracts.IBluetoothLE BluetoothService { get; set; }
/// <summary> Notifies view about changes. </summary>
public event PropertyChangedEventHandler PropertyChanged;
@ -299,9 +298,6 @@ namespace TINK.ViewModel.Map
/// Invoked when page is shown.
/// Starts update process.
/// </summary>
/// <param name="p_oFilterDictionaryMapPage">Holds map page filter settings.</param>
/// <param name="p_oPolling">Holds polling management object.</param>
/// <param name="p_bIsShowWhatsNewRequired">If true whats new page will be shown.</param>
public async Task OnAppearing()
{
try
@ -821,9 +817,7 @@ namespace TINK.ViewModel.Map
if (Exception != null)
{
// An error occurred getting data from copri.
return TinkApp.IsReportLevelVerbose
? Exception.GetShortErrorInfoText()
: AppResources.ActivityTextException;
return Exception.GetShortErrorInfoText(TinkApp.IsReportLevelVerbose);
}
if (!IsConnected)

View file

@ -12,7 +12,7 @@ using TINK.Settings;
using TINK.Model.Bike.BluetoothLock;
using System.Collections.Generic;
using TINK.Services.BluetoothLock;
using TINK.Model.Services.Geolocation;
using TINK.Services.Geolocation;
using System.Linq;
using TINK.Model;
using Xamarin.Forms;

View file

@ -7,7 +7,7 @@ using System.Threading.Tasks;
using TINK.Model;
using TINK.Model.Connector;
using TINK.Repository.Exception;
using TINK.Model.Services.Geolocation;
using TINK.Services.Geolocation;
using TINK.Settings;
using TINK.View;
using TINK.ViewModel.Map;
@ -65,18 +65,12 @@ namespace TINK.ViewModel
/// <summary> Constructs a settings page view model object.</summary>
/// <param name="tinkApp"> Reference to tink app model.</param>
/// <param name="p_oUser"></param>
/// <param name="p_oDevice"></param>
/// <param name="p_oFilterGroup">Filter to apply on stations and bikes.</param>
/// <param name="p_oUris">Available copri server host uris including uri to use for next start.</param>
/// <param name="p_oPolling"> Holds whether to poll or not and the periode leght is polling is on. </param>
/// <param name="p_oDefaultPollingPeriode">Default polling periode lenght.</param>
/// <param name="p_oMinimumLogEventLevel">Controls logging level.</param>
/// <param name="p_oViewService">Interface to view</param>
/// <param name="geoloctionServicesContainer"></param>
/// <param name="viewService">Interface to view</param>
public SettingsPageViewModel(
ITinkApp tinkApp,
IServicesContainer<IGeolocation> geoloctionServicesContainer,
IViewService p_oViewService)
IViewService viewService)
{
TinkApp = tinkApp
?? throw new ArgumentException("Can not instantiate settings page view model- object. No tink app object available.");
@ -84,7 +78,7 @@ namespace TINK.ViewModel
GeoloctionServicesContainer = geoloctionServicesContainer
?? throw new ArgumentException($"Can not instantiate {nameof(SettingsPageViewModel)}- object. Geolocation services container object must not be null.");
m_oViewService = p_oViewService
m_oViewService = viewService
?? throw new ArgumentException("Can not instantiate settings page view model- object. No user view service available.");
m_oMinimumLogEventLevel = TinkApp.MinimumLogEventLevel;
@ -155,8 +149,10 @@ namespace TINK.ViewModel
GeolocationServices = new ServicesViewModel(
GeoloctionServicesContainer.Select(x => x.GetType().FullName),
new Dictionary<string, string> {
{ typeof(LastKnownGeolocationService).FullName, "Smartdevice-LastKnowGeolocation" },
{ typeof(GeolocationService).FullName, "Smartdevice-MediumAccuracy" },
{ typeof(LastKnownGeolocationService).FullName, "LastKnowGeolocation" },
{ typeof(GeolocationAccuracyMediumService).FullName, "Medium Accuracy" },
{ typeof(GeolocationAccuracyHighService).FullName, "High Accuracy" },
{ typeof(GeolocationAccuracyBestService).FullName, "Best Accuracy" },
{ typeof(SimulatedGeolocationService).FullName, "Simulation-AlwaysSamePosition" } },
GeoloctionServicesContainer.Active.GetType().FullName);
}
@ -323,6 +319,9 @@ namespace TINK.ViewModel
}
}
/// <summary> Empty if no user is logged in session cookie otherwise. </summary>
public string SessionCookie => TinkApp.ActiveUser.IsLoggedIn ? TinkApp.ActiveUser.SessionCookie : "";
/// <summary>Polling periode.</summary>
public PollingViewModel Polling { get; }

View file

@ -112,13 +112,22 @@ namespace TINK.ViewModel
}
/// <summary> Gets message that logged in user has not booked any bikes. </summary>
public static string GetShortErrorInfoText(this Exception exception)
/// <param name="isReportLevelVerbose">Holds whether report level is verbose or not.</param>
public static string GetShortErrorInfoText(this Exception exception, bool isReportLevelVerbose)
{
if (exception == null)
{
return string.Empty;
}
if (exception is AuthcookieNotDefinedException)
return AppResources.ActivityTextAuthcookieNotDefinedException;
if (!isReportLevelVerbose)
{
return AppResources.ActivityTextException;
}
// An error occurred getting bikes information.
#if USCSHARP9
switch (exception)