mirror of
https://dev.azure.com/TeilRad/sharee.bike%20App/_git/Code
synced 2025-06-22 13:57:28 +02:00
Version 3.0.363
This commit is contained in:
parent
4ff3307997
commit
91d42552c7
212 changed files with 1799 additions and 1318 deletions
|
@ -129,7 +129,7 @@ namespace TINK.Model.Connector
|
|||
string BikeId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the current chargeing level of the battery in bars, null if unkonwn.
|
||||
/// Holds the current charging level of the battery in bars, null if unknown.
|
||||
/// </summary>
|
||||
int? CurrentChargeBars { get; set; }
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
|
||||
|
||||
namespace TINK.Model.Connector
|
||||
{
|
||||
#if USCSHARP9
|
||||
|
@ -18,7 +18,7 @@ namespace TINK.Model.Connector
|
|||
public string BikeId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the current chargeing level of the battery in bars, null if unkonwn.
|
||||
/// Holds the current charging level of the battery in bars, null if unknown.
|
||||
/// </summary>
|
||||
public int? CurrentChargeBars { get; set; }
|
||||
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using TINK.Model.Bikes;
|
||||
using TINK.Model.Connector.Filter;
|
||||
using TINK.Model.Services.CopriApi;
|
||||
using TINK.Model.Station;
|
||||
|
||||
using TINK.Model.Stations;
|
||||
using TINK.Model.Stations.StationNS;
|
||||
using BikeInfo = TINK.Model.Bikes.BikeInfoNS.BC.BikeInfo;
|
||||
|
||||
namespace TINK.Model.Connector
|
||||
{
|
||||
/// <summary> Filters connector respones.</summary>
|
||||
/// <summary> Filters connector responses.</summary>
|
||||
/// <remarks>Former name: Filter</remarks>
|
||||
public class FilteredConnector : IFilteredConnector
|
||||
{
|
||||
|
@ -50,7 +50,7 @@ namespace TINK.Model.Connector
|
|||
/// <summary> Holds the filter. </summary>
|
||||
private IGroupFilter Filter { get; }
|
||||
|
||||
/// <summary> Holds the reference to object which performs copry queries.</summary>
|
||||
/// <summary> Holds the reference to object which performs copri queries.</summary>
|
||||
private IQuery m_oInnerQuery;
|
||||
|
||||
/// <summary> Constructs a query object.</summary>
|
||||
|
@ -111,7 +111,7 @@ namespace TINK.Model.Connector
|
|||
return bikes.Where(x => filter.DoFilter(x.Group).Count() > 0).ToDictionary(x => x.Id);
|
||||
}
|
||||
|
||||
/// <summary> Filter stations by broup. </summary>
|
||||
/// <summary> Filter stations by group. </summary>
|
||||
/// <returns></returns>
|
||||
private static Dictionary<string, IStation> DoFilter(StationDictionary stations, IGroupFilter filter)
|
||||
{
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using TINK.Model.Bikes;
|
||||
using TINK.Model.Services.CopriApi;
|
||||
using TINK.Model.Station;
|
||||
|
||||
using TINK.Model.Stations;
|
||||
using TINK.Model.Stations.StationNS;
|
||||
using BikeInfo = TINK.Model.Bikes.BikeInfoNS.BC.BikeInfo;
|
||||
|
||||
namespace TINK.Model.Connector
|
||||
{
|
||||
/// <summary> Filters connector respones.</summary>
|
||||
/// <summary> Filters connector responses.</summary>
|
||||
public class NullFilterConnector : IFilteredConnector
|
||||
{
|
||||
/// <summary> Constructs a filter object. </summary>
|
||||
|
@ -44,7 +44,7 @@ namespace TINK.Model.Connector
|
|||
/// <summary> Object to perform filtered queries.</summary>
|
||||
private class QueryProvider : IQuery
|
||||
{
|
||||
/// <summary> Holds the reference to object which performs copry queries.</summary>
|
||||
/// <summary> Holds the reference to object which performs copri queries.</summary>
|
||||
private IQuery m_oInnerQuery;
|
||||
|
||||
/// <summary> Constructs a query object.</summary>
|
||||
|
@ -100,7 +100,7 @@ namespace TINK.Model.Connector
|
|||
return bikes.Where(x => x.Group.Intersect(filter).Count() > 0).ToDictionary(x => x.Id);
|
||||
}
|
||||
|
||||
/// <summary> Filter stations by broup. </summary>
|
||||
/// <summary> Filter stations by group. </summary>
|
||||
/// <returns></returns>
|
||||
public static Dictionary<string, IStation> DoFilter(StationDictionary stations, IEnumerable<string> p_oFilter)
|
||||
{
|
||||
|
|
|
@ -29,7 +29,7 @@ namespace TINK.Model.Connector
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary> Gets all stations including postions and bikes.</summary>
|
||||
/// <summary> Gets all stations including positions and bikes.</summary>
|
||||
public async Task<Result<StationsAndBikesContainer>> GetBikesAndStationsAsync()
|
||||
{
|
||||
var resultStations = await server.GetStations();
|
||||
|
@ -73,7 +73,7 @@ namespace TINK.Model.Connector
|
|||
/// <returns>Collection of bikes.</returns>
|
||||
public async Task<Result<BikeCollection>> GetBikesOccupiedAsync()
|
||||
{
|
||||
Log.ForContext<CachedQuery>().Error("Unexpected call to get be bikes occpied detected. No user is logged in.");
|
||||
Log.ForContext<CachedQuery>().Error("Unexpected call to get be bikes occupied detected. No user is logged in.");
|
||||
|
||||
return new Result<BikeCollection>(
|
||||
typeof(CopriCallsMonkeyStore),
|
||||
|
|
|
@ -29,7 +29,7 @@ namespace TINK.Model.Connector
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary> Gets all stations including postions.</summary>
|
||||
/// <summary> Gets all stations including positions.</summary>
|
||||
public async Task<Result<StationsAndBikesContainer>> GetBikesAndStationsAsync()
|
||||
{
|
||||
var stationsResponse = await Server.GetStations();
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks;
|
||||
using TINK.Model.Bikes;
|
||||
using TINK.Model.Services.CopriApi;
|
||||
|
||||
|
@ -6,7 +6,7 @@ namespace TINK.Model.Connector
|
|||
{
|
||||
public interface IQuery
|
||||
{
|
||||
/// <summary> Gets all stations including postions.</summary>
|
||||
/// <summary> Gets all stations including positions.</summary>
|
||||
Task<Result<StationsAndBikesContainer>> GetBikesAndStationsAsync();
|
||||
|
||||
/// <summary> Gets bikes occupied is a user is logged in. </summary>
|
||||
|
|
|
@ -28,7 +28,7 @@ namespace TINK.Model.Connector
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary> Gets all stations including postions.</summary>
|
||||
/// <summary> Gets all stations including positions.</summary>
|
||||
public async Task<Result<StationsAndBikesContainer>> GetBikesAndStationsAsync()
|
||||
{
|
||||
var stationsAllResponse = await server.GetStationsAsync();
|
||||
|
@ -44,7 +44,7 @@ namespace TINK.Model.Connector
|
|||
/// <returns>Collection of bikes.</returns>
|
||||
public async Task<Result<BikeCollection>> GetBikesOccupiedAsync()
|
||||
{
|
||||
Log.ForContext<Query>().Error("Unexpected call to get be bikes occpied detected. No user is logged in.");
|
||||
Log.ForContext<Query>().Error("Unexpected call to get be bikes occupied detected. No user is logged in.");
|
||||
return new Result<BikeCollection>(
|
||||
typeof(CopriCallsMonkeyStore),
|
||||
await Task.FromResult(new BikeCollection(new Dictionary<string, BikeInfo>())),
|
||||
|
|
|
@ -30,7 +30,7 @@ namespace TINK.Model.Connector
|
|||
server = copriServer as ICopriServer;
|
||||
}
|
||||
|
||||
/// <summary> Gets all stations including postions.</summary>
|
||||
/// <summary> Gets all stations including positions.</summary>
|
||||
public async Task<Result<StationsAndBikesContainer>> GetBikesAndStationsAsync()
|
||||
{
|
||||
var stationResponse = await server.GetStationsAsync();
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
@ -9,6 +9,7 @@ using TINK.Model.Services.CopriApi.ServerUris;
|
|||
using TINK.Model.State;
|
||||
using TINK.Repository.Exception;
|
||||
using TINK.Repository.Response;
|
||||
using TINK.Repository.Response.Stations.Station;
|
||||
|
||||
namespace TINK.Model.Connector
|
||||
{
|
||||
|
@ -26,7 +27,7 @@ namespace TINK.Model.Connector
|
|||
/// </summary>
|
||||
/// <param name="stationInfo">Object to get information from.</param>
|
||||
/// <returns>Position information.</returns>
|
||||
public static IPosition GetPosition(this StationsAvailableResponse.StationInfo stationInfo)
|
||||
public static IPosition GetPosition(this StationInfo stationInfo)
|
||||
=> GetPosition(stationInfo.gps);
|
||||
|
||||
/// <summary> Gets the position from StationInfo object. </summary>
|
||||
|
@ -52,14 +53,14 @@ namespace TINK.Model.Connector
|
|||
if (group == null || group.Length == 0)
|
||||
{
|
||||
// If not logged in stations groups are empty form COPRI version v4.1.
|
||||
Log.Debug("Can not get goup form string. Group text can not be null.");
|
||||
Log.Debug("Can not get group form string. Group text can not be null.");
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
return new HashSet<string>(group).ToList();
|
||||
}
|
||||
|
||||
/// <summary> Gets if user acknowldged ags or not. </summary>
|
||||
/// <summary> Gets if user acknowledged AGBs or not. </summary>
|
||||
/// <param name="authorizationResponse">Object to get information from.</param>
|
||||
/// <returns>Position information.</returns>
|
||||
public static bool GetIsAgbAcknowledged(this AuthorizationResponse authorizationResponse)
|
||||
|
@ -77,7 +78,7 @@ namespace TINK.Model.Connector
|
|||
/// <summary> Gets the position from StationInfo object. </summary>
|
||||
/// <param name="stationInfo">Object to get information from.</param>
|
||||
/// <returns>Position information.</returns>
|
||||
public static IEnumerable<string> GetGroup(this StationsAvailableResponse.StationInfo stationInfo)
|
||||
public static IEnumerable<string> GetGroup(this StationInfo stationInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -297,8 +298,19 @@ namespace TINK.Model.Connector
|
|||
? typeOfBike
|
||||
: (TypeOfBike?)null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether bike is a AA bike (bike must be always returned a the same station) or AB bike (start and end stations can be different stations).
|
||||
/// </summary>
|
||||
/// <param name="bikeInfo">Object to get AA info from.</param>
|
||||
/// <returns>AA info.</returns>
|
||||
public static AaRideType? GetAaRideType(this BikeInfoBase bikeInfo)
|
||||
=> Enum.TryParse(bikeInfo?.aa_ride, true, out AaRideType aaRide)
|
||||
? aaRide
|
||||
: (AaRideType?)null;
|
||||
|
||||
|
||||
/// <summary> Get position from a ,- separated string. </summary>
|
||||
/// <param name="gps">Text to extract positon from.</param>
|
||||
/// <param name="gps">Text to extract position from.</param>
|
||||
/// <returns>Position object.</returns>
|
||||
public static IPosition GetPosition(Repository.Response.Position gps)
|
||||
=> PositionFactory.Create(
|
||||
|
@ -306,7 +318,7 @@ namespace TINK.Model.Connector
|
|||
double.TryParse(gps?.longitude, NumberStyles.Float, CultureInfo.InvariantCulture, out double longitude) ? longitude : double.NaN);
|
||||
|
||||
/// <summary> Get position from a ,- separated string. </summary>
|
||||
/// <param name="gps">Text to extract positon from.</param>
|
||||
/// <param name="gps">Text to extract position from.</param>
|
||||
/// <returns>Position object.</returns>
|
||||
public static Map.IMapSpan GetMapSpan(this MapSpan mapSpan)
|
||||
=> Map.MapSpanFactory.Create(
|
||||
|
@ -343,7 +355,7 @@ namespace TINK.Model.Connector
|
|||
/// Gets the operator Uri from response.
|
||||
/// </summary>
|
||||
/// <param name="bikeInfo"> Response to get uri from.</param>
|
||||
/// <returns>Operatore Uri</returns>
|
||||
/// <returns>Operator Uri</returns>
|
||||
public static Uri GetOperatorUri(this BikeInfoBase bikeInfo)
|
||||
{
|
||||
return bikeInfo?.uri_operator != null && !string.IsNullOrEmpty(bikeInfo?.uri_operator)
|
||||
|
@ -351,7 +363,7 @@ namespace TINK.Model.Connector
|
|||
: null;
|
||||
}
|
||||
|
||||
/// <summary> Tries to get the copriversion from response.</summary>
|
||||
/// <summary> Tries to get the copri version from response.</summary>
|
||||
/// <param name="response">Response to get version info from.</param>
|
||||
/// <returns>COPRI version</returns>
|
||||
public static bool TryGetCopriVersion(this CopriVersion response, out Version copriVersion)
|
||||
|
@ -362,7 +374,7 @@ namespace TINK.Model.Connector
|
|||
&& Version.TryParse(response.copri_version, out copriVersion);
|
||||
}
|
||||
|
||||
/// <summary> Gets the copriversion from.</summary>
|
||||
/// <summary> Gets the copri version from.</summary>
|
||||
/// <param name="response">Response to get version info from.</param>
|
||||
/// <returns>COPRI version</returns>
|
||||
public static Version GetCopriVersion(this CopriVersion response)
|
||||
|
|
|
@ -7,7 +7,6 @@ using TINK.Model.MiniSurvey;
|
|||
using TINK.Model.State;
|
||||
using TINK.Repository.Response;
|
||||
using BikeExtension = TINK.Model.Bikes.BikeInfoNS.BikeNS.BikeExtension;
|
||||
using BikeInfo = TINK.Model.Bikes.BikeInfoNS.BC.BikeInfo;
|
||||
|
||||
namespace TINK.Model.Connector.Updater
|
||||
{
|
||||
|
@ -63,7 +62,7 @@ namespace TINK.Model.Connector.Updater
|
|||
|
||||
var lockType = lockModel.HasValue
|
||||
? BikeExtension.GetLockType(lockModel.Value)
|
||||
: BikeExtension.GetLockType(DEFAULTLOCKMODEL); // Map bikes without "system"- entry in response to backend- locks.
|
||||
: BikeExtension.GetLockType(DEFAULTLOCKMODEL); // Map bikes without "system"- entry in response to back end- locks.
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -76,6 +75,7 @@ namespace TINK.Model.Connector.Updater
|
|||
LockModel.Sigo,
|
||||
bikeInfo.GetWheelType(),
|
||||
bikeInfo.GetTypeOfBike(),
|
||||
bikeInfo.GetAaRideType(),
|
||||
bikeInfo.description),
|
||||
DriveFactory.Create(bikeInfo?.bike_type),
|
||||
dataSource,
|
||||
|
@ -94,7 +94,7 @@ namespace TINK.Model.Connector.Updater
|
|||
bikeInfo.GetGroup(),
|
||||
miniSurvey: bikeInfo.user_miniquery != null
|
||||
? new MiniSurveyModel(new Dictionary<string, IQuestionModel> {
|
||||
{ "q1", new QuestionModel()} // Add a dummy querry. Querries are not yet read from COPRI but compiled into the app.
|
||||
{ "q1", new QuestionModel()} // Add a dummy query. Queries are not yet read from COPRI but compiled into the app.
|
||||
})
|
||||
: new MiniSurveyModel(),
|
||||
co2Saving: bikeInfo.co2saving);
|
||||
|
@ -106,6 +106,7 @@ namespace TINK.Model.Connector.Updater
|
|||
LockModel.ILockIt,
|
||||
bikeInfo.GetWheelType(),
|
||||
bikeInfo.GetTypeOfBike(),
|
||||
bikeInfo.GetAaRideType(),
|
||||
bikeInfo.description),
|
||||
DriveFactory.Create(bikeInfo?.bike_type),
|
||||
dataSource,
|
||||
|
@ -129,7 +130,7 @@ namespace TINK.Model.Connector.Updater
|
|||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
// Contructor reported invalid arguemts (missing lock id, ....).
|
||||
// Constructor reported invalid arguments (missing lock id, ....).
|
||||
Log.Error($"Can not create new {nameof(BikeInfo)}-object from {nameof(BikeInfoAvailable)} argument. Invalid response detected. Available bike with id {bikeInfo.bike} skipped. {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
|
@ -184,6 +185,7 @@ namespace TINK.Model.Connector.Updater
|
|||
LockModel.ILockIt,
|
||||
bikeInfo.GetWheelType(),
|
||||
bikeInfo.GetTypeOfBike(),
|
||||
bikeInfo.GetAaRideType(),
|
||||
bikeInfo.description),
|
||||
DriveFactory.Create(bikeInfo?.bike_type),
|
||||
dataSource,
|
||||
|
@ -214,6 +216,7 @@ namespace TINK.Model.Connector.Updater
|
|||
LockModel.Sigo,
|
||||
bikeInfo.GetWheelType(),
|
||||
bikeInfo.GetTypeOfBike(),
|
||||
bikeInfo.GetAaRideType(),
|
||||
bikeInfo.description),
|
||||
DriveFactory.Create(bikeInfo?.bike_type),
|
||||
dataSource,
|
||||
|
@ -238,7 +241,7 @@ namespace TINK.Model.Connector.Updater
|
|||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
// Contructor reported invalid arguemts (missing lock id, ....).
|
||||
// Constructor reported invalid arguments (missing lock id, ....).
|
||||
Log.Error($"Can not create new {nameof(BikeInfo)}-object from {nameof(BikeInfoReservedOrBooked)} argument. Invalid response detected. Reserved bike with id {bikeInfo.bike} skipped. {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
|
@ -255,6 +258,7 @@ namespace TINK.Model.Connector.Updater
|
|||
LockModel.ILockIt,
|
||||
bikeInfo.GetWheelType(),
|
||||
bikeInfo.GetTypeOfBike(),
|
||||
bikeInfo.GetAaRideType(),
|
||||
bikeInfo.description),
|
||||
DriveFactory.Create(bikeInfo?.bike_type),
|
||||
dataSource,
|
||||
|
@ -278,29 +282,8 @@ namespace TINK.Model.Connector.Updater
|
|||
bikeInfo.GetGroup());
|
||||
|
||||
case LockModel.BordComputer:
|
||||
return new BikeInfo(
|
||||
new Bike(
|
||||
bikeInfo.bike,
|
||||
LockModel.BordComputer,
|
||||
bikeInfo.GetWheelType(),
|
||||
bikeInfo.GetTypeOfBike(),
|
||||
bikeInfo.description),
|
||||
DriveFactory.Create(bikeInfo?.bike_type),
|
||||
dataSource,
|
||||
bikeInfo.GetIsDemo(),
|
||||
bikeInfo.GetGroup(),
|
||||
bikeInfo.station,
|
||||
bikeInfo.GetOperatorUri(),
|
||||
#if !NOTARIFFDESCRIPTION
|
||||
bikeInfo.rental_description != null
|
||||
? RentalDescriptionFactory.Create(bikeInfo.rental_description)
|
||||
: TariffDescriptionFactory.Create(bikeInfo.tariff_description),
|
||||
#else
|
||||
Create((TINK.Repository.Response.TariffDescription)null),
|
||||
#endif
|
||||
bikeInfo.GetFrom(),
|
||||
mailAddress,
|
||||
bikeInfo.timeCode);
|
||||
throw new NotSupportedException($"Bikes with lock model of type {lockModel} are no more supported.");
|
||||
|
||||
default:
|
||||
return new Bikes.BikeInfoNS.CopriLock.BikeInfo(
|
||||
new Bike(
|
||||
|
@ -308,6 +291,7 @@ namespace TINK.Model.Connector.Updater
|
|||
LockModel.Sigo,
|
||||
bikeInfo.GetWheelType(),
|
||||
bikeInfo.GetTypeOfBike(),
|
||||
bikeInfo.GetAaRideType(),
|
||||
bikeInfo.description),
|
||||
DriveFactory.Create(bikeInfo?.bike_type),
|
||||
DataSource.Copri,
|
||||
|
|
|
@ -1,17 +1,14 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Serilog;
|
||||
using TINK.Model.Bikes;
|
||||
using TINK.Model.Bikes.BikeInfoNS.BC;
|
||||
using TINK.Model.State;
|
||||
using TINK.Model.Station;
|
||||
using TINK.Model.Station.Operator;
|
||||
using TINK.Model.Stations;
|
||||
using TINK.Model.Stations.StationNS.Operator;
|
||||
using TINK.Model.User.Account;
|
||||
using TINK.Repository.Exception;
|
||||
using TINK.Repository.Response;
|
||||
using TINK.Repository.Response.Stations;
|
||||
using TINK.Services.CopriApi;
|
||||
using Xamarin.Forms;
|
||||
using BikeInfo = TINK.Model.Bikes.BikeInfoNS.BC.BikeInfo;
|
||||
using IBikeInfoMutable = TINK.Model.Bikes.BikeInfoNS.BC.IBikeInfoMutable;
|
||||
|
||||
namespace TINK.Model.Connector.Updater
|
||||
|
@ -31,7 +28,7 @@ namespace TINK.Model.Connector.Updater
|
|||
=> bike.State.Load(InUseStateEnum.Disposable, notifyLevel: notifyLevel);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all statsion for station provider and add them into station list.
|
||||
/// Gets all station 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 StationsAvailableResponse stationsAllResponse)
|
||||
|
@ -57,7 +54,7 @@ namespace TINK.Model.Connector.Updater
|
|||
string.Format("Station id {0} is not unique.", station.Value.station), stationsAllResponse);
|
||||
}
|
||||
|
||||
stations.Add(new Station.Station(
|
||||
stations.Add(new Stations.StationNS.Station(
|
||||
station.Value.station,
|
||||
station.Value.GetGroup(),
|
||||
station.Value.GetPosition(),
|
||||
|
@ -89,7 +86,7 @@ namespace TINK.Model.Connector.Updater
|
|||
new ResourceUrls(response.tariff_info_html, response.bike_info_html, response.agb_html, response.privacy_html, response.impress_html));
|
||||
|
||||
/// <summary> Gets account object from login response.</summary>
|
||||
/// <param name="merchantId">Needed to extract cookie from autorization response.</param>
|
||||
/// <param name="merchantId">Needed to extract cookie from authorization response.</param>
|
||||
/// <param name="loginResponse">Response to get session cookie and debug level from.</param>
|
||||
/// <param name="mail">Mail address needed to construct a complete account object (is not part of response).</param>
|
||||
/// <param name="password">Password needed to construct a complete account object (is not part of response).</param>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue