Version 3.0.376

This commit is contained in:
Anja 2023-11-21 15:26:57 +01:00
parent ca080c87c0
commit f963c0a219
158 changed files with 3228 additions and 1279 deletions

View file

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using TINK.Model.Bikes;
@ -15,20 +15,16 @@ namespace TINK.Model
public static BikeCollection GetAtStation(
this BikeCollection bikesAtAnyStation,
string selectedStation)
{
return new BikeCollection(bikesAtAnyStation?
=> new BikeCollection(bikesAtAnyStation?
.Where(bike => !string.IsNullOrEmpty(selectedStation) && bike.StationId == selectedStation)
.ToDictionary(bike => bike.Id) ?? new Dictionary<string, BikeInfo>());
}
/// <summary> Filters bikes by bike type. </summary>
/// <param name="bcAndLockItBikes">Bikes available, requested and/ or occupied bikes to filter.</param>
/// <returns>BikeCollection holding LockIt-bikes empty BikeCollection, if there are no LockIt-bikes.</returns>
public static BikeCollection GetLockIt(this BikeCollection bcAndLockItBikes)
{
return new BikeCollection(bcAndLockItBikes?
=> new BikeCollection(bcAndLockItBikes?
.Where(bike => bike is Bikes.BikeInfoNS.BluetoothLock.BikeInfo)
.ToDictionary(x => x.Id) ?? new Dictionary<string, BikeInfo>());
}
}
}

View file

@ -65,9 +65,11 @@ namespace TINK.Model.Connector
/// <summary> Gets bikes either bikes available if no user is logged in or bikes available and bikes occupied if a user is logged in. </summary>
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
public async Task<Result<BikeCollection>> GetBikesAsync(Uri operatorUri = null)
/// <param name="stationId"> Id of station which is used for filtering bikes. Null if no filtering should be applied.</param>
/// <param name="bikeId"> Id of bike which is used for filtering bikes. Null if no filtering should be applied.</param>
public async Task<Result<BikeCollection>> GetBikesAsync(Uri operatorUri = null, string stationId = null, string bikeId = null)
{
var result = await m_oInnerQuery.GetBikesAsync(operatorUri);
var result = await m_oInnerQuery.GetBikesAsync(operatorUri, stationId, bikeId);
return new Result<BikeCollection>(
result.Source,
new BikeCollection(DoFilter(result.Response, Filter)),

View file

@ -57,9 +57,11 @@ namespace TINK.Model.Connector
/// <summary> Gets bikes either bikes available if no user is logged in or bikes available and bikes occupied if a user is logged in. </summary>
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
public async Task<Result<BikeCollection>> GetBikesAsync(Uri operatorUri = null)
/// <param name="stationId"> Id of station which is used for filtering bikes. Null if no filtering should be applied.</param>
/// <param name="bikeId"> Id of bike which is used for filtering bikes. Null if no filtering should be applied.</param>
public async Task<Result<BikeCollection>> GetBikesAsync(Uri operatorUri = null, string stationId = null, string bikeId = null)
{
var result = await m_oInnerQuery.GetBikesAsync(operatorUri);
var result = await m_oInnerQuery.GetBikesAsync(operatorUri, stationId, bikeId);
return new Result<BikeCollection>(
result.Source,
new BikeCollection(result.Response.ToDictionary(x => x.Id)),

View file

@ -72,17 +72,18 @@ namespace TINK.Model.Connector
/// <summary> Gets bikes available. </summary>
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
/// <param name="stationId"> Id of station which is used for filtering bikes. Null if no filtering should be applied.</param>
/// <param name="bikeId"> Id of bike which is used for filtering bikes. Null if no filtering should be applied.</param>
/// <returns>Collection of bikes.</returns>
public async Task<Result<BikeCollection>> GetBikesAsync(Uri operatorUri = null)
public async Task<Result<BikeCollection>> GetBikesAsync(Uri operatorUri = null, string stationId = null, string bikeId = null)
{
var result = await server.GetBikesAvailable(operatorUri: operatorUri);
var result = await server.GetBikesAvailable(operatorUri: operatorUri, stationId: stationId, bikeId: bikeId);
if (result.Source != typeof(CopriCallsMonkeyStore))
{
server.AddToCache(result, operatorUri);
server.AddToCache(result, operatorUri, stationId, bikeId);
}
return new Result<BikeCollection>(
result.Source,
result.Response.GetBikesAvailable(result.Source == typeof(CopriCallsMonkeyStore)

View file

@ -128,10 +128,12 @@ namespace TINK.Model.Connector
/// <summary> Gets bikes available and bikes occupied. </summary>
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
/// <param name="stationId"> Id of station which is used for filtering bikes. Null if no filtering should be applied.</param>
/// <param name="bikeId"> Id of bike which is used for filtering bikes. Null if no filtering should be applied.</param>
/// <returns>Collection of bikes.</returns>
public async Task<Result<BikeCollection>> GetBikesAsync(Uri operatorUri = null)
public async Task<Result<BikeCollection>> GetBikesAsync(Uri operatorUri = null, string stationId = null, string bikeId = null)
{
var bikesAvailableResponse = await Server.GetBikesAvailable(operatorUri: operatorUri);
var bikesAvailableResponse = await Server.GetBikesAvailable(operatorUri: operatorUri, stationId: stationId, bikeId: bikeId);
if (bikesAvailableResponse.Source == typeof(CopriCallsMonkeyStore)
|| bikesAvailableResponse.Exception != null)
@ -155,7 +157,7 @@ namespace TINK.Model.Connector
if (operatorUri?.AbsoluteUri != null)
{
// Both types bikes could read from copri successfully => update cache
Server.AddToCache(bikesAvailableResponse, operatorUri);
Server.AddToCache(bikesAvailableResponse, operatorUri, stationId, bikeId);
Log.ForContext<CachedQueryLoggedIn>().Debug("Bikes available and occupied read successfully from server invoking one single request.");
return new Result<BikeCollection>(
@ -181,7 +183,7 @@ namespace TINK.Model.Connector
return new Result<BikeCollection>(
bikesOccupiedResponse.Source,
BikeCollectionFactory.GetBikesAll(
(await Server.GetBikesAvailable(true, operatorUri)).Response?.bikes?.Values,
(await Server.GetBikesAvailable(true, operatorUri, stationId, bikeId)).Response?.bikes?.Values,
bikesOccupiedResponse.Response?.bikes_occupied?.Values,
Mail,
DateTimeProvider,
@ -191,7 +193,7 @@ namespace TINK.Model.Connector
}
// Both types bikes could read from copri => update cache
Server.AddToCache(bikesAvailableResponse, operatorUri);
Server.AddToCache(bikesAvailableResponse, operatorUri, stationId, bikeId);
Server.AddToCache(bikesOccupiedResponse);
Log.ForContext<CachedQueryLoggedIn>().Debug("Bikes available and occupied read successfully from server.");

View file

@ -16,7 +16,8 @@ namespace TINK.Model.Connector
/// <summary> Gets bikes either bikes available if no user is logged in or bikes available and bikes occupied if a user is logged in. </summary>
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
/// <param name="stationId"> Id of station which is used for filtering bikes. Null if no filtering should be applied.</param>
/// <returns>Collection of bikes.</returns>
Task<Result<BikeCollection>> GetBikesAsync(Uri operatorUri = null);
Task<Result<BikeCollection>> GetBikesAsync(Uri operatorUri = null, string stationId = null, string bikeId = null);
}
}

View file

@ -55,14 +55,18 @@ namespace TINK.Model.Connector
/// <summary> Gets bikes occupied. </summary>
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
/// <param name="stationId"> Id of station which is used for filtering bikes. Null if no filtering should be applied.</param>
/// <param name="bikeId"> Id of bike which is used for filtering bikes. Null if no filtering should be applied.</param>
/// <returns> Collection of bikes. </returns>
public async Task<Result<BikeCollection>> GetBikesAsync(Uri operatorUri = null)
public async Task<Result<BikeCollection>> GetBikesAsync(Uri operatorUri = null, string stationId = null, string bikeId = null)
{
var bikesAvailableResponse = await server.GetBikesAvailableAsync(operatorUri);
var bikesAvailableResponse = await server.GetBikesAvailableAsync(operatorUri, stationId, bikeId);
return new Result<BikeCollection>(
typeof(CopriCallsMonkeyStore),
bikesAvailableResponse.GetBikesAvailable(Bikes.BikeInfoNS.BC.DataSource.Cache),
bikesAvailableResponse.GetGeneralData());
bikesAvailableResponse != null
? bikesAvailableResponse.GetBikesAvailable(Bikes.BikeInfoNS.BC.DataSource.Cache)
: await Task.FromResult(new BikeCollection(new Dictionary<string, BikeInfo>())),
bikesAvailableResponse?.GetGeneralData());
}
}
}

View file

@ -70,10 +70,26 @@ namespace TINK.Model.Connector
/// <summary> Gets bikes available and bikes occupied. </summary>
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
/// <param name="stationId"> Id of station which is used for filtering bikes. Null if no filtering should be applied.</param>
/// <param name="bikeId"> Id of bike which is used for filtering bikes. Null if no filtering should be applied.</param>
/// <returns>Collection of bikes.</returns>
public async Task<Result<BikeCollection>> GetBikesAsync(Uri operatorUri = null)
public async Task<Result<BikeCollection>> GetBikesAsync(Uri operatorUri = null, string stationId = null, string bikeId = null)
{
var bikesAvailableResponse = await server.GetBikesAvailableAsync(operatorUri);
var bikesAvailableResponse = await server.GetBikesAvailableAsync(operatorUri, stationId, bikeId);
if (operatorUri?.AbsoluteUri != null)
{
return new Result<BikeCollection>(
typeof(CopriCallsMonkeyStore),
BikeCollectionFactory.GetBikesAll(
bikesAvailableResponse?.bikes?.Values,
bikesAvailableResponse?.bikes_occupied?.Values,
Mail,
DateTimeProvider,
Bikes.BikeInfoNS.BC.DataSource.Cache),
bikesAvailableResponse?.GetGeneralData());
}
var bikesOccupiedResponse = await server.GetBikesOccupiedAsync();
return new Result<BikeCollection>(
@ -84,7 +100,7 @@ namespace TINK.Model.Connector
Mail,
DateTimeProvider,
Bikes.BikeInfoNS.BC.DataSource.Cache),
bikesAvailableResponse.GetGeneralData());
bikesAvailableResponse?.GetGeneralData());
}
}
}

View file

@ -122,11 +122,11 @@ namespace TINK.Model
public interface IResourceUrls
{
string FeesResourcePath { get; }
string TariffsResourcePath { get; }
string BikesResourcePath { get; }
string ManualResourcePath { get; }
string AgbResourcePath { get; }
string GtcResourcePath { get; }
string PrivacyResourcePath { get; }

View file

@ -3,24 +3,24 @@ namespace TINK.Model
public class ResourceUrls : IResourceUrls
{
public ResourceUrls(
string feesResourcePath = null,
string bikesResourcePath = null,
string agbResourcePath = null,
string tariffsResourcePath = null,
string manualResourcePath = null,
string gtcResourcePath = null,
string privacyResourcePath = null,
string impressResourcePath = null)
{
FeesResourcePath = !string.IsNullOrEmpty(feesResourcePath) ? feesResourcePath : "";
BikesResourcePath = !string.IsNullOrEmpty(bikesResourcePath) ? bikesResourcePath : "";
AgbResourcePath = !string.IsNullOrEmpty(agbResourcePath) ? agbResourcePath : "";
TariffsResourcePath = !string.IsNullOrEmpty(tariffsResourcePath) ? tariffsResourcePath : "";
ManualResourcePath = !string.IsNullOrEmpty(manualResourcePath) ? manualResourcePath : "";
GtcResourcePath = !string.IsNullOrEmpty(gtcResourcePath) ? gtcResourcePath : "";
PrivacyResourcePath = !string.IsNullOrEmpty(privacyResourcePath) ? privacyResourcePath : "";
ImpressResourcePath = !string.IsNullOrEmpty(impressResourcePath) ? impressResourcePath : "";
}
public string FeesResourcePath { get; }
public string TariffsResourcePath { get; }
public string BikesResourcePath { get; }
public string ManualResourcePath { get; }
public string AgbResourcePath { get; }
public string GtcResourcePath { get; }
public string PrivacyResourcePath { get; }

View file

@ -733,7 +733,7 @@ namespace TINK.Model
new List<AppFlavor> { AppFlavor.MeinKonrad, AppFlavor.ShareeBike }
},
{
new Version(3, 0, 375),
new Version(3, 0, 376),
string.Format("{0} <br /> {1} <br /> {2}", AppResources.ChangeLog_MinorImprovements, AppResources.ChangeLog_PackageUpdates, AppResources.ChangeLog_MinorBugFixes),
new List<AppFlavor> { AppFlavor.MeinKonrad, AppFlavor.ShareeBike }
},

View file

@ -60,15 +60,6 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Rent bike.
/// </summary>
public static string ActionRentBike {
get {
return ResourceManager.GetString("ActionRentBike", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Cancel reservation.
/// </summary>
@ -105,6 +96,15 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to End rental.
/// </summary>
public static string ActionEndRental {
get {
return ResourceManager.GetString("ActionEndRental", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Give feedback.
/// </summary>
@ -150,15 +150,6 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Open lock &amp; rent bike.
/// </summary>
public static string ActionOpenLockAndRentBike {
get {
return ResourceManager.GetString("ActionOpenLockAndRentBike", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Open lock.
/// </summary>
@ -168,6 +159,24 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Open lock &amp; rent bike.
/// </summary>
public static string ActionOpenLockAndRentBike {
get {
return ResourceManager.GetString("ActionOpenLockAndRentBike", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Rent bike.
/// </summary>
public static string ActionRentBike {
get {
return ResourceManager.GetString("ActionRentBike", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Reserve bike.
/// </summary>
@ -177,15 +186,6 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to End rental.
/// </summary>
public static string ActionEndRental {
get {
return ResourceManager.GetString("ActionEndRental", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Search lock.
/// </summary>
@ -438,15 +438,6 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Loading Bikes....
/// </summary>
public static string ActivityTextFindBikeLoadingBikes {
get {
return ResourceManager.GetString("ActivityTextFindBikeLoadingBikes", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Lock out of reach.
/// </summary>
@ -600,6 +591,15 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Loading Bikes....
/// </summary>
public static string ActivityTextSelectBikeLoadingBikes {
get {
return ResourceManager.GetString("ActivityTextSelectBikeLoadingBikes", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Updating....
/// </summary>
@ -2091,15 +2091,6 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Legal Information.
/// </summary>
public static string MarkingAbout {
get {
return ResourceManager.GetString("MarkingAbout", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Account.
/// </summary>
@ -2183,6 +2174,15 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Bike Locations.
/// </summary>
public static string MarkingBikeLocations {
get {
return ResourceManager.GetString("MarkingBikeLocations", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to There are currently no bicycles available at this station..
/// </summary>
@ -2246,6 +2246,15 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Contact.
/// </summary>
public static string MarkingContact {
get {
return ResourceManager.GetString("MarkingContact", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Customer support.
/// </summary>
@ -2328,29 +2337,56 @@ namespace TINK.MultilingualResources {
}
/// <summary>
/// Looks up a localized string similar to Contact.
/// Looks up a localized string similar to Menu.
/// </summary>
public static string MarkingFeedbackAndContact {
public static string MarkingFlyoutHeader {
get {
return ResourceManager.GetString("MarkingFeedbackAndContact", resourceCulture);
return ResourceManager.GetString("MarkingFlyoutHeader", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Geolocation Control.
/// </summary>
public static string MarkingGeolocationControl {
get {
return ResourceManager.GetString("MarkingGeolocationControl", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Help.
/// </summary>
public static string MarkingFeesAndBikes {
public static string MarkingHelp {
get {
return ResourceManager.GetString("MarkingFeesAndBikes", resourceCulture);
return ResourceManager.GetString("MarkingHelp", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Select Bike.
/// Looks up a localized string similar to Last selected station.
/// </summary>
public static string MarkingFindBike {
public static string MarkingLastSelectedStation {
get {
return ResourceManager.GetString("MarkingFindBike", resourceCulture);
return ResourceManager.GetString("MarkingLastSelectedStation", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Legal information.
/// </summary>
public static string MarkingLegalInformation {
get {
return ResourceManager.GetString("MarkingLegalInformation", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Lock control.
/// </summary>
public static string MarkingLockControl {
get {
return ResourceManager.GetString("MarkingLockControl", resourceCulture);
}
}
@ -2381,42 +2417,6 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Menu.
/// </summary>
public static string MarkingFlyoutHeader {
get {
return ResourceManager.GetString("MarkingFlyoutHeader", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Geolocation Control.
/// </summary>
public static string MarkingGeolocationControl {
get {
return ResourceManager.GetString("MarkingGeolocationControl", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Last selected station.
/// </summary>
public static string MarkingLastSelectedStation {
get {
return ResourceManager.GetString("MarkingLastSelectedStation", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Lock control.
/// </summary>
public static string MarkingLockControl {
get {
return ResourceManager.GetString("MarkingLockControl", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Logged in as {0}..
/// </summary>
@ -2535,15 +2535,6 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Bike Locations.
/// </summary>
public static string MarkingMapPage {
get {
return ResourceManager.GetString("MarkingMapPage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to My Bikes.
/// </summary>
@ -2783,11 +2774,38 @@ namespace TINK.MultilingualResources {
}
/// <summary>
/// Looks up a localized string similar to Search bike.
/// Looks up a localized string similar to Select Bike.
/// </summary>
public static string MarkingSearchBike {
public static string MarkingSelectBike {
get {
return ResourceManager.GetString("MarkingSearchBike", resourceCulture);
return ResourceManager.GetString("MarkingSelectBike", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Search.
/// </summary>
public static string MarkingSelectBikeButton {
get {
return ResourceManager.GetString("MarkingSelectBikeButton", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Bike id.
/// </summary>
public static string MarkingSelectBikeLabel {
get {
return ResourceManager.GetString("MarkingSelectBikeLabel", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to You search a .
/// </summary>
public static string MarkingSelectBikeTypeOfBikeText {
get {
return ResourceManager.GetString("MarkingSelectBikeTypeOfBikeText", resourceCulture);
}
}
@ -2836,15 +2854,6 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Manual.
/// </summary>
public static string MarkingTabBikes {
get {
return ResourceManager.GetString("MarkingTabBikes", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to FAQ.
/// </summary>
@ -2854,15 +2863,6 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Tariffs.
/// </summary>
public static string MarkingTabFees {
get {
return ResourceManager.GetString("MarkingTabFees", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to GTC.
/// </summary>
@ -2881,6 +2881,15 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Manual.
/// </summary>
public static string MarkingTabManual {
get {
return ResourceManager.GetString("MarkingTabManual", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Privacy.
/// </summary>
@ -2890,6 +2899,15 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Tariffs.
/// </summary>
public static string MarkingTabTariffs {
get {
return ResourceManager.GetString("MarkingTabTariffs", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Verbose error messages.
/// </summary>
@ -3387,9 +3405,9 @@ namespace TINK.MultilingualResources {
/// <summary>
/// Looks up a localized string similar to Prefix and No., e.g. TR15.
/// </summary>
public static string PlaceholderFindBike {
public static string PlaceholderSelectBike {
get {
return ResourceManager.GetString("PlaceholderFindBike", resourceCulture);
return ResourceManager.GetString("PlaceholderSelectBike", resourceCulture);
}
}

View file

@ -39,7 +39,7 @@
<data name="ActionEndRental" xml:space="preserve">
<value>Miete beenden</value>
</data>
<data name="MarkingMapPage" xml:space="preserve">
<data name="MarkingBikeLocations" xml:space="preserve">
<value>Radstandorte</value>
</data>
<data name="MessageLoginWelcome" xml:space="preserve">
@ -63,13 +63,10 @@
<data name="MessageAppVersionIsOutdated" xml:space="preserve">
<value>Diese Version der {0} App ist veraltet. Bitte auf aktuelle Version aktualisieren.</value>
</data>
<data name="MarkingAbout" xml:space="preserve">
<value>Rechtliches</value>
</data>
<data name="MarkingAccount" xml:space="preserve">
<value>Konto</value>
</data>
<data name="MarkingFeedbackAndContact" xml:space="preserve">
<data name="MarkingContact" xml:space="preserve">
<value>Kontakt</value>
</data>
<data name="MarkingLogin" xml:space="preserve">
@ -78,16 +75,16 @@
<data name="MarkingMyBikes" xml:space="preserve">
<value>Meine Räder</value>
</data>
<data name="MarkingFeesAndBikes" xml:space="preserve">
<data name="MarkingHelp" xml:space="preserve">
<value>Hilfe</value>
</data>
<data name="MarkingSettings" xml:space="preserve">
<value>Einstellungen</value>
</data>
<data name="MarkingTabBikes" xml:space="preserve">
<data name="MarkingTabManual" xml:space="preserve">
<value>Anleitung</value>
</data>
<data name="MarkingTabFees" xml:space="preserve">
<data name="MarkingTabTariffs" xml:space="preserve">
<value>Tarife</value>
</data>
<data name="ErrorLockMoving" xml:space="preserve">
@ -511,13 +508,13 @@ Layout Anzeige Radnamen und nummern verbessert.</value>
<data name="ChangeLog_3_0_239" xml:space="preserve">
<value>Fehlerbehebung: Radname wird wieder korrekt angezeigt.</value>
</data>
<data name="MarkingFindBike" xml:space="preserve">
<data name="MarkingSelectBike" xml:space="preserve">
<value>Rad auswählen</value>
</data>
<data name="ActivityTextCheckBluetoothState" xml:space="preserve">
<value>Prüfe Status und Berechtigungen...</value>
</data>
<data name="ActivityTextFindBikeLoadingBikes" xml:space="preserve">
<data name="ActivityTextSelectBikeLoadingBikes" xml:space="preserve">
<value>Lade Räder...</value>
</data>
<data name="ChangeLog_3_0_240" xml:space="preserve">
@ -863,7 +860,7 @@ Außerdem: Kleine Grafiken lassen auf einen Blick erkennen um was für einen Rad
<data name="QuestionSupportmailAttachmentTitle" xml:space="preserve">
<value>Einwilligung</value>
</data>
<data name="PlaceholderFindBike" xml:space="preserve">
<data name="PlaceholderSelectBike" xml:space="preserve">
<value>Präfix und Nr., z.B. TR15</value>
</data>
<data name="ChangeLog_3_0_339_MK" xml:space="preserve">
@ -1008,12 +1005,9 @@ Außerdem:&lt;br/&gt;
- Fehlerbehebungen&lt;br/&gt;
- Paketaktualisierungen</value>
</data>
<data name="MarkingFindBikeLabel" xml:space="preserve">
<data name="MarkingSelectBikeLabel" xml:space="preserve">
<value>Fahrrad-ID</value>
</data>
<data name="MarkingSearchBike" xml:space="preserve">
<value>Rad suchen</value>
</data>
<data name="MarkingReturnBikeBikeIsStateOkQuestion" xml:space="preserve">
<value>Fahrrad ist in Ordnung?</value>
</data>
@ -1026,7 +1020,7 @@ Außerdem:&lt;br/&gt;
<data name="ChangeLog_3_0_365_MK_SB" xml:space="preserve">
<value>Kleine Verbesserungen in Design und Performance.</value>
</data>
<data name="MarkingFindBikeTypeOfBikeText" xml:space="preserve">
<data name="MarkingSelectBikeTypeOfBikeText" xml:space="preserve">
<value>Sie suchen ein </value>
</data>
<data name="MessageBikeTypeInfoText" xml:space="preserve">
@ -1035,7 +1029,7 @@ Außerdem:&lt;br/&gt;
<data name="MessageBikeTypeInfoTitle" xml:space="preserve">
<value>Ausgewählter Fahrradtyp</value>
</data>
<data name="MarkingFindBikeButton" xml:space="preserve">
<data name="MarkingSelectBikeButton" xml:space="preserve">
<value>Suchen</value>
</data>
<data name="ChangeLog_3_0_366_MK_SB" xml:space="preserve">
@ -1329,4 +1323,7 @@ Die Kosten werden in den nächsten Tagen automatisch abgebucht. Sorgen Sie für
<data name="QuestionBookBike" xml:space="preserve">
<value>Rad {0} mieten?</value>
</data>
<data name="MarkingLegalInformation" xml:space="preserve">
<value>Rechtliches</value>
</data>
</root>

View file

@ -177,16 +177,13 @@ Rental can be ended if
<data name="ErrorSupportmailPhoningFailed" xml:space="preserve">
<value>Opening phone app failed. Make sure you have a phone app installed on your mobile device!</value>
</data>
<data name="MarkingAbout" xml:space="preserve">
<value>Legal Information</value>
</data>
<data name="MarkingAccount" xml:space="preserve">
<value>Account</value>
</data>
<data name="MarkingFeedbackAndContact" xml:space="preserve">
<data name="MarkingContact" xml:space="preserve">
<value>Contact</value>
</data>
<data name="MarkingFeesAndBikes" xml:space="preserve">
<data name="MarkingHelp" xml:space="preserve">
<value>Help</value>
</data>
<data name="MarkingLoggedInStateInfoLoggedIn" xml:space="preserve">
@ -201,7 +198,7 @@ Rental can be ended if
<data name="MarkingLogin" xml:space="preserve">
<value>Login</value>
</data>
<data name="MarkingMapPage" xml:space="preserve">
<data name="MarkingBikeLocations" xml:space="preserve">
<value>Bike Locations</value>
</data>
<data name="MarkingMyBikes" xml:space="preserve">
@ -210,10 +207,10 @@ Rental can be ended if
<data name="MarkingSettings" xml:space="preserve">
<value>Settings</value>
</data>
<data name="MarkingTabBikes" xml:space="preserve">
<data name="MarkingTabManual" xml:space="preserve">
<value>Manual</value>
</data>
<data name="MarkingTabFees" xml:space="preserve">
<data name="MarkingTabTariffs" xml:space="preserve">
<value>Tariffs</value>
</data>
<data name="MessageAnswerOk" xml:space="preserve">
@ -644,13 +641,13 @@ Layout of bike names and id display improved.</value>
<data name="ChangeLog_3_0_239" xml:space="preserve">
<value>Bugfix: Bike description is displayed correctly again.</value>
</data>
<data name="MarkingFindBike" xml:space="preserve">
<data name="MarkingSelectBike" xml:space="preserve">
<value>Select Bike</value>
</data>
<data name="ActivityTextCheckBluetoothState" xml:space="preserve">
<value>Checking state and permissions...</value>
</data>
<data name="ActivityTextFindBikeLoadingBikes" xml:space="preserve">
<data name="ActivityTextSelectBikeLoadingBikes" xml:space="preserve">
<value>Loading Bikes...</value>
</data>
<data name="ChangeLog_3_0_240" xml:space="preserve">
@ -997,7 +994,7 @@ In addition: Small graphics let you see at a glance what type of bike it is.</va
<data name="QuestionSupportmailAttachmentTitle" xml:space="preserve">
<value>Consent</value>
</data>
<data name="PlaceholderFindBike" xml:space="preserve">
<data name="PlaceholderSelectBike" xml:space="preserve">
<value>Prefix and No., e.g. TR15</value>
</data>
<data name="ChangeLog_3_0_339_MK" xml:space="preserve">
@ -1134,12 +1131,9 @@ Also:&lt;br/&gt;
- Bug fixes&lt;br/&gt;
- Package updates</value>
</data>
<data name="MarkingFindBikeLabel" xml:space="preserve">
<data name="MarkingSelectBikeLabel" xml:space="preserve">
<value>Bike id</value>
</data>
<data name="MarkingSearchBike" xml:space="preserve">
<value>Search bike</value>
</data>
<data name="QuestionRentalProcessCloseLockContinueRentalAnswer" xml:space="preserve">
<value>Park bike</value>
</data>
@ -1155,7 +1149,7 @@ Also:&lt;br/&gt;
<data name="ChangeLog_3_0_365_MK_SB" xml:space="preserve">
<value>Minor design and performance improvements.</value>
</data>
<data name="MarkingFindBikeTypeOfBikeText" xml:space="preserve">
<data name="MarkingSelectBikeTypeOfBikeText" xml:space="preserve">
<value>You search a </value>
</data>
<data name="MessageBikeTypeInfoText" xml:space="preserve">
@ -1164,7 +1158,7 @@ Also:&lt;br/&gt;
<data name="MessageBikeTypeInfoTitle" xml:space="preserve">
<value>Selected bike type</value>
</data>
<data name="MarkingFindBikeButton" xml:space="preserve">
<data name="MarkingSelectBikeButton" xml:space="preserve">
<value>Search</value>
</data>
<data name="ChangeLog_3_0_366_MK_SB" xml:space="preserve">
@ -1421,4 +1415,7 @@ The costs will be debited automatically within the next days. Ensure sufficient
<data name="QuestionBookBike" xml:space="preserve">
<value>Rent bike {0}?</value>
</data>
</root>
<data name="MarkingLegalInformation" xml:space="preserve">
<value>Legal information</value>
</data>
</root>

View file

@ -42,7 +42,7 @@
<source>End rental</source>
<target state="translated">Miete beenden</target>
</trans-unit>
<trans-unit id="MarkingMapPage" translate="yes" xml:space="preserve">
<trans-unit id="MarkingBikeLocations" translate="yes" xml:space="preserve">
<source>Bike Locations</source>
<target state="final">Radstandorte</target>
</trans-unit>
@ -74,15 +74,11 @@
<source>This version of the {0} App is outdated. Please update to the latest version.</source>
<target state="final">Diese Version der {0} App ist veraltet. Bitte auf aktuelle Version aktualisieren.</target>
</trans-unit>
<trans-unit id="MarkingAbout" translate="yes" xml:space="preserve">
<source>Legal Information</source>
<target state="final">Rechtliches</target>
</trans-unit>
<trans-unit id="MarkingAccount" translate="yes" xml:space="preserve">
<source>Account</source>
<target state="final">Konto</target>
</trans-unit>
<trans-unit id="MarkingFeedbackAndContact" translate="yes" xml:space="preserve">
<trans-unit id="MarkingContact" translate="yes" xml:space="preserve">
<source>Contact</source>
<target state="translated">Kontakt</target>
</trans-unit>
@ -94,7 +90,7 @@
<source>My Bikes</source>
<target state="final">Meine Räder</target>
</trans-unit>
<trans-unit id="MarkingFeesAndBikes" translate="yes" xml:space="preserve">
<trans-unit id="MarkingHelp" translate="yes" xml:space="preserve">
<source>Help</source>
<target state="translated">Hilfe</target>
</trans-unit>
@ -102,11 +98,11 @@
<source>Settings</source>
<target state="final">Einstellungen</target>
</trans-unit>
<trans-unit id="MarkingTabBikes" translate="yes" xml:space="preserve">
<trans-unit id="MarkingTabManual" translate="yes" xml:space="preserve">
<source>Manual</source>
<target state="translated">Anleitung</target>
</trans-unit>
<trans-unit id="MarkingTabFees" translate="yes" xml:space="preserve">
<trans-unit id="MarkingTabTariffs" translate="yes" xml:space="preserve">
<source>Tariffs</source>
<target state="translated">Tarife</target>
</trans-unit>
@ -687,7 +683,7 @@ Layout Anzeige Radnamen und nummern verbessert.</target>
<source>Bugfix: Bike description is displayed correctly again.</source>
<target state="translated">Fehlerbehebung: Radname wird wieder korrekt angezeigt.</target>
</trans-unit>
<trans-unit id="MarkingFindBike" translate="yes" xml:space="preserve">
<trans-unit id="MarkingSelectBike" translate="yes" xml:space="preserve">
<source>Select Bike</source>
<target state="translated">Rad auswählen</target>
</trans-unit>
@ -695,7 +691,7 @@ Layout Anzeige Radnamen und nummern verbessert.</target>
<source>Checking state and permissions...</source>
<target state="translated">Prüfe Status und Berechtigungen...</target>
</trans-unit>
<trans-unit id="ActivityTextFindBikeLoadingBikes" translate="yes" xml:space="preserve">
<trans-unit id="ActivityTextSelectBikeLoadingBikes" translate="yes" xml:space="preserve">
<source>Loading Bikes...</source>
<target state="translated">Lade Räder...</target>
</trans-unit>
@ -1175,7 +1171,7 @@ Außerdem: Kleine Grafiken lassen auf einen Blick erkennen um was für einen Rad
<source>Consent</source>
<target state="translated">Einwilligung</target>
</trans-unit>
<trans-unit id="PlaceholderFindBike" translate="yes" xml:space="preserve">
<trans-unit id="PlaceholderSelectBike" translate="yes" xml:space="preserve">
<source>Prefix and No., e.g. TR15</source>
<target state="translated">Präfix und Nr., z.B. TR15</target>
</trans-unit>
@ -1391,14 +1387,10 @@ Außerdem:&lt;br/&gt;
- Fehlerbehebungen&lt;br/&gt;
- Paketaktualisierungen</target>
</trans-unit>
<trans-unit id="MarkingFindBikeLabel" translate="yes" xml:space="preserve">
<trans-unit id="MarkingSelectBikeLabel" translate="yes" xml:space="preserve">
<source>Bike id</source>
<target state="translated">Fahrrad-ID</target>
</trans-unit>
<trans-unit id="MarkingSearchBike" translate="yes" xml:space="preserve">
<source>Search bike</source>
<target state="translated">Rad suchen</target>
</trans-unit>
<trans-unit id="MarkingReturnBikeBikeIsStateOkQuestion" translate="yes" xml:space="preserve">
<source>Is bike okay?</source>
<target state="translated">Fahrrad ist in Ordnung?</target>
@ -1415,7 +1407,7 @@ Außerdem:&lt;br/&gt;
<source>Minor design and performance improvements.</source>
<target state="translated">Kleine Verbesserungen in Design und Performance.</target>
</trans-unit>
<trans-unit id="MarkingFindBikeTypeOfBikeText" translate="yes" xml:space="preserve">
<trans-unit id="MarkingSelectBikeTypeOfBikeText" translate="yes" xml:space="preserve">
<source>You search a </source>
<target state="translated">Sie suchen ein </target>
</trans-unit>
@ -1427,7 +1419,7 @@ Außerdem:&lt;br/&gt;
<source>Selected bike type</source>
<target state="translated">Ausgewählter Fahrradtyp</target>
</trans-unit>
<trans-unit id="MarkingFindBikeButton" translate="yes" xml:space="preserve">
<trans-unit id="MarkingSelectBikeButton" translate="yes" xml:space="preserve">
<source>Search</source>
<target state="translated">Suchen</target>
</trans-unit>
@ -1837,6 +1829,10 @@ Die Kosten werden in den nächsten Tagen automatisch abgebucht. Sorgen Sie für
<source>Rent bike {0}?</source>
<target state="translated">Rad {0} mieten?</target>
</trans-unit>
<trans-unit id="MarkingLegalInformation" translate="yes" xml:space="preserve">
<source>Legal information</source>
<target state="translated">Rechtliches</target>
</trans-unit>
</group>
</body>
</file>

View file

@ -85,11 +85,13 @@ namespace TINK.Repository
/// <summary>Gets bikes available.</summary>
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
/// <param name="stationId"> Id of station which is used for filtering bikes. Null if no filtering should be applied.</param>
/// <param name="bikeId"> Id of bike which is used for filtering bikes. Null if no filtering should be applied.</param>
/// <returns>Response holding list of bikes.</returns>
public async Task<BikesAvailableResponse> GetBikesAvailableAsync(Uri operatorUri = null)
public async Task<BikesAvailableResponse> GetBikesAvailableAsync(Uri operatorUri = null, string stationId = null, string bikeId = null)
=> await GetBikesAvailableAsync(
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
requestBuilder.GetBikesAvailable(),
requestBuilder.GetBikesAvailable(stationId, bikeId),
UserAgent);
/// <summary> Gets a list of bikes reserved/ booked by active user. </summary>

File diff suppressed because it is too large Load diff

View file

@ -119,7 +119,7 @@ namespace TINK.Repository
public string SessionCookie => requestBuilder.SessionCookie;
/// <summary> Initializes a instance of the copri monkey store object. </summary>
/// <param name="merchantId">Id of the merchant.</param>
/// <param name="merchantId">Id of the merchant. Used to access </param>
/// <param name="uiIsoLangugageName">Two letter ISO language name.</param>
/// <param name="sessionCookie">Session cookie if user is logged in, null otherwise.</param>
/// <param name="smartDevice">Holds info about smart device.</param>
@ -235,13 +235,19 @@ namespace TINK.Repository
throw new System.Exception(AppResources.ErrorNoWeb);
}
/// <summary>Gets bikes available.</summary>
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
public async Task<BikesAvailableResponse> GetBikesAvailableAsync(Uri operatorUri = null)
/// <param name="stationId"> Id of station which is used for filtering bikes. Null if no filtering should be applied.</param>
/// <param name="bikeId"> Id of bike which is used for filtering bikes. Null if no filtering should be applied.</param>
public async Task<BikesAvailableResponse> GetBikesAvailableAsync(
Uri operatorUri = null,
string stationId = null,
string bikeId = null)
{
var bikesAvailableTask = new TaskCompletionSource<BikesAvailableResponse>();
lock (monkeyLock)
{
bikesAvailableTask.SetResult(Barrel.Current.Get<BikesAvailableResponse>($"{operatorUri?.AbsoluteUri ?? string.Empty}{requestBuilder.GetBikesAvailable()}"));
bikesAvailableTask.SetResult(Barrel.Current.Get<BikesAvailableResponse>($"{operatorUri?.AbsoluteUri ?? string.Empty}{requestBuilder.GetBikesAvailable(stationId, bikeId)}"));
}
return await bikesAvailableTask.Task;
}
@ -298,7 +304,7 @@ namespace TINK.Repository
/// <summary> Adds a stations all response to cache.</summary>
/// <param name="stations">Stations to add.</param>
/// <param name="expiresAfter">Time after which anser is considered to be expired.</param>
/// <param name="expiresAfter">Time after which answer is considered to be expired.</param>
private void AddToCache(StationsAvailableResponse stations, TimeSpan expiresAfter)
{
lock (monkeyLock)
@ -323,21 +329,29 @@ namespace TINK.Repository
}
/// <summary> Adds a bikes response to cache.</summary>
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
/// <param name="bikes">Bikes to add.</param>
public void AddToCache(BikesAvailableResponse bikes, Uri operatorUri = null)
=> AddToCache(bikes, ExpiresAfter, operatorUri);
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
/// <param name="stationId"> Id of station which was used for filtering bikes. Null if no filtering was applied.</param>
/// <param name="bikeId"> Id of bike which was used for filtering bikes. Null if no filtering was applied.</param>
public void AddToCache(BikesAvailableResponse bikes, Uri operatorUri = null, string stationId = null, string bikeId = null)
=> AddToCache(bikes, ExpiresAfter, operatorUri, stationId, bikeId);
/// <summary> Adds a bikes response to cache.</summary>
/// <param name="bikes">Bikes to add.</param>
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
/// <param name="expiresAfter">Time after which answer is considered to be expired.</param>
private void AddToCache(BikesAvailableResponse bikes, TimeSpan expiresAfter, Uri operatorUri = null)
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
/// <param name="stationId"> Id of station which is used for filtering bikes. Null if no filtering should be applied.</param>
private void AddToCache(
BikesAvailableResponse bikes,
TimeSpan expiresAfter,
Uri operatorUri = null,
string stationId = null,
string bikeId = null)
{
lock (monkeyLock)
{
Barrel.Current.Add(
$"{operatorUri?.AbsoluteUri ?? string.Empty}{requestBuilder.GetBikesAvailable()}",
$"{operatorUri?.AbsoluteUri ?? string.Empty}{requestBuilder.GetBikesAvailable(stationId, bikeId)}",
JsonConvertRethrow.SerializeObject(bikes),
expiresAfter);
}
@ -364,7 +378,7 @@ namespace TINK.Repository
}
/// <summary> Adds a bikes response to cache.</summary>
/// <param name="bikes">Bikes to add.</param>
/// <param name="expiresAfter">Time after which anser is considered to be expired.</param>
/// <param name="expiresAfter">Time after which answer is considered to be expired.</param>
private void AddToCache(BikesReservedOccupiedResponse bikes, TimeSpan expiresAfter)
{
lock (monkeyLock)

View file

@ -29,7 +29,7 @@ namespace TINK.Repository
/// <returns>Response object.</returns>
public static T DeserializeResponse<T>(this string response, Func<string, T> emptyResponseFactory) where T : class
{
// Get COPRI version from respone.
// Get COPRI version from response.
var bikeInfoBase = JsonConvertRethrow.DeserializeObject<VersionindependentResponse>(response)?.shareejson;
if (bikeInfoBase.GetCopriVersion() < UNSUPPORTEDFUTURECOPRIVERSIONLOWER
@ -41,15 +41,17 @@ namespace TINK.Repository
return JsonConvertRethrow.DeserializeObject<ResponseContainer<T>>(response)?.shareejson;
}
/// <summary> Deserializes reponse JSON if response is of supported version or throws an exception. </summary>
/// <summary> Deserializes response JSON if response is of supported version or throws an exception. </summary>
/// <typeparam name="T">Type of response object.</typeparam>
/// <param name="response">Response JSON.</param>
/// <param name="unsupportedVersionExectpion">Exception to fire.</param>
/// <returns>Response object.</returns>
public static T DeserializeResponse<T>(this string response, Func<string, System.Exception> unsupportedVersionExectpion = null) where T : class
public static T DeserializeResponse<T>(
this string response,
Func<string, System.Exception> unsupportedVersionExectpion = null) where T : class
{
// Get COPRI version from respone.
// Get COPRI version from response.
var bikeInfoBase = JsonConvertRethrow.DeserializeObject<VersionindependentResponse>(response)?.shareejson;
if (bikeInfoBase.GetCopriVersion() < UNSUPPORTEDFUTURECOPRIVERSIONLOWER

View file

@ -160,8 +160,10 @@ namespace TINK.Repository
/// <summary> Gets a list of bikes from Copri. </summary>
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
/// <param name="stationId"> Id of station which is used for filtering bikes. Null if no filtering should be applied.</param>
/// <param name="bikeId"> Id of bike which is used for filtering bikes. Null if no filtering should be applied.</param>
/// <returns>Response holding list of bikes.</returns>
Task<BikesAvailableResponse> GetBikesAvailableAsync(Uri operatorUri = null);
Task<BikesAvailableResponse> GetBikesAvailableAsync(Uri operatorUri = null, string stationId = null, string bikeId = null);
/// <summary> Gets a list of bikes reserved/ booked by active user from Copri.</summary>
/// <returns>Response holding list of bikes.</returns>

View file

@ -34,8 +34,10 @@ namespace TINK.Repository.Request
string GetStations();
/// <summary>Gets bikes available.</summary>
/// <param name="stationId"> Id of station which is used for filtering bikes. Null if no filtering should be applied.</param>
/// <param name="bikeId"> Id of bike to get.</param>
/// <returns>Request to query list of bikes available.</returns>
string GetBikesAvailable();
string GetBikesAvailable(string stationId = null, string bikeId = null);
/// <summary> Gets a list of bikes reserved/ booked by active user from Copri.</summary>
/// <returns>Request to query list of bikes occupied.</returns>

View file

@ -67,9 +67,13 @@ namespace TINK.Repository.Request
=> throw new CallNotRequiredException();
/// <summary>Gets bikes available.</summary>
/// <param name="stationId"> Id of station which is used for filtering bikes. Null if no filtering should be applied.</param>
/// <param name="bikeId"> Id of bike to get.</param>
/// <returns>Request to query list of bikes available.</returns>
public string GetBikesAvailable()
public string GetBikesAvailable(string stationId = null, string bikeId = null)
=> "request=bikes_available&system=all" +
stationId.GetStationId() +
bikeId.GetBikeId() +
AuthCookieParameter +
UiIsoLanguageNameParameter;

View file

@ -65,5 +65,19 @@ namespace TINK.Repository.Request
return null;
}
}
/// <summary> Gets the station id filter. </summary>
/// <returns>Station id filter.</returns>
public static string GetStationId(this string stationId)
=> !string.IsNullOrEmpty(stationId)
? $"&station={WebUtility.UrlEncode(stationId)}"
: string.Empty;
/// <summary> Gets the bike id filter. </summary>
/// <returns>Bike id filter.</returns>
public static string GetBikeId(this string bikeId)
=> !string.IsNullOrEmpty(bikeId)
? $"&bike={WebUtility.UrlEncode(bikeId)}"
: string.Empty;
}
}

View file

@ -72,9 +72,13 @@ namespace TINK.Repository.Request
UiIsoLanguageNameParameter;
/// <summary>Gets bikes available.</summary>
/// <param name="stationId"> Id of station which is used for filtering bikes. Null if no filtering should be applied.</param>
/// <param name="bikeId"> Id of bike to get.</param>
/// <returns>Request to query list of bikes available.</returns>
public string GetBikesAvailable()
public string GetBikesAvailable(string stationId = null, string bikeId = null)
=> "request=bikes_available&system=all" +
stationId.GetStationId() +
bikeId.GetBikeId() +
AuthCookieParameter +
UiIsoLanguageNameParameter;

View file

@ -55,7 +55,7 @@ namespace TINK.Repository.Response
/// <remarks>
/// <table>
/// <tr><th>Value </th><th>Type of bike </th><th>Member to extract info.</th></tr>
/// <tr><td>LOCK </td><td>Bike with manual lock. </td><td>TextToTypeHelper.GetIsNonBikeComputerBike</td></tr>
/// <tr><td>LOCK </td><td>Bike with manualHtml lock. </td><td>TextToTypeHelper.GetIsNonBikeComputerBike</td></tr>
/// <tr><td>BC </td><td>Bike with a bord computer. </td><td></td></tr>
/// <tr><td>Ilockit </td><td>Bike with a bluetooth lock.</td><td></td></tr>
/// <tr><td>sigo </td><td>Sigo bike.</td><td></td></tr>

View file

@ -55,10 +55,14 @@ namespace TINK.Model.Services.CopriApi
/// <summary>Gets bikes available.</summary>
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
/// <param name="stationId"> Id of station which is used for filtering bikes. Null if no filtering should be applied.</param>
/// <param name="bikeId"> Id of bike which is used for filtering bikes. Null if no filtering should be applied.</param>
/// <returns>Response holding list of bikes.</returns>
public async Task<Result<BikesAvailableResponse>> GetBikesAvailable(
bool fromCache = false,
Uri operatorUri = null)
Uri operatorUri = null,
string stationId = null,
string bikeId = null)
{
Log.ForContext<CopriProviderHttps>().Debug($"Request to get bikes available{(fromCache ? " from cache" : "")}...");
if (!CacheServer.IsBikesAvailableExpired
@ -66,14 +70,14 @@ namespace TINK.Model.Services.CopriApi
{
// No need to query because previous answer is not yet outdated.
Log.ForContext<CopriProviderHttps>().Debug($"Returning bikes available from cache.");
var bikesAvailableResponse = await CacheServer.GetBikesAvailableAsync(operatorUri);
var bikesAvailableResponse = await CacheServer.GetBikesAvailableAsync(operatorUri, stationId, bikeId);
return new Result<BikesAvailableResponse>(typeof(CopriCallsMonkeyStore), bikesAvailableResponse, bikesAvailableResponse.GetGeneralData());
}
try
{
Log.ForContext<CopriProviderHttps>().Debug($"Querying bikes available from copri.");
var bikesAvailableResponse = await HttpsServer.GetBikesAvailableAsync(operatorUri);
var bikesAvailableResponse = await HttpsServer.GetBikesAvailableAsync(operatorUri, stationId, bikeId);
return new Result<BikesAvailableResponse>(
typeof(CopriCallsHttps),
bikesAvailableResponse.GetIsResponseOk(MultilingualResources.AppResources.ErrorBikesAvailableResponseNotOk),
@ -83,7 +87,7 @@ namespace TINK.Model.Services.CopriApi
{
// Return response from cache.
Log.ForContext<CopriProviderHttps>().Debug("An error occurred querying bikes available. {Exception}.", exception);
var bikesAvailableResponse = await CacheServer.GetBikesAvailableAsync(operatorUri);
var bikesAvailableResponse = await CacheServer.GetBikesAvailableAsync(operatorUri, stationId, bikeId);
return new Result<BikesAvailableResponse>(typeof(CopriCallsMonkeyStore), bikesAvailableResponse, bikesAvailableResponse.GetGeneralData(), exception);
}
}
@ -176,8 +180,12 @@ namespace TINK.Model.Services.CopriApi
/// <summary>Adds https--response to cache if response is ok. </summary>
/// <param name="result">Response to add to cache.</param>
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
/// <returns></returns>
public void AddToCache(Result<BikesAvailableResponse> result, Uri operatorUri = null)
/// <param name="stationId"> Id of station which is used for filtering bikes. Null if no filtering should be applied.</param>
public void AddToCache(
Result<BikesAvailableResponse> result,
Uri operatorUri = null,
string stationId = null,
string bikeId = null)
{
Log.ForContext<CopriProviderHttps>().Debug($"Request to add bikes available response to cache...");
if (result.Source == typeof(CopriCallsMonkeyStore)
@ -188,8 +196,7 @@ namespace TINK.Model.Services.CopriApi
}
Log.ForContext<CopriProviderHttps>().Debug($"Add bikes available response to cache.");
CacheServer.AddToCache(result.Response, operatorUri);
CacheServer.AddToCache(result.Response, operatorUri, stationId, bikeId);
}
/// <summary>Adds https--response to cache if response is ok. </summary>

View file

@ -108,9 +108,12 @@ namespace TINK.Model.Services.CopriApi
public async Task<AuthorizationoutResponse> DoAuthoutAsync()
=> await monkeyStore.DoAuthoutAsync();
/// <summary> Gets a list of bikes from Copri. </summary>
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
public async Task<BikesAvailableResponse> GetBikesAvailableAsync(Uri operatorUri = null)
=> await monkeyStore.GetBikesAvailableAsync(operatorUri);
/// <param name="stationId"> Id of station which is used for filtering bikes. Null if no filtering should be applied.</param>
/// <param name="bikeId"> Id of bike which is used for filtering bikes. Null if no filtering should be applied.</param>
public async Task<BikesAvailableResponse> GetBikesAvailableAsync(Uri operatorUri = null, string stationId = null, string bikeId = null)
=> await monkeyStore.GetBikesAvailableAsync(operatorUri, stationId, bikeId);
public async Task<BikesReservedOccupiedResponse> GetBikesOccupiedAsync()
=> await monkeyStore.GetBikesOccupiedAsync();

View file

@ -16,8 +16,14 @@ namespace TINK.Model.Services.CopriApi
/// <summary> Gets a list of bikes from Copri. </summary>
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
/// <param name="stationId"> Id of station which is used for filtering bikes. Null if no filtering should be applied.</param>
/// <param name="bikeId"> Id of bike which is used for filtering bikes. Null if no filtering should be applied.</param>
/// <returns>Response holding list of bikes.</returns>
Task<Result<BikesAvailableResponse>> GetBikesAvailable(bool fromCache = false, Uri operatorUri = null);
Task<Result<BikesAvailableResponse>> GetBikesAvailable(
bool fromCache = false,
Uri operatorUri = null,
string stationId = null,
string bikeId = null);
/// <summary> Gets a list of bikes reserved/ booked by active user from Copri.</summary>
/// <returns>Response holding list of bikes.</returns>
@ -28,9 +34,15 @@ namespace TINK.Model.Services.CopriApi
void AddToCache(Result<StationsAvailableResponse> result);
/// <summary>Adds https--response to cache if response is ok. </summary>
/// <param name="result">Response to add to cache.</param>
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
/// <param name="response">Response to add to cache.</param>
void AddToCache(Result<BikesAvailableResponse> result, Uri operatorUri = null);
/// <param name="stationId"> Id of station which is used for filtering bikes. Null if no filtering should be applied.</param>
/// <param name="bikeId"> Id of bike which is used for filtering bikes. Null if no filtering should be applied.</param>
void AddToCache(
Result<BikesAvailableResponse> result,
Uri operatorUri = null,
string stationId = null,
string bikeId = null);
/// <summary>Adds https--response to cache if response is ok. </summary>
/// <param name="response">Response to add to cache.</param>

View file

@ -18,9 +18,11 @@ namespace TINK.Model.Services.CopriApi
bool IsBikesAvailableExpired { get; }
/// <summary> Adds a bikes response to cache.</summary>
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
/// <param name="bikes">Bikes to add.</param>
void AddToCache(BikesAvailableResponse bikes, Uri operatorUri = null);
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
/// <param name="stationId"> Id of station which is used for filtering bikes. Null if no filtering should be applied.</param>
/// <param name="bikeId"> Id of bike to get.</param>
void AddToCache(BikesAvailableResponse bikes, Uri operatorUri = null, string stationId = null, string bikeId = null);
/// <summary> Gets a value indicating whether stations are expired or not.</summary>
bool IsBikesOccupiedExpired { get; }

View file

@ -10,7 +10,7 @@ namespace TINK.Services.Geolocation
public abstract class GeolocationService : IGeolocationService
{
/// <summary> Timeout for geolocation request operations.</summary>
private const int GEOLOCATIONREQUEST_TIMEOUT_MS = 5000;
private const int GEOLOCATIONREQUEST_TIMEOUT_MS = 10000;
private IGeolodationDependent Dependent { get; }

View file

@ -0,0 +1,19 @@
using System;
using System.Globalization;
using Xamarin.Forms;
namespace TINK.View
{
/// <summary> Inverts a bool.</summary>
public class BoolInverterConverter : IValueConverter
{
/// <summary> Inverts a bool.</summary>
/// <param name="value">Bool to invert.</param>
/// <returns>Inverted bool.</returns>
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
=> value is bool flag && !flag;
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> value is bool flag && !flag;
}
}

View file

@ -0,0 +1,35 @@
using System.Windows.Input;
using Xamarin.Forms;
namespace TINK.View
{
public static class ListViewAttachedBehavior
{
public static readonly BindableProperty CommandProperty =
BindableProperty.CreateAttached(
"Command",
typeof(ICommand),
typeof(ListViewAttachedBehavior),
null,
propertyChanged: OnCommandChanged);
static void OnCommandChanged(BindableObject view, object oldValue, object newValue)
{
var entry = view as ListView;
if (entry == null)
return;
entry.ItemTapped += (sender, e) =>
{
var command = (newValue as ICommand);
if (command == null)
return;
if (command.CanExecute(e.Item))
{
command.Execute(e.Item);
}
};
}
}
}

View file

@ -0,0 +1,23 @@
using System;
using System.Globalization;
using Xamarin.Forms;
namespace TINK.View
{
/// <summary> Converts a string into visible state. If string is null or empty element becomes invisible.</summary>
public class StringNotNullOrEmptyToVisibleConverter : IValueConverter
{
/// <summary> Converts a string into visible state.</summary>
/// <param name="value">Text value from view model used to derive whether object is visible or not.</param>
/// <returns>Boolean value indicating whether object is visible or not.</returns>
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value != null && value is string text && !string.IsNullOrEmpty(text);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return "";
}
}
}

View file

@ -118,6 +118,7 @@
<Setter Property="VerticalOptions" Value="Start"/>
<Setter Property="HorizontalOptions" Value="Start"/>
<Setter Property="Padding" Value="0,14,0,12"/>
<Setter Property="MaxLines" Value="1"/>
</Style>
<Style x:Key="Image-Navbar" TargetType="Image">
<Setter Property="Source" Value="navbar_theme.png"/>

View file

@ -166,6 +166,7 @@
<Setter Property="VerticalOptions" Value="Start"/>
<Setter Property="HorizontalOptions" Value="Start"/>
<Setter Property="Padding" Value="0,14,0,12"/>
<Setter Property="MaxLines" Value="1"/>
</Style>
<Style x:Key="Image-Navbar" TargetType="Image">
<Setter Property="Source" Value="navbar_theme.png"/>

View file

@ -40,7 +40,7 @@ namespace TINK.ViewModel.Bikes
/// <summary> Context is bikes at station page. </summary>
BikesAtStation,
/// <summary> Context is find bike page. </summary>
FindBike,
SelectBike,
/// <summary> Context is my bikes page. </summary>
MyBikes
}

View file

@ -12,7 +12,6 @@ using TINK.Model.Bikes;
using TINK.Model.Bikes.BikeInfoNS.BluetoothLock;
using TINK.Model.Connector;
using TINK.Model.Device;
using TINK.Model.Stations;
using TINK.Model.Stations.StationNS;
using TINK.Model.User;
using TINK.MultilingualResources;
@ -233,11 +232,11 @@ namespace TINK.ViewModel.BikesAtStation
ActionText = AppResources.ActivityTextBikesAtStationGetBikes;
var bikesAll = await ConnectorFactory(IsConnected).Query.GetBikesAsync(Station?.OperatorUri);
var result = await ConnectorFactory(IsConnected).Query.GetBikesAsync(Station?.OperatorUri, Station.Id);
Exception = bikesAll.Exception; // Update communication error from query for bikes at station.
Exception = result.Exception; // Update communication error from query for bikes at station.
var bikesAtStation = bikesAll.Response.GetAtStation(Station.Id);
var bikesAtStation = result.Response;
var lockIdList = bikesAtStation
.GetLockIt()
.Cast<BikeInfo>()
@ -354,9 +353,9 @@ namespace TINK.ViewModel.BikesAtStation
},
null);
var result = ConnectorFactory(IsConnected).Query.GetBikesAsync().Result;
var result = ConnectorFactory(IsConnected).Query.GetBikesAsync(Station?.OperatorUri, Station.Id).Result;
BikeCollection bikes = result.Response.GetAtStation(Station.Id);
BikeCollection bikes = result.Response;
var exception = result.Exception;
if (exception != null)

View file

@ -12,7 +12,7 @@ using TINK.View;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace TINK.ViewModel.Info
namespace TINK.ViewModel.Contact
{
/// <summary> View model for contact page.</summary>
public class ContactPageViewModel : INotifyPropertyChanged

View file

@ -3,14 +3,13 @@ using System.ComponentModel;
using System.Threading.Tasks;
using Serilog;
using TINK.Model;
using TINK.Model.Bikes;
using TINK.Model.Connector;
using TINK.Model.Services.CopriApi;
using Xamarin.Forms;
namespace TINK.ViewModel.Contact
namespace TINK.ViewModel.Help
{
public class FeesAndBikesPageViewModel : INotifyPropertyChanged
public class HelpPageViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
@ -25,12 +24,12 @@ namespace TINK.ViewModel.Contact
/// <summary>
/// Relative path to fees resources of empty if value was not yet querried from backend.
/// </summary>
private string FeesResourcePath { get; set; }
private string TariffsResourcePath { get; set; }
/// <summary>
/// Relative path to bike info resources of empty if value was not yet querried from backend.
/// </summary>
private string BikesResourcePath { get; set; }
private string ManualResourcePath { get; set; }
private bool _IsIdle = false;
@ -56,7 +55,7 @@ namespace TINK.ViewModel.Contact
/// <summary>
/// Object to query resources urls object from backend if required.
/// This object is used to update resources path values <see cref="FeesResourcePath"/>, and <see cref="BikesResourcePath"/> from.
/// This object is used to update resources path values <see cref="TariffsResourcePath"/>, and <see cref="ManualResourcePath"/> from.
/// </summary>
private Func<IQuery> QueryProvider { get; }
@ -69,18 +68,18 @@ namespace TINK.ViewModel.Contact
/// <param name="isSiteCachingOn">Set of user permissions</param>
/// <param name="resourceProvider">Delegate to get embedded html resource. Used as fallback if download from web page does not work and cache is empty.</param>
/// <param name="query">Object to query resources path values if required.</param>
public FeesAndBikesPageViewModel(
public HelpPageViewModel(
string hostName,
string feesResourcePath,
string bikesResourcePath,
string tariffsResourcePath,
string manualResourcePath,
bool isSiteCachingOn,
string uiIsoLangugageName,
Func<IQuery> queryProvider,
Action<IResourceUrls> updateUrlsAction)
{
HostName = hostName;
FeesResourcePath = feesResourcePath;
BikesResourcePath = bikesResourcePath;
TariffsResourcePath = tariffsResourcePath;
ManualResourcePath = manualResourcePath;
IsSiteCachingOn = isSiteCachingOn;
QueryProvider = queryProvider;
UpdateUrlsAction = updateUrlsAction;
@ -90,98 +89,98 @@ namespace TINK.ViewModel.Contact
/// <summary> Holds the name of the host.</summary>
private string HostName { get; }
// Get resource urls object from backend.
public async Task<IResourceUrls> GetUrls()
{
Result<StationsAndBikesContainer> stations;
try
{
stations = await QueryProvider().GetBikesAndStationsAsync();
}
catch (Exception ex)
{
Log.ForContext<HelpPageViewModel>().Error($"Getting resource urls from COPRI failed. {ex.Message}");
return new ResourceUrls();
}
return stations?.GeneralData?.ResourceUrls ?? new ResourceUrls();
}
/// <summary> Called when page is shown. </summary>
public async void OnAppearing()
{
// Get resource urls object from backend.
async Task<IResourceUrls> GetUrls()
{
Result<BikeCollection> bikes;
try
{
bikes = await QueryProvider().GetBikesAsync();
}
catch (Exception ex)
{
Log.ForContext<FeesAndBikesPageViewModel>().Error($"Getting resource urls from COPRI failed. {ex.Message}");
return new ResourceUrls();
}
return bikes?.GeneralData?.ResourceUrls ?? new ResourceUrls();
}
// Set state to busy.
IsIdle = false;
if (string.IsNullOrEmpty(FeesResourcePath))
if (string.IsNullOrEmpty(TariffsResourcePath))
{
var urls = await GetUrls();
FeesResourcePath = urls.FeesResourcePath;
BikesResourcePath = urls.BikesResourcePath;
TariffsResourcePath = urls.TariffsResourcePath;
ManualResourcePath = urls.ManualResourcePath;
UpdateUrlsAction(urls); // Update main model to prevent duplicate queries.
}
//set font size for html content (necessary for iOS)
string headerString = "<header><meta name='viewport' content='width=device-width, intial-scale=2.0, maximum-scale=5.0, minimum-scale=1.0, user-scalable=no'></header>";
RentBikeText = new HtmlWebViewSource
ManualHtml = new HtmlWebViewSource
{
Html = !string.IsNullOrEmpty(FeesResourcePath)
? headerString + await ViewModelHelper.GetSource($"https://{HostName}/{FeesResourcePath}", IsSiteCachingOn)
: headerString + await Task.FromResult(ViewModelHelper.FromBody("No fees resource available. Resource path is null or empty."))
Html = !string.IsNullOrEmpty(ManualResourcePath)
? headerString + await ViewModelHelper.GetSource($"https://{HostName}/{ManualResourcePath}" /*"site/bike_info_....html"*/, IsSiteCachingOn)
: headerString + await Task.FromResult(ViewModelHelper.FromBody("No manual resource available. Resource path is null or empty."))
};
TypesOfBikesText = new HtmlWebViewSource
TariffsHtml = new HtmlWebViewSource
{
Html = !string.IsNullOrEmpty(BikesResourcePath)
? headerString + await ViewModelHelper.GetSource($"https://{HostName}/{BikesResourcePath}" /*"site/bike_info.html"*/, IsSiteCachingOn)
: headerString + await Task.FromResult(ViewModelHelper.FromBody("No bikes instruction resource available. Resource path is null or empty."))
Html = !string.IsNullOrEmpty(TariffsResourcePath)
? headerString + await ViewModelHelper.GetSource($"https://{HostName}/{TariffsResourcePath}" /*"site/tarif_info_....html"*/, IsSiteCachingOn)
: headerString + await Task.FromResult(ViewModelHelper.FromBody("No tariffs resource available. Resource path is null or empty."))
};
FAQ = new HtmlWebViewSource
FaqHtml = new HtmlWebViewSource
{
Html = UiIsoLanguageName == "de"
? await ViewModelHelper.GetSource($"https://sharee.bike/faq/fahrrad-nutzung/", IsSiteCachingOn)
: await ViewModelHelper.GetSource($"https://sharee.bike/faq/bike-usage/", IsSiteCachingOn)
? await ViewModelHelper.GetSource($"https://sharee.bike/faqHtml/fahrrad-nutzung/", IsSiteCachingOn)
: await ViewModelHelper.GetSource($"https://sharee.bike/faqHtml/bike-usage/", IsSiteCachingOn)
};
// Set state to idle.
IsIdle = true;
}
private HtmlWebViewSource rentBikeText;
private HtmlWebViewSource manualHtml;
private HtmlWebViewSource typesOfBikesText;
private HtmlWebViewSource tariffsHtml;
private HtmlWebViewSource faq;
private HtmlWebViewSource faqHtml;
public HtmlWebViewSource RentBikeText
public HtmlWebViewSource ManualHtml
{
get => rentBikeText;
get => manualHtml;
set
{
rentBikeText = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(RentBikeText)));
manualHtml = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ManualHtml)));
}
}
public HtmlWebViewSource TypesOfBikesText
public HtmlWebViewSource TariffsHtml
{
get => typesOfBikesText;
get => tariffsHtml;
set
{
typesOfBikesText = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(TypesOfBikesText)));
tariffsHtml = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(TariffsHtml)));
}
}
public HtmlWebViewSource FAQ
public HtmlWebViewSource FaqHtml
{
get => faq;
get => faqHtml;
set
{
faq = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FAQ)));
faqHtml = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FaqHtml)));
}
}
}

View file

@ -2,18 +2,18 @@ using System;
using System.ComponentModel;
using System.Threading.Tasks;
using Serilog;
using TINK.Model.Bikes;
using TINK.Model;
using TINK.Model.Device;
using TINK.Model.Services.CopriApi;
using Xamarin.Essentials;
using Xamarin.Forms;
using TINK.Model.Connector;
using TINK.ViewModel.Contact;
namespace TINK.ViewModel.Info
namespace TINK.ViewModel.LegalInformation
{
/// <summary> Manges the tabbed info page. </summary>
public class InfoPageViewModel : INotifyPropertyChanged
public class LegalInformationPageViewModel : INotifyPropertyChanged
{
/// <summary> Fired whenever a property changed.</summary>
public event PropertyChangedEventHandler PropertyChanged;
@ -27,7 +27,7 @@ namespace TINK.ViewModel.Info
/// <summary>
/// Relative path to agb resources of empty if value was not yet querried from backend.
/// </summary>
private string AgbResourcePath { get; set; }
private string GtcResourcePath { get; set; }
/// <summary>
/// Relative path to privacy resources of empty if value was not yet querried from backend.
@ -63,7 +63,7 @@ namespace TINK.ViewModel.Info
/// <summary>
/// Object to query resources urls object from backend if required.
/// This object is used to update resources path values <see cref="AgbResourcePath"/>, <see cref="PrivacyResourcePath"/> and <see cref="ImpressResourcePath"/> from.
/// This object is used to update resources path values <see cref="GtcResourcePath"/>, <see cref="PrivacyResourcePath"/> and <see cref="ImpressResourcePath"/> from.
/// </summary>
private Func<IQuery> QueryProvider { get; }
@ -75,15 +75,15 @@ namespace TINK.ViewModel.Info
/// <summary> Constructs Info view model</summary>
/// <param name="hostName">Name of the host to get html resources from.</param>
/// <param name="isSiteCachingOn">Holds value whether site caching is on or off.</param>
/// <param name="agbResourcePath"> Agb resource path received from backend.</param>
/// <param name="privacyResourcePath"> Privacy resource path received from backend.</param>
/// <param name="impressResourcePath"> Impress resource path received from backend.</param>
/// <param name="gtcResourcePath"> Agb resource path received from backend.</param>
/// <param name="privacyResourcePath"> PrivacyHtml resource path received from backend.</param>
/// <param name="impressResourcePath"> ImpressHtml resource path received from backend.</param>
/// <param name="resourceProvider">Delegate to get embedded html resource. Used as fallback if download from web page does not work and cache is empty.</param>
/// <param name="queryProvider">Object to query resources urls object from backend if required.</param>
/// <param name="updateUrlsAction">Action to update shared resources urls object</param>
public InfoPageViewModel(
public LegalInformationPageViewModel(
string hostName,
string agbResourcePath,
string gtcResourcePath,
string privacyResourcePath,
string impressResourcePath,
bool isSiteCachingOn,
@ -92,40 +92,39 @@ namespace TINK.ViewModel.Info
Action<IResourceUrls> updateUrlsAction)
{
HostName = hostName;
AgbResourcePath = agbResourcePath;
GtcResourcePath = gtcResourcePath;
PrivacyResourcePath = privacyResourcePath;
ImpressResourcePath = impressResourcePath;
IsSiteCachingOn = isSiteCachingOn;
QueryProvider = queryProvider;
UpdateUrlsAction = updateUrlsAction;
InfoAgb = new HtmlWebViewSource { Html = "<html>Loading...</html>" };
GtcHtml = new HtmlWebViewSource { Html = "<html>Loading...</html>" };
ResourceProvider = resourceProvider
?? throw new ArgumentException($"Can not instantiate {typeof(InfoPageViewModel)}-object. No resource provider centered.");
?? throw new ArgumentException($"Can not instantiate {typeof(LegalInformationPageViewModel)}-object. No resource provider centered.");
}
// Get resource urls object from backend.
public async Task<IResourceUrls> GetUrls()
{
Result<StationsAndBikesContainer> stations;
try
{
stations = await QueryProvider().GetBikesAndStationsAsync();
}
catch (Exception ex)
{
Log.ForContext<LegalInformationPageViewModel>().Error($"Getting resource urls from COPRI failed. {ex.Message}");
return new ResourceUrls();
}
return stations?.GeneralData?.ResourceUrls ?? new ResourceUrls();
}
/// <summary> Called when page is shown. </summary>
public async void OnAppearing()
{
// Get resource urls object from backend.
async Task<IResourceUrls> GetUrls()
{
Result<BikeCollection> bikes;
try
{
bikes = await QueryProvider().GetBikesAsync();
}
catch (Exception ex)
{
Log.ForContext<InfoPageViewModel>().Error($"Getting resource urls from COPRI failed. {ex.Message}");
return new ResourceUrls();
}
IResourceUrls resourceUrls = bikes?.GeneralData?.ResourceUrls ?? new ResourceUrls();
return resourceUrls;
}
// Set state to busy.
IsIdle = false;
@ -135,7 +134,7 @@ namespace TINK.ViewModel.Info
var urls = await GetUrls();
PrivacyResourcePath = urls.PrivacyResourcePath;
ImpressResourcePath = urls.ImpressResourcePath;
AgbResourcePath = urls.AgbResourcePath;
GtcResourcePath = urls.GtcResourcePath;
UpdateUrlsAction(urls); // Update main model to prevent duplicate queries.
}
@ -143,7 +142,7 @@ namespace TINK.ViewModel.Info
string headerString = "<header><meta name='viewport' content='width=device-width, intial-scale=2.0, maximum-scale=5.0, minimum-scale=1.0, user-scalable=no'></header>";
// Gets privacy info from server.
async Task<HtmlWebViewSource> GetInfoPrivacy()
async Task<HtmlWebViewSource> GetPrivacy()
{
string GetUriText()
=> $"https://{HostName}/{PrivacyResourcePath}";
@ -168,7 +167,7 @@ namespace TINK.ViewModel.Info
}
// Gets impress info from server.
async Task<HtmlWebViewSource> GetImpressum()
async Task<HtmlWebViewSource> GetImpress()
{
string GetUriText()
=> $"https://{HostName}/{ImpressResourcePath}";
@ -192,11 +191,11 @@ namespace TINK.ViewModel.Info
};
}
InfoAgb = await GetAgb(HostName, AgbResourcePath, IsSiteCachingOn);
GtcHtml = await GetGtc(HostName, GtcResourcePath, IsSiteCachingOn);
InfoPrivacy = await GetInfoPrivacy();
PrivacyHtml = await GetPrivacy();
InfoImpressum = await GetImpressum();
ImpressHtml = await GetImpress();
// Set state to idle.
IsIdle = true;
@ -205,18 +204,18 @@ namespace TINK.ViewModel.Info
/// <summary> Gets the AGBs</summary>
/// <param name="resourceProvider"></param>
/// <returns> AGBs</returns>
public static async Task<HtmlWebViewSource> GetAgb(
public static async Task<HtmlWebViewSource> GetGtc(
string hostName,
string agbResourcePath,
string gtcResourcePath,
bool isSiteCachingOn)
{
string GetUriText()
=> $"https://{hostName}/{agbResourcePath}";
=> $"https://{hostName}/{gtcResourcePath}";
//set font size for html content (necessary for iOS)
string headerString = "<header><meta name='viewport' content='width=device-width, intial-scale=2.0, maximum-scale=5.0, minimum-scale=1.0, user-scalable=no'></header>";
if (string.IsNullOrEmpty(agbResourcePath))
if (string.IsNullOrEmpty(gtcResourcePath))
{
return new HtmlWebViewSource
{
@ -238,7 +237,7 @@ namespace TINK.ViewModel.Info
private Func<string, string> ResourceProvider { get; set; }
/// <summary> Gets the app related information (app version and licenses). </summary>
public HtmlWebViewSource InfoLicenses => new HtmlWebViewSource
public HtmlWebViewSource AppHtml => new HtmlWebViewSource
{
Html = ResourceProvider("HtmlResouces.V02.InfoLicenses.html")
.Replace("CURRENT_VERSION_TINKAPP", DependencyService.Get<IAppInfo>().Version.ToString())
@ -246,44 +245,44 @@ namespace TINK.ViewModel.Info
.Replace("APPSUPPORTMAILADDRESS", ContactPageViewModel.APPSUPPORTMAILADDRESS)
};
/// <summary> Privacy text.</summary>
private HtmlWebViewSource infoImpress;
/// <summary> PrivacyHtml text.</summary>
private HtmlWebViewSource impressHtml;
/// <summary> Gets the privacy related information. </summary>
public HtmlWebViewSource InfoImpressum
public HtmlWebViewSource ImpressHtml
{
get => infoImpress;
get => impressHtml;
set
{
infoImpress = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(InfoImpressum)));
impressHtml = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ImpressHtml)));
}
}
/// <summary> Agb information text.</summary>
private HtmlWebViewSource infoAgb;
private HtmlWebViewSource gtcHtml;
/// <summary> Privacy text.</summary>
private HtmlWebViewSource infoPrivacy;
/// <summary> PrivacyHtml text.</summary>
private HtmlWebViewSource privacyHtml;
/// <summary> Agb information text.</summary>
public HtmlWebViewSource InfoAgb
public HtmlWebViewSource GtcHtml
{
get => infoAgb;
get => gtcHtml;
set
{
infoAgb = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(InfoAgb)));
gtcHtml = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(GtcHtml)));
}
}
/// <summary> Agb information text.</summary>
public HtmlWebViewSource InfoPrivacy
public HtmlWebViewSource PrivacyHtml
{
get => infoPrivacy;
get => privacyHtml;
set
{
infoPrivacy = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(InfoPrivacy)));
privacyHtml = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(PrivacyHtml)));
}
}
}

View file

@ -1,10 +1,10 @@
using System.Collections.Generic;
using System.Collections.Generic;
using TINK.Model;
namespace TINK.ViewModel.Map
{
/// <summary> Interface for filtering. </summary>
/// <remarks> Holds a dictionary of filters which might or might not be appield depending on filter state.</remarks>
/// <remarks> Holds a dictionary of filters which might or might not be applied depending on filter state.</remarks>
public interface IGroupFilterMapPage : IDictionary<string /* Filter*/, FilterState /* on or off*/>
{
/// <summary> Performs filtering on response-group. </summary>

View file

@ -170,22 +170,9 @@ namespace TINK.ViewModel.Map
/// <summary>
/// Counts the number of reserved or occupied bikes -> visualized in MyBikes-Icon
/// </summary>
public void GetMyBikesCount(BikeCollection bikesAll)
public void GetMyBikesCount(BikeCollection bikes_occupied)
{
int MyBikesCount = 0;
Log.ForContext<MapPageViewModel>().Debug($"Number of reserved or rented bikes is extracted.");
if (bikesAll != null)
{
foreach (var bike in bikesAll)
{
if (bike.State.Value.IsOccupied())
{
MyBikesCount = MyBikesCount + 1;
continue;
}
}
}
int MyBikesCount = bikes_occupied.Count;
MyBikesCountText = MyBikesCount > 0 ? string.Format(MyBikesCount.ToString()) : string.Empty;
}

View file

@ -99,7 +99,7 @@ namespace TINK.ViewModel
var aggregateException = exception as AggregateException;
if (aggregateException == null)
{
// Unexpected exception detected. Exception should alyways be of type AggregateException
// Unexpected exception detected. Exception should always be of type AggregateException
Log.Error("An/ several errors occurred on update task. {@Exceptions}.", exception);
}
else

View file

@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
@ -10,7 +9,6 @@ using Plugin.BLE.Abstractions.Contracts;
using Serilog;
using TINK.Model;
using TINK.Model.Bikes;
using TINK.Model.Bikes.BikeInfoNS.BikeNS;
using TINK.Model.Bikes.BikeInfoNS.BluetoothLock;
using TINK.Model.Connector;
using TINK.Model.Device;
@ -30,9 +28,9 @@ using TINK.ViewModel.Map;
using Xamarin.Forms;
using Command = Xamarin.Forms.Command;
namespace TINK.ViewModel.FindBike
namespace TINK.ViewModel.SelectBike
{
public class FindBikePageViewModel : BikesViewModel, INotifyCollectionChanged, INotifyPropertyChanged
public class SelectBikePageViewModel : BikesViewModel, INotifyCollectionChanged, INotifyPropertyChanged
{
private string bikeIdUserInput = string.Empty;
@ -64,7 +62,7 @@ namespace TINK.ViewModel.FindBike
if (value == IsIdle)
return;
Log.ForContext<FindBikePageViewModel>().Debug($"Switch value of {nameof(IsIdle)} to {value}.");
Log.ForContext<SelectBikePageViewModel>().Debug($"Switch value of {nameof(IsIdle)} to {value}.");
base.IsIdle = value;
OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsSelectBikeEnabled))); // Enable select bike button.
}
@ -109,6 +107,7 @@ namespace TINK.ViewModel.FindBike
/// Constructs bike collection view model in case information about occupied bikes is available.
/// </summary>
/// <param name="user">Mail address of active user.</param>
/// <param name="tinkApp"> Reference to tink app model.</param>
/// <param name="isReportLevelVerbose">True if report level is verbose, false if not.</param>
/// <param name="permissions">Holds object to query location permissions.</param>
/// <param name="bluetoothLE">Holds object to query bluetooth state.</param>
@ -122,7 +121,7 @@ namespace TINK.ViewModel.FindBike
/// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...).</param>
/// <param name="viewService">Interface to actuate methods on GUI.</param>
/// <param name="openUrlInBrowser">Delegate to open browser.</param>
public FindBikePageViewModel(
public SelectBikePageViewModel(
User user,
ITinkApp tinkApp,
ILocationPermission permissions,
@ -137,7 +136,7 @@ namespace TINK.ViewModel.FindBike
Action<SendOrPostCallback, object> postAction,
ISmartDevice smartDevice,
IViewService viewService,
Action<string> openUrlInBrowser) : base(user, new ViewContext(PageContext.FindBike), permissions, bluetoothLE, runtimPlatform, isConnectedDelegate, connectorFactory, geolocation, lockService, polling, postAction, smartDevice, viewService, openUrlInBrowser, () => new MyBikeInUseStateInfoProvider())
Action<string> openUrlInBrowser) : base(user, new ViewContext(PageContext.SelectBike), permissions, bluetoothLE, runtimPlatform, isConnectedDelegate, connectorFactory, geolocation, lockService, polling, postAction, smartDevice, viewService, openUrlInBrowser, () => new MyBikeInUseStateInfoProvider())
{
CollectionChanged += (sender, eventargs) =>
{
@ -174,7 +173,7 @@ namespace TINK.ViewModel.FindBike
{
IsIdle = false;
Log.ForContext<FindBikePageViewModel>().Information("User request to show page FindBike- page re-appearing");
Log.ForContext<SelectBikePageViewModel>().Information("User request to show page SelectBike- page re-appearing");
IsConnected = IsConnectedDelegate();
@ -201,25 +200,15 @@ namespace TINK.ViewModel.FindBike
return;
}
// Get Active Filtered BikeType
ActiveFilteredBikeType = GetActiveFilteredBikeType(GroupFilterMapPage);
ActionText = string.Empty;
IsIdle = true;
var result = await ConnectorFactory(IsConnected).Query.GetBikesAsync();
var bikes = result.Response;
var exception = result.Exception;
if (exception != null)
{
Log.ForContext<MapPageViewModel>().Error("Getting bikes in polling context failed with exception {Exception}.", exception);
}
// Get Active Filtered BikeType
GetActiveFilteredBikeType(bikes);
}
/// <summary> Command object to bind select bike button to view model. </summary>
public System.Windows.Input.ICommand OnSelectBikeRequest => new Xamarin.Forms.Command(async () => await SelectBike());
public System.Windows.Input.ICommand OnSelectBikeRequest => new Command(async () => await SelectBike());
/// <summary> Select a bike by ID</summary>
public async Task SelectBike()
@ -236,7 +225,7 @@ namespace TINK.ViewModel.FindBike
{
// Get List of bike to be able to connect to.
ActionText = AppResources.ActivityTextFindBikeLoadingBikes;
ActionText = AppResources.ActivityTextSelectBikeLoadingBikes;
IsIdle = false;
IsConnected = IsConnectedDelegate();
@ -244,14 +233,14 @@ namespace TINK.ViewModel.FindBike
Result<BikeCollection> bikes = null;
try
{
bikes = await ConnectorFactory(IsConnected).Query.GetBikesAsync();
bikes = await ConnectorFactory(IsConnected).Query.GetBikesAsync(bikeId: BikeIdUserInput.Trim());
}
catch (Exception exception)
{
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<FindBikePageViewModel>().Information("Getting bikes failed (Copri server not reachable).");
Log.ForContext<SelectBikePageViewModel>().Information("Getting bikes failed (Copri server not reachable).");
await ViewService.DisplayAlert(
AppResources.ErrorSelectBikeTitle,
@ -260,7 +249,7 @@ namespace TINK.ViewModel.FindBike
}
else
{
Log.ForContext<FindBikePageViewModel>().Error("Getting bikes failed. {Exception}", exception);
Log.ForContext<SelectBikePageViewModel>().Error("Getting bikes failed. {Exception}", exception);
await ViewService.DisplayAlert(
AppResources.ErrorSelectBikeTitle,
@ -280,7 +269,7 @@ namespace TINK.ViewModel.FindBike
try
{
var selectedBike = Bikes.FirstOrDefault(x => x.Id.Equals(BikeIdUserInput.Trim(), StringComparison.OrdinalIgnoreCase));
var selectedBike = bikes.Response.FirstOrDefault();
if (selectedBike == null)
{
@ -419,7 +408,7 @@ namespace TINK.ViewModel.FindBike
}
catch (Exception exception)
{
Log.ForContext<FindBikePageViewModel>().Error("Getting bluetooth state failed. {Exception}", exception);
Log.ForContext<SelectBikePageViewModel>().Error("Getting bluetooth state failed. {Exception}", exception);
locksInfoTdo = new List<LockInfoTdo>();
}
@ -439,7 +428,7 @@ namespace TINK.ViewModel.FindBike
exception.Message,
AppResources.MessageAnswerOk);
Log.ForContext<FindBikePageViewModel>().Error("Running command to select bike failed. {Exception}", exception);
Log.ForContext<SelectBikePageViewModel>().Error("Running command to select bike failed. {Exception}", exception);
ActionText = string.Empty;
IsIdle = true;
@ -467,7 +456,7 @@ namespace TINK.ViewModel.FindBike
var exception = result.Exception;
if (exception != null)
{
Log.ForContext<FindBikePageViewModel>().Error("Getting bikes in polling context failed with exception {Exception}.", exception);
Log.ForContext<SelectBikePageViewModel>().Error("Getting bikes in polling context failed with exception {Exception}.", exception);
}
var selectedBike = bikes.FirstOrDefault(x => x.Id.Equals(BikeIdUserInput.Trim(), StringComparison.OrdinalIgnoreCase));
@ -486,9 +475,12 @@ namespace TINK.ViewModel.FindBike
null);
}
/// <summary>
/// Selected Bike Type in MapFilter shown as label in View. Default empty.
/// </summary>
private string activeFilteredBikeType = string.Empty;
/// <summary>
/// Selected Bike Type in MapFilter
/// Selected Bike Type in MapFilter shown as label in View. If empty, label is not shown = no wrong info.
/// </summary>
public string ActiveFilteredBikeType
{
@ -504,23 +496,27 @@ namespace TINK.ViewModel.FindBike
}
}
/// <summary>Gets the group filter from map page. </summary>
private IGroupFilterMapPage GroupFilterMapPage => TinkApp.GroupFilterMapPage;
/// <summary>
/// Get Selected Bike Type in MapFilter
/// </summary>
public void GetActiveFilteredBikeType(BikeCollection bikesAll)
public static string GetActiveFilteredBikeType(IGroupFilterMapPage filter)
{
Log.ForContext<FindBikePageViewModel>().Debug($"Bike type of active filter is extracted.");
if (bikesAll != null)
Log.ForContext<SelectBikePageViewModel>().Debug($"Bike type of active filter is extracted.");
List<string> currentFilteredBikeType = filter.Where(x => x.Value == FilterState.On).Select(x => x.Key).ToList();
string filteredBikeType = currentFilteredBikeType.Count == 1 ? currentFilteredBikeType[0] : string.Empty;
if (filteredBikeType == FilterHelper.CARGOBIKE)
{
var firstOrDefaultBikeType = bikesAll.FirstOrDefault().TypeOfBike;
if(firstOrDefaultBikeType == TypeOfBike.Cargo)
{
ActiveFilteredBikeType = AppResources.MarkingCargoBike;
}
else if(firstOrDefaultBikeType == TypeOfBike.City)
{
ActiveFilteredBikeType = AppResources.MarkingCityBike;
}
return AppResources.MarkingCargoBike;
}else if (filteredBikeType == FilterHelper.CITYBIKE)
{
return AppResources.MarkingCityBike;
}
else
{
return string.Empty;
}
}
}

View file

@ -183,8 +183,8 @@ namespace TINK.ViewModel
StartupSettings = new PickerViewModel(
new Dictionary<string, string> {
{ ViewTypes.MapPage.ToString(), AppResources.MarkingMapPage },
{ ViewTypes.FindBikePage.ToString(), AppResources.MarkingFindBike },
{ ViewTypes.MapPage.ToString(), AppResources.MarkingBikeLocations },
{ ViewTypes.SelectBikePage.ToString(), AppResources.MarkingSelectBike },
},
tinkApp.StartupSettings.StartupPage.ToString());

View file

@ -3,12 +3,12 @@ using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows.Input;
using TINK.View;
using TINK.ViewModel.Info;
using TINK.ViewModel.LegalInformation;
using Xamarin.Forms;
namespace TINK.ViewModel.WhatsNew.Agb
namespace TINK.ViewModel.WhatsNew.Gtc
{
public class AgbViewModel : INotifyPropertyChanged
public class GtcViewModel : INotifyPropertyChanged
{
/// <summary> Fired whenever a property changed.</summary>
public event PropertyChangedEventHandler PropertyChanged;
@ -24,7 +24,7 @@ namespace TINK.ViewModel.WhatsNew.Agb
/// <param name="uiIsoLangugageName">Two letter ISO language name.</param>
/// <param name="resourceProvider">Delegate to get embedded html resource. Used as fallback if download from web page does not work and cache is empty.</param>
/// <param name="viewService">View service to close page.</param>
public AgbViewModel(
public GtcViewModel(
string hostName,
bool isSiteCachingOn,
Func<string, string> resourceProvider,
@ -44,23 +44,23 @@ namespace TINK.ViewModel.WhatsNew.Agb
private Func<string, string> ResourceProvider { get; set; }
/// <summary> Agb information text.</summary>
private HtmlWebViewSource infoAgb;
private HtmlWebViewSource gtcHtml;
/// <summary> Agb information text.</summary>
public HtmlWebViewSource InfoAgb
public HtmlWebViewSource GtcHtml
{
get => infoAgb;
get => gtcHtml;
set
{
infoAgb = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(InfoAgb)));
gtcHtml = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(GtcHtml)));
}
}
/// <summary> Called when page is shown. </summary>
public async Task OnAppearing()
{
InfoAgb = await InfoPageViewModel.GetAgb(HostName, "agbResourcePath", IsSiteCachingOn);
GtcHtml = await LegalInformationPageViewModel.GetGtc(HostName, "agbResourcePath", IsSiteCachingOn);
}
/// <summary> User clicks OK button.</summary>

View file

@ -70,7 +70,7 @@ namespace TINK.ViewModel.WhatsNew
{
get
{
return new Command(async () => await ViewService.PushModalAsync(ViewTypes.AgbPage));
return new Command(async () => await ViewService.PushModalAsync(ViewTypes.GtcPage));
}
}

View file

@ -9,15 +9,15 @@ namespace TINK
PasswordForgottenPage,
MyBikesPage,
SettingsPage,
TabbedPageInfo,
FeesAndBikesPage,
LegalInformationPage,
HelpPage,
ManageAccountPage,
AgbPage,
GtcPage,
WhatsNewPage,
BikesAtStation,
ContactPage,
SelectStationPage,
MiniSurvey,
FindBikePage
SelectBikePage
}
}