This commit is contained in:
Oliver Hauff 2022-01-22 18:16:10 +01:00
parent e0c75d5b37
commit f38b516d25
57 changed files with 12835 additions and 9925 deletions

View file

@ -74,7 +74,7 @@ namespace TINK.Model.Connector
Log.ForContext<CachedQuery>().Error("Unexpected call to get be bikes occpied detected. No user is logged in.");
return new Result<BikeCollection>(
typeof(CopriCallsMonkeyStore),
await Task.Run(() => new BikeCollection(new Dictionary<string, BikeInfo>())),
await Task.FromResult(new BikeCollection(new Dictionary<string, BikeInfo>())),
new GeneralData(),
new Exception("Abfrage der reservierten/ gebuchten Räder nicht möglich. Kein Benutzer angemeldet."));
}

View file

@ -46,7 +46,7 @@ namespace TINK.Model.Connector
Log.ForContext<Query>().Error("Unexpected call to get be bikes occpied detected. No user is logged in.");
return new Result<BikeCollection>(
typeof(CopriCallsMonkeyStore),
await Task.Run(() => new BikeCollection(new Dictionary<string, BikeInfo>())),
await Task.FromResult(new BikeCollection(new Dictionary<string, BikeInfo>())),
new GeneralData(),
new Exception("Abfrage der reservierten/ gebuchten Räder fehlgeschlagen. Kein Benutzer angemeldet."));
}

View file

@ -28,11 +28,9 @@ namespace TINK.Model.Connector
/// </summary>
/// <param name="stationInfo">Object to get information from.</param>
/// <returns>Position information.</returns>
public static Station.Position GetPosition(this StationsAvailableResponse.StationInfo stationInfo)
{
return GetPosition(stationInfo.gps);
}
public static IPosition GetPosition(this StationsAvailableResponse.StationInfo stationInfo)
=> GetPosition(stationInfo.gps);
/// <summary> Gets the position from StationInfo object. </summary>
/// <param name="authorizationResponse">Object to get information from.</param>
/// <returns>Position information.</returns>
@ -160,9 +158,9 @@ namespace TINK.Model.Connector
{
return bikeInfo?.bike_group?.GetGroup()?.ToList() ?? new List<string>();
}
catch (System.Exception l_oException)
catch (Exception l_oException)
{
throw new System.Exception($"Can not get group of user from text \"{bikeInfo.bike_group}\".", l_oException);
throw new Exception($"Can not get group of user from text \"{bikeInfo.bike_group}\".", l_oException);
}
}
@ -304,33 +302,27 @@ namespace TINK.Model.Connector
return null;
}
/// <summary>
/// Get position from a ,- separated string.
/// </summary>
/// <param name="p_strGps">Text to extract positon from.</param>
/// <summary> Get position from a ,- separated string. </summary>
/// <param name="gps">Text to extract positon from.</param>
/// <returns>Position object.</returns>
public static Station.Position GetPosition(GpsInfo gps)
{
if (gps == null)
{
return null;
}
public static IPosition GetPosition(Repository.Response.Position gps)
=> PositionFactory.Create(
double.TryParse(gps?.latitude, NumberStyles.Float, CultureInfo.InvariantCulture, out double latitude) ? latitude : double.NaN,
double.TryParse(gps?.longitude, NumberStyles.Float, CultureInfo.InvariantCulture, out double longitude) ? longitude : double.NaN);
double l_oLatitude;
if (!double.TryParse(gps.latitude, NumberStyles.Float, CultureInfo.InvariantCulture, out l_oLatitude))
return null;
/// <summary> Get position from a ,- separated string. </summary>
/// <param name="gps">Text to extract positon from.</param>
/// <returns>Position object.</returns>
public static Map.IMapSpan GetMapSpan(this MapSpan mapSpan)
=> Map.MapSpanFactory.Create(
GetPosition(mapSpan?.center),
double.TryParse(mapSpan?.radius, NumberStyles.Float, CultureInfo.InvariantCulture, out double radius) ? radius: double.NaN);
double l_oLongitude;
if (!double.TryParse(gps.longitude, NumberStyles.Float, CultureInfo.InvariantCulture, out l_oLongitude))
return null;
return new Station.Position(l_oLatitude, l_oLongitude);
}
/// <summary> Gets text of bike from. </summary>
/// <param name="p_eType">Type to get text for.</param>
/// <returns></returns>
public static string GetCopriText(this TypeOfBike p_eType)
/// <summary> Gets text of bike from. </summary>
/// <param name="p_eType">Type to get text for.</param>
/// <returns></returns>
public static string GetCopriText(this TypeOfBike p_eType)
{
switch (p_eType)
{

View file

@ -85,9 +85,12 @@ namespace TINK.Model.Connector
/// <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.TryGetCopriVersion(out Version copriVersion)
? new Version(0,0)
: copriVersion);
=> new GeneralData(
response.init_map.GetMapSpan(),
response.merchant_message,
response.TryGetCopriVersion(out Version copriVersion)
? new Version(0,0)
: copriVersion);
/// <summary> Gets account object from login response.</summary>
/// <param name="merchantId">Needed to extract cookie from autorization response.</param>

View file

@ -0,0 +1,13 @@
using System;
namespace TINK.Model
{
public interface IPosition : IEquatable<IPosition>
{
double Latitude { get; }
double Longitude { get; }
bool IsValid { get; }
}
}

View file

@ -0,0 +1,14 @@
namespace TINK.Model.Map
{
public interface IMapSpan
{
/// <summary> Center of map displayed area.</summary>
IPosition Center { get; }
/// <summary> Radius of displayed map area. </summary>
double Radius { get; }
/// <summary> Gets if map span is valid or not. </summary>
bool IsValid { get; }
}
}

View file

@ -0,0 +1,30 @@
using System;
namespace TINK.Model.Map
{
/// <summary> Holds the displayed map area. </summary>
public class MapSpan : IMapSpan
{
internal MapSpan(IPosition center, double radius)
{
if (!GetIsValid(center, radius))
throw new ArgumentNullException();
Center = center;
Radius = radius;
}
/// <summary> Center of map displayed area.</summary>
public IPosition Center { get; }
/// <summary> Radius of displayed map area. </summary>
public double Radius { get; }
public bool IsValid => GetIsValid(Center, Radius);
public static bool GetIsValid(IPosition center, double radius)
=> center != null
&& center.IsValid
&& !double.IsNaN(radius);
}
}

View file

@ -0,0 +1,11 @@

namespace TINK.Model.Map
{
public static class MapSpanFactory
{
public static IMapSpan Create(IPosition position = null, double radius = double.NaN)
=> MapSpan.GetIsValid(position, radius)
? new MapSpan(position, radius) as IMapSpan
: new NullMapSpan();
}
}

View file

@ -0,0 +1,16 @@

namespace TINK.Model.Map
{
public class NullMapSpan : IMapSpan
{
internal NullMapSpan() { }
/// <summary> Center of map displayed area.</summary>
public IPosition Center { get; } = PositionFactory.Create();
/// <summary> Radius of displayed map area. </summary>
public double Radius => double.NaN;
public bool IsValid => MapSpan.GetIsValid(Center, Radius);
}
}

View file

@ -0,0 +1,17 @@

namespace TINK.Model
{
public class NullPostion : IPosition
{
internal NullPostion() { }
public double Latitude => double.NaN;
public double Longitude => double.NaN;
public bool IsValid => Position.GetIsValid(Latitude, Longitude);
public bool Equals(IPosition other)
=> Position.GetEquals(this, other);
}
}

64
TINKLib/Model/Position.cs Normal file
View file

@ -0,0 +1,64 @@
using System;
namespace TINK.Model
{
public class Position : IPosition
{
private const double PRECISSION_LATITUDE_LONGITUDE = 0.000000000000001;
internal Position(double latitude, double longitude)
{
if (!GetIsValid(latitude, longitude))
throw new ArgumentNullException();
Latitude = latitude;
Longitude = longitude;
}
public double Latitude { get; private set; }
public double Longitude { get; private set; }
/// <summary>
/// Compares position with a target position.
/// </summary>
/// <param name="target">Target position to compare with.</param>
/// <returns>True if positions are equal.</returns>
public override bool Equals(object target)
{
if (!(target is IPosition targetPosition))
return false;
return GetEquals(this, targetPosition);
}
public bool Equals(IPosition other)
=> (other is Position position) && position.Equals(this as object);
public override int GetHashCode()
{
var hashCode = -1416534245;
hashCode = hashCode * -1521134295 + Latitude.GetHashCode();
hashCode = hashCode * -1521134295 + Longitude.GetHashCode();
return hashCode;
}
public bool IsValid => GetIsValid(Latitude, Longitude);
public static bool GetEquals(IPosition source, IPosition target)
{
if (source == null || target == null)
return source == null ^ target == null;
if (!source.IsValid || !target.IsValid)
return !source.IsValid ^ !target.IsValid;
return Math.Abs(source.Latitude - target.Latitude) < PRECISSION_LATITUDE_LONGITUDE
&& Math.Abs(source.Longitude - target.Longitude) < PRECISSION_LATITUDE_LONGITUDE;
}
public static bool GetIsValid(double latitude, double longitude)
=> !double.IsNaN(latitude) && !double.IsNaN(longitude);
}
}

View file

@ -0,0 +1,11 @@

namespace TINK.Model
{
public static class PositionFactory
{
public static IPosition Create(double latitude = double.NaN, double longitude = double.NaN)
=> Position.GetIsValid(longitude, latitude)
? new Position(latitude, longitude) as IPosition
: new NullPostion();
}
}

View file

@ -15,7 +15,7 @@ namespace TINK.Model.Station
string StationName { get; }
/// <summary> Holds the gps- position of the station.</summary>
Position Position { get; }
IPosition Position { get; }
/// <summary> Holds operator related data.</summary>
IData OperatorData { get; }

View file

@ -16,7 +16,7 @@ namespace TINK.Model.Station
public string StationName => string.Empty;
/// <summary> Holds the gps- position of the station.</summary>
public Position Position => new Position(double.NaN, double.NaN);
public IPosition Position => PositionFactory.Create();
/// <summary> Holds operator related data.</summary>
public IData OperatorData => new Data();

View file

@ -1,48 +0,0 @@
using System;
namespace TINK.Model.Station
{
public class Position
{
private const double PRECISSION_LATITUDE_LONGITUDE = 0.000000000000001;
public Position()
{
}
public Position(double latitude, double longitude)
{
Latitude = latitude;
Longitude = longitude;
}
public double Latitude { get; private set; }
public double Longitude { get; private set; }
/// <summary>
/// Compares position with a target position.
/// </summary>
/// <param name="p_oTarget">Target position to compare with.</param>
/// <returns>True if positions are equal.</returns>
public override bool Equals(object p_oTarget)
{
var l_oTarget = p_oTarget as Position;
if (l_oTarget is null)
{
return false;
}
return Math.Abs(Latitude - l_oTarget.Latitude) < PRECISSION_LATITUDE_LONGITUDE
&& Math.Abs(Longitude - l_oTarget.Longitude) < PRECISSION_LATITUDE_LONGITUDE;
}
public override int GetHashCode()
{
var hashCode = -1416534245;
hashCode = hashCode * -1521134295 + Latitude.GetHashCode();
hashCode = hashCode * -1521134295 + Longitude.GetHashCode();
return hashCode;
}
}
}

View file

@ -15,7 +15,7 @@ namespace TINK.Model.Station
public Station(
string id,
IEnumerable<string> group,
Position position,
IPosition position,
string stationName = "",
Data operatorData = null)
{
@ -36,7 +36,7 @@ namespace TINK.Model.Station
public string StationName { get; }
/// <summary> Holds the gps- position of the station.</summary>
public Position Position { get; }
public IPosition Position { get; }
/// <summary> Holds operator related info.</summary>
public IData OperatorData { get; }

View file

@ -476,7 +476,7 @@ namespace TINK.Model
AppResources.ChangeLog3_0_266
},
{
new Version(3, 0, 270),
new Version(3, 0, 271),
AppResources.ChangeLog3_0_231 // Minor improvements.
}
};

View file

@ -730,4 +730,7 @@ Deep linking wird unterstützt.</value>
<data name="MessageBikesManagementTariffDescriptionTariffHeaderNameId" xml:space="preserve">
<value>Tarif {0}, Nr. {1}</value>
</data>
<data name="ChangeLog3_0_266" xml:space="preserve">
<value>Abfrage Erlaubnis für Zugriff azf Standort verbessert.</value>
</data>
</root>

View file

@ -980,6 +980,10 @@ Deep linking wird unterstützt.</target>
<source>Tariff {0}, nr. {1}</source>
<target state="translated">Tarif {0}, Nr. {1}</target>
</trans-unit>
<trans-unit id="ChangeLog3_0_266" translate="yes" xml:space="preserve">
<source>Location permissions handling improved.</source>
<target state="translated">Abfrage Erlaubnis für Zugriff azf Standort verbessert.</target>
</trans-unit>
</group>
</body>
</file>

View file

@ -769,6 +769,7 @@ namespace TINK.Repository
// Returns a http request.
var request = WebRequest.CreateHttp(l_strHost);
request.Timeout = 5000; // Default value for HttpWebRequest is 100 secs. According to doc this has no impact on async call but when debugging it has.
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.UserAgent = userAgent;

View file

@ -9,7 +9,7 @@ namespace TINK.Repository.Response
/// Position of the bike.
/// </summary>
[DataMember]
public GpsInfo gps { get; private set; }
public Position gps { get; private set; }
[DataMember]
/// <summary> Full advertisement name.</summary>

View file

@ -1,23 +0,0 @@
using System.Runtime.Serialization;
namespace TINK.Repository.Response
{
/// <summary>
/// Holds info about a single bike.
/// </summary>
[DataContract]
public class GpsInfo
{
/// <summary>
/// Latitude position of the bike.
/// </summary>
[DataMember]
public string latitude { get; private set; }
/// <summary>
/// Longitude position of the bike.
/// </summary>
[DataMember]
public string longitude { get; private set; }
}
}

View file

@ -0,0 +1,17 @@
using System.Runtime.Serialization;
namespace TINK.Repository.Response
{
/// <summary> Holds information about map area to display.</summary>
[DataContract]
public class MapSpan
{
/// <summary> Center position of the map. </summary>
[DataMember]
public Position center { get; private set; }
/// <summary> Radius to the map area. </summary>
[DataMember]
public string radius { get; private set; }
}
}

View file

@ -0,0 +1,17 @@
using System.Runtime.Serialization;
namespace TINK.Repository.Response
{
/// <summary> Holds position info. </summary>
[DataContract]
public class Position
{
/// <summary> Latitude position (bike, station, map center...). </summary>
[DataMember]
public string latitude { get; private set; }
/// <summary> Longitude position (bike, station, map center...). </summary>
[DataMember]
public string longitude { get; private set; }
}
}

View file

@ -1,5 +1,7 @@

using System.Runtime.Serialization;
namespace TINK.Repository.Response
{
/// <summary>

View file

@ -21,6 +21,10 @@ namespace TINK.Repository.Response
[DataMember]
public string merchant_message { get; private set; }
/// <summary> Initial map display area.</summary>
[DataMember]
public MapSpan init_map { get; private set; }
/// <summary> Textual description of response. </summary>
public new string ToString()
{

View file

@ -54,7 +54,7 @@ namespace TINK.Repository.Response
/// Position of the station.
/// </summary>
[DataMember]
public GpsInfo gps { get; private set; }
public Position gps { get; private set; }
[DataMember]
public OperatorData operator_data { get; private set; }

View file

@ -1,18 +1,27 @@
using System;
using TINK.Model.Map;
namespace TINK.Services.CopriApi
{
/// <summary> Holds general purpose data returned from COPRI. </summary>
public class GeneralData
{
/// <summary> Constructs an empty general data object. </summary>
public GeneralData() : this(null, null, null) { }
public GeneralData(string merachantMessage = null, Version apiVersion = null)
{
public GeneralData(
IMapSpan initialMapSpan,
string merachantMessage,
Version apiVersion)
{
InitialMapSpan = initialMapSpan ?? MapSpanFactory.Create();
MerchantMessage = merachantMessage ?? string.Empty;
ApiVersion = apiVersion ?? new Version(0, 0);
}
/// <summary> Initial map display area.</summary>
public IMapSpan InitialMapSpan { get; private set; }
/// <summary> Message to be shown to user.</summary>
public string MerchantMessage { get; private set; }

View file

@ -353,7 +353,30 @@ namespace TINK.ViewModel.Map
// Move and scale before getting stations and bikes which takes some time.
ActionText = AppResources.ActivityTextCenterMap;
await MoveMapToCurrentPositionOfUser(status);
// Get map display area
Model.Map.IMapSpan mapSpan = null;
if (TinkApp.CenterMapToCurrentLocation && status == Status.Granted)
{
// Get from smart device
mapSpan = await GetFromLocationService(status);
}
if (mapSpan == null)
{
// Use map display are from COPRI
mapSpan = resultStationsAndBikes.GeneralData.InitialMapSpan;
}
if (mapSpan.IsValid)
{
TinkApp.UserMapSpan = MapSpan.FromCenterAndRadius(
new Xamarin.Forms.GoogleMaps.Position(mapSpan.Center.Latitude, mapSpan.Center.Longitude),
new Distance(mapSpan.Radius * 1000));
TinkApp.Save();
MoveAndScale(m_oMoveToRegionDelegate, TinkApp.ActiveMapSpan);
}
m_oViewUpdateManager = CreateUpdateTask();
@ -462,41 +485,31 @@ namespace TINK.ViewModel.Map
/// 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)
private async Task<Model.Map.IMapSpan> GetFromLocationService(Status status)
{
if (status == Status.Granted)
Location currentLocation = null;
try
{
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);
currentLocation = await GeolocationService.GetAsync();
}
catch (Exception ex)
{
Log.ForContext<MapPageViewModel>().Error("Getting location failed. {Exception}", ex);
}
if (currentLocation == null)
return null;
return Model.Map.MapSpanFactory.Create(
PositionFactory.Create(currentLocation.Latitude, currentLocation.Longitude),
TinkApp.ActiveMapSpan.Radius.Kilometers);
}
/// <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()
/// <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();