Contact page shows operator specific info

This commit is contained in:
Oliver Hauff 2021-07-20 23:06:09 +02:00
parent e436e83c1d
commit a58c33f005
51 changed files with 948 additions and 221 deletions

View file

@ -28,7 +28,7 @@ namespace TINK.Model.Connector
/// </summary>
/// <param name="p_oStationInfo">Object to get information from.</param>
/// <returns>Position information.</returns>
public static Station.Position GetPosition(this StationsAllResponse.StationInfo p_oStationInfo)
public static Station.Position GetPosition(this StationsAvailableResponse.StationInfo p_oStationInfo)
{
return GetPosition(p_oStationInfo.gps);
}
@ -74,7 +74,7 @@ namespace TINK.Model.Connector
/// <summary> Gets the position from StationInfo object. </summary>
/// <param name="p_oStationInfo">Object to get information from.</param>
/// <returns>Position information.</returns>
public static IEnumerable<string> GetGroup(this StationsAllResponse.StationInfo p_oStationInfo)
public static IEnumerable<string> GetGroup(this StationsAvailableResponse.StationInfo p_oStationInfo)
{
try
{

View file

@ -11,6 +11,8 @@ using Serilog;
using BikeInfo = TINK.Model.Bike.BC.BikeInfo;
using IBikeInfoMutable = TINK.Model.Bikes.Bike.BC.IBikeInfoMutable;
using System.Globalization;
using TINK.Model.Station.Operator;
using Xamarin.Forms;
namespace TINK.Model.Connector
{
@ -34,37 +36,44 @@ namespace TINK.Model.Connector
/// Gets all statsion for station provider and add them into station list.
/// </summary>
/// <param name="p_oStationList">List of stations to update.</param>
public static StationDictionary GetStationsAllMutable(this StationsAllResponse p_oStationsAllResponse)
public static StationDictionary GetStationsAllMutable(this StationsAvailableResponse stationsAllResponse)
{
// Get stations from Copri/ file/ memory, ....
if (p_oStationsAllResponse == null
|| p_oStationsAllResponse.stations == null)
if (stationsAllResponse == null
|| stationsAllResponse.stations == null)
{
// Latest list of stations could not be retrieved from provider.
return new StationDictionary();
}
Version.TryParse(p_oStationsAllResponse.copri_version, out Version l_oCopriVersion);
Version.TryParse(stationsAllResponse.copri_version, out Version copriVersion);
var l_oStations = new StationDictionary(p_oVersion: l_oCopriVersion);
var stations = new StationDictionary(p_oVersion: copriVersion);
foreach (var l_oStation in p_oStationsAllResponse.stations)
foreach (var station in stationsAllResponse.stations)
{
if (l_oStations.GetById(l_oStation.Value.station) != null)
if (stations.GetById(station.Value.station) != null)
{
// Can not add station to list of station. Id is not unique.
throw new InvalidResponseException<StationsAllResponse>(
string.Format("Station id {0} is not unique.", l_oStation.Value.station), p_oStationsAllResponse);
throw new InvalidResponseException<StationsAvailableResponse>(
string.Format("Station id {0} is not unique.", station.Value.station), stationsAllResponse);
}
l_oStations.Add(new Station.Station(
l_oStation.Value.station,
l_oStation.Value.GetGroup(),
l_oStation.Value.GetPosition(),
l_oStation.Value.description));
stations.Add(new Station.Station(
station.Value.station,
station.Value.GetGroup(),
station.Value.GetPosition(),
station.Value.description,
new Data(station.Value.operator_data?.operator_name,
station.Value.operator_data?.operator_phone,
station.Value.operator_data?.operator_hours,
station.Value.operator_data?.operator_email,
!string.IsNullOrEmpty(station.Value.operator_data?.operator_color)
? Color.FromHex(station.Value.operator_data?.operator_color)
: (Color?)null)));
}
return l_oStations;
return stations;
}
/// <summary> Gets account object from login response.</summary>

View file

@ -1,4 +1,5 @@
using System.Collections.Generic;
using TINK.Model.Station.Operator;
namespace TINK.Model.Station
{
@ -15,5 +16,8 @@ namespace TINK.Model.Station
/// <summary> Holds the gps- position of the station.</summary>
Position Position { get; }
/// <summary> Holds operator related data.</summary>
IData OperatorData { get; }
}
}

View file

@ -1,4 +1,5 @@
using System.Collections.Generic;
using TINK.Model.Station.Operator;
namespace TINK.Model.Station
{
@ -16,5 +17,8 @@ namespace TINK.Model.Station
/// <summary> Holds the gps- position of the station.</summary>
public Position Position => new Position(double.NaN, double.NaN);
/// <summary> Holds operator related data.</summary>
public IData OperatorData => new Data();
}
}

View file

@ -0,0 +1,36 @@
using Xamarin.Forms;
namespace TINK.Model.Station.Operator
{
/// <summary> Holds operator related data.</summary>
public class Data : IData
{
public Data(
string name = null,
string phoneNumberText = null,
string hours = null,
string mailAddressText = null, Color? color = null)
{
Name = name ?? string.Empty;
PhoneNumberText = phoneNumberText ?? string.Empty;
Hours = hours ?? string.Empty;
MailAddressText = mailAddressText ?? string.Empty;
Color = color;
}
/// <summary> Name of the operator.</summary>
public string Name { get; private set; }
/// <summary> Support phone number of the operator.</summary>
public string PhoneNumberText { get; private set; }
/// <summary> Office times when support is available.</summary>
public string Hours { get; private set; }
/// <summary> Support mails address of the operator.</summary>
public string MailAddressText { get; private set; }
/// <summary> Color of the operator (operator specific skin)</summary>
public Color? Color { get; private set; }
}
}

View file

@ -0,0 +1,22 @@
using Xamarin.Forms;
namespace TINK.Model.Station.Operator
{
public interface IData
{
/// <summary> Name of the operator.</summary>
string Name { get; }
/// <summary> Support phone number of the operator.</summary>
string PhoneNumberText { get; }
/// <summary> Office times when support is available.</summary>
string Hours { get; }
/// <summary> Support mails address of the operator.</summary>
string MailAddressText { get; }
/// <summary> Color of the operator (operator specific skin)</summary>
Color? Color { get; }
}
}

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using TINK.Model.Station.Operator;
namespace TINK.Model.Station
{
@ -7,20 +8,22 @@ namespace TINK.Model.Station
public class Station : IStation
{
/// <summary> Constructs a station object.</summary>
/// <param name="p_iId">Id of the station.</param>
/// <param name="p_oGroup">Group (TINK, Konrad) to which station is related.</param>
/// <param name="p_oPosition">GPS- position of the station.</param>
/// <param name="p_strStationName">Name of the station.</param>
/// <param name="id">Id of the station.</param>
/// <param name="group">Group (TINK, Konrad) to which station is related.</param>
/// <param name="position">GPS- position of the station.</param>
/// <param name="stationName">Name of the station.</param>
public Station(
string p_iId,
IEnumerable<string> p_oGroup,
Position p_oPosition,
string p_strStationName = "")
string id,
IEnumerable<string> group,
Position position,
string stationName = "",
Data operatorData = null)
{
Id = p_iId;
Group = p_oGroup ?? throw new ArgumentException("Can not construct station object. Group of stations must not be null.");
Position = p_oPosition;
StationName = p_strStationName ?? string.Empty;
Id = id;
Group = group ?? throw new ArgumentException("Can not construct station object. Group of stations must not be null.");
Position = position;
StationName = stationName ?? string.Empty;
OperatorData = operatorData ?? new Data();
}
/// <summary> Holds the unique id of the station.c</summary>
@ -34,5 +37,8 @@ namespace TINK.Model.Station
/// <summary> Holds the gps- position of the station.</summary>
public Position Position { get; }
/// <summary> Holds operator related info.</summary>
public IData OperatorData { get; }
}
}

View file

@ -322,7 +322,7 @@ namespace TINK.Model
public ICipher Cipher { get; }
/// <summary> Name of the station which is selected. </summary>
public IStation SelectedStation { get; set; } = new Station.Station(null, new List<string>(), null);
public IStation SelectedStation { get; set; } = new NullStation();
/// <summary> Holds the stations availalbe. </summary>
public IEnumerable<IStation> Stations { get; set; } = new List<Station.Station>();

View file

@ -413,6 +413,10 @@ namespace TINK.Model
{
new Version(3, 0, 240),
AppResources.ChangeLog3_0_240
},
{
new Version(3, 0, 241),
AppResources.ChangeLog3_0_241
}
};

View file

@ -742,6 +742,15 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Bike sharing system operator specific support info displayed on contact page..
/// </summary>
public static string ChangeLog3_0_241 {
get {
return ResourceManager.GetString("ChangeLog3_0_241", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Lock of rented bike can not be found..
/// </summary>
@ -1047,6 +1056,33 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Please open a bike station page to to contact the bike sharing operator..
/// </summary>
public static string MarkingContactNoStationInfoAvailableNoButton {
get {
return ResourceManager.GetString("MarkingContactNoStationInfoAvailableNoButton", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Contact {0}.
/// </summary>
public static string MarkingContactPageTitle {
get {
return ResourceManager.GetString("MarkingContactPageTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to &lt;font color=&quot;blue&quot;&gt;&lt;u&gt;Contact&lt;/u&gt;&lt;/font&gt; {0}..
/// </summary>
public static string MarkingContactSupport {
get {
return ResourceManager.GetString("MarkingContactSupport", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Contact.
/// </summary>
@ -1174,6 +1210,15 @@ namespace TINK.MultilingualResources {
}
}
/// <summary>
/// Looks up a localized string similar to Please login to reserve bikes! Tap &lt;font color=&quot;blue&quot;&gt;&lt;u&gt;here&lt;/u&gt;&lt;/font&gt; to switch to login page..
/// </summary>
public static string MarkingLoginRequiredToRerserve {
get {
return ResourceManager.GetString("MarkingLoginRequiredToRerserve", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Bike Locations.
/// </summary>
@ -1517,7 +1562,7 @@ namespace TINK.MultilingualResources {
}
/// <summary>
/// Looks up a localized string similar to Urgent question related to {0}? (Monday-Friday: 10:00 18:00).
/// Looks up a localized string similar to Urgent question related to {0}?.
/// </summary>
public static string MessagePhoneMail {
get {

View file

@ -158,7 +158,7 @@ Eine Radrückgabe ist nur möglich, wenn das Rad in Reichweite ist und Standorti
<value>Fragen? Hinweise? Kritik?</value>
</data>
<data name="MessagePhoneMail" xml:space="preserve">
<value>Eilige Frage rund um {0}? (Montag-Freitag: 10:00 18:00)</value>
<value>Eilige Frage rund um {0}?</value>
</data>
<data name="MessageRateMail" xml:space="preserve">
<value>Gefällt die {0}-App?</value>
@ -613,4 +613,19 @@ Layout Anzeige Radnamen und nummern verbessert.</value>
<data name="ChangeLog3_0_240" xml:space="preserve">
<value>Wahl eines Rads über die Radnummer hinzugefügt.</value>
</data>
<data name="MarkingContactSupport" xml:space="preserve">
<value>{0} &lt;font color="blue"&gt;&lt;u&gt;kontaktieren&lt;/u&gt;&lt;/font&gt;.</value>
</data>
<data name="MarkingLoginRequiredToRerserve" xml:space="preserve">
<value>Bitte Anmelden um Fahrräder zu reservieren! &lt;font color="blue"&gt;&lt;u&gt;Hier&lt;/u&gt;&lt;/font&gt; tippen, um auf Anmeldeseite zu wechseln.</value>
</data>
<data name="MarkingContactNoStationInfoAvailableNoButton" xml:space="preserve">
<value>Bitte eine Fahrradstationsseite öffen, um den Betreiber zu kontaktieren.</value>
</data>
<data name="MarkingContactPageTitle" xml:space="preserve">
<value>Kontakt {0}</value>
</data>
<data name="ChangeLog3_0_241" xml:space="preserve">
<value>Auf der Kontaktseite werden Kontaktinformationen betreiberspezifisch angezeigt.</value>
</data>
</root>

View file

@ -248,7 +248,7 @@ Use of app is restricted to maximu 8 devices per account.
Please login to app once again. In case this fails please check on website if the account is still valid.</value>
</data>
<data name="MessagePhoneMail" xml:space="preserve">
<value>Urgent question related to {0}? (Monday-Friday: 10:00 18:00)</value>
<value>Urgent question related to {0}?</value>
</data>
<data name="MessageRateMail" xml:space="preserve">
<value>Are you enjoying the {0}-App?</value>
@ -709,4 +709,19 @@ Layout of bike names and id display improved.</value>
<data name="ChangeLog3_0_240" xml:space="preserve">
<value>Find bike by id functionality added.</value>
</data>
<data name="MarkingContactNoStationInfoAvailableNoButton" xml:space="preserve">
<value>Please open a bike station page to to contact the bike sharing operator.</value>
</data>
<data name="MarkingContactSupport" xml:space="preserve">
<value>&lt;font color="blue"&gt;&lt;u&gt;Contact&lt;/u&gt;&lt;/font&gt; {0}.</value>
</data>
<data name="MarkingLoginRequiredToRerserve" xml:space="preserve">
<value>Please login to reserve bikes! Tap &lt;font color="blue"&gt;&lt;u&gt;here&lt;/u&gt;&lt;/font&gt; to switch to login page.</value>
</data>
<data name="MarkingContactPageTitle" xml:space="preserve">
<value>Contact {0}</value>
</data>
<data name="ChangeLog3_0_241" xml:space="preserve">
<value>Bike sharing system operator specific support info displayed on contact page.</value>
</data>
</root>

View file

@ -203,8 +203,8 @@ Eine Radrückgabe ist nur möglich, wenn das Rad in Reichweite ist und Standorti
<target state="translated">Fragen? Hinweise? Kritik?</target>
</trans-unit>
<trans-unit id="MessagePhoneMail" translate="yes" xml:space="preserve">
<source>Urgent question related to {0}? (Monday-Friday: 10:00 18:00)</source>
<target state="translated">Eilige Frage rund um {0}? (Montag-Freitag: 10:00 18:00)</target>
<source>Urgent question related to {0}?</source>
<target state="translated">Eilige Frage rund um {0}?</target>
</trans-unit>
<trans-unit id="MessageRateMail" translate="yes" xml:space="preserve">
<source>Are you enjoying the {0}-App?</source>
@ -821,6 +821,26 @@ Layout Anzeige Radnamen und nummern verbessert.</target>
<source>Find bike by id functionality added.</source>
<target state="translated">Wahl eines Rads über die Radnummer hinzugefügt.</target>
</trans-unit>
<trans-unit id="MarkingContactSupport" translate="yes" xml:space="preserve">
<source><bpt id="1">&lt;font color="blue"&gt;</bpt><bpt id="2">&lt;u&gt;</bpt>Contact<ept id="2">&lt;/u&gt;</ept><ept id="1">&lt;/font&gt;</ept> {0}.</source>
<target state="translated">{0} <bpt id="1">&lt;font color="blue"&gt;</bpt><bpt id="2">&lt;u&gt;</bpt>kontaktieren<ept id="2">&lt;/u&gt;</ept><ept id="1">&lt;/font&gt;</ept>.</target>
</trans-unit>
<trans-unit id="MarkingLoginRequiredToRerserve" translate="yes" xml:space="preserve">
<source>Please login to reserve bikes! Tap <bpt id="1">&lt;font color="blue"&gt;</bpt><bpt id="2">&lt;u&gt;</bpt>here<ept id="2">&lt;/u&gt;</ept><ept id="1">&lt;/font&gt;</ept> to switch to login page.</source>
<target state="translated">Bitte Anmelden um Fahrräder zu reservieren! <bpt id="1">&lt;font color="blue"&gt;</bpt><bpt id="2">&lt;u&gt;</bpt>Hier<ept id="2">&lt;/u&gt;</ept><ept id="1">&lt;/font&gt;</ept> tippen, um auf Anmeldeseite zu wechseln.</target>
</trans-unit>
<trans-unit id="MarkingContactNoStationInfoAvailableNoButton" translate="yes" xml:space="preserve">
<source>Please open a bike station page to to contact the bike sharing operator.</source>
<target state="translated">Bitte eine Fahrradstationsseite öffen, um den Betreiber zu kontaktieren.</target>
</trans-unit>
<trans-unit id="MarkingContactPageTitle" translate="yes" xml:space="preserve">
<source>Contact {0}</source>
<target state="translated">Kontakt {0}</target>
</trans-unit>
<trans-unit id="ChangeLog3_0_241" translate="yes" xml:space="preserve">
<source>Bike sharing system operator specific support info displayed on contact page.</source>
<target state="translated">Auf der Kontaktseite werden Kontaktinformationen betreiberspezifisch angezeigt.</target>
</trans-unit>
</group>
</body>
</file>

View file

@ -106,7 +106,7 @@ namespace TINK.Repository
/// <summary> Get list of stations. </summary>
/// <returns>List of files.</returns>
public async Task<StationsAllResponse> GetStationsAsync()
public async Task<StationsAvailableResponse> GetStationsAsync()
{
var stations = await GetStationsAsync(m_oCopriHost.AbsoluteUri, requestBuilder.GetStations(), UserAgent);
return stations;
@ -316,7 +316,7 @@ namespace TINK.Repository
/// <param name="p_strCopriHost">URL of the copri host to connect to.</param>
/// <param name="p_oCommand">Command to get stations.</param>
/// <returns>List of files.</returns>
public static async Task<StationsAllResponse> GetStationsAsync(
public static async Task<StationsAvailableResponse> GetStationsAsync(
string p_strCopriHost,
string p_oCommand,
string userAgent = null)

View file

@ -120,6 +120,284 @@ namespace TINK.Repository
}
}";
/// <summary></summary>
public const string STATIONS_AVAILABLE_LOGGEDIN_20210720 = @"
{
""shareejson"": {
""lang"": ""DE"",
""impress_html"": ""site/impress.html"",
""tariff_info_html"": ""site/tariff_info_1.html"",
""debuglevel"": ""1"",
""user_tour"": [
null,
""""
],
""response"": ""stations_available"",
""user_id"": ""ohauff@posteo.de"",
""stations"": {
""LV_3"": {
""service_tour"": ""LV_1"",
""uri_operator"": ""https://shareeapp-lv.copri.eu"",
""authed"": ""1"",
""station"": ""LV_3"",
""gps"": {
""latitude"": ""47.9973"",
""longitude"": ""7.8585""
},
""gps_radius"": ""100"",
""description"": ""Katholische Akademie"",
""state"": ""available"",
""operator_data"": {
""operator_color"": ""#006269"",
""operator_hours"": """",
""operator_name"": ""LastenVelo Freiburg"",
""operator_email"": ""info@lastenvelofreiburg.de"",
""operator_phone"": """"
},
""station_group"": [
""LV_300005""
]
},
""LV_1"": {
""uri_operator"": ""https://shareeapp-lv.copri.eu"",
""station"": ""LV_1"",
""gps"": {
""latitude"": ""47.9848"",
""longitude"": ""7.848666""
},
""authed"": ""1"",
""service_tour"": ""LV_1"",
""operator_data"": {
""operator_hours"": """",
""operator_email"": ""info@lastenvelofreiburg.de"",
""operator_name"": ""LastenVelo Freiburg"",
""operator_color"": ""#006269"",
""operator_phone"": """"
},
""state"": ""available"",
""description"": ""Parkplatz Feuerwehr Wiehre"",
""station_group"": [
""LV_300005""
],
""gps_radius"": ""100""
},
""FR_105"": {
""gps_radius"": ""50"",
""station_group"": [
""FR_300029""
],
""state"": ""available"",
""description"": ""Contributor-Station Rainer"",
""operator_data"": {
""operator_phone"": ""+49 761 45370097"",
""operator_hours"": ""B<EFBFBD>rozeiten: Montag, Mittwoch, Freitag 9-12 Uhr"",
""operator_name"": ""sharee.bike | TeilRad GmbH"",
""operator_email"": ""hotline@sharee.bike"",
""operator_color"": ""#009699""
},
""service_tour"": ""FR_1"",
""authed"": ""1"",
""station"": ""FR_105"",
""gps"": {
""longitude"": "" 7.973855"",
""latitude"": ""47.927738""
},
""uri_operator"": ""https://shareeapp-fr01.copri.eu""
},
""FR_104"": {
""gps_radius"": ""50"",
""operator_data"": {
""operator_phone"": ""+49 761 45370097"",
""operator_color"": ""#009699"",
""operator_email"": ""hotline@sharee.bike"",
""operator_name"": ""sharee.bike | TeilRad GmbH"",
""operator_hours"": ""B<EFBFBD>rozeiten: Montag, Mittwoch, Freitag 9-12 Uhr""
},
""description"": ""Contributor-Station fahrradspezialitäten"",
""state"": ""available"",
""station_group"": [
""FR_300029""
],
""service_tour"": ""FR_1"",
""uri_operator"": ""https://shareeapp-fr01.copri.eu"",
""gps"": {
""latitude"": ""47.989807"",
""longitude"": "" 7.837621""
},
""station"": ""FR_104"",
""authed"": ""1""
},
""FR_103"": {
""uri_operator"": ""https://shareeapp-fr01.copri.eu"",
""authed"": ""1"",
""gps"": {
""longitude"": "" 7.785428"",
""latitude"": ""47.997930""
},
""station"": ""FR_103"",
""service_tour"": ""FR_1"",
""description"": ""Contributor-Station Oliver"",
""state"": ""available"",
""operator_data"": {
""operator_phone"": ""+49 761 45370097"",
""operator_hours"": ""B<EFBFBD>rozeiten: Montag, Mittwoch, Freitag 9-12 Uhr"",
""operator_name"": ""sharee.bike | TeilRad GmbH"",
""operator_email"": ""hotline@sharee.bike"",
""operator_color"": ""#009699""
},
""station_group"": [
""FR_300029""
],
""gps_radius"": ""50""
},
""LV_88"": {
""station_group"": [
""LV_300005""
],
""state"": ""available"",
""description"": ""TINK Test KN"",
""operator_data"": {
""operator_name"": ""LastenVelo Freiburg"",
""operator_email"": ""info@lastenvelofreiburg.de"",
""operator_hours"": """",
""operator_color"": ""#006269"",
""operator_phone"": """"
},
""gps_radius"": ""50"",
""authed"": ""1"",
""gps"": {
""latitude"": ""47.65934079179006"",
""longitude"": "" 9.166126178863573""
},
""station"": ""LV_88"",
""uri_operator"": ""https://shareeapp-lv.copri.eu"",
""service_tour"": ""LV_2""
},
""LV_4"": {
""uri_operator"": ""https://shareeapp-lv.copri.eu"",
""authed"": ""1"",
""station"": ""LV_4"",
""gps"": {
""latitude"": ""48.01095"",
""longitude"": ""7.8553""
},
""service_tour"": ""LV_1"",
""state"": ""available"",
""description"": ""Fabrik Habsburger Straße"",
""operator_data"": {
""operator_phone"": """",
""operator_hours"": """",
""operator_email"": ""info@lastenvelofreiburg.de"",
""operator_name"": ""LastenVelo Freiburg"",
""operator_color"": ""#006269""
},
""station_group"": [
""LV_300005""
],
""gps_radius"": ""100""
},
""LV_2"": {
""uri_operator"": ""https://shareeapp-lv.copri.eu"",
""authed"": ""1"",
""gps"": {
""longitude"": ""7.84795"",
""latitude"": ""47.99762""
},
""station"": ""LV_2"",
""service_tour"": ""LV_1"",
""description"": ""Predigertor ADAC"",
""state"": ""available"",
""operator_data"": {
""operator_phone"": """",
""operator_name"": ""LastenVelo Freiburg"",
""operator_email"": ""info@lastenvelofreiburg.de"",
""operator_hours"": """",
""operator_color"": ""#006269""
},
""station_group"": [
""LV_300005""
],
""gps_radius"": ""100""
},
""FR_102"": {
""description"": ""Contributor-Station Dieter"",
""state"": ""available"",
""operator_data"": {
""operator_phone"": ""+49 761 45370097"",
""operator_hours"": ""B<EFBFBD>rozeiten: Montag, Mittwoch, Freitag 9-12 Uhr"",
""operator_email"": ""hotline@sharee.bike"",
""operator_name"": ""sharee.bike | TeilRad GmbH"",
""operator_color"": ""#009699""
},
""station_group"": [
""FR_300029""
],
""gps_radius"": ""50"",
""uri_operator"": ""https://shareeapp-fr01.copri.eu"",
""authed"": ""1"",
""station"": ""FR_102"",
""gps"": {
""longitude"": "" 7.835669"",
""latitude"": ""47.994371""
},
""service_tour"": ""FR_1""
},
""FR_101"": {
""station_group"": [
""FR_300001""
],
""state"": ""available"",
""description"": ""Villaban sharee Station"",
""operator_data"": {
""operator_color"": ""#009699"",
""operator_email"": ""hotline@sharee.bike"",
""operator_name"": ""sharee.bike | TeilRad GmbH"",
""operator_hours"": ""B<EFBFBD>rozeiten: Montag, Mittwoch, Freitag 9-12 Uhr"",
""operator_phone"": ""+49 761 45370097""
},
""gps_radius"": ""50"",
""authed"": ""1"",
""gps"": {
""longitude"": "" 7.825490"",
""latitude"": ""47.976634""
},
""station"": ""FR_101"",
""uri_operator"": ""https://shareeapp-fr01.copri.eu"",
""service_tour"": ""FR_1""
}
},
""response_state"": ""OK, nothing todo"",
""agb_checked"": ""1"",
""agb_html"": ""site/agb.html"",
""authcookie"": ""5781_f172cf59108fe53e7524c841847fee69_oiF2kahH"",
""Ilockit_admin"": ""1"",
""copri_version"": ""4.1.0.0"",
""bike_info_html"": ""site/bike_info.html"",
""uri_operator_array"": [
""https://shareeapp-lv.copri.eu"",
""https://shareeapp-fr01.copri.eu""
],
""uri_primary"": ""https://shareeapp-primary.copri.eu"",
""apiserver"": ""https://shareeapp-primary.copri.eu"",
""last_used_operator"": {
""operator_phone"": ""+49 761 45370097"",
""operator_color"": ""#009699"",
""operator_email"": ""hotline@sharee.bike"",
""operator_name"": ""sharee.bike | TeilRad GmbH"",
""operator_hours"": ""Bürozeiten: Montag, Mittwoch, Freitag 9-12 Uhr""
},
""clearing_cache"": ""0"",
""user_group"": [
""LV_300005"",
""FR_300029"",
""FR_300001""
],
""privacy_html"": ""site/privacy.html"",
""new_authcoo"": ""0""
}
}
";
const string BOOKING_REQUEST_SET02_001_FILE = @"
{
""shareejson"" : {
@ -1065,7 +1343,7 @@ namespace TINK.Repository
/// </summary>
/// <param name="p_strCookie">Auto cookie of user if user is logged in.</param>
/// <returns>List of files.</returns>
public async Task<StationsAllResponse> GetStationsAsync()
public async Task<StationsAvailableResponse> GetStationsAsync()
{
return await Task.Run(() => GetStationsAll(null, SessionCookie, ActiveSampleSet, ActiveStageIndex));
}
@ -1256,7 +1534,7 @@ namespace TINK.Repository
/// <param name="p_eSampleSet"></param>
/// <param name="p_lStageIndex"></param>
/// <returns></returns>
public static StationsAllResponse GetStationsAll(
public static StationsAvailableResponse GetStationsAll(
string p_strMerchantId,
string p_strCookie = null,
SampleSets p_eSampleSet = DEFAULT_SAMPLE_SET,
@ -1268,7 +1546,7 @@ namespace TINK.Repository
switch (p_lStageIndex)
{
case 1:
return JsonConvertRethrow.DeserializeObject<ResponseContainer<StationsAllResponse>>(STATIONS_SET02_001_FILE).shareejson;
return JsonConvertRethrow.DeserializeObject<ResponseContainer<StationsAvailableResponse>>(STATIONS_SET02_001_FILE).shareejson;
default:
return null;

View file

@ -95,8 +95,8 @@ namespace TINK.Repository
/// <summary> Gets an empty response. </summary>
/// <param name="copriVersion">Version of empty response.</param>
/// <returns>Response.</returns>
public static StationsAllResponse GetEmptyStationsAllResponse(string copriVersion)
=> JsonConvertRethrow.DeserializeObject<StationsAllResponse>(STATIONSALL.Replace("4.1.0.0", copriVersion));
public static StationsAvailableResponse GetEmptyStationsAllResponse(string copriVersion)
=> JsonConvertRethrow.DeserializeObject<StationsAvailableResponse>(STATIONSALL.Replace("4.1.0.0", copriVersion));
/// <summary>
/// Holds the seconds after which station and bikes info is considered to be invalid.
@ -141,7 +141,7 @@ namespace TINK.Repository
if (!Barrel.Current.Exists(requestBuilder.GetStations()))
{
AddToCache(JsonConvertRethrow.DeserializeObject<StationsAllResponse>(STATIONSALL), new TimeSpan(0));
AddToCache(JsonConvertRethrow.DeserializeObject<StationsAvailableResponse>(STATIONSALL), new TimeSpan(0));
}
}
@ -224,12 +224,12 @@ namespace TINK.Repository
}
}
public async Task<StationsAllResponse> GetStationsAsync()
public async Task<StationsAvailableResponse> GetStationsAsync()
{
var l_oStationsAllTask = new TaskCompletionSource<StationsAllResponse>();
var l_oStationsAllTask = new TaskCompletionSource<StationsAvailableResponse>();
lock (monkeyLock)
{
l_oStationsAllTask.SetResult(Barrel.Current.Get<StationsAllResponse>(requestBuilder.GetStations()));
l_oStationsAllTask.SetResult(Barrel.Current.Get<StationsAvailableResponse>(requestBuilder.GetStations()));
}
return await l_oStationsAllTask.Task;
}
@ -248,7 +248,7 @@ namespace TINK.Repository
/// <summary> Adds a stations all response to cache.</summary>
/// <param name="stations">Stations to add.</param>
public void AddToCache(StationsAllResponse stations)
public void AddToCache(StationsAvailableResponse stations)
{
AddToCache(stations, ExpiresAfter);
}
@ -256,7 +256,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>
private void AddToCache(StationsAllResponse stations, TimeSpan expiresAfter)
private void AddToCache(StationsAvailableResponse stations, TimeSpan expiresAfter)
{
lock (monkeyLock)
{

View file

@ -112,7 +112,7 @@ namespace TINK.Repository
{
/// <summary> Get list of stations. </summary>
/// <returns>List of all stations.</returns>
Task<StationsAllResponse> GetStationsAsync();
Task<StationsAvailableResponse> GetStationsAsync();
/// <summary> Gets a list of bikes from Copri. </summary>
/// <returns>Response holding list of bikes.</returns>

View file

@ -7,14 +7,37 @@ namespace TINK.Repository.Response
/// Holds the information about all stations and is used for deserialization of copri answer.
/// </summary>
[DataContract]
public class StationsAllResponse : ResponseBase
public class StationsAvailableResponse : ResponseBase
{
/// <summary>
/// <summary>
/// Holds info about a single station.
/// </summary>
[DataContract]
public class StationInfo
{
/// <summary>
/// Holds info about opertor data.
/// </summary>
[DataContract]
public class OperatorData
{
[DataMember]
public string operator_name { get; private set; }
[DataMember]
public string operator_phone { get; private set; }
[DataMember]
public string operator_hours { get; private set; }
[DataMember]
public string operator_email { get; private set; }
[DataMember]
public string operator_color { get; private set; }
}
/// <summary>
/// Unique id of the station.
/// </summary>
@ -32,6 +55,9 @@ namespace TINK.Repository.Response
/// </summary>
[DataMember]
public GpsInfo gps { get; private set; }
[DataMember]
public OperatorData operator_data { get; private set; }
}
/// <summary>

View file

@ -109,7 +109,7 @@ namespace TINK.Model.Services.CopriApi
/// <summary> Get list of stations. </summary>
/// <returns>List of files.</returns>
public async Task<Result<StationsAllResponse>> GetStations(bool fromCache = false)
public async Task<Result<StationsAvailableResponse>> GetStations(bool fromCache = false)
{
Log.ForContext<CopriProviderHttps>().Debug($"Request to get stations{(fromCache ? " from cache" : "")}...");
if (!CacheServer.IsStationsExpired
@ -117,7 +117,7 @@ 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<StationsAllResponse>(typeof(CopriCallsMonkeyStore), await CacheServer.GetStationsAsync());
return new Result<StationsAvailableResponse>(typeof(CopriCallsMonkeyStore), await CacheServer.GetStationsAsync());
}
try
@ -126,7 +126,7 @@ namespace TINK.Model.Services.CopriApi
var stations = await HttpsServer.GetStationsAsync();
return new Result<StationsAllResponse>(
return new Result<StationsAvailableResponse>(
typeof(CopriCallsHttps),
stations.GetIsResponseOk("Abfrage der Stationen fehlsgeschlagen."));
}
@ -134,14 +134,14 @@ namespace TINK.Model.Services.CopriApi
{
// Return response from cache.
Log.ForContext<CopriProviderHttps>().Debug("An error occurred querrying stations. {Exception}.", exception);
return new Result<StationsAllResponse>(typeof(CopriCallsMonkeyStore), await CacheServer.GetStationsAsync(), exception);
return new Result<StationsAvailableResponse>(typeof(CopriCallsMonkeyStore), await CacheServer.GetStationsAsync(), exception);
}
}
/// <summary>Adds https--response to cache if response is ok. </summary>
/// <param name="response">Response to add to cache.</param>
/// <returns></returns>
public void AddToCache(Result<StationsAllResponse> result)
public void AddToCache(Result<StationsAvailableResponse> result)
{
Log.ForContext<CopriProviderHttps>().Debug($"Request to add stations all response to cache...");
if (result.Source == typeof(CopriCallsMonkeyStore)

View file

@ -83,7 +83,7 @@ namespace TINK.Model.Services.CopriApi
return await monkeyStore.GetBikesOccupiedAsync();
}
public async Task<StationsAllResponse> GetStationsAsync()
public async Task<StationsAvailableResponse> GetStationsAsync()
{
return await monkeyStore.GetStationsAsync();
}

View file

@ -10,7 +10,7 @@ namespace TINK.Model.Services.CopriApi
{
/// <summary> Get list of stations. </summary>
/// <returns>List of all stations.</returns>
Task<Result<StationsAllResponse>> GetStations(bool fromCache = false);
Task<Result<StationsAvailableResponse>> GetStations(bool fromCache = false);
/// <summary> Gets a list of bikes from Copri. </summary>
/// <returns>Response holding list of bikes.</returns>
@ -22,7 +22,7 @@ namespace TINK.Model.Services.CopriApi
/// <summary>Adds https--response to cache if response is ok. </summary>
/// <param name="response">Response to add to cache.</param>
void AddToCache(Result<StationsAllResponse> result);
void AddToCache(Result<StationsAvailableResponse> result);
/// <summary>Adds https--response to cache if response is ok. </summary>
/// <param name="response">Response to add to cache.</param>

View file

@ -10,7 +10,7 @@ namespace TINK.Model.Services.CopriApi
/// <summary> Adds a stations all response to cache.</summary>
/// <param name="stations">Stations to add.</param>
void AddToCache(StationsAllResponse stations);
void AddToCache(StationsAvailableResponse stations);
/// <summary> Gets a value indicating whether stations are expired or not.</summary>
bool IsBikesAvailableExpired { get; }

View file

@ -113,23 +113,13 @@ namespace TINK.ViewModel.BikesAtStation
/// <summary>
/// Informs about need to log in before requesting an bike.
/// </summary>
public FormattedString LoginRequiredHintText
{
get
{
if (ActiveUser.IsLoggedIn)
{
return string.Empty;
}
public string LoginRequiredHintText
=> ActiveUser.IsLoggedIn
? string.Empty
: AppResources.MarkingLoginRequiredToRerserve;
var l_oHint = new FormattedString();
l_oHint.Spans.Add(new Span { Text = "Bitte Anmelden um Fahrräder zu reservieren! " });
l_oHint.Spans.Add(new Span { Text = "Hier", ForegroundColor = ViewModelHelper.LINK_COLOR });
l_oHint.Spans.Add(new Span { Text = " tippen um auf Anmeldeseite zu wechseln."});
return l_oHint;
}
}
public string ContactSupportHintText
=> string.Format(AppResources.MarkingContactSupport, m_oStation?.OperatorData?.Name ?? "Operator");
/// <summary> Returns if info about the fact that user did not request or book any bikes is visible or not.<summary>
/// Gets message that logged in user has not booked any bikes.
@ -154,21 +144,22 @@ namespace TINK.ViewModel.BikesAtStation
}
/// <summary> Command object to bind login page redirect link to view model.</summary>
public System.Windows.Input.ICommand LoginRequiredHintClickedCommand
{
get
{
public System.Windows.Input.ICommand ContactSupportClickedCommand
#if USEMASTERDETAIL || USEFLYOUT
return new Xamarin.Forms.Command(() => OpenLoginPageAsync());
=> new Xamarin.Forms.Command(() => OpenSupportPageAsync());
#else
return new Xamarin.Forms.Command(async () => await OpenLoginPageAsync());
=> new Xamarin.Forms.Command(async () => await OpenLoginPageAsync());
#endif
}
}
/// <summary>
/// Opens login page.
/// </summary>
/// <summary> Command object to bind login page redirect link to view model.</summary>
public System.Windows.Input.ICommand LoginRequiredHintClickedCommand
#if USEMASTERDETAIL || USEFLYOUT
=> new Xamarin.Forms.Command(() => OpenLoginPageAsync());
#else
=> new Xamarin.Forms.Command(async () => await OpenLoginPageAsync());
#endif
/// <summary> Opens login page. </summary>
#if USEMASTERDETAIL || USEFLYOUT
public void OpenLoginPageAsync()
#else
@ -192,6 +183,30 @@ namespace TINK.ViewModel.BikesAtStation
}
}
/// <summary> Opens login page. </summary>
#if USEMASTERDETAIL || USEFLYOUT
public void OpenSupportPageAsync()
#else
public async Task OpenLoginPageAsync()
#endif
{
try
{
// Switch to map page
#if USEMASTERDETAIL || USEFLYOUT
ViewService.ShowPage(ViewTypes.ContactPage, m_oStation?.OperatorData?.Name ?? AppResources.MarkingFeedbackAndContact);
#else
await ViewService.ShowPage("//LoginPage");
#endif
}
catch (Exception p_oException)
{
Log.Error("Ein unerwarteter Fehler ist auf der Seite Kontakt aufgetreten. Kontext: Klick auf Hinweistext auf Station N- seite ohne Anmeldung. {@Exception}", p_oException);
return;
}
}
/// <summary>
/// Invoked when page is shown.
/// Starts update process.

View file

@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows.Input;
using TINK.Model.Services.CopriApi.ServerUris;
using TINK.Model.Station;
using TINK.MultilingualResources;
using TINK.View;
using Xamarin.Essentials;
@ -18,6 +19,9 @@ namespace TINK.ViewModel.Info
/// <summary> Reference on view service to show modal notifications and to perform navigation. </summary>
private IViewService ViewService { get; }
/// <summary> Station selected by user. </summary>
private IStation SelectedStation;
Uri ActiveUri { get; }
/// <summary> Holds a reference to the external trigger service. </summary>
@ -29,6 +33,7 @@ namespace TINK.ViewModel.Info
/// <param name="openUrlInExternalBrowser">Action to open an external browser.</param>
/// <param name="viewService">View service to notify user.</param>
public ContactPageViewModel(
IStation selectedStation,
Uri activeUri,
Func<string> createAttachment,
Action openUrlInExternalBrowser,
@ -45,6 +50,9 @@ namespace TINK.ViewModel.Info
OpenUrlInExternalBrowser = openUrlInExternalBrowser
?? throw new ArgumentException("Can not instantiate contact page view model- object. No user external browse service available.");
// Station might be null-station, if no station info is avialable.
SelectedStation = selectedStation;
}
/// <summary> Command object to bind login button to view model. </summary>
@ -65,45 +73,19 @@ namespace TINK.ViewModel.Info
/// <summary>Holds the mail address to mail to.</summary>
public string MailAddressText
{
get
{
switch (ActiveUri.AbsoluteUri)
{
case CopriServerUriList.TINK_DEVEL:
case CopriServerUriList.TINK_LIVE:
return "tink@fahrradspezialitaeten.com";
case CopriServerUriList.SHAREE_DEVEL:
case CopriServerUriList.SHAREE_LIVE:
return "hotline@sharee.bike";
default:
return "post@sharee.bike";
}
}
}
=> SelectedStation?.OperatorData?.MailAddressText ?? string.Empty;
/// <summary>Holds the mail address to send mail to.</summary>
public string PhoneNumberText
{
get
{
switch (ActiveUri.AbsoluteUri)
{
case CopriServerUriList.TINK_DEVEL:
case CopriServerUriList.TINK_LIVE:
return "+49 7531 - 3694389";
=> SelectedStation?.OperatorData?.PhoneNumberText ?? string.Empty;
case CopriServerUriList.SHAREE_DEVEL:
case CopriServerUriList.SHAREE_LIVE:
return "+49 761 - 45370097";
/// <summary>Holds the mail address to send mail to.</summary>
public string OfficeHoursText
=> SelectedStation?.OperatorData?.Hours ?? string.Empty;
default:
return "+49 761 5158912";
}
}
}
/// <summary> Gets whether any operator support info is avaliable. </summary>
public bool IsOperatorInfoAvaliable
=> MailAddressText.Length > 0 || PhoneNumberText.Length > 0;
/// <summary> Request to do a phone call. </summary>
public async Task DoSendMail()
@ -256,7 +238,10 @@ namespace TINK.ViewModel.Info
get
{
var l_oHint = new FormattedString();
l_oHint.Spans.Add(new Span { Text = string.Format(AppResources.MessagePhoneMail, GetAppName(ActiveUri)) });
l_oHint.Spans.Add(new Span
{
Text = string.Format(AppResources.MessagePhoneMail, GetAppName(ActiveUri)) + $"{(OfficeHoursText.Length > 0 ? $" {OfficeHoursText}" : string.Empty)}"
});
return l_oHint;
}
}

View file

@ -11,10 +11,11 @@
MyBikesPage,
SettingsPage,
TabbedPageInfo,
TabbedPageHelpContact,
FeesAndBikesPage,
ManageAccountPage,
AgbPage,
WhatsNewPage,
BikesAtStation
BikesAtStation,
ContactPage
}
}