using Plugin.Connectivity; using Serilog; using System; using System.Collections.Generic; using System.ComponentModel; using System.Threading.Tasks; using TINK.Model; using TINK.Model.Connector; using TINK.Repository.Exception; using TINK.View; using TINK.ViewModel.Settings; using System.Linq; using TINK.MultilingualResources; using TINK.ViewModel.Info; namespace TINK.ViewModel.Account { public class AccountPageViewModel : INotifyPropertyChanged { /// /// Count of requested/ booked bikes of logged in user. /// private int? m_iMyBikesCount; /// /// Reference on view service to show modal notifications and to perform navigation. /// private readonly IViewService m_oViewService; /// /// Holds the exception which occurred getting bikes occupied information. /// private Exception m_oException; /// /// Fired if a property changes. /// public event PropertyChangedEventHandler PropertyChanged; /// Object to manage update of view model objects from Copri. private IPollingUpdateTaskManager m_oViewUpdateManager; /// Holds a reference to the external trigger service. private Action OpenUrlInExternalBrowser { get; } /// Reference on the tink app instance. private ITinkApp TinkApp { get; } /// Constructs a settings page view model object. /// Reference to tink app model. /// /// /// Filter to apply on stations and bikes. /// Available copri server host uris including uri to use for next start. /// Holds whether to poll or not and the periode leght is polling is on. /// Default polling periode lenght. /// Controls logging level. /// Interface to view public AccountPageViewModel( ITinkApp tinkApp, Action openUrlInExternalBrowser, IViewService viewService) { TinkApp = tinkApp ?? throw new ArgumentException("Can not instantiate settings page view model- object. No tink app object available."); OpenUrlInExternalBrowser = openUrlInExternalBrowser ?? throw new ArgumentException("Can not instantiate settings page view model- object. No user external browse service available."); m_oViewService = viewService ?? throw new ArgumentException("Can not instantiate settings page view model- object. No user view service available."); m_oViewUpdateManager = new IdlePollingUpdateTaskManager(); Polling = new PollingViewModel(TinkApp.Polling); TinkApp.ActiveUser.StateChanged += OnStateChanged; } /// /// Log in state of user changed. /// /// /// private void OnStateChanged(object p_oSender, EventArgs p_oEventArgs) { var l_oPropertyChanged = PropertyChanged; if (l_oPropertyChanged != null) { l_oPropertyChanged(this, new PropertyChangedEventArgs(nameof(LoggedInInfo))); l_oPropertyChanged(this, new PropertyChangedEventArgs(nameof(IsBookingStateInfoVisible))); l_oPropertyChanged(this, new PropertyChangedEventArgs(nameof(BookingStateInfo))); } } /// Holds information whether app is connected to web or not. private bool? isConnected = null; /// Exposes the is connected state. private bool IsConnected { get => isConnected ?? false; set { var bookingStateInfo = BookingStateInfo; isConnected = value; if (bookingStateInfo == BookingStateInfo) { // Nothing to do. return; } PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(BookingStateInfo))); } } /// /// Gets the value of sessionn cookie (for debugging purposes). /// public string SessionCookie { get { return TinkApp.ActiveUser.SessionCookie; } } /// /// Gets value whether a user is logged in or not. /// public bool IsLogoutPossible { get { return m_oException == null && TinkApp.ActiveUser.IsLoggedIn; } } /// /// Is true if user is logged in. /// public bool IsLoggedIn { get { return TinkApp.ActiveUser.IsLoggedIn; } } /// /// Shows info about logged in user if user is logged in. Text "Logged out" otherwise. /// public string LoggedInInfo { get { if (!TinkApp.ActiveUser.IsLoggedIn) { return AppResources.MarkingLoggedInStateInfoNotLoggedIn; } return string.Format(AppResources.MarkingLoggedInStateInfoLoggedIn, TinkApp.ActiveUser.Mail); } } /// /// Gets a value indicating whether booking state info is avilable or not. /// public bool IsBookingStateInfoVisible { get { return BookingStateInfo.Length > 0; } } /// /// Count of bikes reserved and/ or occupied. /// private int? BikesOccupiedCount { get { return m_iMyBikesCount; } set { var isBookingStateInfoVisible = IsBookingStateInfoVisible; var bookingStateInfo = BookingStateInfo; m_iMyBikesCount = value; if (isBookingStateInfoVisible != IsBookingStateInfoVisible) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsBookingStateInfoVisible))); } if (bookingStateInfo != BookingStateInfo) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(BookingStateInfo))); } } } /// /// Shows info about reserved/ booked bikes if there are. /// public string BookingStateInfo { get { if (!TinkApp.ActiveUser.IsLoggedIn || !m_iMyBikesCount.HasValue || m_iMyBikesCount.Value <= 0) { // Either // - user is not logged in or // - user has no bikes requested/ booked return string.Empty; } var bookingStateInfo = string.Format("Aktuell {0} Fahrräder reserviert/ gebucht.", m_iMyBikesCount.Value); if (!IsConnected) { // Append offline info return $"{bookingStateInfo} Verbindungsstatus: Offline."; } if (Exception != null) { // Append offline info return $"{bookingStateInfo} Verbindungstatus: Verbindung unterbrochen. "; } return bookingStateInfo; } } /// Command object to bind logout button to view model. public System.Windows.Input.ICommand OnLogoutRequest { get { return new Xamarin.Forms.Command(async () => await Logout(), () => IsLogoutPossible); } } /// Processes request to view personal data. public System.Windows.Input.ICommand OnManageAccount => new Xamarin.Forms.Command(async () => { if (CrossConnectivity.Current.IsConnected) { await m_oViewService.PushAsync(ViewTypes.ManageAccountPage); } else { await m_oViewService.DisplayAlert( "Hinweis", "Bitte mit Internet verbinden zum Verwalten der persönlichen Daten.", "OK"); } }); /// /// Request to log out. /// public async Task Logout() { try { // Backup logout message before logout. var l_oMessage = string.Format("Benutzer {0} abgemeldet.", TinkApp.ActiveUser.Mail); // Stop polling before requesting bike. await m_oViewUpdateManager.StopUpdatePeridically(); try { // Log out user. IsConnected = TinkApp.GetIsConnected(); await TinkApp.GetConnector(IsConnected).Command.DoLogout(); } catch (UnsupportedCopriVersionDetectedException) { await m_oViewService.DisplayAlert( AppResources.MessageLogoutErrorTitle, string.Format(AppResources.MessageAppVersionIsOutdated, ContactPageViewModel.GetAppName(TinkApp.Uris.ActiveUri)), AppResources.MessageAnswerOk); // Restart polling again. await m_oViewUpdateManager.StartUpdateAyncPeridically(Polling.ToImmutable()); return; } catch (Exception l_oException) { // Copri server is not reachable. if (l_oException is WebConnectFailureException) { await m_oViewService.DisplayAlert( "Verbingungsfehler bei Abmeldung!", string.Format("{0}\r\n{1}", l_oException.Message, WebConnectFailureException.GetHintToPossibleExceptionsReasons), "OK"); } else { await m_oViewService.DisplayAlert("Fehler bei Abmeldung!", l_oException.Message, "OK"); } // Restart polling again. await m_oViewUpdateManager.StartUpdateAyncPeridically(Polling.ToImmutable()); return; } TinkApp.ActiveUser.Logout(); // Display information that log out was perfomrmed. await m_oViewService.DisplayAlert("Auf Wiedersehen!", l_oMessage, "OK"); } catch (Exception p_oException) { Log.Error("An unexpected error occurred displaying log out page. {@Exception}", p_oException); return; } try { // Switch to map view after log out. #if USEFLYOUT m_oViewService.ShowPage(ViewTypes.MapPage); #else await m_oViewService.ShowPage("//MapPage"); #endif } catch (Exception p_oException) { Log.Error("An unexpected error occurred switching back to map page after displaying log out page. {@Exception}", p_oException); return; } } /// /// Invoked when page is shown. /// Starts update process. /// public async Task OnAppearing() { IsConnected = TinkApp.GetIsConnected(); BikesOccupiedCount = TinkApp.ActiveUser.IsLoggedIn ? (await TinkApp.GetConnector(IsConnected).Query.GetBikesOccupiedAsync()).Response.Count : 0; m_oViewUpdateManager = new PollingUpdateTaskManager( () => { TinkApp.PostAction( unused => IsConnected = TinkApp.GetIsConnected(), null); int? l_iBikesCount = null; var bikesOccupied = TinkApp.GetConnector(IsConnected).Query.GetBikesOccupiedAsync().Result; l_iBikesCount = TinkApp.ActiveUser.IsLoggedIn ? bikesOccupied.Response.Count : 0; TinkApp.PostAction( unused => { BikesOccupiedCount = l_iBikesCount; Exception = bikesOccupied.Exception; }, null); } ); try { // Update bikes at station or my bikes depending on context. await m_oViewUpdateManager.StartUpdateAyncPeridically(Polling.ToImmutable()); } catch (Exception l_oExcetion) { Log.Error("Getting count of bikes on settings pags failed. {@l_oExcetion}.", l_oExcetion); } } /// /// Invoked when page is shutdown. /// Currently invoked by code behind, would be nice if called by XAML in future versions. /// public async Task OnDisappearing() { try { Log.ForContext().Information($"Entering {nameof(OnDisappearing)}..."); await m_oViewUpdateManager.StopUpdatePeridically(); } catch (Exception l_oException) { await m_oViewService.DisplayAlert( "Fehler", $"Ein unerwarteter Fehler ist aufgetreten. \r\n{l_oException.Message}", "OK"); } } /// /// Exception which occurred getting bike information. /// protected Exception Exception { get { return m_oException; } set { var l_oException = m_oException; var bookingStateInfo = BookingStateInfo; m_oException = value; if ((m_oException != null && l_oException == null) || (m_oException == null && l_oException != null)) { // Error information is available and was not or the other way round. PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsLogoutPossible))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsLogoutPossible))); } if (bookingStateInfo != BookingStateInfo) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(BookingStateInfo))); } } } /// Polling periode. public PollingViewModel Polling { get; } /// Opens login page. public void RegisterRequest(string url) { try { OpenUrlInExternalBrowser(url); } catch (Exception p_oException) { Log.Error("Ein unerwarteter Fehler ist auf der Einstellungs- Seite beim Öffnen eines Browsers, Seite {url}, aufgetreten. {@Exception}", url, p_oException); return; } } } }