Version 3.0.364

This commit is contained in:
Anja 2023-05-09 08:47:52 +02:00
parent 91d42552c7
commit 0b9196a78d
91 changed files with 3452 additions and 555 deletions

View file

@ -54,7 +54,7 @@ namespace TINK.Model.Bikes.BikeInfoNS.BC
public enum DataSource
{
/// <summary>
/// Data source corpi.
/// Data source copri.
/// </summary>
Copri,
/// <summary>

View file

@ -7,6 +7,7 @@ using TINK.Model.Connector.Filter;
using TINK.Model.Services.CopriApi;
using TINK.Model.Stations;
using TINK.Model.Stations.StationNS;
using TINK.Model.Stations.StationNS.Operator;
using BikeInfo = TINK.Model.Bikes.BikeInfoNS.BC.BikeInfo;
namespace TINK.Model.Connector
@ -92,11 +93,18 @@ namespace TINK.Model.Connector
var providerBikesAndStations = await m_oInnerQuery.GetBikesAndStationsAsync();
// Do filtering.
var filteredStationsDictionary = new StationDictionary(providerBikesAndStations.Response.StationsAll.CopriVersion, DoFilter(providerBikesAndStations.Response.StationsAll, Filter));
var filteredBikesDictionary = new BikeCollection(DoFilter(providerBikesAndStations.Response.Bikes, Filter));
var filteredStationsDictionary = new StationDictionary(
providerBikesAndStations.Response.StationsAll.CopriVersion,
DoFilter(providerBikesAndStations.Response.StationsAll, Filter));
var filteredBikesOccupiedDictionary = new BikeCollection(
DoFilter(providerBikesAndStations.Response.BikesOccupied, Filter));
var filteredBikesAndStations = new Result<StationsAndBikesContainer>(
providerBikesAndStations.Source,
new StationsAndBikesContainer(filteredStationsDictionary, filteredBikesDictionary),
new StationsAndBikesContainer(
filteredStationsDictionary,
filteredBikesOccupiedDictionary),
providerBikesAndStations.GeneralData,
providerBikesAndStations.Exception);
@ -106,17 +114,25 @@ namespace TINK.Model.Connector
/// <summary> Filter bikes by group. </summary>
/// <param name="bikes">Bikes to filter.</param>
/// <returns>Filtered bikes.</returns>
private static Dictionary<string, BikeInfo> DoFilter(BikeCollection bikes, IGroupFilter filter)
{
return bikes.Where(x => filter.DoFilter(x.Group).Count() > 0).ToDictionary(x => x.Id);
}
private static Dictionary<string, BikeInfo> DoFilter(BikeCollection bikes, IGroupFilter filter) =>
bikes
.Where(x => filter.DoFilter(x.Group).Count() > 0)
.ToDictionary(x => x.Id);
/// <summary> Filter stations by group. </summary>
/// <returns></returns>
private static Dictionary<string, IStation> DoFilter(StationDictionary stations, IGroupFilter filter)
{
return stations.Where(x => filter.DoFilter(x.Group).Count() > 0).ToDictionary(x => x.Id);
}
/// <summary> Filter stations by group and removes bike group collection entries which do not match group filter. </summary>
/// <returns>Matching stations.</returns>
private static Dictionary<string, IStation> DoFilter(StationDictionary stations, IGroupFilter filter) =>
stations
.Where(station => filter.DoFilter(station.Group).Count() > 0)
.Select(station => new Station(
station.Id,
station.Group,
station.Position,
station.StationName,
station.OperatorData,
new BikeGroupCol(station.BikeGroups
.Where(group => filter.DoFilter(new List<string> { group.Group }).Count() > 0))) as IStation)
.ToDictionary(x => x.Id);
}
}
}

View file

@ -86,8 +86,11 @@ namespace TINK.Model.Connector
return new Result<StationsAndBikesContainer>(
result.Source,
new StationsAndBikesContainer(
new StationDictionary(result.Response.StationsAll.CopriVersion, result.Response.StationsAll.ToDictionary(x => x.Id)),
new BikeCollection(result.Response.Bikes.ToDictionary(x => x.Id))),
new StationDictionary(
result.Response.StationsAll.CopriVersion,
result.Response.StationsAll.ToDictionary(x => x.Id)),
new BikeCollection(
result.Response.BikesOccupied?.ToDictionary(x => x.Id) ?? new Dictionary<string, BikeInfo>())),
result.GeneralData,
result.Exception);
}

View file

@ -41,31 +41,19 @@ namespace TINK.Model.Connector
resultStations.Source,
new StationsAndBikesContainer(
resultStations.Response.GetStationsAllMutable(),
(await server.GetBikesAvailable(true)).Response.GetBikesAvailable(Bikes.BikeInfoNS.BC.DataSource.Cache)),
new BikeCollection() /* There are no bikes occupied because user is not logged in. */),
resultStations.GeneralData,
resultStations.Exception);
}
var resultBikes = await server.GetBikesAvailable();
if (resultBikes.Source == typeof(CopriCallsMonkeyStore))
{
// Communication with copri in order to get bikes failed.
return new Result<StationsAndBikesContainer>(
resultBikes.Source,
new StationsAndBikesContainer(
(await server.GetStations(true)).Response.GetStationsAllMutable(),
resultBikes.Response.GetBikesAvailable(Bikes.BikeInfoNS.BC.DataSource.Cache)),
resultBikes.GeneralData,
resultBikes.Exception);
}
// Communicatin with copri succeeded.
// Communication with copri succeeded.
server.AddToCache(resultStations);
server.AddToCache(resultBikes);
return new Result<StationsAndBikesContainer>(
resultStations.Source,
new StationsAndBikesContainer(resultStations.Response.GetStationsAllMutable(), resultBikes.Response.GetBikesAvailable(Bikes.BikeInfoNS.BC.DataSource.Copri)),
new StationsAndBikesContainer(
resultStations.Response.GetStationsAllMutable(),
new BikeCollection()),
resultStations.GeneralData);
}

View file

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Serilog;
@ -6,6 +7,7 @@ using TINK.Model.Bikes;
using TINK.Model.Connector.Updater;
using TINK.Model.Services.CopriApi;
using TINK.Repository;
using TINK.Repository.Response;
namespace TINK.Model.Connector
{
@ -25,90 +27,47 @@ namespace TINK.Model.Connector
Server = copriServer as ICachedCopriServer;
if (Server == null)
{
throw new ArgumentException($"Copri server is not of expected typ. Type detected is {copriServer.GetType()}.");
throw new ArgumentException($"Copri server is not of expected type. Type detected is {copriServer.GetType()}.");
}
}
/// <summary> Gets all stations including positions.</summary>
public async Task<Result<StationsAndBikesContainer>> GetBikesAndStationsAsync()
{
BikeCollection GetBikeCollection(IEnumerable<BikeInfoReservedOrBooked> bikeInfoEnumerable, Bikes.BikeInfoNS.BC.DataSource dataSource) =>
BikeCollectionFactory.GetBikesAll(
null, // Bikes available are no more of interest because count of available bikes at each given station is was added to station object.
bikeInfoEnumerable ?? new Dictionary<string, BikeInfoReservedOrBooked>().Values,
Mail,
DateTimeProvider,
dataSource);
var stationsResponse = await Server.GetStations();
if (stationsResponse.Source == typeof(CopriCallsMonkeyStore)
|| stationsResponse.Exception != null)
{
// Stations were read from cache ==> get bikes availbalbe and occupied from cache as well to avoid inconsistencies
// Stations were read from cache ==> get bikes available and occupied from cache as well to avoid inconsistencies
return new Result<StationsAndBikesContainer>(
stationsResponse.Source,
new StationsAndBikesContainer(
stationsResponse.Response.GetStationsAllMutable(),
BikeCollectionFactory.GetBikesAll(
(await Server.GetBikesAvailable(true)).Response?.bikes?.Values,
(await Server.GetBikesOccupied(true)).Response?.bikes_occupied?.Values,
Mail,
DateTimeProvider,
Bikes.BikeInfoNS.BC.DataSource.Cache)),
GetBikeCollection(stationsResponse.Response.bikes_occupied?.Values, Bikes.BikeInfoNS.BC.DataSource.Cache)),
stationsResponse.GeneralData,
stationsResponse.Exception);
}
var bikesAvailableResponse = await Server.GetBikesAvailable();
if (bikesAvailableResponse.Source == typeof(CopriCallsMonkeyStore)
|| bikesAvailableResponse.Exception != null)
{
// Bikes avilable were read from cache ==> get bikes occupied from cache as well to avoid inconsistencies
return new Result<StationsAndBikesContainer>(
bikesAvailableResponse.Source,
new StationsAndBikesContainer(
(await Server.GetStations(true)).Response.GetStationsAllMutable(),
BikeCollectionFactory.GetBikesAll(bikesAvailableResponse.Response?.bikes?.Values,
(await Server.GetBikesOccupied(true)).Response?.bikes_occupied?.Values,
Mail,
DateTimeProvider,
Bikes.BikeInfoNS.BC.DataSource.Cache)),
bikesAvailableResponse.GeneralData,
bikesAvailableResponse.Exception);
}
var bikesOccupiedResponse = await Server.GetBikesOccupied();
if (bikesOccupiedResponse.Source == typeof(CopriCallsMonkeyStore)
|| bikesOccupiedResponse.Exception != null)
{
// Bikes occupied were read from cache ==> get bikes available from cache as well to avoid inconsistencies
return new Result<StationsAndBikesContainer>(
bikesOccupiedResponse.Source,
new StationsAndBikesContainer(
(await Server.GetStations(true)).Response.GetStationsAllMutable(),
BikeCollectionFactory.GetBikesAll(
(await Server.GetBikesAvailable(true)).Response?.bikes?.Values,
bikesOccupiedResponse.Response?.bikes_occupied?.Values,
Mail,
DateTimeProvider,
Bikes.BikeInfoNS.BC.DataSource.Cache)),
bikesOccupiedResponse.GeneralData,
bikesOccupiedResponse.Exception);
}
// Both types bikes could read from copri => update cache
Server.AddToCache(stationsResponse);
Server.AddToCache(bikesAvailableResponse);
Server.AddToCache(bikesOccupiedResponse);
var exceptions = new[] { stationsResponse?.Exception, bikesAvailableResponse?.Exception, bikesOccupiedResponse?.Exception }.Where(x => x != null).ToArray();
var stationsMutable = stationsResponse.Response.GetStationsAllMutable();
var bikesMutable = BikeCollectionFactory.GetBikesAll(
bikesAvailableResponse.Response?.bikes?.Values,
bikesOccupiedResponse.Response?.bikes_occupied?.Values,
Mail,
DateTimeProvider,
Bikes.BikeInfoNS.BC.DataSource.Copri);
return new Result<StationsAndBikesContainer>(
stationsResponse.Source,
new StationsAndBikesContainer(stationsMutable, bikesMutable),
new StationsAndBikesContainer(
stationsResponse.Response.GetStationsAllMutable(),
GetBikeCollection(stationsResponse.Response.bikes_occupied?.Values, Bikes.BikeInfoNS.BC.DataSource.Copri)),
stationsResponse.GeneralData,
exceptions.Length > 0 ? new AggregateException(exceptions) : null);
stationsResponse?.Exception);
}
/// <summary> Gets bikes occupied. </summary>

View file

@ -32,11 +32,12 @@ namespace TINK.Model.Connector
public async Task<Result<StationsAndBikesContainer>> GetBikesAndStationsAsync()
{
var stationsAllResponse = await server.GetStationsAsync();
var bikesAvailableResponse = await server.GetBikesAvailableAsync();
return new Result<StationsAndBikesContainer>(
typeof(CopriCallsMonkeyStore),
new StationsAndBikesContainer(stationsAllResponse.GetStationsAllMutable(), bikesAvailableResponse.GetBikesAvailable(Bikes.BikeInfoNS.BC.DataSource.Cache)),
new StationsAndBikesContainer(
stationsAllResponse.GetStationsAllMutable(),
new BikeCollection() /* There are no bikes occupied because user is not logged in. */),
stationsAllResponse.GetGeneralData());
}

View file

@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using TINK.Model.Bikes;
using TINK.Model.Connector.Updater;
using TINK.Model.Services.CopriApi;
using TINK.Repository;
using TINK.Repository.Response;
namespace TINK.Model.Connector
{
@ -34,16 +36,14 @@ namespace TINK.Model.Connector
public async Task<Result<StationsAndBikesContainer>> GetBikesAndStationsAsync()
{
var stationResponse = await server.GetStationsAsync();
var bikesAvailableResponse = await server.GetBikesAvailableAsync();
var bikesOccupiedResponse = await server.GetBikesOccupiedAsync();
return new Result<StationsAndBikesContainer>(
typeof(CopriCallsMonkeyStore),
new StationsAndBikesContainer(
stationResponse.GetStationsAllMutable(),
BikeCollectionFactory.GetBikesAll(
bikesAvailableResponse?.bikes?.Values,
bikesOccupiedResponse?.bikes_occupied?.Values,
null, // Bikes available are no more of interest because count of available bikes at each given station is was added to station object.
stationResponse.bikes_occupied?.Values ?? new Dictionary<string, BikeInfoReservedOrBooked>().Values,
Mail,
DateTimeProvider,
Bikes.BikeInfoNS.BC.DataSource.Cache)),

View file

@ -7,9 +7,12 @@ using Serilog;
using TINK.Model.Bikes.BikeInfoNS.BikeNS;
using TINK.Model.Services.CopriApi.ServerUris;
using TINK.Model.State;
using TINK.Model.Stations.StationNS;
using TINK.Model.Stations.StationNS.Operator;
using TINK.Repository.Exception;
using TINK.Repository.Response;
using TINK.Repository.Response.Stations.Station;
using Xamarin.Forms;
namespace TINK.Model.Connector
{
@ -388,5 +391,52 @@ namespace TINK.Model.Connector
/// <param name="bike">Bike get to state from.</param>
public static bool GetIsFeedbackPending(this BikeInfoAvailable bike)
=> bike.co2saving != null;
/// <summary>
/// Gets the count of bikes available at station.
/// </summary>
/// <param name="stationInfo">Object to get information from.</param>
/// <returns>Count of bikes available or null if information is unknown.</returns>
public static int? GetBikesAvailableCount(this StationInfo stationInfo)
=> !string.IsNullOrWhiteSpace(stationInfo?.bike_count)
&& int.TryParse(stationInfo?.bike_count, out int bikeCount)
? bikeCount
: (int?)null;
/// <summary>
/// Gets station object from response object.
/// </summary>
/// <param name="station">Response object to get station object from.</param>
/// <returns>Station object.</returns>
public static Station GetStation(this StationInfo station) =>
new Station(
station.station,
station.GetGroup(),
station.GetPosition(),
station.description,
new Data(station.operator_data?.operator_name,
station.operator_data?.operator_phone,
station.operator_data?.operator_hours,
station.operator_data?.operator_email,
!string.IsNullOrEmpty(station.operator_data?.operator_color)
? Color.FromHex(station.operator_data?.operator_color)
: (Color?)null),
new BikeGroupCol(station.station_type?.Select(x => new BikeGroupCol.Entry(
x.Key,
int.TryParse(x.Value.bike_count, out int count) ? count : 0,
x.Value.bike_group)) ?? new List<BikeGroupCol.Entry>()
));
/// <summary>
/// Gets the bike group object from response object.
/// </summary>
/// <param name="bikeGroup">Response object to get station from.</param>
/// <param name="name">Name of the bike group.</param>
/// <returns></returns>
public static BikeGroupCol.Entry GetBikeGroup(this BikeGroup bikeGroup, string name) =>
new BikeGroupCol.Entry(
name,
int.TryParse(bikeGroup?.bike_count ?? "0", out var countCity) ? countCity : 0,
bikeGroup?.bike_group ?? string.Empty);
}
}

View file

@ -30,7 +30,7 @@ namespace TINK.Model.Connector.Updater
/// <summary>
/// Gets all station for station provider and add them into station list.
/// </summary>
/// <param name="p_oStationList">List of stations to update.</param>
/// <param name="stationsAllResponse">List of stations to update.</param>
public static StationDictionary GetStationsAllMutable(this StationsAvailableResponse stationsAllResponse)
{
// Get stations from Copri/ file/ memory, ....
@ -43,7 +43,7 @@ namespace TINK.Model.Connector.Updater
Version.TryParse(stationsAllResponse.copri_version, out Version copriVersion);
var stations = new StationDictionary(p_oVersion: copriVersion);
var stations = new StationDictionary(version: copriVersion);
foreach (var station in stationsAllResponse.stations)
{
@ -54,18 +54,7 @@ namespace TINK.Model.Connector.Updater
string.Format("Station id {0} is not unique.", station.Value.station), stationsAllResponse);
}
stations.Add(new Stations.StationNS.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)));
stations.Add(station.Value.GetStation());
}
return stations;

View file

@ -148,7 +148,7 @@ namespace TINK.Model.Settings
/// <returns>Logging level</returns>
public static Uri GetCopriHostUri(this IDictionary<string, string> settingsJSON)
{
// Get uri of corpi server.
// Get uri of copri server.
if (!settingsJSON.TryGetValue(typeof(CopriServerUriList).ToString(), out string uriText)
|| string.IsNullOrEmpty(uriText))
{
@ -381,7 +381,7 @@ namespace TINK.Model.Settings
/// <returns>Active lock service name.</returns>
public static string GetActiveLockService(this IDictionary<string, string> settingsJSON)
{
// Get uri of corpi server.
// Get uri of copri server.
if (!settingsJSON.TryGetValue(typeof(ILocksService).Name, out string activeLockService)
|| string.IsNullOrEmpty(activeLockService))
{
@ -418,7 +418,7 @@ namespace TINK.Model.Settings
/// <returns>Active lock service name.</returns>
public static string GetActiveGeolocationService(this IDictionary<string, string> settingsJSON)
{
// Get uri of corpi server.
// Get uri of copri server.
if (!settingsJSON.TryGetValue(typeof(IGeolocationService).Name, out string activeGeolocationService)
|| string.IsNullOrEmpty(activeGeolocationService))
{

View file

@ -10,19 +10,27 @@ namespace TINK.Model.Stations
/// <summary> Holds the list of stations. </summary>
private readonly IDictionary<string, IStation> m_oStationDictionary;
/// <summary>
/// Gets a station by key.
/// </summary>
/// <param name="key">Key to get station.</param>
/// <returns>Station for given key.</returns>
public IStation this[string key] =>
ContainsKey(key) ? m_oStationDictionary[key] : new NullStation();
/// <summary> Count of stations. </summary>
public int Count { get { return m_oStationDictionary.Count; } }
public Version CopriVersion { get; }
/// <summary> Constructs a station dictionary object. </summary>
/// <param name="p_oVersion">Version of copri- service.</param>
public StationDictionary(Version p_oVersion = null, IDictionary<string, IStation> p_oStations = null)
/// <param name="version">Version of copri- service.</param>
public StationDictionary(Version version = null, IDictionary<string, IStation> stations = null)
{
m_oStationDictionary = p_oStations ?? new Dictionary<string, IStation>();
m_oStationDictionary = stations ?? new Dictionary<string, IStation>();
CopriVersion = p_oVersion != null
? new Version(p_oVersion.Major, p_oVersion.Minor, p_oVersion.Revision, p_oVersion.Build)
CopriVersion = version != null
? new Version(version.Major, version.Minor, version.Revision, version.Build)
: new Version(0, 0, 0, 0);
}

View file

@ -3,6 +3,9 @@ using TINK.Model.Stations.StationNS.Operator;
namespace TINK.Model.Stations.StationNS
{
/// <summary>
/// Holds station information, i.e. static information like station name, station position and dynamic information like bikes available.
/// </summary>
public interface IStation
{
/// <summary> Holds the unique id of the station.c</summary>
@ -19,5 +22,12 @@ namespace TINK.Model.Stations.StationNS
/// <summary> Holds operator related data.</summary>
IData OperatorData { get; }
/// <summary> Gets the count of bikes available at station. </summary>
int? AvailableBikesCount { get; }
/// <summary> Gets bike <see cref="BikeGroupCol.Entry"/> objects. </summary>
/// <remarks> Each entry has a name, holds the count of available bikes at station and the group value. /// </remarks>
IBikeGroupCol BikeGroups { get; }
}
}

View file

@ -20,5 +20,12 @@ namespace TINK.Model.Stations.StationNS
/// <summary> Holds operator related data.</summary>
public IData OperatorData => new Data();
/// <summary> Gets the count of bikes available at station. </summary>
public int? AvailableBikesCount => null;
/// <summary> Gets bike <see cref="BikeGroupCol.Entry"/> objects. </summary>
/// <remarks> Each entry has a name, holds the count of available bikes at station and the group value. /// </remarks>
public IBikeGroupCol BikeGroups => null;
}
}

View file

@ -0,0 +1,49 @@
using System.Collections.Generic;
using System.Linq;
namespace TINK.Model.Stations.StationNS.Operator
{
public class BikeGroupCol : List<BikeGroupCol.Entry> , IBikeGroupCol
{
/// <summary>
/// Holds a group of bikes at a station.
/// </summary>
public class Entry
{
/// <summary>
/// Bike group entry.
/// </summary>
/// <param name="name">Name of the group, either "Citybikes" or "Cargobikes".</param>
/// <param name="availableCount"></param>
/// <param name="group"></param>
public Entry(string name, int? availableCount = null, string group = null)
{
Name = name;
AvailableCount = availableCount ?? 0;
Group = group ?? string.Empty;
}
/// <summary>
/// Name of the group, either "Citybikes" or "Cargobikes".
/// </summary>
public string Name { get; }
/// <summary> Holds the count of bikes available of given type at station. </summary>
public int AvailableCount { get; }
/// <summary>
/// Holds the group of the bikes. Konrad separates cargo and city bikes by group.
/// </summary>
public string Group { get; }
}
public BikeGroupCol() : base() { }
public BikeGroupCol(IEnumerable<Entry> source) : base(source) { }
/// <summary> Holds the count of bikes available of any type at station. </summary>
public int AvailableCount => this.Select(x => x.AvailableCount).Sum();
}
}

View file

@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace TINK.Model.Stations.StationNS.Operator
{
public interface IBikeGroupCol : IEnumerable<BikeGroupCol.Entry>
{
/// <summary> Holds the count of bikes available of given type at station. </summary>
int AvailableCount { get; }
}
}

View file

@ -14,16 +14,18 @@ namespace TINK.Model.Stations.StationNS
/// <param name="stationName">Name of the station.</param>
public Station(
string id,
IEnumerable<string> group,
IPosition position,
IEnumerable<string> group = null,
IPosition position = null,
string stationName = "",
Data operatorData = null)
IData operatorData = null,
IBikeGroupCol bikeGropCol = null)
{
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();
BikeGroups = bikeGropCol ?? new BikeGroupCol();
}
/// <summary> Holds the unique id of the station.c</summary>
@ -40,5 +42,12 @@ namespace TINK.Model.Stations.StationNS
/// <summary> Holds operator related info.</summary>
public IData OperatorData { get; }
/// <summary> Gets the count of bikes available at station. </summary>
public int? AvailableBikesCount => BikeGroups.AvailableCount;
/// <summary> Gets bike <see cref="BikeGroupCol.Entry"/> objects. </summary>
/// <remarks> Each entry has a name, holds the count of available bikes at station and the group value. /// </remarks>
public IBikeGroupCol BikeGroups { get; }
}
}

View file

@ -408,7 +408,7 @@ namespace TINK.Model
/// <summary> Holds available app themes.</summary>
public IServicesContainer<IGeolocationService> GeolocationServices { get; }
/// <summary> Holds the flavor of the app, i.e. specifies if app is sharee.bike, Mein konrad or Lastenrad Bayern.</summary>
/// <summary> Holds the flavor of the app, i.e. specifies if app is sharee.bike, Mein konrad or LastenRad Bayern.</summary>
public AppFlavor Flavor { get; private set; }
/// <summary> Manages the different types of LocksService objects.</summary>
@ -418,7 +418,7 @@ namespace TINK.Model
private LoggingLevelSwitch m_oLoggingLevelSwitch;
/// <summary>
/// Object to allow swithing logging level
/// Object to allow switching logging level
/// </summary>
public LoggingLevelSwitch Level
{
@ -448,7 +448,7 @@ namespace TINK.Model
return;
}
Log.CloseAndFlush(); // Close before modifying logger configuration. Otherwise a sharing vialation occurs.
Log.CloseAndFlush(); // Close before modifying logger configuration. Otherwise a sharing violation occurs.
Level.MinimumLevel = minimumLevel;
@ -480,7 +480,7 @@ namespace TINK.Model
var sourceContex = e.Properties[Constants.SourceContextPropertyName].ToString();
if ((e.Level == LogEventLevel.Information) &&
(sourceContex.Contains(typeof(AppAndEnvironmentInfo).Namespace) /* Log App and enviroment info. */
(sourceContex.Contains(typeof(AppAndEnvironmentInfo).Namespace) /* Log App and environment info. */
|| sourceContex.Contains(typeof(ViewModel.Bikes.Bike.BluetoothLock.RequestHandler.Base).Namespace /* Log info-level messages to provide context for bluetooth log. */ )))
{
return true;

View file

@ -687,6 +687,11 @@ namespace TINK.Model
AppResources.ChangeLog_3_0_363_MK_SB,
new List<AppFlavor> { AppFlavor.MeinKonrad, AppFlavor.ShareeBike }
},
{
new Version(3, 0, 364),
AppResources.ChangeLog_MinorImprovements,
new List<AppFlavor> { AppFlavor.MeinKonrad, AppFlavor.ShareeBike }
},
};
/// <summary> Manges the whats new information.</summary>

View file

@ -1459,7 +1459,7 @@ namespace TINK.MultilingualResources {
///1. close app,
///2. press the button at the top of the lock briefly and release it as soon as it starts flashing,
///3. make sure that the lock is completely closed and remains closed.
///4. send e-mail to operator (otherwise your chargeable rental will continue!): Problem description, Drop-off station..
///4. send e-mail to operator (otherwise your chargeable rental will continue!): Please include problem description, bike-id and drop-off station..
/// </summary>
public static string ErrorBookedSearchMessageEscalationLevel2 {
get {

View file

@ -761,7 +761,7 @@ Alternativ:
1. App schließen,
2. auf den Knopf oben am Schloss kurz drücken und sofort loslassen, sobald Knopf zu blinken beginnt,
3. sicherstellen, dass das Schloss vollständig geschlossen ist und geschlossen bleibt.
4. E-Mail an Betreiber schicken (ansonsten läuft Ihre kostenpflichtige Miete weiter!): Problembeschreibung, Rad-ID, Abgabestation.</value>
4. E-Mail an Betreiber schicken (ansonsten läuft Ihre kostenpflichtige Miete weiter!): Bitte Problembeschreibung, Rad-ID und Abgabestation angeben.</value>
</data>
<data name="ChangeLog3_0_278" xml:space="preserve">
<value>Hinweise hinzugefügt, um das Schließen des Schlosses zu erleichtern, falls dies nicht beim ersten Versuch gelingt.</value>
@ -840,7 +840,7 @@ Activity Indicator zu Konto-Verwaltungsseiten hinzugefügt und Logging erweitert
<value>Starte Miete beenden...</value>
</data>
<data name="ExceptionTextWebConnectFailureException" xml:space="preserve">
<value>Ist WLAN/Mobilfunknetz vefügbar und mobile Daten aktiviert?</value>
<value>Ist WLAN/Mobilfunknetz verfügbar und mobile Daten aktiviert?</value>
</data>
<data name="ChangeLog3_0_289" xml:space="preserve">
<value>Flyout-Menü Überschift verschönert.</value>

View file

@ -869,7 +869,7 @@ Alternative:
1. close app,
2. press the button at the top of the lock briefly and release it as soon as it starts flashing,
3. make sure that the lock is completely closed and remains closed.
4. send e-mail to operator (otherwise your chargeable rental will continue!): Problem description, Drop-off station.</value>
4. send e-mail to operator (otherwise your chargeable rental will continue!): Please include problem description, bike-id and drop-off station.</value>
</data>
<data name="ChangeLog3_0_278" xml:space="preserve">
<value>Hints added to ease closing of lock in case closing does not succeed on first try.</value>

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en-GB" target-language="de" original="TINKLIB/MULTILINGUALRESOURCES/APPRESOURCES.RESX" tool-id="MultilingualAppToolkit" product-name="n/a" product-version="n/a" build-num="n/a">
<header>
@ -1026,14 +1026,14 @@ Alternative:
1. close app,
2. press the button at the top of the lock briefly and release it as soon as it starts flashing,
3. make sure that the lock is completely closed and remains closed.
4. send e-mail to operator (otherwise your chargeable rental will continue!): Problem description, Drop-off station.</source>
4. send e-mail to operator (otherwise your chargeable rental will continue!): Please include problem description, bike-id and drop-off station.</source>
<target state="translated">Es kann weiterhin keine Verbindung zwischen Ihrem mobilen Gerät und dem Schloss aufgebaut werden. Rufen Sie den Betreiber an!
Alternativ:
1. App schließen,
2. auf den Knopf oben am Schloss kurz drücken und sofort loslassen, sobald Knopf zu blinken beginnt,
3. sicherstellen, dass das Schloss vollständig geschlossen ist und geschlossen bleibt.
4. E-Mail an Betreiber schicken (ansonsten läuft Ihre kostenpflichtige Miete weiter!): Problembeschreibung, Rad-ID, Abgabestation.</target>
4. E-Mail an Betreiber schicken (ansonsten läuft Ihre kostenpflichtige Miete weiter!): Bitte Problembeschreibung, Rad-ID und Abgabestation angeben.</target>
</trans-unit>
<trans-unit id="ChangeLog3_0_278" translate="yes" xml:space="preserve">
<source>Hints added to ease closing of lock in case closing does not succeed on first try.</source>
@ -1141,7 +1141,7 @@ Activity Indicator zu Konto-Verwaltungsseiten hinzugefügt und Logging erweitert
</trans-unit>
<trans-unit id="ExceptionTextWebConnectFailureException" translate="yes" xml:space="preserve">
<source>Is WIFI/mobile network available and mobile data activated?</source>
<target state="translated">Ist WLAN/Mobilfunknetz vefügbar und mobile Daten aktiviert?</target>
<target state="translated">Ist WLAN/Mobilfunknetz verfügbar und mobile Daten aktiviert?</target>
</trans-unit>
<trans-unit id="ChangeLog3_0_289" translate="yes" xml:space="preserve">
<source>Flyout menu header improved.</source>
@ -1631,4 +1631,4 @@ Außerdem:&lt;br/&gt;
</group>
</body>
</file>
</xliff>
</xliff>

View file

@ -1,4 +1,4 @@
using System;
using System;
using Serilog;
using TINK.Model.Connector;
using TINK.Repository.Response;
@ -22,7 +22,7 @@ namespace TINK.Repository
public static Version UnsupportedVersionUpper => UNSUPPORTEDFUTURECOPRIVERSIONUPPER;
/// <summary> Deserializes reponse JSON if response is of supported version or provides default response otherwise. </summary>
/// <summary> Deserializes response JSON if response is of supported version or provides default response otherwise. </summary>
/// <typeparam name="T">Type of response object.</typeparam>
/// <param name="response">Response JSON.</param>
/// <param name="emptyResponseFactory">Factory providing default delegate.</param>

View file

@ -43,12 +43,12 @@ namespace TINK.Repository.Request
/// <summary> Gets reservation request (synonym: reservation == request == reservieren). </summary>
/// <param name="bikeId">Id of the bike to reserve.</param>
/// <returns>Requst to reserve bike.</returns>
/// <returns>Request to reserve bike.</returns>
string DoReserve(string bikeId);
/// <summary> Gets request to cancel reservation. </summary>
/// <param name="bikeId">Id of the bike to cancel reservation for.</param>
/// <returns>Requst on cancel booking request.</returns>
/// <returns>Request on cancel booking request.</returns>
string DoCancelReservation(string bikeId);
/// <summary> Request to get keys. </summary>
@ -96,17 +96,17 @@ namespace TINK.Repository.Request
/// <summary> Gets request for returning the bike. </summary>
/// <param name="bikeId">Id of the bike to return.</param>
/// <param name="location">Geolocation of lock when returning bike.</param>
/// <returns>Requst on returning request.</returns>
/// <returns>Request on returning request.</returns>
string DoReturn(string bikeId, LocationDto location);
/// <summary> Returns a bike and starts closing. </summary>
/// <param name="bikeId">Id of the bike to return.</param>
/// <param name="smartDevice">Provides info about hard and software.</param>
/// <returns>Response to send to corpi.</returns>
/// <returns>Response to send to copri.</returns>
string ReturnAndStartClosing(string bikeId);
/// <summary>
/// Gets request for submiting feedback to copri server.
/// Gets request for submitting feedback to copri server.
/// </summary>
/// <param name="bikeId">Id of the bike to which the feedback is related to.</param>
/// <param name="currentChargeBars">Null if bike has no engine or charge is unknown. Otherwise the charge filling level of the drive battery.</param>
@ -119,7 +119,7 @@ namespace TINK.Repository.Request
bool isBikeBroken = false);
/// <summary>
/// Gets request for submiting mini survey to copri server.
/// Gets request for submitting mini survey to copri server.
/// </summary>
/// <param name="answers">Collection of answers.</param>
string DoSubmitMiniSurvey(IDictionary<string, string> answers);

View file

@ -139,7 +139,7 @@ namespace TINK.Repository.Request
/// <summary> Returns a bike and starts closing. </summary>
/// <param name="bikeId">Id of the bike to return.</param>
/// <param name="smartDevice">Provides info about hard and software.</param>
/// <returns>Response to send to corpi.</returns>
/// <returns>Response to send to copri.</returns>
public string ReturnAndStartClosing(string bikeId)
=> throw new NotSupportedException();
@ -152,7 +152,7 @@ namespace TINK.Repository.Request
=> throw new NotSupportedException();
/// <summary>
/// Gets request for submiting mini survey to copri server.
/// Gets request for submitting mini survey to copri server.
/// </summary>
/// <param name="answers">Collection of answers.</param>
public string DoSubmitMiniSurvey(IDictionary<string, string> answers) =>

View file

@ -55,7 +55,7 @@ namespace TINK.Repository.Request
private ISmartDevice SmartDevice { get; }
/// <summary> Gets request to log user in. </summary>
/// <param name="mailAddress">Mailaddress of user to log in.</param>
/// <param name="mailAddress">Mail address of user to log in.</param>
/// <param name="password">Password to log in.</param>
/// <param name="deviceId">Id specifying user and hardware.</param>
/// <remarks>Response which holds auth cookie <see cref="ResponseBase.authcookie"/></remarks>
@ -96,7 +96,7 @@ namespace TINK.Repository.Request
/// <summary> Gets reservation request (synonym: reservation == request == reservieren). </summary>
/// <remarks> Operator specific call.</remarks>
/// <param name="bikeId">Id of the bike to reserve.</param>
/// <returns>Requst to reserve bike.</returns>
/// <returns>Request to reserve bike.</returns>
public string DoReserve(string bikeId)
=> "request=booking_request" +
GetBikeIdParameter(bikeId) +
@ -107,7 +107,7 @@ namespace TINK.Repository.Request
/// <summary> Gets request to cancel reservation. </summary>
/// <remarks> Operator specific call.</remarks>
/// <param name="bikeId">Id of the bike to cancel reservation for.</param>
/// <returns>Requst on cancel booking request.</returns>
/// <returns>Request on cancel booking request.</returns>
public string DoCancelReservation(string bikeId)
=> "request=booking_cancel" +
GetBikeIdParameter(bikeId) +
@ -158,7 +158,7 @@ namespace TINK.Repository.Request
UiIsoLanguageNameParameter;
/// <summary> Gets booking request request (synonym: booking == renting == mieten). </summary>
/// <summary> Gets booking request (synonym: booking == renting == mieten). </summary>
/// <remarks> Operator specific call.</remarks>
/// <param name="bikeId">Id of the bike to book.</param>
/// <param name="guid">Used to publish GUID from app to copri. Used for initial setup of bike in copri.</param>
@ -202,7 +202,7 @@ namespace TINK.Repository.Request
/// <remarks> Operator specific call.</remarks>
/// <param name="bikeId">Id of bike to return.</param>
/// <param name="geolocation">Geolocation of lock when returning bike.</param>
/// <returns>Requst on returning request.</returns>
/// <returns>Request on returning request.</returns>
public string DoReturn(string bikeId, LocationDto geolocation)
=> "request=booking_update" +
GetBikeIdParameter(bikeId) +
@ -218,7 +218,7 @@ namespace TINK.Repository.Request
/// <summary> Returns a bike and starts closing. </summary>
/// <param name="bikeId">Id of the bike to return.</param>
/// <param name="smartDevice">Provides info about hard and software.</param>
/// <returns>Response to send to corpi.</returns>
/// <returns>Response to send to copri.</returns>
public string ReturnAndStartClosing(string bikeId)
=> "request=booking_update" +
GetBikeIdParameter(bikeId) +
@ -258,7 +258,7 @@ namespace TINK.Repository.Request
}
/// <summary>
/// Gets request for submiting mini survey to copri server.
/// Gets request for submitting mini survey to copri server.
/// </summary>
/// <param name="answers">Collection of answers.</param>
public string DoSubmitMiniSurvey(IDictionary<string, string> answers)
@ -313,7 +313,7 @@ namespace TINK.Repository.Request
/// <summary> Gets the geolocation parameter. </summary>
/// <param name="geolocation">Geolocation or null.</param>
/// <returns>Empty string if geoloction is null otherwise parmeter including latitude, longitude and age in a format which is urlencode invariant.</returns>
/// <returns>Empty string if geolocation is null otherwise parameter including latitude, longitude and age in a format which is urlencode invariant.</returns>
private static string GetLocationParameters(LocationDto geolocation)
{
if (geolocation == null)

View file

@ -0,0 +1,21 @@
using System.Runtime.Serialization;
namespace TINK.Repository.Response.Stations.Station
{
/// <summary>
/// Holds a single bike group.
/// </summary>
[DataContract]
public class BikeGroup
{
/// <summary> Holds the count of bikes for the group. </summary>
[DataMember]
public string bike_count { get; private set;}
/// <summary>
/// Holds the group identifying the bike group type.
/// </summary>
[DataMember]
public string bike_group { get; private set; }
}
}

View file

@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace TINK.Repository.Response.Stations.Station
@ -28,5 +29,17 @@ namespace TINK.Repository.Response.Stations.Station
[DataMember]
public OperatorData operator_data { get; private set; }
/// <summary>
/// Holds the count of available bikes at the station.
/// </summary>
[DataMember]
public string bike_count { get; private set; }
/// <summary>
/// Holds the count type of station, i.e. which bikes are located at station.
/// </summary>
[DataMember]
public Dictionary<string, BikeGroup> station_type { get; private set; }
}
}

View file

@ -15,5 +15,11 @@ namespace TINK.Repository.Response.Stations
/// </summary>
[DataMember]
public Dictionary<string, StationInfo> stations { get; private set; }
/// <summary>
/// Dictionary of bikes reserved (requested) and rented (occupied) by current user if user is logged in and has reserved or rented bikes.
/// </summary>
[DataMember]
public Dictionary<string, BikeInfoReservedOrBooked> bikes_occupied { get; private set; }
}
}

View file

@ -34,7 +34,7 @@ namespace TINK.Services.CopriApi
}
/// <summary> Opens lock.</summary>
/// <param name="corpiServer"> Instance to communicate with backend.</param>
/// <param name="cachedServer"> Instance to communicate with backend.</param>
/// <param name="bike">Bike object holding id of bike to open. Lock state of object is updated after open request.</param>
public static async Task OpenAync(
this ICachedCopriServer cachedServer,
@ -70,11 +70,11 @@ namespace TINK.Services.CopriApi
/// <summary>
/// Books a bike and opens the lock.
/// </summary>
/// <param name="corpiServer"> Instance to communicate with backend.</param>
/// <param name="copriServer"> Instance to communicate with backend.</param>
/// <param name="bike">Bike to book and open.</param>
/// <param name="mailAddress">Mail address of user which books bike.</param>
public static async Task BookAndOpenAync(
this ICopriServerBase corpiServer,
this ICopriServerBase copriServer,
IBikeInfoMutable bike,
string mailAddress)
{
@ -83,13 +83,13 @@ namespace TINK.Services.CopriApi
throw new ArgumentNullException(nameof(bike), "Can not book bike and open lock. No bike object available.");
}
if (!(corpiServer is ICachedCopriServer cachedServer))
throw new ArgumentNullException(nameof(corpiServer));
if (!(copriServer is ICachedCopriServer cachedServer))
throw new ArgumentNullException(nameof(copriServer));
// Send command to open lock
var response = bike.State.Value == Model.State.InUseStateEnum.Disposable
? (await corpiServer.BookAvailableAndStartOpeningAsync(bike.Id, bike.OperatorUri)).GetIsBookingResponseOk(bike.Id)
: (await corpiServer.BookReservedAndStartOpeningAsync(bike.Id, bike.OperatorUri)).GetIsBookingResponseOk(bike.Id);
? (await copriServer.BookAvailableAndStartOpeningAsync(bike.Id, bike.OperatorUri)).GetIsBookingResponseOk(bike.Id)
: (await copriServer.BookReservedAndStartOpeningAsync(bike.Id, bike.OperatorUri)).GetIsBookingResponseOk(bike.Id);
// Upated locking state.
var lockingState = await cachedServer.GetOccupiedBikeLockStateAsync(bike.Id);
@ -128,17 +128,17 @@ namespace TINK.Services.CopriApi
/// <summary>
/// Returns a bike and closes the lock.
/// </summary>
/// <param name="corpiServer"> Instance to communicate with backend.</param>
/// <param name="copriServer"> Instance to communicate with backend.</param>
/// <param name="bike">Bike to close.</param>
public static async Task CloseAync(
this ICopriServerBase corpiServer,
this ICopriServerBase copriServer,
IBikeInfoMutable bike)
{
if (!(corpiServer is ICachedCopriServer cachedServer))
throw new ArgumentNullException(nameof(corpiServer));
if (!(copriServer is ICachedCopriServer cachedServer))
throw new ArgumentNullException(nameof(copriServer));
// Send command to close lock
await corpiServer.UpdateLockingStateAsync(
await copriServer.UpdateLockingStateAsync(
bike.Id,
Repository.Request.lock_state.locking,
bike.OperatorUri);
@ -166,20 +166,20 @@ namespace TINK.Services.CopriApi
/// <summary>
/// Returns a bike and closes the lock.
/// </summary>
/// <param name="corpiServer"> Instance to communicate with backend.</param>
/// <param name="copriServer"> Instance to communicate with backend.</param>
/// <param name="smartDevice">Smart device on which app runs on.</param>
/// <param name="mailAddress">Mail address of user which books bike.</param>
public static async Task<BookingFinishedModel> ReturnAndCloseAync(
this ICopriServerBase corpiServer,
this ICopriServerBase copriServer,
ISmartDevice smartDevice,
IBikeInfoMutable bike)
{
if (!(corpiServer is ICachedCopriServer cachedServer))
throw new ArgumentNullException(nameof(corpiServer));
if (!(copriServer is ICachedCopriServer cachedServer))
throw new ArgumentNullException(nameof(copriServer));
// Send command to open lock
DoReturnResponse response =
await corpiServer.ReturnAndStartClosingAsync(bike.Id, bike.OperatorUri);
await copriServer.ReturnAndStartClosingAsync(bike.Id, bike.OperatorUri);
// Upate booking state
bike.Load(Model.Bikes.BikeInfoNS.BC.NotifyPropertyChangedLevel.None);
@ -209,21 +209,21 @@ namespace TINK.Services.CopriApi
/// <summary>
/// Queries the locking state from copri.
/// </summary>
/// <param name="corpiServer">Service to use.</param>
/// <param name="copriServer">Service to use.</param>
/// <param name="bikeId">Bike id to query lock state for.</param>
/// <returns>Locking state</returns>
private static async Task<LockingState> GetLockStateAsync(
this ICachedCopriServer corpiServer,
this ICachedCopriServer copriServer,
string bikeId)
{
// Querry reserved or booked bikes first for performance reasons.
var bikeReservedOrBooked = (await corpiServer.GetBikesOccupied(false))?.Response.bikes_occupied?.Values?.FirstOrDefault(x => x.bike == bikeId);
var bikeReservedOrBooked = (await copriServer.GetBikesOccupied(false))?.Response.bikes_occupied?.Values?.FirstOrDefault(x => x.bike == bikeId);
if (bikeReservedOrBooked != null)
{
return bikeReservedOrBooked.GetCopriLockingState();
}
var bikeAvailable = (await corpiServer.GetBikesAvailable(false))?.Response.bikes?.Values?.FirstOrDefault(x => x.bike == bikeId);
var bikeAvailable = (await copriServer.GetBikesAvailable(false))?.Response.bikes?.Values?.FirstOrDefault(x => x.bike == bikeId);
if (bikeAvailable != null)
{
return bikeAvailable.GetCopriLockingState();
@ -235,14 +235,14 @@ namespace TINK.Services.CopriApi
/// <summary>
/// Queries the locking state of a occupied bike from copri.
/// </summary>
/// <param name="corpiServer">Service to use.</param>
/// <param name="copriServer">Service to use.</param>
/// <param name="bikeId">Bike id to query lock state for.</param>
/// <returns>Locking state if bike is still occupied, null otherwise.</returns>
private static async Task<LockingState?> GetOccupiedBikeLockStateAsync(
this ICachedCopriServer corpiServer,
this ICachedCopriServer copriServer,
string bikeId)
{
var bikeReservedOrBooked = (await corpiServer.GetBikesOccupied(false))?.Response.bikes_occupied?.Values?.FirstOrDefault(x => x.bike == bikeId);
var bikeReservedOrBooked = (await copriServer.GetBikesOccupied(false))?.Response.bikes_occupied?.Values?.FirstOrDefault(x => x.bike == bikeId);
if (bikeReservedOrBooked != null)
{
return bikeReservedOrBooked.GetCopriLockingState();

View file

@ -3,15 +3,37 @@ using TINK.Model.Stations;
namespace TINK.Model.Services.CopriApi
{
/// <summary>
/// Holds stations and bikes.
/// </summary>
public class StationsAndBikesContainer
{
public StationsAndBikesContainer(StationDictionary stations, BikeCollection bikes)
/// <summary>
/// Holds station and bikes.
/// </summary>
/// <param name="stations">Stations information which contains some information about bikes available at each station (bike count, ...).</param>
/// <param name="bikesOccupied"></param>
public StationsAndBikesContainer(StationDictionary stations, BikeCollection bikesOccupied)
{
StationsAll = stations;
Bikes = bikes;
BikesOccupied = bikesOccupied;
}
/// <summary>
/// Holds all stations.
/// </summary>
/// <remarks>
/// Since copri version writing <see cref="StationType"/> (>= 4.1.23.03) stations contain bikes available information.
/// Prior to this copri version bikes available were part of <see cref="BikesOccupied"/>
/// </remarks>
public StationDictionary StationsAll { get; }
public BikeCollection Bikes { get; }
/// <summary>
/// Holds bikes occupied (i.e. bike reserved or booked).
/// </summary>
/// <remarks>
/// Up to copri version writing <see cref="StationType"/> (>= 4.1.23.03) bike available were contained beside bikes occupied.
/// </remarks>
public BikeCollection BikesOccupied { get; }
}
}

View file

@ -84,4 +84,7 @@
<LastGenOutput>AppResources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="TestShareeLib" />
</ItemGroup>
</Project>

View file

@ -101,7 +101,7 @@ namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
}
}
// Notify corpi about unlock action in order to start booking.
// Notify copri about unlock action in order to start booking.
BikesViewModel.ActionText = AppResources.ActivityTextRentingBike;
IsConnected = IsConnectedDelegate();
try

View file

@ -204,7 +204,7 @@ namespace TINK.ViewModel.Contact
? $"{stationId}" // there is a station marker with index letter for given station id
: "Open"; // there is no station marker. Use open marker.
var colorPartPrefix = GetRessourceNameColorPart(stationsColorList[pinIndex]);
var colorPartPrefix = GetResourceNameColorPart(stationsColorList[pinIndex]);
var l_iName = $"{indexPartPrefix.ToString().PadLeft(2, '0')}_{colorPartPrefix}{(DeviceInfo.Platform == DevicePlatform.Android ? ".png" : string.Empty)}";
try
@ -234,7 +234,7 @@ namespace TINK.ViewModel.Contact
/// <summary> Gets the color related part of the ressrouce name.</summary>
/// <param name="color">Color to get name for.</param>
/// <returns>Resource name.</returns>
private static string GetRessourceNameColorPart(Color color)
private static string GetResourceNameColorPart(Color color)
{
if (color == Color.Blue)
{
@ -395,7 +395,7 @@ namespace TINK.ViewModel.Contact
var colors = GetStationColors(
Pins.Select(x => x.Tag.ToString()).ToList(),
resultStationsAndBikes.Response.Bikes);
resultStationsAndBikes.Response.BikesOccupied);
// Update pins color form count of bikes located at station.
UpdatePinsColor(colors);

View file

@ -16,7 +16,7 @@ namespace TINK.ViewModel.Contact
public event PropertyChangedEventHandler PropertyChanged;
/// <summary> Holds value wether site caching is on or off.</summary>
/// <summary> Holds value whether site caching is on or off.</summary>
bool IsSiteCachingOn { get; }
/// <summary>
@ -64,7 +64,7 @@ namespace TINK.ViewModel.Contact
/// <summary> Constructs view model.</summary>
/// <param name="isSiteCachingOn">Set of user permissions</param>
/// <param name="resourceProvider">Delegate to get an an embedded html ressource. Used as fallback if download from web page does not work and cache is empty.</param>
/// <param name="resourceProvider">Delegate to get embedded html resource. Used as fallback if download from web page does not work and cache is empty.</param>
/// <param name="query">Object to query resources path values if required.</param>
public FeesAndBikesPageViewModel(
string hostName,

View file

@ -21,7 +21,7 @@ namespace TINK.ViewModel.Info
/// <summary> Holds the name of the host.</summary>
private string HostName { get; }
/// <summary> Holds value wether site caching is on or off.</summary>
/// <summary> Holds value whether site caching is on or off.</summary>
bool IsSiteCachingOn { get; }
/// <summary>
@ -74,11 +74,11 @@ namespace TINK.ViewModel.Info
/// <summary> Constructs Info view model</summary>
/// <param name="hostName">Name of the host to get html resources from.</param>
/// <param name="isSiteCachingOn">Holds value wether site caching is on or off.</param>
/// <param name="agbResourcePath"> Agb resouce path received from backend.</param>
/// <param name="privacyResourcePath"> Privacy resouce path received from backend.</param>
/// <param name="impressResourcePath"> Impress resouce path received from backend.</param>
/// <param name="resourceProvider">Delegate to get an an embedded html ressource. Used as fallback if download from web page does not work and cache is empty.</param>
/// <param name="isSiteCachingOn">Holds value whether site caching is on or off.</param>
/// <param name="agbResourcePath"> Agb resource path received from backend.</param>
/// <param name="privacyResourcePath"> Privacy resource path received from backend.</param>
/// <param name="impressResourcePath"> Impress resource path received from backend.</param>
/// <param name="resourceProvider">Delegate to get embedded html resource. Used as fallback if download from web page does not work and cache is empty.</param>
/// <param name="queryProvider">Object to query resources urls object from backend if required.</param>
/// <param name="updateUrlsAction">Action to update shared resources urls object</param>
public InfoPageViewModel(
@ -102,7 +102,7 @@ namespace TINK.ViewModel.Info
InfoAgb = new HtmlWebViewSource { Html = "<html>Loading...</html>" };
ResourceProvider = resourceProvider
?? throw new ArgumentException($"Can not instantiate {typeof(InfoPageViewModel)}-object. No ressource provider centered.");
?? throw new ArgumentException($"Can not instantiate {typeof(InfoPageViewModel)}-object. No resource provider centered.");
}
/// <summary> Called when page is shown. </summary>
@ -147,7 +147,7 @@ namespace TINK.ViewModel.Info
if (string.IsNullOrEmpty(PrivacyResourcePath))
{
// Information to access ressource is missing
// Information to access resource is missing
return new HtmlWebViewSource
{
Html = await Task.FromResult(ViewModelHelper.FromBody("No privacy resource available. Resource path is null or empty."))
@ -172,7 +172,7 @@ namespace TINK.ViewModel.Info
if (string.IsNullOrEmpty(ImpressResourcePath))
{
// Information to access ressource is missing
// Information to access resource is missing
return new HtmlWebViewSource
{
Html = await Task.FromResult(ViewModelHelper.FromBody("No impress resource available. Resource path is null or empty."))

View file

@ -329,7 +329,7 @@ namespace TINK.ViewModel
return;
}
// Swich to map page
// Switch to map page
#if USEFLYOUT
m_oViewService.ShowPage(ViewTypes.BikeInfoCarouselPage, AppResources.MarkingLoginInstructions);
#else

View file

@ -22,11 +22,11 @@ using TINK.Services.Permissions;
using Xamarin.Essentials;
using System.Threading;
using TINK.MultilingualResources;
using TINK.Services.BluetoothLock;
using TINK.Repository;
using TINK.Services.Geolocation;
using TINK.Model.State;
using TINK.ViewModel.Bikes;
using TINK.Model.Bikes.BikeInfoNS.BC;
using TINK.Model.Stations.StationNS;
#if !TRYNOTBACKSTYLE
#endif
@ -263,7 +263,7 @@ namespace TINK.ViewModel.Map
? $"{stationId}" // there is a station marker with index letter for given station id
: "Open"; // there is no station marker. Use open marker.
var colorPartPrefix = GetRessourceNameColorPart(stationsColorList[pinIndex]);
var colorPartPrefix = GetResourceNameColorPart(stationsColorList[pinIndex]);
var name = $"{indexPartPrefix.ToString().PadLeft(2, '0')}_{colorPartPrefix}{(DeviceInfo.Platform == DevicePlatform.Android ? ".png" : string.Empty)}";
try
{
@ -306,7 +306,7 @@ namespace TINK.ViewModel.Map
/// <summary> Gets the color related part of the ressrouce name.</summary>
/// <param name="color">Color to get name for.</param>
/// <returns>Resource name.</returns>
private static string GetRessourceNameColorPart(Color color)
private static string GetResourceNameColorPart(Color color)
{
if (color == Color.Blue)
{
@ -394,7 +394,8 @@ namespace TINK.ViewModel.Map
var colors = GetStationColors(
Pins.Select(x => x.Tag.ToString()).ToList(),
resultStationsAndBikes.Response.Bikes);
resultStationsAndBikes.Response.StationsAll,
resultStationsAndBikes.Response.BikesOccupied);
// Update pins color form count of bikes located at station.
UpdatePinsColor(colors);
@ -402,7 +403,7 @@ namespace TINK.ViewModel.Map
Log.ForContext<MapPageViewModel>().Verbose("Update pins color done.");
// Load MyBikes Count -> MyBikes Icon/Button
GetMyBikesCount(resultStationsAndBikes.Response.Bikes);
GetMyBikesCount(resultStationsAndBikes.Response.BikesOccupied);
// Move and scale before getting stations and bikes which takes some time.
ActionText = AppResources.ActivityTextCenterMap;
@ -655,7 +656,7 @@ namespace TINK.ViewModel.Map
}
// Load MyBikes Count -> MyBikes Icon/Button
GetMyBikesCount(resultStationsAndBikes.Response.Bikes);
GetMyBikesCount(resultStationsAndBikes.Response.BikesOccupied);
// Check if there are already any pins to the map.
// If no initialize pins.
@ -670,7 +671,8 @@ namespace TINK.ViewModel.Map
// Set/ update pins colors.
var l_oColors = GetStationColors(
Pins.Select(x => x.Tag.ToString()).ToList(),
resultStationsAndBikes.Response.Bikes);
resultStationsAndBikes.Response.StationsAll,
resultStationsAndBikes.Response.BikesOccupied);
// Update pins color form count of bikes located at station.
TinkApp.PostAction(
@ -758,10 +760,13 @@ namespace TINK.ViewModel.Map
/// Gets the list of station color for all stations.
/// </summary>
/// <param name="stationsId">Station id list to get color for.</param>
/// <param name="stations">Station object dictionary to get count of available bike from for each station.</param>
/// <param name="bikesReserved">Bike collection to get count of reserved/ rented bikes from for each station.</param>
/// <returns></returns>
private static IList<Color> GetStationColors(
internal static IList<Color> GetStationColors(
IEnumerable<string> stationsId,
BikeCollection bikesAll)
IEnumerable<IStation> stations,
IEnumerable<BikeInfo> bikesReserved)
{
if (stationsId == null)
{
@ -769,11 +774,14 @@ namespace TINK.ViewModel.Map
return new List<Color>();
}
if (bikesAll == null)
if (stations == null)
{
// If object is null an error occurred querying bikes centered or bikes occupied which results in an unknown state.
Log.ForContext<MapPageViewModel>().Error("No bikes available to determine pins color.");
return new List<Color>(stationsId.Select(x => Color.Blue));
Log.ForContext<MapPageViewModel>().Error("No stations info available to get count of bikes available to determine whether a pin is green or not.");
}
if (bikesReserved == null)
{
Log.ForContext<MapPageViewModel>().Error("No bikes info available to determine whether a pins is light blue or not.");
}
// Get state for each station.
@ -781,15 +789,14 @@ namespace TINK.ViewModel.Map
foreach (var stationId in stationsId)
{
// Get color of given station.
var bikesAtStation = bikesAll.Where(x => x.StationId == stationId).ToList();
if (bikesAtStation.FirstOrDefault(x => x.State.Value.IsOccupied()) != null)
if (bikesReserved?.Where(x => x.StationId == stationId).Count() > 0)
{
// There is at least one requested or booked bike
colors.Add(Color.LightBlue);
continue;
}
if (bikesAtStation.ToList().Count > 0)
if (stations?.FirstOrDefault(x => x.Id == stationId)?.AvailableBikesCount > 0)
{
// There is at least one bike available
colors.Add(Color.Green);
@ -1030,7 +1037,8 @@ namespace TINK.ViewModel.Map
Log.ForContext<MapPageViewModel>().Verbose("Starting update pins color on toggle...");
var l_oColors = GetStationColors(
Pins.Select(x => x.Tag.ToString()).ToList(),
resultStationsAndBikes.Response.Bikes);
resultStationsAndBikes.Response.StationsAll,
resultStationsAndBikes.Response.BikesOccupied);
// Update pins color form count of bikes located at station.
UpdatePinsColor(l_oColors);

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
@ -66,12 +66,12 @@ namespace TINK.Model.Connector
{
NextActiveUri = new Uri(serverTextToUri.ContainsKey(value) ? serverTextToUri[value] : value);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CorpiServerUriDescription)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CopriServerUriDescription)));
}
}
/// <summary> Holds the description of the picker, i.e. binds to label Text.</summary>
public string CorpiServerUriDescription
public string CopriServerUriDescription
{
get
{

View file

@ -65,21 +65,21 @@ namespace TINK.ViewModel
/// <summary> Reference on the tink app instance. </summary>
private ITinkApp TinkApp { get; }
IServicesContainer<IGeolocationService> GeoloctionServicesContainer { get; }
IServicesContainer<IGeolocationService> GeolocationServicesContainer { get; }
/// <summary> Constructs a settings page view model object.</summary>
/// <param name="tinkApp"> Reference to tink app model.</param>
/// <param name="geoloctionServicesContainer"></param>
/// <param name="geolocationServicesContainer"></param>
/// <param name="viewService">Interface to view</param>
public SettingsPageViewModel(
ITinkApp tinkApp,
IServicesContainer<IGeolocationService> geoloctionServicesContainer,
IServicesContainer<IGeolocationService> geolocationServicesContainer,
IViewService viewService)
{
TinkApp = tinkApp
?? throw new ArgumentException("Can not instantiate settings page view model- object. No tink app object available.");
GeoloctionServicesContainer = geoloctionServicesContainer
GeolocationServicesContainer = geolocationServicesContainer
?? throw new ArgumentException($"Can not instantiate {nameof(SettingsPageViewModel)}- object. Geolocation services container object must not be null.");
m_oViewService = viewService
@ -175,14 +175,14 @@ namespace TINK.ViewModel
TinkApp.LocksServices.Active.GetType().FullName));
GeolocationServices = new ServicesViewModel(
GeoloctionServicesContainer.Select(x => x.GetType().FullName),
GeolocationServicesContainer.Select(x => x.GetType().FullName),
new Dictionary<string, string> {
{ typeof(LastKnownGeolocationService).FullName, "LastKnowGeolocation" },
{ typeof(GeolocationAccuracyMediumService).FullName, "Medium Accuracy" },
{ typeof(GeolocationAccuracyHighService).FullName, "High Accuracy" },
{ typeof(GeolocationAccuracyBestService).FullName, "Best Accuracy" },
{ typeof(SimulatedGeolocationService).FullName, "Simulation-AlwaysSamePosition" } },
GeoloctionServicesContainer.Active.GetType().FullName);
GeolocationServicesContainer.Active.GetType().FullName);
StartupSettings = new PickerViewModel(
new Dictionary<string, string> {
@ -310,7 +310,7 @@ namespace TINK.ViewModel
TinkApp.LocksServices.SetActive(LocksServices.Services.Active);
GeoloctionServicesContainer.SetActive(GeolocationServices.Active);
GeolocationServicesContainer.SetActive(GeolocationServices.Active);
TinkApp.LocksServices.SetTimeOut(TimeSpan.FromSeconds(LocksServices.ConnectTimeoutSec));

View file

@ -174,7 +174,7 @@ namespace TINK.ViewModel
}
/// <summary> Gets error message and handles aggegate exceptions. </summary>
/// <summary> Gets error message and handles aggregate exceptions. </summary>
public static string GetErrorMessage(this Exception exception)
{
if (exception == null)
@ -239,8 +239,8 @@ namespace TINK.ViewModel
/// <summary> Called when page is shown. </summary>
/// <param name="resourceUrl">Url to load data from.</param>
/// <param name="isSiteCachingOn">Holds value wether site caching is on or off.</param>
/// <param name="resourceProvider"> Provides resource from embedded ressources.</param>
/// <param name="isSiteCachingOn">Holds value whether site caching is on or off.</param>
/// <param name="resourceProvider"> Provides resource from embedded resources.</param>
public static async Task<string> GetSource(
string resourceUrl,
bool isSiteCachingOn,
@ -271,7 +271,7 @@ namespace TINK.ViewModel
// An error occurred getting resource from web
htmlContent = Barrel.Current.Exists(resourceUrl)
? Barrel.Current.Get<string>(key: resourceUrl) // Get from MonkeyCache
: resourceProvider != null ? resourceProvider() : $"<DOCTYPE html>Error loading {resourceUrl}."; // Get build in ressource.
: resourceProvider != null ? resourceProvider() : $"<DOCTYPE html>Error loading {resourceUrl}."; // Get build in resource.
break;
default:
@ -280,7 +280,7 @@ namespace TINK.ViewModel
break;
}
return htmlContent ?? FromBody("An error occurred loading html- ressource.");
return htmlContent ?? FromBody("An error occurred loading html- resource.");
}
public static string FromBody(string message) => $"<!DOCTYPE html><html lang=\"de\"><head><title>Error Information</title></head><body>{message}</body></html>";

View file

@ -16,13 +16,13 @@ namespace TINK.ViewModel.WhatsNew.Agb
/// <summary> Holds the name of the host.</summary>
private string HostName { get; }
/// <summary> Holds value wether site caching is on or off.</summary>
/// <summary> Holds value whether site caching is on or off.</summary>
bool IsSiteCachingOn { get; }
/// <summary> Constructs AGB view model</summary>
/// <param name="isSiteCachingOn">Holds value wether site caching is on or off.</param>
/// <param name="isSiteCachingOn">Holds value whether site caching is on or off.</param>
/// <param name="uiIsoLangugageName">Two letter ISO language name.</param>
/// <param name="resourceProvider">Delegate to get an an embedded html ressource. Used as fallback if download from web page does not work and cache is empty.</param>
/// <param name="resourceProvider">Delegate to get embedded html resource. Used as fallback if download from web page does not work and cache is empty.</param>
/// <param name="viewService">View service to close page.</param>
public AgbViewModel(
string hostName,
@ -37,7 +37,7 @@ namespace TINK.ViewModel.WhatsNew.Agb
?? throw new ArgumentException($"Can not instantiate {typeof(WhatsNewViewModel)}-object. No view available.");
ResourceProvider = resourceProvider
?? throw new ArgumentException($"Can not instantiate {typeof(WhatsNewViewModel)}-object. No ressource provider centered.");
?? throw new ArgumentException($"Can not instantiate {typeof(WhatsNewViewModel)}-object. No resource provider centered.");
}
/// <summary> Gets the platfrom specific prefix. </summary>