This commit is contained in:
Oliver Hauff 2022-01-22 18:30:23 +01:00
parent 578fcee611
commit 6ed1579494
34 changed files with 357 additions and 89 deletions

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="internalOnly" package="com.TeilRad.LastenradBayern" android:versionName="3.0.275" android:versionCode="275">
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="internalOnly" package="com.TeilRad.LastenradBayern" android:versionName="3.0.276" android:versionCode="276">
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="30" />
<!-- Google Maps related permissions -->
<!-- Permission to receive remote notifications from Google Play Services -->
@ -18,5 +18,19 @@
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
<application android:icon="@drawable/sharee" android:label="LastenradBayern" android:allowBackup="false"></application>
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="http"/>
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="https"/>
</intent>
<intent>
<action android:name="android.intent.action.SENDTO" />
<data android:scheme="mailto" />
</intent>
</queries>
<meta-data android:name="com.google.android.geo.API_KEY" android:value="000000000000000000000000000000000000000" />
</manifest>

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
@ -24,6 +24,10 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>mailto</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>XSLaunchImageAssets</key>
@ -49,8 +53,8 @@
<key>CFBundleDisplayName</key>
<string>LastenradBayern</string>
<key>CFBundleVersion</key>
<string>275</string>
<string>276</string>
<key>CFBundleShortVersionString</key>
<string>3.0.275</string>
<string>3.0.276</string>
</dict>
</plist>

View file

@ -102,7 +102,11 @@
Text="{Binding TariffDescription.OperatorAgb}"
IsVisible="{Binding TariffDescription.OperatorAgb, Converter={StaticResource Label_Converter}}"
Grid.Row="5"
Grid.ColumnSpan="3"/>
Grid.ColumnSpan="3">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding ShowAgbTappedCommand}"/>
</Label.GestureRecognizers>
</Label>
</Grid>
</StackLayout>
</ContentView>

View file

@ -2,6 +2,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using TINK.Model.Device;
using TINK.ViewModel.FindBike;
using Xamarin.CommunityToolkit.Extensions;
using Xamarin.Forms;
@ -51,7 +52,8 @@ namespace TINK.View.FindBike
model.Polling,
(d, obj) => synchronizationContext.Post(d, obj),
model.SmartDevice,
this)
this,
(url) => DependencyService.Get<IExternalBrowserService>().OpenUrl(url))
{
IsReportLevelVerbose = model.IsReportLevelVerbose
};

View file

@ -11,6 +11,7 @@ using Xamarin.Forms.Xaml;
namespace TINK.View.MyBikes
{
using Serilog;
using TINK.Model.Device;
using TINK.ViewModel.MyBikes;
using Xamarin.CommunityToolkit.Extensions;
@ -68,7 +69,8 @@ namespace TINK.View.MyBikes
model.Polling,
(d, obj) => synchronizationContext.Post(d, obj),
model.SmartDevice,
this)
this,
(url) => DependencyService.Get<IExternalBrowserService>().OpenUrl(url))
{
IsReportLevelVerbose = model.IsReportLevelVerbose
};

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="internalOnly" package="com.TeilRad.Meinkonrad" android:versionName="3.0.275" android:versionCode="275">
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="internalOnly" package="com.TeilRad.Meinkonrad" android:versionName="3.0.276" android:versionCode="276">
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="30" />
<!-- Google Maps related permissions -->
<!-- Permission to receive remote notifications from Google Play Services -->
@ -18,5 +18,19 @@
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
<application android:icon="@drawable/sharee" android:label="Meinkonrad" android:allowBackup="false"></application>
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="http"/>
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="https"/>
</intent>
<intent>
<action android:name="android.intent.action.SENDTO" />
<data android:scheme="mailto" />
</intent>
</queries>
<meta-data android:name="com.google.android.geo.API_KEY" android:value="000000000000000000000000000000000000000" />
</manifest>

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
@ -24,6 +24,10 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>mailto</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>XSLaunchImageAssets</key>
@ -49,8 +53,8 @@
<key>CFBundleDisplayName</key>
<string>Mein konrad</string>
<key>CFBundleVersion</key>
<string>275</string>
<string>276</string>
<key>CFBundleShortVersionString</key>
<string>3.0.275</string>
<string>3.0.276</string>
</dict>
</plist>

View file

@ -102,7 +102,11 @@
Text="{Binding TariffDescription.OperatorAgb}"
IsVisible="{Binding TariffDescription.OperatorAgb, Converter={StaticResource Label_Converter}}"
Grid.Row="5"
Grid.ColumnSpan="3"/>
Grid.ColumnSpan="3">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding ShowAgbTappedCommand}"/>
</Label.GestureRecognizers>
</Label>
</Grid>
</StackLayout>
</ContentView>

View file

@ -2,6 +2,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using TINK.Model.Device;
using TINK.ViewModel.FindBike;
using Xamarin.CommunityToolkit.Extensions;
using Xamarin.Forms;
@ -51,7 +52,8 @@ namespace TINK.View.FindBike
model.Polling,
(d, obj) => synchronizationContext.Post(d, obj),
model.SmartDevice,
this)
this,
(url) => DependencyService.Get<IExternalBrowserService>().OpenUrl(url))
{
IsReportLevelVerbose = model.IsReportLevelVerbose
};

View file

@ -11,6 +11,7 @@ using Xamarin.Forms.Xaml;
namespace TINK.View.MyBikes
{
using Serilog;
using TINK.Model.Device;
using TINK.ViewModel.MyBikes;
using Xamarin.CommunityToolkit.Extensions;
@ -68,7 +69,8 @@ namespace TINK.View.MyBikes
model.Polling,
(d, obj) => synchronizationContext.Post(d, obj),
model.SmartDevice,
this)
this,
(url) => DependencyService.Get<IExternalBrowserService>().OpenUrl(url))
{
IsReportLevelVerbose = model.IsReportLevelVerbose
};

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="preferExternal" package="com.hauffware.sharee" android:versionName="3.0.275" android:versionCode="275">
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="preferExternal" package="com.hauffware.sharee" android:versionName="3.0.276" android:versionCode="276">
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="30" />
<!-- Google Maps related permissions -->
<!-- Permission to receive remote notifications from Google Play Services -->
@ -18,5 +18,19 @@
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
<application android:icon="@drawable/sharee" android:label="sharee.bike" android:allowBackup="false"></application>
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="http"/>
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="https"/>
</intent>
<intent>
<action android:name="android.intent.action.SENDTO" />
<data android:scheme="mailto" />
</intent>
</queries>
<meta-data android:name="com.google.android.geo.API_KEY" android:value="000000000000000000000000000000000000000" />
</manifest>

View file

@ -24,6 +24,10 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>mailto</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>XSLaunchImageAssets</key>
@ -49,8 +53,8 @@
<key>CFBundleDisplayName</key>
<string>sharee.bike</string>
<key>CFBundleVersion</key>
<string>275</string>
<string>276</string>
<key>CFBundleShortVersionString</key>
<string>3.0.275</string>
<string>3.0.276</string>
</dict>
</plist>

View file

@ -102,7 +102,11 @@
Text="{Binding TariffDescription.OperatorAgb}"
IsVisible="{Binding TariffDescription.OperatorAgb, Converter={StaticResource Label_Converter}}"
Grid.Row="5"
Grid.ColumnSpan="3"/>
Grid.ColumnSpan="3">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding ShowAgbTappedCommand}"/>
</Label.GestureRecognizers>
</Label>
</Grid>
</StackLayout>
</ContentView>

View file

@ -2,6 +2,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using TINK.Model.Device;
using TINK.ViewModel.FindBike;
using Xamarin.CommunityToolkit.Extensions;
using Xamarin.Forms;
@ -51,7 +52,8 @@ namespace TINK.View.FindBike
model.Polling,
(d, obj) => synchronizationContext.Post(d, obj),
model.SmartDevice,
this)
this,
(url) => DependencyService.Get<IExternalBrowserService>().OpenUrl(url))
{
IsReportLevelVerbose = model.IsReportLevelVerbose
};

View file

@ -11,6 +11,7 @@ using Xamarin.Forms.Xaml;
namespace TINK.View.MyBikes
{
using Serilog;
using TINK.Model.Device;
using TINK.ViewModel.MyBikes;
using Xamarin.CommunityToolkit.Extensions;
@ -68,7 +69,8 @@ namespace TINK.View.MyBikes
model.Polling,
(d, obj) => synchronizationContext.Post(d, obj),
model.SmartDevice,
this)
this,
(url) => DependencyService.Get<IExternalBrowserService>().OpenUrl(url))
{
IsReportLevelVerbose = model.IsReportLevelVerbose
};

View file

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

View file

@ -909,6 +909,17 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Link to operator AGB is displayed if required.
///Display of status information extended.
///Bugfix: Sending support mails works again. .
/// </summary>
public static string ChangeLog3_0_276 {
get {
return ResourceManager.GetString("ChangeLog3_0_276", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Lock of rented bike can not be found..
/// </summary>
@ -1818,6 +1829,16 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to An error occurred switching from cargo bikes/ city bikes.
///{0}.
/// </summary>
public static string MessageMapPageErrorSwitch {
get {
return ResourceManager.GetString("MessageMapPageErrorSwitch", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Urgent questions?.
/// </summary>

View file

@ -733,4 +733,13 @@ Deep linking wird unterstützt.</value>
<data name="ChangeLog3_0_266" xml:space="preserve">
<value>Abfrage Erlaubnis für Zugriff azf Standort verbessert.</value>
</data>
<data name="ChangeLog3_0_276" xml:space="preserve">
<value>Link auf Betreiber-AGB wird bei Bedarf angezeigt.
Anzeige von Statusinformationen erweitert.
Fehlerbehebung: Supportmails können wieder verschickt werden.</value>
</data>
<data name="MessageMapPageErrorSwitch" xml:space="preserve">
<value>Beim Umschalten zwischen Lasten-/ Stadträdern ist ein Fehler aufgetreten.
{0}</value>
</data>
</root>

View file

@ -828,4 +828,13 @@ App supports deep linking.</value>
<data name="ChangeLog3_0_266" xml:space="preserve">
<value>Location permissions handling improved.</value>
</data>
<data name="ChangeLog3_0_276" xml:space="preserve">
<value>Link to operator AGB is displayed if required.
Display of status information extended.
Bugfix: Sending support mails works again. </value>
</data>
<data name="MessageMapPageErrorSwitch" xml:space="preserve">
<value>An error occurred switching from cargo bikes/ city bikes.
{0}</value>
</data>
</root>

View file

@ -984,6 +984,20 @@ Deep linking wird unterstützt.</target>
<source>Location permissions handling improved.</source>
<target state="translated">Abfrage Erlaubnis für Zugriff azf Standort verbessert.</target>
</trans-unit>
<trans-unit id="ChangeLog3_0_276" translate="yes" xml:space="preserve">
<source>Link to operator AGB is displayed if required.
Display of status information extended.
Bugfix: Sending support mails works again. </source>
<target state="translated">Link auf Betreiber-AGB wird bei Bedarf angezeigt.
Anzeige von Statusinformationen erweitert.
Fehlerbehebung: Supportmails können wieder verschickt werden.</target>
</trans-unit>
<trans-unit id="MessageMapPageErrorSwitch" translate="yes" xml:space="preserve">
<source>An error occurred switching from cargo bikes/ city bikes.
{0}</source>
<target state="translated">Beim Umschalten zwischen Lasten-/ Stadträdern ist ein Fehler aufgetreten.
{0}</target>
</trans-unit>
</group>
</body>
</file>

View file

@ -36,6 +36,7 @@ namespace TINK.ViewModel.Bikes.Bike.BC
/// <param name="activeUser">Object holding logged in user or an empty user object.</param>
/// <param name="stateInfoProvider">Provides in use state information.</param>
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
/// <param name="openUrlInBrowser">Delegate to open browser.</param>
public BikeViewModel(
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
@ -46,7 +47,8 @@ namespace TINK.ViewModel.Bikes.Bike.BC
BikeInfoMutable selectedBike,
IUser activeUser,
IInUseStateInfoProvider stateInfoProvider,
IBikesViewModel bikesViewModel) : base(isConnectedDelegate, connectorFactory, bikeRemoveDelegate, viewUpdateManager, smartDevice, viewService, selectedBike, activeUser, stateInfoProvider, bikesViewModel)
IBikesViewModel bikesViewModel,
Action<string> openUrlInBrowser) : base(isConnectedDelegate, connectorFactory, bikeRemoveDelegate, viewUpdateManager, smartDevice, viewService, selectedBike, activeUser, stateInfoProvider, bikesViewModel, openUrlInBrowser)
{
RequestHandler = activeUser.IsLoggedIn
? RequestHandlerFactory.Create(

View file

@ -1,5 +1,7 @@
using System;
using Serilog;
using System;
using System.ComponentModel;
using System.Text.RegularExpressions;
using TINK.Model.Connector;
using TINK.Model.Device;
using TINK.Model.State;
@ -62,6 +64,9 @@ namespace TINK.ViewModel.Bikes.Bike
/// <summary>View model to be used for progress report and unlocking/ locking view.</summary>
protected IBikesViewModel BikesViewModel { get; }
/// <summary> Delegate to open browser. </summary>
private Action<string> OpenUrlInBrowser;
/// <summary>
/// Notifies GUI about changes.
/// </summary>
@ -83,6 +88,7 @@ namespace TINK.ViewModel.Bikes.Bike
/// <param name="activeUser">Object holding logged in user or an empty user object.</param>
/// <param name="stateInfoProvider">Provides in use state information.</param>
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
/// <param name="openUrlInBrowser">Delegate to open browser.</param>
public BikeViewModelBase(
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
@ -93,7 +99,8 @@ namespace TINK.ViewModel.Bikes.Bike
BikeInfoMutable selectedBike,
IUser activeUser,
IInUseStateInfoProvider stateInfoProvider,
IBikesViewModel bikesViewModel)
IBikesViewModel bikesViewModel,
Action<string> openUrlInBrowser)
{
IsConnectedDelegate = isConnectedDelegate;
@ -121,6 +128,8 @@ namespace TINK.ViewModel.Bikes.Bike
BikesViewModel = bikesViewModel
?? throw new ArgumentException($"Can not construct {GetType().Name}-object. {nameof(bikesViewModel)} must not be null.");
OpenUrlInBrowser = openUrlInBrowser ?? (url => { Log.ForContext<BikeViewModelBase>().Error($"No browse service avialble to upen {url}."); });
}
/// <summary>
@ -309,5 +318,63 @@ namespace TINK.ViewModel.Bikes.Bike
/// <summary> Gets the value of property <see cref="StateColor"/> when PropertyChanged was fired. </summary>
public Color LastStateColor { get; set; }
/// <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 OpenLoginPageAsync());
#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))
{
// No url contained in string.
return;
}
OpenUrlInBrowser(url);
#endif
}
catch (Exception p_oException)
{
Log.Error("An unexpected error occurred opening broser. {@Exception}", p_oException);
return;
}
}
/// <summary> Gets first url from text.</summary>
/// <param name="htmlSource">url to extract text from.</param>
/// <returns>Gets first url or an empty string if on url is contained in text.</returns>
public static string GetUrlFirstOrDefault(string htmlSource)
{
if (string.IsNullOrEmpty(htmlSource))
return string.Empty;
try
{
var matches = new Regex(@"https://[-a-zA-Z0-9+&@#/%?=~_|!:, .;]*[-a-zA-Z0-9+&@#/%=~_|]").Matches(htmlSource);
return matches.Count > 0
? matches[0].Value
: string.Empty;
} catch (Exception e)
{
Log.ForContext<BikeViewModelBase>().Error("Extracting URL failed. {Exception}", e);
return string.Empty;
}
}
}
}

View file

@ -13,6 +13,7 @@ namespace TINK.ViewModel.Bikes.Bike
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...).</param>
/// <param name="stateInfoProvider">Provides in use state information.</param>
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
/// <param name="openUrlInBrowser">Delegate to open browser.</param>
public static BikeViewModelBase Create(
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
@ -25,7 +26,8 @@ namespace TINK.ViewModel.Bikes.Bike
Model.Bike.BC.BikeInfoMutable bikeInfo,
IUser activeUser,
IInUseStateInfoProvider stateInfoProvider,
IBikesViewModel bikesViewModel)
IBikesViewModel bikesViewModel,
Action<string> openUrlInBrowser)
{
return bikeInfo as Model.Bikes.Bike.BluetoothLock.IBikeInfoMutable != null
? new BluetoothLock.BikeViewModel(
@ -40,7 +42,8 @@ namespace TINK.ViewModel.Bikes.Bike
bikeInfo as Model.Bike.BluetoothLock.BikeInfoMutable,
activeUser,
stateInfoProvider,
bikesViewModel) as BikeViewModelBase
bikesViewModel,
openUrlInBrowser) as BikeViewModelBase
: new BC.BikeViewModel(
isConnectedDelegate,
connectorFactory,
@ -51,7 +54,8 @@ namespace TINK.ViewModel.Bikes.Bike
bikeInfo,
activeUser,
stateInfoProvider,
bikesViewModel);
bikesViewModel,
openUrlInBrowser);
}
}
}

View file

@ -83,6 +83,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
/// <param name="user">Object holding logged in user or an empty user object.</param>
/// <param name="stateInfoProvider">Provides in use state information.</param>
/// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
/// <param name="openUrlInBrowser">Delegate to open browser.</param>
public BikeViewModel(
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
@ -95,7 +96,8 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock
BikeInfoMutable selectedBike,
IUser user,
IInUseStateInfoProvider stateInfoProvider,
IBikesViewModel bikesViewModel) : base(isConnectedDelegate, connectorFactory, bikeRemoveDelegate, viewUpdateManager, smartDevice, viewService, selectedBike, user, stateInfoProvider, bikesViewModel)
IBikesViewModel bikesViewModel,
Action<string> openUrlInBrowser) : base(isConnectedDelegate, connectorFactory, bikeRemoveDelegate, viewUpdateManager, smartDevice, viewService, selectedBike, user, stateInfoProvider, bikesViewModel, openUrlInBrowser)
{
RequestHandler = user.IsLoggedIn
? RequestHandlerFactory.Create(

View file

@ -54,6 +54,9 @@ namespace TINK.ViewModel.Bikes
/// <summary> Action to post to GUI thread.</summary>
public Action<SendOrPostCallback, object> PostAction { get; }
/// <summary> Delegate to open browser. </summary>
private Action<string> OpenUrlInBrowser { get; }
/// <summary>Enables derived class to fire property changed event. </summary>
/// <param name="p_oEventArgs"></param>
protected override void OnPropertyChanged(PropertyChangedEventArgs p_oEventArgs) => base.OnPropertyChanged(p_oEventArgs);
@ -89,6 +92,7 @@ namespace TINK.ViewModel.Bikes
/// <param name="postAction">Executes actions on GUI thread.</param>
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...)</param>
/// <param name="viewService">Interface to actuate methodes on GUI.</param>
/// <param name="openUrlInBrowser">Delegate to open browser.</param>
public BikesViewModel(
User user,
ILocationPermission permissions,
@ -102,6 +106,7 @@ namespace TINK.ViewModel.Bikes
Action<SendOrPostCallback, object> postAction,
ISmartDevice smartDevice,
IViewService viewService,
Action<string> openUrlInBrowser,
Func<IInUseStateInfoProvider> itemFactory)
{
User = user
@ -150,6 +155,8 @@ namespace TINK.ViewModel.Bikes
isConnected = IsConnectedDelegate();
OpenUrlInBrowser = openUrlInBrowser;
CollectionChanged += (sender, eventargs) =>
{
// Notify about bikes occuring/ vanishing from list.
@ -186,7 +193,8 @@ namespace TINK.ViewModel.Bikes
l_oBike,
User,
m_oItemFactory(),
this);
this,
OpenUrlInBrowser);
bikeViewModel.PropertyChanged += OnBikeRequestHandlerPropertyChanged;

View file

@ -36,9 +36,6 @@ namespace TINK.ViewModel.BikesAtStation
/// </summary>
private readonly IStation m_oStation;
/// <summary> Holds a reference to the external trigger service. </summary>
private Action<string> OpenUrlInExternalBrowser { get; }
/// <summary>
/// Constructs bike collection view model.
/// </summary>
@ -70,11 +67,8 @@ namespace TINK.ViewModel.BikesAtStation
Action<string> openUrlInExternalBrowser,
Action<SendOrPostCallback, object> postAction,
ISmartDevice smartDevice,
IViewService viewService) : base(user, permissions, bluetoothLE, runtimPlatform, isConnectedDelegate, connectorFactory, geolocation, lockService, polling, postAction, smartDevice, viewService, () => new BikeAtStationInUseStateInfoProvider())
IViewService viewService) : base(user, permissions, bluetoothLE, runtimPlatform, isConnectedDelegate, connectorFactory, geolocation, lockService, polling, postAction, smartDevice, viewService, openUrlInExternalBrowser, () => new BikeAtStationInUseStateInfoProvider())
{
OpenUrlInExternalBrowser = openUrlInExternalBrowser
?? throw new ArgumentException("Can not instantiate login page view model- object. No user external browse service available.");
m_oStation = selectedStation;
Title = string.Format(m_oStation?.StationName != null
@ -387,20 +381,5 @@ namespace TINK.ViewModel.BikesAtStation
base.OnPropertyChanged(new PropertyChangedEventArgs(nameof(NoBikesAtStationText)));
}
}
/// <summary> Opens login page.</summary>
/// <param name="url">Url to open.</param>
private void RegisterRequest(string url)
{
try
{
OpenUrlInExternalBrowser(url);
}
catch (Exception p_oException)
{
Log.Error("Ein unerwarteter Fehler ist auf der Login Seite beim Öffnen eines Browsers, Seite {url}, aufgetreten. {@Exception}", url, p_oException);
return;
}
}
}
}

View file

@ -75,6 +75,7 @@ namespace TINK.ViewModel.FindBike
/// <param name="postAction">Executes actions on GUI thread.</param>
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...).</param>
/// <param name="viewService">Interface to actuate methodes on GUI.</param>
/// <param name="openUrlInBrowser">Delegate to open browser.</param>
public FindBikePageViewModel(
User p_oUser,
ILocationPermission permissions,
@ -88,7 +89,8 @@ namespace TINK.ViewModel.FindBike
PollingParameters polling,
Action<SendOrPostCallback, object> postAction,
ISmartDevice smartDevice,
IViewService viewService) : base(p_oUser, permissions, bluetoothLE, runtimPlatform, isConnectedDelegate, connectorFactory, geolocation, lockService, polling, postAction, smartDevice, viewService, () => new MyBikeInUseStateInfoProvider())
IViewService viewService,
Action<string> openUrlInBrowser) : base(p_oUser, permissions, bluetoothLE, runtimPlatform, isConnectedDelegate, connectorFactory, geolocation, lockService, polling, postAction, smartDevice, viewService, openUrlInBrowser, () => new MyBikeInUseStateInfoProvider())
{
CollectionChanged += (sender, eventargs) =>
{

View file

@ -333,7 +333,7 @@ namespace TINK.ViewModel.Map
await ViewService.DisplayAlert(
"Information",
resultStationsAndBikes.GeneralData.MerchantMessage,
"OK");
AppResources.MessageAnswerOk);
WasMerchantMessageAlreadyShown = true;
}
@ -876,9 +876,13 @@ namespace TINK.ViewModel.Map
{
try
{
IsMapPageEnabled = false;
IsRunning = true;
Log.ForContext<MapPageViewModel>().Information($"Request to toggle to \"{p_strSelectedFilter}\".");
// Stop polling.
ActionText = AppResources.ActivityTextOneMomentPlease;
await m_oViewUpdateManager.StopUpdatePeridically();
// Clear error info.
@ -896,27 +900,29 @@ namespace TINK.ViewModel.Map
Pins.Clear();
// Check location permission
ActionText = AppResources.ActivityTextRequestingLocationPermissions;
var status = await PermissionsService.CheckStatusAsync();
if (TinkApp.CenterMapToCurrentLocation
&& !GeolocationService.IsSimulation
&& status != Status.Granted)
{
var permissionResult = await PermissionsService.RequestAsync();
status = await PermissionsService.RequestAsync();
if (permissionResult != Status.Granted)
if (status != Status.Granted)
{
var dialogResult = await ViewService.DisplayAlert(
AppResources.MessageTitleHint,
AppResources.MessageBikesManagementLocationPermission,
"Ja",
"Nein");
AppResources.MessageTitleHint,
AppResources.MessageCenterMapLocationPermissionOpenDialog,
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
if (dialogResult)
{
// User decided to give access to locations permissions.
PermissionsService.OpenAppSettings();
IsMapPageEnabled = true;
ActionText = "";
IsRunning = false;
IsMapPageEnabled = true;
return;
}
}
@ -930,13 +936,16 @@ namespace TINK.ViewModel.Map
AppResources.MessageTitleHint,
AppResources.MessageBikesManagementBluetoothActivation,
AppResources.MessageAnswerOk);
IsMapPageEnabled = true;
ActionText = "";
IsRunning = false;
IsMapPageEnabled = true;
return;
}
}
// Move and scale before getting stations and bikes which takes some time.
ActionText = AppResources.ActivityTextCenterMap;
if (TinkApp.CenterMapToCurrentLocation)
{
Location currentLocation = null;
@ -962,6 +971,7 @@ namespace TINK.ViewModel.Map
// Update stations
MoveAndScale(m_oMoveToRegionDelegate, TinkApp.ActiveMapSpan);
ActionText = AppResources.ActivityTextMapLoadingStationsAndBikes;
IsConnected = TinkApp.GetIsConnected();
var resultStationsAndBikes = await TinkApp.GetConnector(IsConnected).Query.GetBikesAndStationsAsync();
@ -990,16 +1000,24 @@ namespace TINK.ViewModel.Map
// Excpetions are handled insde update task;
}
ActionText = "";
IsRunning = false;
IsMapPageEnabled = true;
Log.ForContext<MapPageViewModel>().Information($"Toggle to \"{p_strSelectedFilter}\" done.");
}
catch (Exception l_oException)
{
Log.ForContext<MapPageViewModel>().Error("An error occurred switching view Cargobike/ Citybike.{}");
ActionText = "";
IsRunning = false;
await ViewService.DisplayAlert(
"Fehler",
$"Beim Umschalten TINK/ Konrad ist ein Fehler aufgetreten.\r\n{l_oException.Message}",
"OK");
AppResources.MessageMapPageErrorSwitch,
String.Format(AppResources.MessageMapPageErrorSwitch, l_oException.Message),
AppResources.MessageAnswerOk);
IsMapPageEnabled = true;
}
}

View file

@ -47,6 +47,7 @@ namespace TINK.ViewModel.MyBikes
/// <param name="postAction">Executes actions on GUI thread.</param>
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...).</param>
/// <param name="viewService">Interface to actuate methodes on GUI.</param>
/// <param name="openUrlInBrowser">Delegate to open browser.</param>
public MyBikesPageViewModel(
User p_oUser,
ILocationPermission permissions,
@ -60,7 +61,8 @@ namespace TINK.ViewModel.MyBikes
PollingParameters p_oPolling,
Action<SendOrPostCallback, object> postAction,
ISmartDevice smartDevice,
IViewService viewService) : base(p_oUser, permissions, bluetoothLE, runtimPlatform, isConnectedDelegate, connectorFactory, geolocation, lockService, p_oPolling, postAction, smartDevice, viewService, () => new MyBikeInUseStateInfoProvider())
IViewService viewService,
Action<string> openUrlInBrowser) : base(p_oUser, permissions, bluetoothLE, runtimPlatform, isConnectedDelegate, connectorFactory, geolocation, lockService, p_oPolling, postAction, smartDevice, viewService, openUrlInBrowser, () => new MyBikeInUseStateInfoProvider())
{
CollectionChanged += (sender, eventargs) =>
{

View file

@ -0,0 +1,33 @@
using NUnit.Framework;
using TINK.ViewModel.Bikes.Bike;
namespace TestShareeLib.ViewModel.Bikes.Bike
{
[TestFixture]
public class TestBikeViewModelBase
{
[Test]
public void TestGetUrl()
{
Assert.That(
BikeViewModelBase.GetUrlFirstOrDefault(@"Mit der Mietrad Anmietung wird folgender Betreiber <a href='https://shareeapp-fr01.copri.eu/site/agb.html' target='_blank'>AGB</a> zugestimmt (als Demo sharee AGB)."),
Is.EqualTo(@"https://shareeapp-fr01.copri.eu/site/agb.html"));
}
[Test]
public void TestGetUrlNoProtocol()
{
Assert.That(
BikeViewModelBase.GetUrlFirstOrDefault(@"Mit der Mietrad Anmietung wird folgender Betreiber <a href='shareeapp-fr01.copri.eu/site/agb.html' target='_blank'>AGB</a> zugestimmt (als Demo sharee AGB)."),
Is.EqualTo(""));
}
[Test]
public void TestGetUrlNull()
{
Assert.That(
BikeViewModelBase.GetUrlFirstOrDefault(null),
Is.EqualTo(""));
}
}
}

View file

@ -68,7 +68,8 @@ namespace UITest.Fixtures.ViewModel
l_oBike,
l_oUser,
new MyBikeInUseStateInfoProvider(),
MockRepository.GenerateStub<IBikesViewModel>());
MockRepository.GenerateStub<IBikesViewModel>(),
url => { });
Assert.AreEqual("2", l_oViewModel.Name);
Assert.AreEqual("", l_oViewModel.DisplayId);
@ -94,7 +95,8 @@ namespace UITest.Fixtures.ViewModel
bike,
user,
new BikeAtStationInUseStateInfoProvider(),
MockRepository.GenerateStub<IBikesViewModel>()));
MockRepository.GenerateStub<IBikesViewModel>(),
url => { }));
Assert.AreEqual("Still 15 minutes reserved.", l_oViewModel.StateText);
}
@ -116,7 +118,8 @@ namespace UITest.Fixtures.ViewModel
bike,
user,
new BikeAtStationInUseStateInfoProvider(),
MockRepository.GenerateStub<IBikesViewModel>()));
MockRepository.GenerateStub<IBikesViewModel>(),
url => { }));
Assert.AreEqual("Code 4asdfA, still 7 minutes reserved.", l_oViewModel.StateText);
}
@ -138,7 +141,8 @@ namespace UITest.Fixtures.ViewModel
bike,
user,
new BikeAtStationInUseStateInfoProvider(),
MockRepository.GenerateStub<IBikesViewModel>()));
MockRepository.GenerateStub<IBikesViewModel>(),
url => { }));
Assert.AreEqual(
$"Code 4asdfA, rented since {new DateTime(2018, 10, 24, 21, 49, 00).ToString("dd. MMMM HH:mm")}.",
@ -174,7 +178,8 @@ namespace UITest.Fixtures.ViewModel
l_oStoreMock.Load().Result,
"123456789"), // Device id
new BikeAtStationInUseStateInfoProvider(),
MockRepository.GenerateStub<IBikesViewModel>());
MockRepository.GenerateStub<IBikesViewModel>(),
url => { });
Assert.AreEqual("Test description", l_oViewModel.Name);
Assert.AreEqual("2", l_oViewModel.DisplayId);
@ -212,7 +217,8 @@ namespace UITest.Fixtures.ViewModel
l_oStoreMock.Load().Result,
"123456789"),
new BikeAtStationInUseStateInfoProvider(),
MockRepository.GenerateStub<IBikesViewModel>());
MockRepository.GenerateStub<IBikesViewModel>(),
url => { });
Assert.AreEqual("Test description", l_oViewModel.Name);
Assert.AreEqual("2", l_oViewModel.DisplayId);

View file

@ -31,7 +31,8 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel
new TINK.Model.Bike.BC.BikeInfoMutable(new TINK.Model.Bike.BluetoothLock.BikeInfo("42", 5200544, new Guid("00000000-0000-0000-0000-000000000001"), "42"), "My Station Name"),
MockRepository.GenerateStub<IUser>(), // user
MockRepository.GenerateStub<IInUseStateInfoProvider>(),
MockRepository.GenerateStub<IBikesViewModel>()).GetType()); // stateInfoProvider
MockRepository.GenerateStub<IBikesViewModel>(),
url => { }).GetType()); // stateInfoProvider
Assert.AreEqual(
typeof(TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel),
@ -47,7 +48,8 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel
new TINK.Model.Bike.BluetoothLock.BikeInfoMutable(new TINK.Model.Bike.BluetoothLock.BikeInfo("42", 5200544, new Guid("00000000-0000-0000-0000-000000000001"), "42"), "My Station Name"),
MockRepository.GenerateStub<IUser>(), // user
MockRepository.GenerateStub<IInUseStateInfoProvider>(),
MockRepository.GenerateStub<IBikesViewModel>()).GetType()); // stateInfoProvider
MockRepository.GenerateStub<IBikesViewModel>(),
url => { }).GetType()); // stateInfoProvider
}
}
}

View file

@ -28,7 +28,8 @@ namespace UITest.Fixtures.ViewModel
bike,
user,
new MyBikeInUseStateInfoProvider(),
MockRepository.GenerateStub<IBikesViewModel>()));
MockRepository.GenerateStub<IBikesViewModel>(),
url => { }));
Assert.AreEqual("Location Station 3, still 15 minutes reserved.", l_oViewModel.StateText);
@ -51,7 +52,8 @@ namespace UITest.Fixtures.ViewModel
bike,
user,
new MyBikeInUseStateInfoProvider(),
MockRepository.GenerateStub<IBikesViewModel>()));
MockRepository.GenerateStub<IBikesViewModel>(),
url => { }));
Assert.AreEqual("Code 4asdfA, location Station 3, still 7 minutes reserved.", l_oViewModel.StateText);
}
@ -75,7 +77,8 @@ namespace UITest.Fixtures.ViewModel
bike,
user,
new MyBikeInUseStateInfoProvider(),
MockRepository.GenerateStub<IBikesViewModel>()));
MockRepository.GenerateStub<IBikesViewModel>(),
url => { }));
Assert.AreEqual(
$"Code 4asdfA, location Station 3, rented since {new DateTime(2018, 10, 24, 21, 49, 00).ToString("dd. MMMM HH:mm")}.",

View file

@ -98,7 +98,8 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel
tinkApp.Polling,
(d, obj) => d(obj),
Substitute.For<ISmartDevice>(),
viewService);
viewService,
url => { });
await myBikes.OnAppearing();
@ -191,7 +192,8 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel
tinkApp.Polling,
(d, obj) => d(obj),
Substitute.For<ISmartDevice>(),
viewService);
viewService,
url => { });
await myBikes.OnAppearing();
@ -292,7 +294,8 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel
tinkApp.Polling,
(d, obj) => d(obj),
Substitute.For<ISmartDevice>(),
viewService);
viewService,
url => { });
await myBikes.OnAppearing();
@ -388,7 +391,8 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel
tinkApp.Polling,
(d, obj) => d(obj),
Substitute.For<ISmartDevice>(),
viewService);
viewService,
url => { });
await myBikes.OnAppearing();
@ -482,7 +486,8 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel
tinkApp.Polling,
(d, obj) => d(obj),
Substitute.For<ISmartDevice>(),
viewService);
viewService,
url => { });
await myBikes.OnAppearing();
@ -570,7 +575,8 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel
tinkApp.Polling,
(d, obj) => d(obj),
Substitute.For<ISmartDevice>(),
viewService);
viewService,
url => { });
await myBikes.OnAppearing();
@ -633,7 +639,8 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel
tinkApp.Polling,
(d, obj) => d(obj),
Substitute.For<ISmartDevice>(),
viewService);
viewService,
url => { });
await myBikes.OnAppearing();
@ -710,7 +717,8 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel
tinkApp.Polling,
(d, obj) => d(obj),
Substitute.For<ISmartDevice>(),
viewService)
viewService,
url => { })
{
IsReportLevelVerbose = true
};
@ -790,7 +798,8 @@ namespace TestTINKLib.Fixtures.ObjectTests.ViewModel
tinkApp.Polling,
(d, obj) => d(obj),
Substitute.For<ISmartDevice>(),
viewService)
viewService,
url => { })
{
IsReportLevelVerbose = true
};