3.0.267 merged

This commit is contained in:
Oliver Hauff 2022-01-04 18:54:03 +01:00
parent b6fb6394db
commit 67999ef4ae
171 changed files with 6473 additions and 1093 deletions

View file

@ -122,13 +122,13 @@ namespace TINK.Model.Connector
Log.ForContext<Command>().Error("Unexpected booking request detected. No user logged in.");
await Task.CompletedTask;
}
public async Task<MiniSurveyModel> DoReturn(
public async Task<BookingFinishedModel> DoReturn(
Bikes.Bike.BluetoothLock.IBikeInfoMutable bike,
LocationDto location,
ISmartDevice smartDevice)
{
Log.ForContext<Command>().Error("Unexpected returning request detected. No user logged in.");
return await Task.FromResult(new MiniSurveyModel());
return await Task.FromResult(new BookingFinishedModel());
}
/// <summary>

View file

@ -248,7 +248,7 @@ namespace TINK.Model.Connector
/// <param name="bike">Bike to return.</param>
/// <param name="locaton">Position of the bike.</param>
/// <param name="smartDevice">Provides info about hard and software.</param>
public async Task<MiniSurveyModel> DoReturn(
public async Task<BookingFinishedModel> DoReturn(
Bikes.Bike.BluetoothLock.IBikeInfoMutable bike,
LocationDto location,
ISmartDevice smartDevice)
@ -258,7 +258,7 @@ namespace TINK.Model.Connector
throw new ArgumentNullException("Can not return bike. No bike object available.");
}
ReservationCancelReturnResponse response;
DoReturnResponse response;
try
{
response = (await CopriServer.DoReturn(bike.Id, location, smartDevice, bike.OperatorUri)).GetIsReturnBikeResponseOk(bike.Id);
@ -270,7 +270,7 @@ namespace TINK.Model.Connector
}
bike.Load(Bikes.Bike.BC.NotifyPropertyChangedLevel.None);
return response?.Create() ?? new MiniSurveyModel();
return response?.Create() ?? new BookingFinishedModel();
}
/// <summary>

View file

@ -50,7 +50,7 @@ namespace TINK.Model.Connector
/// <param name="bike">Bike to return.</param>
/// <param name="location">Geolocation of lock when returning bike.</param>
/// <param name="smartDevice">Provides info about hard and software.</param>
Task<MiniSurveyModel> DoReturn(Bikes.Bike.BluetoothLock.IBikeInfoMutable bike, LocationDto geolocation = null, ISmartDevice smartDevice = null);
Task<BookingFinishedModel> DoReturn(Bikes.Bike.BluetoothLock.IBikeInfoMutable bike, LocationDto geolocation = null, ISmartDevice smartDevice = null);
/// <summary> True if connector has access to copri server, false if cached values are used. </summary>
bool IsConnected { get; }

View file

@ -11,26 +11,26 @@ namespace TINK.Model.Connector
{
/// <summary>Constructs a copri connector object.</summary>
/// <param name="activeUri"> Uri to connect to.</param>
/// <param name="userAgent">Holds the name and version of the TINKApp.</param>
/// <param name="appContextInfo">Provides app related info (app name and version, merchantid) to pass to COPRI.</param>
/// /// <param name="sessionCookie"> Holds the session cookie.</param>
/// <param name="p_strMail">Mail of user.</param>
/// <param name="expiresAfter">Timespan which holds value after which cache expires.</param>
/// <param name="server"> Provides cached addess to copri.</param>
public Connector(
Uri activeUri,
string userAgent,
AppContextInfo appContextInfo,
string sessionCookie,
string mail,
TimeSpan? expiresAfter = null,
ICachedCopriServer server = null )
{
Command = GetCommand(
server ?? new CopriProviderHttps(activeUri, TinkApp.MerchantId, userAgent, sessionCookie),
server ?? new CopriProviderHttps(activeUri, TinkApp.MerchantId, appContextInfo, sessionCookie),
sessionCookie,
mail);
Query = GetQuery(
server ?? new CopriProviderHttps(activeUri, TinkApp.MerchantId, userAgent, sessionCookie, expiresAfter),
server ?? new CopriProviderHttps(activeUri, TinkApp.MerchantId, appContextInfo, sessionCookie, expiresAfter),
sessionCookie,
mail);
}

View file

@ -1,4 +1,5 @@
using System;
using TINK.Repository;
namespace TINK.Model.Connector
{
@ -7,12 +8,19 @@ namespace TINK.Model.Connector
/// <summary>
/// Gets a connector object depending on whether beein onlin or offline.
/// </summary>
/// <param name="isConnected">True if online, false if offline</param>
/// <param name="isConnected">True if online, false if offline. If offline cache connector is returned.</param>
/// <param name="appContextInfo">Provides app related info (app name and version, merchantid) to pass to COPRI.</param>
/// <returns></returns>
public static IConnector Create(bool isConnected, Uri activeUri, string userAgent, string sessionCookie, string mail, TimeSpan? expiresAfter = null)
public static IConnector Create(
bool isConnected,
Uri activeUri,
AppContextInfo appContextInfo,
string sessionCookie,
string mail,
TimeSpan? expiresAfter = null)
{
return isConnected
? new Connector(activeUri, userAgent, sessionCookie, mail, expiresAfter: expiresAfter) as IConnector
? new Connector(activeUri, appContextInfo, sessionCookie, mail, expiresAfter: expiresAfter) as IConnector
: new ConnectorCache(sessionCookie, mail);
}
}

View file

@ -7,13 +7,13 @@
/// Was "Konrad" up to version 3.0.258.
/// Specified first: "KN300001" (RG).
/// </remarks>
public const string FILTERKONRAD = "300101";
public const string CITYBIKE = "300103";
/// <summary> Holds the tink group (Lastenräder).</summary>
/// <remarks>
/// Was "TINK" up to version 3.0.258.
/// Specified first: "KN300029" (RG).
/// </remarks>
public const string FILTERTINKGENERAL = "300103";
public const string CARGOBIKE = "300101";
}
}

View file

@ -68,7 +68,8 @@ namespace TINK.Model.Connector
var result = await m_oInnerQuery.GetBikesAsync();
return new Result<BikeCollection>(
result.Source,
new BikeCollection(DoFilter(result.Response, Filter)),
new BikeCollection(DoFilter(result.Response, Filter)),
result.GeneralData,
result.Exception);
}
@ -78,7 +79,8 @@ namespace TINK.Model.Connector
var result = await m_oInnerQuery.GetBikesOccupiedAsync();
return new Result<BikeCollection>(
result.Source,
new BikeCollection(result.Response.ToDictionary(x => x.Id)),
new BikeCollection(result.Response.ToDictionary(x => x.Id)),
result.GeneralData,
result.Exception);
}
@ -95,7 +97,8 @@ namespace TINK.Model.Connector
var filteredBikesAndStations = new Result<StationsAndBikesContainer>(
providerBikesAndStations.Source,
new StationsAndBikesContainer(filteredStationsDictionary, filteredBikesDictionary),
providerBikesAndStations.Exception); ;
providerBikesAndStations.GeneralData,
providerBikesAndStations.Exception);
return filteredBikesAndStations;
}

View file

@ -62,6 +62,7 @@ namespace TINK.Model.Connector
return new Result<BikeCollection>(
result.Source,
new BikeCollection(result.Response.ToDictionary(x => x.Id)),
result.GeneralData,
result.Exception);
}
@ -72,6 +73,7 @@ namespace TINK.Model.Connector
return new Result<BikeCollection>(
result.Source,
new BikeCollection(result.Response.ToDictionary(x => x.Id)),
result.GeneralData,
result.Exception);
}
@ -86,6 +88,7 @@ namespace TINK.Model.Connector
new StationsAndBikesContainer(
new StationDictionary(result.Response.StationsAll.CopriVersion, result.Response.StationsAll.ToDictionary(x => x.Id)),
new BikeCollection(result.Response.Bikes.ToDictionary(x => x.Id))),
result.GeneralData,
result.Exception);
}

View file

@ -5,6 +5,7 @@ using System.Threading.Tasks;
using TINK.Model.Bike;
using TINK.Model.Services.CopriApi;
using TINK.Repository;
using TINK.Services.CopriApi;
using BikeInfo = TINK.Model.Bike.BC.BikeInfo;
namespace TINK.Model.Connector
@ -39,6 +40,7 @@ namespace TINK.Model.Connector
new StationsAndBikesContainer(
resultStations.Response.GetStationsAllMutable(),
(await server.GetBikesAvailable(true)).Response.GetBikesAvailable()),
resultStations.GeneralData,
resultStations.Exception);
}
@ -51,6 +53,7 @@ namespace TINK.Model.Connector
new StationsAndBikesContainer(
(await server.GetStations(true)).Response.GetStationsAllMutable(),
resultBikes.Response.GetBikesAvailable()),
resultBikes.GeneralData,
resultBikes.Exception);
}
@ -60,7 +63,8 @@ namespace TINK.Model.Connector
return new Result<StationsAndBikesContainer>(
resultStations.Source,
new StationsAndBikesContainer(resultStations.Response.GetStationsAllMutable(), resultBikes.Response.GetBikesAvailable()));
new StationsAndBikesContainer(resultStations.Response.GetStationsAllMutable(), resultBikes.Response.GetBikesAvailable()),
resultStations.GeneralData);
}
/// <summary> Gets bikes occupied. </summary>
@ -71,7 +75,8 @@ namespace TINK.Model.Connector
return new Result<BikeCollection>(
typeof(CopriCallsMonkeyStore),
await Task.Run(() => new BikeCollection(new Dictionary<string, BikeInfo>())),
new System.Exception("Abfrage der reservierten/ gebuchten Räder nicht möglich. Kein Benutzer angemeldet."));
new GeneralData(),
new Exception("Abfrage der reservierten/ gebuchten Räder nicht möglich. Kein Benutzer angemeldet."));
}
/// <summary> Gets bikes available. </summary>
@ -80,7 +85,7 @@ namespace TINK.Model.Connector
{
var result = await server.GetBikesAvailable();
server.AddToCache(result);
return new Result<BikeCollection>(result.Source, result.Response.GetBikesAvailable(), result.Exception);
return new Result<BikeCollection>(result.Source, result.Response.GetBikesAvailable(), result.GeneralData, result.Exception);
}
}
}

View file

@ -45,6 +45,7 @@ namespace TINK.Model.Connector
(await server.GetBikesOccupied(true)).Response,
Mail,
DateTimeProvider)),
stationsResponse.GeneralData,
stationsResponse.Exception);
}
@ -61,6 +62,7 @@ namespace TINK.Model.Connector
(await server.GetBikesOccupied(true)).Response,
Mail,
DateTimeProvider)),
bikesAvailableResponse.GeneralData,
bikesAvailableResponse.Exception);
}
@ -78,6 +80,7 @@ namespace TINK.Model.Connector
bikesOccupiedResponse.Response,
Mail,
DateTimeProvider)),
bikesOccupiedResponse.GeneralData,
bikesOccupiedResponse.Exception);
}
@ -98,6 +101,7 @@ namespace TINK.Model.Connector
return new Result<StationsAndBikesContainer>(
stationsResponse.Source,
new StationsAndBikesContainer(stationsMutable, bikesMutable),
stationsResponse.GeneralData,
exceptions.Length > 0 ? new AggregateException(exceptions) : null);
}
@ -107,7 +111,7 @@ namespace TINK.Model.Connector
{
var result = await server.GetBikesOccupied();
server.AddToCache(result);
return new Result<BikeCollection>(result.Source, result.Response.GetBikesOccupied(Mail, DateTimeProvider), result.Exception);
return new Result<BikeCollection>(result.Source, result.Response.GetBikesOccupied(Mail, DateTimeProvider), result.GeneralData, result.Exception);
}
/// <summary> Gets bikes available and bikes occupied. </summary>
@ -127,6 +131,7 @@ namespace TINK.Model.Connector
(await server.GetBikesOccupied(true)).Response,
Mail,
DateTimeProvider),
l_oBikesAvailableResponse.GeneralData,
l_oBikesAvailableResponse.Exception);
}
@ -142,6 +147,7 @@ namespace TINK.Model.Connector
l_oBikesOccupiedResponse.Response,
Mail,
DateTimeProvider),
l_oBikesOccupiedResponse.GeneralData,
l_oBikesOccupiedResponse.Exception);
}
@ -153,6 +159,7 @@ namespace TINK.Model.Connector
return new Result<BikeCollection>(
l_oBikesAvailableResponse.Source,
UpdaterJSON.GetBikesAll(l_oBikesAvailableResponse.Response, l_oBikesOccupiedResponse.Response, Mail, DateTimeProvider),
l_oBikesAvailableResponse.GeneralData,
l_oBikesAvailableResponse.Exception != null || l_oBikesOccupiedResponse.Exception != null ? new AggregateException(new[] { l_oBikesAvailableResponse.Exception, l_oBikesOccupiedResponse.Exception }) : null);
}
}

View file

@ -5,6 +5,7 @@ using System.Threading.Tasks;
using TINK.Model.Bike;
using TINK.Model.Services.CopriApi;
using TINK.Repository;
using TINK.Services.CopriApi;
using BikeInfo = TINK.Model.Bike.BC.BikeInfo;
namespace TINK.Model.Connector
@ -34,7 +35,8 @@ namespace TINK.Model.Connector
return new Result<StationsAndBikesContainer>(
typeof(CopriCallsMonkeyStore),
new StationsAndBikesContainer( stationsAllResponse.GetStationsAllMutable(), bikesAvailableResponse.GetBikesAvailable()));
new StationsAndBikesContainer(stationsAllResponse.GetStationsAllMutable(), bikesAvailableResponse.GetBikesAvailable()),
stationsAllResponse.GetGeneralData());
}
/// <summary> Gets bikes occupied. </summary>
@ -45,16 +47,19 @@ namespace TINK.Model.Connector
return new Result<BikeCollection>(
typeof(CopriCallsMonkeyStore),
await Task.Run(() => new BikeCollection(new Dictionary<string, BikeInfo>())),
new System.Exception("Abfrage der reservierten/ gebuchten Räder fehlgeschlagen. Kein Benutzer angemeldet."));
new GeneralData(),
new Exception("Abfrage der reservierten/ gebuchten Räder fehlgeschlagen. Kein Benutzer angemeldet."));
}
/// <summary> Gets bikes occupied. </summary>
/// <returns> Collection of bikes. </returns>
public async Task<Result<BikeCollection>> GetBikesAsync()
{
var bikesAvailableResponse = await server.GetBikesAvailableAsync();
return new Result<BikeCollection>(
typeof(CopriCallsMonkeyStore),
(await server.GetBikesAvailableAsync()).GetBikesAvailable());
typeof(CopriCallsMonkeyStore),
bikesAvailableResponse.GetBikesAvailable(),
bikesAvailableResponse.GetGeneralData());
}
}
}

View file

@ -39,27 +39,31 @@ namespace TINK.Model.Connector
typeof(CopriCallsMonkeyStore),
new StationsAndBikesContainer(
stationResponse.GetStationsAllMutable(),
UpdaterJSON.GetBikesAll(bikesAvailableResponse, bikesOccupiedResponse, Mail, DateTimeProvider)));
UpdaterJSON.GetBikesAll(bikesAvailableResponse, bikesOccupiedResponse, Mail, DateTimeProvider)),
stationResponse.GetGeneralData());
}
/// <summary> Gets bikes occupied. </summary>
/// <returns>Collection of bikes.</returns>
public async Task<Result<BikeCollection>> GetBikesOccupiedAsync()
{
var bikesOccupiedResponse = (await server.GetBikesOccupiedAsync());
return new Result<BikeCollection>(
typeof(CopriCallsMonkeyStore),
(await server.GetBikesOccupiedAsync()).GetBikesOccupied(Mail, DateTimeProvider));
typeof(CopriCallsMonkeyStore),
bikesOccupiedResponse.GetBikesOccupied(Mail, DateTimeProvider),
bikesOccupiedResponse.GetGeneralData());
}
/// <summary> Gets bikes available and bikes occupied. </summary>
/// <returns>Collection of bikes.</returns>
public async Task<Result<BikeCollection>> GetBikesAsync()
{
var l_oBikesAvailableResponse = await server.GetBikesAvailableAsync();
var l_oBikesOccupiedResponse = await server.GetBikesOccupiedAsync();
var bikesAvailableResponse = await server.GetBikesAvailableAsync();
var bikesOccupiedResponse = await server.GetBikesOccupiedAsync();
return new Result<BikeCollection>(
typeof(CopriCallsMonkeyStore),
UpdaterJSON.GetBikesAll(l_oBikesAvailableResponse, l_oBikesOccupiedResponse, Mail, DateTimeProvider));
UpdaterJSON.GetBikesAll(bikesAvailableResponse, bikesOccupiedResponse, Mail, DateTimeProvider),
bikesAvailableResponse.GetGeneralData());
}
}
}

View file

@ -15,6 +15,7 @@ using TINK.Model.Station.Operator;
using Xamarin.Forms;
using System.Linq;
using TINK.Model.MiniSurvey;
using TINK.Services.CopriApi;
namespace TINK.Model.Connector
{
@ -78,6 +79,14 @@ namespace TINK.Model.Connector
return stations;
}
/// <summary>
/// Gets general data from COPRI response.
/// </summary>
/// <param name="response">Response to get data from.</param>
/// <returns>General data object initialized form COPRI response.</returns>
public static GeneralData GetGeneralData(this ResponseBase response)
=> new GeneralData(response.merchant_message, response.GetCopriVersion());
/// <summary> Gets account object from login response.</summary>
/// <param name="merchantId">Needed to extract cookie from autorization response.</param>
/// <param name="loginResponse">Response to get session cookie and debug level from.</param>
@ -512,22 +521,28 @@ namespace TINK.Model.Connector
};
}
/// <summary> Creates a survey object from response.</summary>
/// <summary> Creates a booking finished object from response.</summary>
/// <param name="response">Response to create survey object from.</param>
public static MiniSurveyModel Create(this ReservationCancelReturnResponse response)
public static BookingFinishedModel Create(this DoReturnResponse response)
{
if (response?.user_miniquery == null)
var bookingFinished = new BookingFinishedModel
{
return new MiniSurveyModel();
Co2Saving = response?.co2saving
};
if (response?.user_miniquery == null)
{
return bookingFinished;
}
var miniquery = response.user_miniquery;
var survey = new MiniSurveyModel
{
Title = miniquery.title,
Subtitle = miniquery.subtitle,
Footer = miniquery.footer
};
bookingFinished.MiniSurvey = new MiniSurveyModel
{
Title = miniquery.title,
Subtitle = miniquery.subtitle,
Footer = miniquery.footer
};
foreach (var question in miniquery?.questions?.OrderBy(x => x.Key) ?? new Dictionary<string, MiniSurveyResponse.Question>().OrderBy(x => x.Key))
{
@ -538,12 +553,12 @@ namespace TINK.Model.Connector
continue;
}
survey.Questions.Add(
bookingFinished.MiniSurvey.Questions.Add(
question.Key,
new MiniSurveyModel.QuestionModel());
}
return survey;
return bookingFinished;
}
}
}

View file

@ -15,8 +15,8 @@ namespace TINK.Model
get
{
return new GroupFilterSettings(new Dictionary<string, FilterState> {
{ FilterHelper.FILTERTINKGENERAL, FilterState.On },
{FilterHelper.FILTERKONRAD, FilterState.On }
{ FilterHelper.CARGOBIKE, FilterState.On },
{FilterHelper.CITYBIKE, FilterState.On }
});
}
}
@ -27,8 +27,8 @@ namespace TINK.Model
get
{
return new GroupFilterMapPage(new Dictionary<string, FilterState> {
{ FilterHelper.FILTERTINKGENERAL, FilterState.On },
{FilterHelper.FILTERKONRAD, FilterState.Off }
{ FilterHelper.CARGOBIKE, FilterState.On },
{FilterHelper.CITYBIKE, FilterState.Off }
});
}
}

View file

@ -3,6 +3,9 @@
namespace TINK.Model.MiniSurvey
{
/// <summary>
/// Holds mini survey.
/// </summary>
public class MiniSurveyModel
{
public enum Type
@ -12,21 +15,17 @@ namespace TINK.Model.MiniSurvey
}
public class QuestionModel
{
public QuestionModel()
{
PossibleAnswers = new Dictionary<string, string>();
}
/// <summary>
/// Holds the query description.
/// </summary>
public string Text { get; set; }
public Type Type { get; set; }
public Dictionary<string, string> PossibleAnswers { get; private set; }
}
public MiniSurveyModel()
{
Questions = new Dictionary<string, QuestionModel>();
/// <summary>
/// Holds the collection of possible answers.
/// </summary>
public Dictionary<string, string> PossibleAnswers { get; private set; } = new Dictionary<string, string>();
}
public string Title { get; set; }
@ -35,6 +34,6 @@ namespace TINK.Model.MiniSurvey
public string Footer { get; set; }
public Dictionary<string, QuestionModel> Questions { get; }
public Dictionary<string, QuestionModel> Questions { get; } = new Dictionary<string, QuestionModel>();
}
}

View file

@ -215,16 +215,8 @@ namespace TINK.Model
GeolocationServices = geolocationServicesContainer
?? throw new ArgumentException($"Can not instantiate {nameof(TinkApp)}- object. No geolocation services container object available.");
if (settings.ActiveUri == new Uri(CopriServerUriList.TINK_LIVE) ||
settings.ActiveUri == new Uri(CopriServerUriList.TINK_DEVEL))
{
FilterGroupSetting = settings.GroupFilterSettings;
GroupFilterMapPage = settings.GroupFilterMapPage;
} else
{
FilterGroupSetting = new GroupFilterSettings();
GroupFilterMapPage = new GroupFilterMapPage();
}
FilterGroupSetting = settings.GroupFilterSettings;
GroupFilterMapPage = settings.GroupFilterMapPage;
CenterMapToCurrentLocation = settings.CenterMapToCurrentLocation;

View file

@ -455,9 +455,29 @@ namespace TINK.Model
AppResources.ChangeLog3_0_250 // Third-party components updated.
},
{
new Version(3, 0, 257),
new Version(3, 0, 260),
// Same info as for version 3.0.251 and 3.0.252
AppResources.ChangeLog3_0_231 // Minor improvements.
},
{
new Version(3, 0, 263),
AppResources.ChangeLog3_0_263
},
{
new Version(3, 0, 264),
AppResources.ChangeLog3_0_264
},
{
new Version(3, 0, 265),
AppResources.ChangeLog3_0_265
},
{
new Version(3, 0, 266),
AppResources.ChangeLog3_0_266
},
{
new Version(3, 0, 267),
AppResources.ChangeLog3_0_231 // Minor improvements.
}
};

View file

@ -871,6 +871,44 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to CO2 saving is displayed for bikes with GPS-lock after returning bike..
/// </summary>
public static string ChangeLog3_0_263 {
get {
return ResourceManager.GetString("ChangeLog3_0_263", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Bug &quot;object reference not set ....&quot; &lt;a href=&quot;https://dev.azure.com/TeilRad/sharee.bike%20App/_workitems/edit/186&quot;&gt;185&lt;/a&gt; fixed..
/// </summary>
public static string ChangeLog3_0_264 {
get {
return ResourceManager.GetString("ChangeLog3_0_264", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Filter functionality city bike/ cargo bike improved.
///Layouting improved.
///App supports deep linking..
/// </summary>
public static string ChangeLog3_0_265 {
get {
return ResourceManager.GetString("ChangeLog3_0_265", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Location permissions handling improved..
/// </summary>
public static string ChangeLog3_0_266 {
get {
return ResourceManager.GetString("ChangeLog3_0_266", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Lock of rented bike can not be found..
/// </summary>
@ -1221,6 +1259,24 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Cargo bike.
/// </summary>
public static string MarkingCargoBike {
get {
return ResourceManager.GetString("MarkingCargoBike", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to City bike.
/// </summary>
public static string MarkingCityBike {
get {
return ResourceManager.GetString("MarkingCityBike", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Please open a bike station page to to contact the bike sharing operator..
/// </summary>

View file

@ -710,4 +710,24 @@ Kleinere Verbesserungen.</value>
Kartenzentrierfehler behoben.
Kleine Verbesserungen.</value>
</data>
<data name="ChangeLog3_0_263" xml:space="preserve">
<value>Bei Fahrrädern mit GPS-Schloss wird die CO2-Einsparung nach der Rückgabe des Fahrrads angezeigt.</value>
</data>
<data name="ChangeLog3_0_264" xml:space="preserve">
<value>Fehler "object reference not set ...." &lt;a href="https://dev.azure.com/TeilRad/sharee.bike%20App/_workitems/edit/186"&gt;185&lt;/a&gt; behoben.</value>
</data>
<data name="ChangeLog3_0_265" xml:space="preserve">
<value>Filterfunktion Stadtrad/ Lastenrad verbessert.
Layouting verbessert.
Deep linking wird unterstützt.</value>
</data>
<data name="MarkingCargoBike" xml:space="preserve">
<value>Lastenrad</value>
</data>
<data name="MarkingCityBike" xml:space="preserve">
<value>Stadtrad</value>
</data>
<data name="MessageBikesManagementTariffDescriptionTariffHeaderNameId" xml:space="preserve">
<value>Tarif {0}, Nr. {1}</value>
</data>
</root>

View file

@ -805,4 +805,27 @@ Minor fixes.</value>
Center map to current position issue fixed.
Minor improvements.</value>
</data>
<data name="ChangeLog3_0_263" xml:space="preserve">
<value>CO2 saving is displayed for bikes with GPS-lock after returning bike.</value>
</data>
<data name="ChangeLog3_0_264" xml:space="preserve">
<value>Bug "object reference not set ...." &lt;a href="https://dev.azure.com/TeilRad/sharee.bike%20App/_workitems/edit/186"&gt;185&lt;/a&gt; fixed.</value>
</data>
<data name="ChangeLog3_0_265" xml:space="preserve">
<value>Filter functionality city bike/ cargo bike improved.
Layouting improved.
App supports deep linking.</value>
</data>
<data name="MarkingCargoBike" xml:space="preserve">
<value>Cargo bike</value>
</data>
<data name="MarkingCityBike" xml:space="preserve">
<value>City bike</value>
</data>
<data name="MessageBikesManagementTariffDescriptionTariffHeaderNameId" xml:space="preserve">
<value>Tariff {0}, nr. {1}</value>
</data>
<data name="ChangeLog3_0_266" xml:space="preserve">
<value>Location permissions handling improved.</value>
</data>
</root>

View file

@ -952,6 +952,34 @@ Minor improvements.</source>
Kartenzentrierfehler behoben.
Kleine Verbesserungen.</target>
</trans-unit>
<trans-unit id="ChangeLog3_0_263" translate="yes" xml:space="preserve">
<source>CO2 saving is displayed for bikes with GPS-lock after returning bike.</source>
<target state="translated">Bei Fahrrädern mit GPS-Schloss wird die CO2-Einsparung nach der Rückgabe des Fahrrads angezeigt.</target>
</trans-unit>
<trans-unit id="ChangeLog3_0_264" translate="yes" xml:space="preserve">
<source>Bug "object reference not set ...." <bpt id="1">&lt;a href="https://dev.azure.com/TeilRad/sharee.bike%20App/_workitems/edit/186"&gt;</bpt>185<ept id="1">&lt;/a&gt;</ept> fixed.</source>
<target state="translated">Fehler "object reference not set ...." <bpt id="1">&lt;a href="https://dev.azure.com/TeilRad/sharee.bike%20App/_workitems/edit/186"&gt;</bpt>185<ept id="1">&lt;/a&gt;</ept> behoben.</target>
</trans-unit>
<trans-unit id="ChangeLog3_0_265" translate="yes" xml:space="preserve">
<source>Filter functionality city bike/ cargo bike improved.
Layouting improved.
App supports deep linking.</source>
<target state="translated">Filterfunktion Stadtrad/ Lastenrad verbessert.
Layouting verbessert.
Deep linking wird unterstützt.</target>
</trans-unit>
<trans-unit id="MarkingCargoBike" translate="yes" xml:space="preserve">
<source>Cargo bike</source>
<target state="translated">Lastenrad</target>
</trans-unit>
<trans-unit id="MarkingCityBike" translate="yes" xml:space="preserve">
<source>City bike</source>
<target state="translated">Stadtrad</target>
</trans-unit>
<trans-unit id="MessageBikesManagementTariffDescriptionTariffHeaderNameId" translate="yes" xml:space="preserve">
<source>Tariff {0}, nr. {1}</source>
<target state="translated">Tarif {0}, Nr. {1}</target>
</trans-unit>
</group>
</body>
</file>

View file

@ -0,0 +1,35 @@
using System;
namespace TINK.Repository
{
/// <summary>
/// Holds info passed to COPRI.
/// </summary>
public class AppContextInfo
{
public AppContextInfo(string merchantId, string name, Version version)
{
Name = !string.IsNullOrEmpty(name)
? name
: throw new ArgumentNullException(nameof(name));
Version = version;
MerchantId = !string.IsNullOrEmpty(merchantId)
? merchantId
: throw new ArgumentNullException(nameof(merchantId));
}
/// <summary> Name of the app. </summary>
public string Name { get; private set; }
/// <summary> Version of the app.</summary>
public Version Version { get; private set; }
/// <summary> Merchang id of the app. </summary>
public string MerchantId { get; private set; }
/// <summary> Returns http- user agent.</summary>
public string UserAgent { get => $"{Name}/{Version}"; }
}
}

View file

@ -22,25 +22,23 @@ namespace TINK.Repository
/// <summary> Initializes a instance of the copri calls https object. </summary>
/// <param name="copriHost">Host to connect to. </param>
/// <param name="merchantId">Id of the merchant.</param>
/// <param name="userAgent">Holds the name and version of the TINKApp.</param>
/// <param name="appContextInfo">Provides app related info (app name and version, merchantid) to pass to COPRI.</param>
/// <param name="sessionCookie">Session cookie if user is logged in, null otherwise.</param>
public CopriCallsHttps(
Uri copriHost,
string merchantId,
string userAgent,
AppContextInfo appContextInfo,
string sessionCookie = null)
{
m_oCopriHost = copriHost
?? throw new System.Exception($"Can not construct {GetType().ToString()}- object. Uri of copri host must not be null.");
?? throw new System.Exception($"Can not construct {GetType()}- object. Uri of copri host must not be null.");
UserAgent = !string.IsNullOrEmpty(userAgent)
? userAgent
: throw new System.Exception($"Can not construct {GetType().ToString()}- object. User agent must not be null or empty.");
UserAgent = appContextInfo != null
? appContextInfo.UserAgent
: throw new System.Exception($"Can not construct {GetType()}- object. User agent must not be null or empty.");
requestBuilder = string.IsNullOrEmpty(sessionCookie)
? new RequestBuilder(merchantId) as IRequestBuilder
: new RequestBuilderLoggedIn(merchantId, sessionCookie);
? new RequestBuilder(appContextInfo.MerchantId) as IRequestBuilder
: new RequestBuilderLoggedIn(appContextInfo.MerchantId, sessionCookie);
}
/// <summary> Holds the URL for rest calls.</summary>
@ -621,10 +619,10 @@ namespace TINK.Repository
string userAgent = null)
{
#if !WINDOWS_UWP
string cancelOrReturnResponse;
string doReturnResponse;
try
{
cancelOrReturnResponse = await PostAsync(copriHost, command, userAgent);
doReturnResponse = await PostAsync(copriHost, command, userAgent);
}
catch (System.Exception l_oException)
{
@ -642,7 +640,7 @@ namespace TINK.Repository
}
// Extract bikes from response.
return JsonConvertRethrow.DeserializeObject<ResponseContainer<ReservationCancelReturnResponse>>(cancelOrReturnResponse)?.shareejson;
return JsonConvertRethrow.DeserializeObject<ResponseContainer<DoReturnResponse>>(doReturnResponse)?.shareejson;
#else
return null;
#endif

View file

@ -1,5 +1,4 @@

using System.Runtime.Serialization;
namespace TINK.Repository.Response
{

View file

@ -17,6 +17,10 @@ namespace TINK.Repository.Response
[DataMember]
public string authcookie { get; private set; }
/// <summary> Message shown to user.</summary>
[DataMember]
public string merchant_message { get; private set; }
/// <summary> Textual description of response. </summary>
public new string ToString()
{

View file

@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using TINK.Model.Connector;
using TINK.Model.Device;
using TINK.Repository;
using TINK.Repository.Request;
@ -29,22 +30,21 @@ namespace TINK.Model.Services.CopriApi
/// <summary> Constructs copri provider object to connet to https using a cache objet. </summary>
/// <param name="copriHost"></param>
/// <param name="merchantId"></param>
/// <param name="userAgent">Holds the name and version of the TINKApp.</param>
/// <param name="appContextInfo">Provides app related info (app name and version, merchantid) to pass to COPRI.</param>
/// <param name="sessionCookie">Cookie of user if a user is logged in, false otherwise.</param>
/// <param name="expiresAfter">Timespan which holds value after which cache expires.</param>
/// <param name="isExpired">Delegate which returns if cache conted is out of date or not.</param>
public CopriProviderHttps(
Uri copriHost,
string merchantId,
string userAgent,
AppContextInfo appContextInfo,
string sessionCookie = null,
TimeSpan? expiresAfter = null,
ICopriCache cacheServer = null,
ICopriServer httpsServer = null)
{
CacheServer = cacheServer ?? new CopriCallsMonkeyStore(merchantId, sessionCookie, expiresAfter);
HttpsServer = httpsServer ?? new CopriCallsHttps(copriHost, merchantId, userAgent, sessionCookie);
HttpsServer = httpsServer ?? new CopriCallsHttps(copriHost, appContextInfo, sessionCookie);
}
/// <summary>Gets bikes available.</summary>
@ -59,22 +59,26 @@ 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.");
return new Result<BikesAvailableResponse>(typeof(CopriCallsMonkeyStore), await CacheServer.GetBikesAvailableAsync());
var bikesAvailableResponse = await CacheServer.GetBikesAvailableAsync();
return new Result<BikesAvailableResponse>(typeof(CopriCallsMonkeyStore), bikesAvailableResponse, bikesAvailableResponse.GetGeneralData());
}
try
{
Log.ForContext<CopriProviderHttps>().Debug($"Querrying bikes available from copri.");
var bikesAvailableResponse = await HttpsServer.GetBikesAvailableAsync();
return new Result<BikesAvailableResponse>(
typeof(CopriCallsHttps),
(await HttpsServer.GetBikesAvailableAsync()).GetIsResponseOk("Abfrage der verfügbaren Räder fehlgeschlagen."));
bikesAvailableResponse.GetIsResponseOk("Abfrage der verfügbaren Räder fehlgeschlagen."),
bikesAvailableResponse.GetGeneralData());
}
catch (Exception exception)
{
// Return response from cache.
Log.ForContext<CopriProviderHttps>().Debug("An error occurred querrying bikes available. {Exception}.", exception);
return new Result<BikesAvailableResponse>(typeof(CopriCallsMonkeyStore), await CacheServer.GetBikesAvailableAsync(), exception);
var bikesAvailableResponse = await CacheServer.GetBikesAvailableAsync();
return new Result<BikesAvailableResponse>(typeof(CopriCallsMonkeyStore), bikesAvailableResponse, bikesAvailableResponse.GetGeneralData(), exception);
}
}
@ -88,23 +92,27 @@ namespace TINK.Model.Services.CopriApi
|| fromCache)
{
// No need to query because previous answer is not yet outdated.
var bikesOccupiedResponse = await CacheServer.GetBikesOccupiedAsync();
Log.ForContext<CopriProviderHttps>().Debug($"Returning bikes occupied from cache.");
return new Result<BikesReservedOccupiedResponse>(typeof(CopriCallsMonkeyStore), await CacheServer.GetBikesOccupiedAsync());
return new Result<BikesReservedOccupiedResponse>(typeof(CopriCallsMonkeyStore), bikesOccupiedResponse, bikesOccupiedResponse.GetGeneralData());
}
try
{
Log.ForContext<CopriProviderHttps>().Debug($"Querrying bikes occupied from copri.");
var bikesOccupiedResponse = await HttpsServer.GetBikesOccupiedAsync();
return new Result<BikesReservedOccupiedResponse>(
typeof(CopriCallsHttps),
(await HttpsServer.GetBikesOccupiedAsync()).GetIsResponseOk("Abfrage der reservierten/ gebuchten Räder fehlgeschlagen."));
bikesOccupiedResponse.GetIsResponseOk("Abfrage der reservierten/ gebuchten Räder fehlgeschlagen."),
bikesOccupiedResponse.GetGeneralData());
}
catch (Exception exception)
{
// Return response from cache.
Log.ForContext<CopriProviderHttps>().Debug("An error occurred querrying bikes occupied. {Exception}.", exception);
return new Result<BikesReservedOccupiedResponse>(typeof(CopriCallsMonkeyStore), await CacheServer.GetBikesOccupiedAsync(), exception);
var bikesOccupiedResponse = await CacheServer.GetBikesOccupiedAsync();
return new Result<BikesReservedOccupiedResponse>(typeof(CopriCallsMonkeyStore), bikesOccupiedResponse, bikesOccupiedResponse.GetGeneralData(), exception);
}
}
@ -118,7 +126,8 @@ namespace TINK.Model.Services.CopriApi
{
// No need to query because previous answer is not yet outdated.
Log.ForContext<CopriProviderHttps>().Debug($"Returning stations from cache.");
return new Result<StationsAvailableResponse>(typeof(CopriCallsMonkeyStore), await CacheServer.GetStationsAsync());
var stationsResponse = await CacheServer.GetStationsAsync();
return new Result<StationsAvailableResponse>(typeof(CopriCallsMonkeyStore), stationsResponse, stationsResponse.GetGeneralData());
}
try
@ -129,13 +138,15 @@ namespace TINK.Model.Services.CopriApi
return new Result<StationsAvailableResponse>(
typeof(CopriCallsHttps),
stations.GetIsResponseOk("Abfrage der Stationen fehlsgeschlagen."));
stations.GetIsResponseOk("Abfrage der Stationen fehlsgeschlagen."),
stations.GetGeneralData());
}
catch (Exception exception)
{
// Return response from cache.
Log.ForContext<CopriProviderHttps>().Debug("An error occurred querrying stations. {Exception}.", exception);
return new Result<StationsAvailableResponse>(typeof(CopriCallsMonkeyStore), await CacheServer.GetStationsAsync(), exception);
var stationsResponse = await CacheServer.GetStationsAsync();
return new Result<StationsAvailableResponse>(typeof(CopriCallsMonkeyStore), stationsResponse, stationsResponse.GetGeneralData(), exception);
}
}

View file

@ -68,14 +68,6 @@ namespace TINK.Model.Services.CopriApi
public Task<ResponseBase> DoSubmitMiniSurvey(IDictionary<string, string> answers)
=> throw new NotSupportedException();
public Task<SubmitFeedbackResponse> DoSubmitFeedback(string bikeId, string messge, bool bIsBikeBroke, Uri operatorUri)
=> throw new NotImplementedException();
/// <summary> Submits mini survey to copri server. </summary>
/// <param name="answers">Collection of answers.</param>
public Task<ResponseBase> DoSubmitMiniSurvey(IDictionary<string, string> answers)
=> throw new NotSupportedException();
public async Task<AuthorizationResponse> DoAuthorizationAsync(string p_strMailAddress, string p_strPassword, string p_strDeviceId)
{

View file

@ -0,0 +1,22 @@
using System;
namespace TINK.Services.CopriApi
{
/// <summary> Holds general purpose data returned from COPRI. </summary>
public class GeneralData
{
public GeneralData(string merachantMessage = null, Version apiVersion = null)
{
MerchantMessage = merachantMessage ?? string.Empty;
ApiVersion = apiVersion ?? new Version(0, 0);
}
/// <summary> Message to be shown to user.</summary>
public string MerchantMessage { get; private set; }
/// <summary> Version of COPRI api. 0.0 if version is not set</summary>
public Version ApiVersion { get; private set; }
}
}

View file

@ -1,23 +1,38 @@
using System;
using TINK.Services.CopriApi;
namespace TINK.Model.Services.CopriApi
{
public class Result<T> where T : class
{
public Result(Type source, T response, System.Exception exception = null)
/// <summary>
/// Constructs a result object.
/// </summary>
/// <param name="source">Type of source (data provider).</param>
/// <param name="response">Requested data (bikes, station).</param>
/// <param name="generalData">General data (common to all respones).</param>
public Result(
Type source,
T response,
GeneralData generalData,
Exception exception = null)
{
Source = source ?? throw new ArgumentException(nameof(source));
Response = response ?? throw new ArgumentException(nameof(response));
GeneralData = generalData ?? new GeneralData();
Exception = exception;
}
/// <summary> Holds the copri respsonse</summary>
/// <summary> Holds the requested data (bikes, stations and bikes).</summary>
public T Response { get; }
/// <summary> Specifies the souce of the copri response.</summary>
/// <summary> Holds the general purpose data (common to all responses).</summary>
public GeneralData GeneralData { get; }
/// <summary> Specifies the source (type of provider) of the copri response.</summary>
public Type Source { get; }
/// <summary> Holds the exception if a communication error occurred.</summary>
public System.Exception Exception { get; private set; }
public Exception Exception { get; private set; }
}
}

View file

@ -10,7 +10,6 @@ namespace TINK.Model.Services.CopriApi
StationsAll = stations;
Bikes = bikes;
}
public StationDictionary StationsAll { get; }
public BikeCollection Bikes { get; }

View file

@ -43,7 +43,7 @@
<PackageReference Include="Xam.Plugin.Connectivity" Version="3.2.0" />
<PackageReference Include="Xam.Plugins.Messaging" Version="5.2.0" />
<PackageReference Include="Xamarin.Essentials" Version="1.7.0" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2125" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2196" />
<PackageReference Include="Xamarin.Forms.GoogleMaps" Version="3.3.0" />
</ItemGroup>
<ItemGroup>

View file

@ -15,9 +15,9 @@ using TINK.Model.User;
using Xamarin.Essentials;
using TINK.Repository.Request;
using TINK.Model.Device;
using TINK.Model.MiniSurvey;
using System.Collections.Generic;
using System.Threading;
using TINK.Model;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
@ -162,10 +162,10 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IsConnected = IsConnectedDelegate();
var feedBackUri = SelectedBike?.OperatorUri;
MiniSurveyModel miniSurvey;
BookingFinishedModel bookingFinished;
try
{
miniSurvey = await ConnectorFactory(IsConnected).Command.DoReturn(
bookingFinished = await ConnectorFactory(IsConnected).Command.DoReturn(
SelectedBike,
currentLocationDto);
@ -289,7 +289,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
}
#endif
if (miniSurvey != null && miniSurvey.Questions.Count > 0)
if (bookingFinished != null && bookingFinished.MiniSurvey.Questions.Count > 0)
{
await ViewService.PushModalAsync(ViewTypes.MiniSurvey);
}

View file

@ -15,9 +15,9 @@ using TINK.Model.Bikes.Bike.BluetoothLock;
using TINK.Model.User;
using TINK.Repository.Request;
using TINK.Model.Device;
using TINK.Model.MiniSurvey;
using System.Collections.Generic;
using System.Threading;
using TINK.Model;
namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
@ -273,10 +273,10 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
IsConnected = IsConnectedDelegate();
var feedBackUri = SelectedBike?.OperatorUri;
MiniSurveyModel miniSurvey;
BookingFinishedModel bookingFinished;
try
{
miniSurvey = await ConnectorFactory(IsConnected).Command.DoReturn(
bookingFinished = await ConnectorFactory(IsConnected).Command.DoReturn(
SelectedBike,
currentLocation != null
? new LocationDto.Builder
@ -404,7 +404,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
#endif
if (miniSurvey != null && miniSurvey.Questions.Count > 0)
if (bookingFinished != null && bookingFinished.MiniSurvey.Questions.Count > 0)
{
await ViewService.PushModalAsync(ViewTypes.MiniSurvey);
}

View file

@ -293,7 +293,6 @@ namespace TINK.ViewModel.BikesAtStation
ActionText = "";
IsIdle = true;
return;
}
// Check if bluetooth is activated.

View file

@ -408,7 +408,7 @@ namespace TINK.ViewModel.Contact
}
catch (Exception l_oException)
{
Log.ForContext<SelectStationPageViewModel>().Error($"An error occurred switching view TINK/ Konrad.\r\n{l_oException.Message}");
Log.ForContext<SelectStationPageViewModel>().Error($"An error occurred opening select station page.\r\n{l_oException.Message}");
IsRunning = false;

View file

@ -297,7 +297,7 @@ namespace TINK.ViewModel
// Display information that login succeeded.
Log.ForContext<LoginPageViewModel>().Information("Login succeeded. {@tinkApp.ActiveUser}.", TinkApp.ActiveUser);
var title = TinkApp.ActiveUser.Group.Intersect(new List<string> { Model.Connector.FilterHelper.FILTERTINKGENERAL, Model.Connector.FilterHelper.FILTERKONRAD }).Any()
var title = TinkApp.ActiveUser.Group.Intersect(new List<string> { Model.Connector.FilterHelper.CARGOBIKE, Model.Connector.FilterHelper.CITYBIKE }).Any()
? string.Format(AppResources.MessageLoginWelcomeTitleGroup, TinkApp.ActiveUser.GetUserGroupDisplayName())
: string.Format(AppResources.MessageLoginWelcomeTitle);
@ -314,7 +314,7 @@ namespace TINK.ViewModel
try
{
if (!TinkApp.ActiveUser.Group.Contains(Model.Connector.FilterHelper.FILTERTINKGENERAL))
if (!TinkApp.ActiveUser.Group.Contains(Model.Connector.FilterHelper.CARGOBIKE))
{
// No need to show "Anleitung TINK Räder" because user can not use tink.
#if USEFLYOUT

View file

@ -314,34 +314,7 @@ namespace TINK.ViewModel.Map
ActiveFilterMap = TinkApp.GroupFilterMapPage;
ActionText = AppResources.ActivityTextRequestingLocationPermissions;
// Check location permission
var status = await PermissionsService.CheckStatusAsync();
if (TinkApp.CenterMapToCurrentLocation
&& !GeolocationService.IsSimulation
&& status != Status.Granted)
{
var permissionResult = await PermissionsService.RequestAsync();
if (permissionResult != Status.Granted)
{
var dialogResult = await ViewService.DisplayAlert(
AppResources.MessageTitleHint,
AppResources.MessageCenterMapLocationPermissionOpenDialog,
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
if (dialogResult)
{
// User decided to give access to locations permissions.
PermissionsService.OpenAppSettings();
ActionText = "";
IsRunning = false;
IsMapPageEnabled = true;
return;
}
}
}
var status = await RequestLocationPermission();
ActionText = AppResources.ActivityTextMapLoadingStationsAndBikes;
IsConnected = TinkApp.GetIsConnected();
@ -349,63 +322,8 @@ namespace TINK.ViewModel.Map
TinkApp.Stations = resultStationsAndBikes.Response.StationsAll;
if (Pins.Count > 0 && Pins.Count != resultStationsAndBikes.Response.StationsAll.Count)
{
// Either
// - user logged in/ logged out which might lead to more/ less stations beeing available
// - new stations were added/ existing ones remove
Pins.Clear();
}
// Check if there are alreay any pins to the map
// i.e detecte first call of member OnAppearing after construction
if (Pins.Count <= 0)
{
Log.ForContext<MapPageViewModel>().Debug($"{(ActiveFilterMap.GetGroup().Any() ? $"Active map filter is {string.Join(",", ActiveFilterMap.GetGroup())}." : "Map filter is off.")}");
// Map was not yet initialized.
// Get stations from Copri
Log.ForContext<MapPageViewModel>().Verbose("No pins detected on page.");
if (resultStationsAndBikes.Response.StationsAll.CopriVersion < CopriCallsStatic.UnsupportedVersionLower)
{
await ViewService.DisplayAlert(
AppResources.MessageWaring,
string.Format(AppResources.MessageCopriVersionIsOutdated, ContactPageViewModel.GetAppName(TinkApp.Uris.ActiveUri)),
AppResources.MessageAnswerOk);
Log.ForContext<MapPageViewModel>().Error($"Outdated version of app detected. Version expected is {resultStationsAndBikes.Response.StationsAll.CopriVersion}.");
}
if (resultStationsAndBikes.Response.StationsAll.CopriVersion >= CopriCallsStatic.UnsupportedVersionUpper)
{
await ViewService.DisplayAlert(
AppResources.MessageWaring,
string.Format(AppResources.MessageAppVersionIsOutdated, ContactPageViewModel.GetAppName(TinkApp.Uris.ActiveUri)),
AppResources.MessageAnswerOk);
Log.ForContext<MapPageViewModel>().Error($"Outdated version of app detected. Version expected is {resultStationsAndBikes.Response.StationsAll.CopriVersion}.");
}
// Set pins to their positions on map.
InitializePins(resultStationsAndBikes.Response.StationsAll);
Log.ForContext<MapPageViewModel>().Verbose("Update of pins done.");
}
if (resultStationsAndBikes.Exception?.GetType() == typeof(AuthcookieNotDefinedException))
{
Log.ForContext<MapPageViewModel>().Error("Map page is shown (probable for the first time after startup of app) and COPRI auth cookie is not defined. {@l_oException}", resultStationsAndBikes.Exception);
// COPRI reports an auth cookie error.
await ViewService.DisplayAlert(
AppResources.MessageWaring,
AppResources.MessageMapPageErrorAuthcookieUndefined,
AppResources.MessageAnswerOk);
await TinkApp.GetConnector(IsConnected).Command.DoLogout();
TinkApp.ActiveUser.Logout();
}
await SetStationsOnMap(resultStationsAndBikes.Response.StationsAll);
await HandleAuthCookieNotDefinedException(resultStationsAndBikes.Exception);
// Update pin colors.
Log.ForContext<MapPageViewModel>().Verbose("Starting update pins color...");
@ -417,21 +335,11 @@ namespace TINK.ViewModel.Map
// Update pins color form count of bikes located at station.
UpdatePinsColor(colors);
Log.ForContext<MapPageViewModel>().Verbose("Update pins color done.");
// Move and scale before getting stations and bikes which takes some time.
ActionText = AppResources.ActivityTextCenterMap;
Location currentLocation = null;
try
{
currentLocation = TinkApp.CenterMapToCurrentLocation
? await GeolocationService.GetAsync()
: null;
}
catch (Exception ex)
{
Log.ForContext<MapPageViewModel>().Error("Getting location failed. {Exception}", ex);
}
MoveAndScale(m_oMoveToRegionDelegate, TinkApp.Uris.ActiveUri, ActiveFilterMap, currentLocation);
await MoveMapToCurrentPositionOfUser(status);
m_oViewUpdateManager = CreateUpdateTask();
@ -467,6 +375,144 @@ namespace TINK.ViewModel.Map
}
}
/// <summary>
/// Invoked when the auth cookie is not defined.
/// </summary>
private async Task HandleAuthCookieNotDefinedException(Exception exception)
{
if (exception?.GetType() == typeof(AuthcookieNotDefinedException))
{
Log.ForContext<MapPageViewModel>().Error("Map page is shown (probable for the first time after startup of app) and COPRI auth cookie is not defined. {@l_oException}", exception);
// COPRI reports an auth cookie error.
await ViewService.DisplayAlert(
AppResources.MessageWaring,
AppResources.MessageMapPageErrorAuthcookieUndefined,
AppResources.MessageAnswerOk);
await TinkApp.GetConnector(IsConnected).Command.DoLogout();
TinkApp.ActiveUser.Logout();
}
}
/// <summary>
/// Sets the available stations on the map.
/// </summary>
private async Task SetStationsOnMap(StationDictionary stations)
{
if (Pins.Count > 0 && Pins.Count != stations.Count)
{
// Either
// - user logged in/ logged out which might lead to more/ less stations beeing available
// - new stations were added/ existing ones remove
Pins.Clear();
}
// Check if there are alreay any pins to the map
// i.e detecte first call of member OnAppearing after construction
if (Pins.Count <= 0)
{
Log.ForContext<MapPageViewModel>().Debug($"{(ActiveFilterMap.GetGroup().Any() ? $"Active map filter is {string.Join(",", ActiveFilterMap.GetGroup())}." : "Map filter is off.")}");
// Map was not yet initialized.
// Get stations from Copri
Log.ForContext<MapPageViewModel>().Verbose("No pins detected on page.");
if (stations.CopriVersion < CopriCallsStatic.UnsupportedVersionLower)
{
await ViewService.DisplayAlert(
AppResources.MessageWaring,
string.Format(AppResources.MessageCopriVersionIsOutdated, ContactPageViewModel.GetAppName(TinkApp.Uris.ActiveUri)),
AppResources.MessageAnswerOk);
Log.ForContext<MapPageViewModel>().Error($"Outdated version of app detected. Version expected is {stations.CopriVersion}.");
}
if (stations.CopriVersion >= CopriCallsStatic.UnsupportedVersionUpper)
{
await ViewService.DisplayAlert(
AppResources.MessageWaring,
string.Format(AppResources.MessageAppVersionIsOutdated, ContactPageViewModel.GetAppName(TinkApp.Uris.ActiveUri)),
AppResources.MessageAnswerOk);
Log.ForContext<MapPageViewModel>().Error($"Outdated version of app detected. Version expected is {stations.CopriVersion}.");
}
// Set pins to their positions on map.
InitializePins(stations);
Log.ForContext<MapPageViewModel>().Verbose("Update of pins done.");
}
}
/// <summary>
/// Moves the map to the current position of the user.
/// If location permission hasn't been granted, the position is not adjusted.
/// </summary>
private async Task MoveMapToCurrentPositionOfUser(Status status)
{
if (status == Status.Granted)
{
ActionText = AppResources.ActivityTextCenterMap;
if (TinkApp.CenterMapToCurrentLocation)
{
Location currentLocation = null;
try
{
currentLocation = await GeolocationService.GetAsync();
}
catch (Exception ex)
{
Log.ForContext<MapPageViewModel>().Error("Getting location failed. {Exception}", ex);
}
if (currentLocation != null)
{
TinkApp.UserMapSpan = MapSpan.FromCenterAndRadius(
new Xamarin.Forms.GoogleMaps.Position(currentLocation.Latitude, currentLocation.Longitude),
TinkApp.ActiveMapSpan.Radius);
TinkApp.Save();
}
}
MoveAndScale(m_oMoveToRegionDelegate, TinkApp.ActiveMapSpan);
}
}
/// <summary>
/// Requests the location permission from the user.
/// If the user declines, a dialog prompot is shown, telling the user to toggle the permission in the device settings.
/// </summary>
/// <returns>The permission status.</returns>
private async Task<Status> RequestLocationPermission()
{
// Check location permission
var status = await PermissionsService.CheckStatusAsync();
if (TinkApp.CenterMapToCurrentLocation
&& !GeolocationService.IsSimulation
&& status != Status.Granted)
{
status = await PermissionsService.RequestAsync();
if (status != Status.Granted)
{
var dialogResult = await ViewService.DisplayAlert(
AppResources.MessageTitleHint,
AppResources.MessageCenterMapLocationPermissionOpenDialog,
AppResources.MessageAnswerYes,
AppResources.MessageAnswerNo);
if (dialogResult)
{
// User decided to give access to locations permissions.
PermissionsService.OpenAppSettings();
ActionText = "";
IsRunning = false;
IsMapPageEnabled = true;
}
}
}
return status;
}
/// <summary> Moves map and scales visible region depending on active filter. </summary>
public static void MoveAndScale(
Action<MapSpan> moveToRegionDelegate,
@ -773,20 +819,20 @@ namespace TINK.ViewModel.Map
/// <summary> User request to toggle from TINK to Konrad. </summary>
public async Task ToggleTinkToKonrad()
{
if (tinkKonradToggleViewModel.CurrentFilter == FilterHelper.FILTERKONRAD)
if (tinkKonradToggleViewModel.CurrentFilter == FilterHelper.CITYBIKE)
{
// Konrad is already activated, nothing to do.
return;
}
Log.ForContext<MapPageViewModel>().Information("User toggles to Konrad.");
await ActivateFilter(FilterHelper.FILTERTINKGENERAL);
await ActivateFilter(FilterHelper.CARGOBIKE);
}
/// <summary> User request to toggle from TINK to Konrad. </summary>
public async Task ToggleKonradToTink()
{
if (tinkKonradToggleViewModel.CurrentFilter == FilterHelper.FILTERTINKGENERAL)
if (tinkKonradToggleViewModel.CurrentFilter == FilterHelper.CARGOBIKE)
{
// Konrad is already activated, nothing to do.
return;
@ -794,7 +840,7 @@ namespace TINK.ViewModel.Map
Log.ForContext<MapPageViewModel>().Information("User toggles to TINK.");
await ActivateFilter(FilterHelper.FILTERKONRAD);
await ActivateFilter(FilterHelper.CITYBIKE);
}
/// <summary> User request to toggle from TINK to Konrad. </summary>
@ -920,7 +966,7 @@ namespace TINK.ViewModel.Map
}
catch (Exception l_oException)
{
Log.ForContext<MapPageViewModel>().Error("An error occurred switching view TINK/ Konrad.{}");
Log.ForContext<MapPageViewModel>().Error("An error occurred switching view Cargobike/ Citybike.{}");
await ViewService.DisplayAlert(
"Fehler",

View file

@ -29,21 +29,21 @@ namespace TINK.ViewModel.Map
}
/// <summary> Gets value whether TINK is enabled or not. </summary>
public bool IsTinkEnabled => !string.IsNullOrEmpty(CurrentFilter) && CurrentFilter.GetBikeCategory() != FilterHelper.FILTERTINKGENERAL;
public bool IsTinkEnabled => !string.IsNullOrEmpty(CurrentFilter) && CurrentFilter.GetBikeCategory() != FilterHelper.CARGOBIKE;
/// <summary> Gets color of the TINK button. </summary>
public Color TinkColor => CurrentFilter.GetBikeCategory() == FilterHelper.FILTERTINKGENERAL ? Color.Blue : Color.Gray;
public Color TinkColor => CurrentFilter.GetBikeCategory() == FilterHelper.CARGOBIKE ? Color.Blue : Color.Gray;
/// <summary> Gets value whether Konrad is enabled or not. </summary>
public bool IsKonradEnabled => !string.IsNullOrEmpty(CurrentFilter) && CurrentFilter.GetBikeCategory() != FilterHelper.FILTERKONRAD;
public bool IsKonradEnabled => !string.IsNullOrEmpty(CurrentFilter) && CurrentFilter.GetBikeCategory() != FilterHelper.CITYBIKE;
/// <summary> Gets color of the Konrad button. </summary>
public Color KonradColor => CurrentFilter.GetBikeCategory() == FilterHelper.FILTERKONRAD ? Color.Red : Color.Gray;
public Color KonradColor => CurrentFilter.GetBikeCategory() == FilterHelper.CITYBIKE ? Color.Red : Color.Gray;
/// <summary> Gets whether toggle functionality is visible or not. </summary>
public bool IsToggleVisible =>
FilterDictionary.Select(x => x.Key).ContainsGroupId(FilterHelper.FILTERKONRAD)
&& FilterDictionary.Select(x => x.Key).ContainsGroupId(FilterHelper.FILTERTINKGENERAL)
FilterDictionary.Select(x => x.Key).ContainsGroupId(FilterHelper.CITYBIKE)
&& FilterDictionary.Select(x => x.Key).ContainsGroupId(FilterHelper.CARGOBIKE)
&& (IsTinkEnabled || IsKonradEnabled);
/// <summary>

View file

@ -20,7 +20,7 @@ namespace TINK.ViewModel.Settings
{
foreach (var filter in filterSettings)
{
if (filter.Key == FilterHelper.FILTERTINKGENERAL)
if (filter.Key == FilterHelper.CARGOBIKE)
{
Add(new FilterItemMutable(
filter.Key,
@ -29,7 +29,7 @@ namespace TINK.ViewModel.Settings
AppResources.MarkingCargoBike));
continue;
}
if (filter.Key == FilterHelper.FILTERKONRAD)
if (filter.Key == FilterHelper.CITYBIKE)
{
Add(new FilterItemMutable(
filter.Key,