Manually merged.

This commit is contained in:
Oliver Hauff 2021-12-08 17:57:30 +01:00
parent d5832e010e
commit c7c9f252af
112 changed files with 1127 additions and 352 deletions

View file

@ -0,0 +1,21 @@
using TINK.Model.MiniSurvey;
namespace TINK.Model
{
/// <summary>
/// Holds tasks to be accoumplished/ information shown to user after booking has finished.
/// </summary>
public class BookingFinishedModel
{
/// <summary>
/// Minisurvey to query user.
/// </summary>
public MiniSurveyModel MiniSurvey { get; set; } = new MiniSurveyModel();
/// <summary>
/// Holds info about co2 saving accomplished by using cargo bike.
/// </summary>
public string Co2Saving { get; set; }
}
}

View file

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

View file

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

View file

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

View file

@ -512,6 +512,46 @@ namespace TINK.Model.Connector
};
}
/// <summary> Creates a booking finished object from response.</summary>
/// <param name="response">Response to create survey object from.</param>
public static BookingFinishedModel Create(this DoReturnResponse response)
{
var bookingFinished = new BookingFinishedModel
{
Co2Saving = response?.co2saving
};
if (response?.user_miniquery == null)
{
return bookingFinished;
}
var miniquery = response.user_miniquery;
bookingFinished.MiniSurvey = new MiniSurveyModel
{
Title = miniquery.title,
Subtitle = miniquery.subtitle,
Footer = miniquery.footer
};
foreach (var question in miniquery?.questions?.OrderBy(x => x.Key) ?? new Dictionary<string, MiniSurveyResponse.Question>().OrderBy(x => x.Key))
{
if (string.IsNullOrEmpty(question.Key.Trim())
|| question.Value.query == null)
{
// Skip invalid entries.
continue;
}
bookingFinished.MiniSurvey.Questions.Add(
question.Key,
new MiniSurveyModel.QuestionModel());
}
return bookingFinished;
}
/// <summary> Creates a survey object from response.</summary>
/// <param name="response">Response to create survey object from.</param>
public static MiniSurveyModel Create(this ReservationCancelReturnResponse response)

View file

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

View file

@ -455,8 +455,13 @@ namespace TINK.Model
AppResources.ChangeLog3_0_250 // Third-party components updated.
},
{
new Version(3, 0, 262),
AppResources.ChangeLog3_0_262
new Version(3, 0, 260),
// Same info as for version 3.0.251 and 3.0.252
AppResources.ChangeLog3_0_231 // Minor improvements.
},
{
new Version(3, 0, 263),
AppResources.ChangeLog3_0_263
}
};

View file

@ -872,11 +872,11 @@ namespace TINK.MultilingualResources {
}
/// <summary>
/// Looks up a localized string similar to Geolocation permission request refactored..
/// Looks up a localized string similar to CO2 saving is displayed for bikes with GPS-lock after returning bike..
/// </summary>
public static string ChangeLog3_0_262 {
public static string ChangeLog3_0_263 {
get {
return ResourceManager.GetString("ChangeLog3_0_262", resourceCulture);
return ResourceManager.GetString("ChangeLog3_0_263", resourceCulture);
}
}

View file

@ -710,7 +710,7 @@ Kleinere Verbesserungen.</value>
Kartenzentrierfehler behoben.
Kleine Verbesserungen.</value>
</data>
<data name="ChangeLog3_0_262" xml:space="preserve">
<value>Anfrage nach Geolocation-Zugriffserlaubnis überarbeitet.</value>
<data name="ChangeLog3_0_263" xml:space="preserve">
<value>Bei Fahrrädern mit GPS-Schloss wird die CO2-Einsparung nach der Rückgabe des Fahrrads angezeigt.</value>
</data>
</root>

View file

@ -805,7 +805,7 @@ Minor fixes.</value>
Center map to current position issue fixed.
Minor improvements.</value>
</data>
<data name="ChangeLog3_0_262" xml:space="preserve">
<value>Geolocation permission request refactored.</value>
<data name="ChangeLog3_0_263" xml:space="preserve">
<value>CO2 saving is displayed for bikes with GPS-lock after returning bike.</value>
</data>
</root>

View file

@ -952,9 +952,9 @@ Minor improvements.</source>
Kartenzentrierfehler behoben.
Kleine Verbesserungen.</target>
</trans-unit>
<trans-unit id="ChangeLog3_0_262" translate="yes" xml:space="preserve">
<source>Geolocation permission request refactored.</source>
<target state="translated">Anfrage nach Geolocation-Zugriffserlaubnis überarbeitet.</target>
<trans-unit id="ChangeLog3_0_263" translate="yes" xml:space="preserve">
<source>CO2 saving is displayed for bikes with GPS-lock after returning bike.</source>
<target state="translated">Bei Fahrrädern mit GPS-Schloss wird die CO2-Einsparung nach der Rückgabe des Fahrrads angezeigt.</target>
</trans-unit>
</group>
</body>

View file

@ -204,7 +204,7 @@ namespace TINK.Repository
/// <param name="smartDevice">Provides info about hard and software.</param>
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
/// <returns>Response on returning request.</returns>
public async Task<ReservationCancelReturnResponse> DoReturn(
public async Task<DoReturnResponse> DoReturn(
string bikeId,
LocationDto location,
ISmartDevice smartDevice,
@ -615,16 +615,16 @@ namespace TINK.Repository
#endif
}
public static async Task<ReservationCancelReturnResponse> DoReturn(
public static async Task<DoReturnResponse> DoReturn(
string copriHost,
string command,
string userAgent = null)
{
#if !WINDOWS_UWP
string cancelOrReturnResponse;
string doReturnResponse;
try
{
cancelOrReturnResponse = await PostAsync(copriHost, command, userAgent);
doReturnResponse = await PostAsync(copriHost, command, userAgent);
}
catch (System.Exception l_oException)
{
@ -642,7 +642,7 @@ namespace TINK.Repository
}
// Extract bikes from response.
return JsonConvertRethrow.DeserializeObject<ResponseContainer<ReservationCancelReturnResponse>>(cancelOrReturnResponse)?.shareejson;
return JsonConvertRethrow.DeserializeObject<ResponseContainer<DoReturnResponse>>(doReturnResponse)?.shareejson;
#else
return null;
#endif

View file

@ -1625,14 +1625,12 @@ namespace TINK.Repository
return null;
}
public Task<ReservationCancelReturnResponse> DoReturn(
public Task<DoReturnResponse> DoReturn(
string bikeId,
LocationDto geolocation,
ISmartDevice smartDevice,
Uri operatorUri)
{
return null;
}
=> null;
public Task<SubmitFeedbackResponse> DoSubmitFeedback(string bikeId, string message, bool isBikeBroken, Uri operatorUri)
=> null;

View file

@ -172,14 +172,12 @@ namespace TINK.Repository
throw new System.Exception("Buchung im Offlinemodus nicht möglich!");
}
public Task<ReservationCancelReturnResponse> DoReturn(
public Task<DoReturnResponse> DoReturn(
string bikeId,
LocationDto geolocation,
ISmartDevice smartDevice,
Uri operatorUri)
{
throw new System.Exception("Rückgabe im Offlinemodus nicht möglich!");
}
=> throw new System.Exception("Rückgabe im Offlinemodus nicht möglich!");
public Task<SubmitFeedbackResponse> DoSubmitFeedback(string bikeId, string message, bool isBikeBroken, Uri operatorUri) =>
throw new System.Exception("Übermittlung von Feedback im Offlinemodus nicht möglich!");

View file

@ -4,7 +4,7 @@ namespace TINK.Repository.Exception
{
public class ReturnBikeException : ResponseException
{
public ReturnBikeException(ReservationCancelReturnResponse response, string message) : base(response, message)
public ReturnBikeException(BikesReservedOccupiedResponse response, string message) : base(response, message)
{ }
}
}

View file

@ -80,7 +80,7 @@ namespace TINK.Repository
/// <param name="smartDevice">Provides info about hard and software.</param>
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
/// <returns>Response on returning request.</returns>
Task<ReservationCancelReturnResponse> DoReturn(
Task<DoReturnResponse> DoReturn(
string bikeId,
LocationDto location,
ISmartDevice smartDevice,

View file

@ -0,0 +1,15 @@
using System.Runtime.Serialization;
namespace TINK.Repository.Response
{
public class DoReturnResponse : BikesReservedOccupiedResponse
{
/// <summary> Mini survey.</summary>
[DataMember]
public MiniSurveyResponse user_miniquery { get; private set; }
[DataMember]
public string co2saving { get; private set; }
}
}

View file

@ -1,5 +1,4 @@

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

View file

@ -181,8 +181,8 @@ namespace TINK.Repository.Response
/// <param name="textOfAction">Text describing request which is shown if validation fails.</param>
/// <param name="bikeId">Id of bike.</param>
/// <returns>Verified response.</returns>
public static ReservationCancelReturnResponse GetIsReturnBikeResponseOk(
this ReservationCancelReturnResponse returnBikeResponse,
public static DoReturnResponse GetIsReturnBikeResponseOk(
this DoReturnResponse returnBikeResponse,
string bikeId)
{
// Check if bike is at station.

View file

@ -234,14 +234,12 @@ namespace TINK.Model.Services.CopriApi
return await HttpsServer.DoBookAsync(bikeId, guid, batteryPercentage, operatorUri);
}
public async Task<ReservationCancelReturnResponse> DoReturn(
public async Task<DoReturnResponse> DoReturn(
string bikeId,
LocationDto location,
ISmartDevice smartDevice,
Uri operatorUri)
{
return await HttpsServer.DoReturn(bikeId, location, smartDevice, operatorUri);
}
=> await HttpsServer.DoReturn(bikeId, location, smartDevice, operatorUri);
/// <summary>
/// Submits feedback to copri server.

View file

@ -53,14 +53,12 @@ namespace TINK.Model.Services.CopriApi
return await monkeyStore.DoBookAsync(bikeId, guid, batteryPercentage, operatorUri);
}
public async Task<ReservationCancelReturnResponse> DoReturn(
public async Task<DoReturnResponse> DoReturn(
string bikeId,
LocationDto geolocation,
ISmartDevice smartDevice,
Uri operatorUri)
{
return await monkeyStore.DoReturn(bikeId, geolocation, smartDevice, operatorUri);
}
=> await monkeyStore.DoReturn(bikeId, geolocation, smartDevice, operatorUri);
public Task<SubmitFeedbackResponse> DoSubmitFeedback(string bikeId, string messge, bool bIsBikeBroke, Uri operatorUri)
=> throw new NotImplementedException();

View file

@ -0,0 +1,59 @@
using System.Threading.Tasks;
namespace TINK.Services.Permissions.Plugin
{
using global::Plugin.Permissions;
public class Permissions : ILocationPermission
{
/// <summary> Checks the permission status.</summary>
public async Task<Status> CheckStatusAsync()
{
switch (await CrossPermissions.Current.CheckPermissionStatusAsync<LocationPermission>())
{
case global::Plugin.Permissions.Abstractions.PermissionStatus.Denied:
return Status.Denied;
case global::Plugin.Permissions.Abstractions.PermissionStatus.Granted:
return Status.Granted;
case global::Plugin.Permissions.Abstractions.PermissionStatus.Unknown:
return Status.Unknown;
default:
// Comprises
// - PermissionStatus.Disabled and
// - PermissionStatus.Restricted.
return Status.DeniedRequiresSettingsUI;
}
}
/// <summary> Requests location permission.</summary>
/// <returns>Permission status after request.</returns>
public async Task<Status> RequestAsync()
{
switch (await CrossPermissions.Current.RequestPermissionAsync<LocationPermission>())
{
case global::Plugin.Permissions.Abstractions.PermissionStatus.Denied:
return Status.Denied;
case global::Plugin.Permissions.Abstractions.PermissionStatus.Granted:
return Status.Granted;
case global::Plugin.Permissions.Abstractions.PermissionStatus.Unknown:
return Status.Unknown;
default:
// Comprises
// - PermissionStatus.Disabled and
// - PermissionStatus.Restricted.
return Status.DeniedRequiresSettingsUI;
}
}
/// <summary> Opens app settings dialog.</summary>
public bool OpenAppSettings()
=> CrossPermissions.Current.OpenAppSettings();
}
}

View file

@ -50,6 +50,7 @@
</ItemGroup>
<ItemGroup>
<Folder Include="Services\Permissions\Essentials\" />
<Folder Include="Services\Permissions\Plugin\" />
<Folder Include="ViewModel\Info\BikeInfo\" />
<Folder Include="ViewModel\FeesAndBikes\" />
</ItemGroup>

View file

@ -77,7 +77,10 @@ namespace TINK.View
/// <summary> Pushes a page onto the modal stack. </summary>
Task PopModalAsync();
Task<IUserFeedback> DisplayUserFeedbackPopup();
/// <summary> Displays user feedback popup.</summary>
/// <param name="co2Saving"> Co2 saving information.</param>
/// <returns>User feedback.</returns>
Task<IUserFeedback> DisplayUserFeedbackPopup(string co2Saving = null);
#if USCSHARP9
/// <summary>

View file

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

View file

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

View file

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