using System.Windows.Input; using Xamarin.Forms; using TINK.View; using TINK.Model.User; using TINK.Model.User.Account; using System.ComponentModel; using System; using System.Threading.Tasks; using TINK.Repository.Exception; using Serilog; using TINK.ViewModel.Map; using Plugin.Connectivity; using TINK.Model; using System.Linq; using System.Collections.Generic; using TINK.MultilingualResources; using TINK.ViewModel.Info; namespace TINK.ViewModel { public class LoginPageViewModel : INotifyPropertyChanged { /// /// Reference on view service to show modal notifications and to perform navigation. /// private readonly IViewService m_oViewService; #if BACKSTYLE /// Reference to naviagion object to navigate back to map page when login succeeded. private INavigation m_oNavigation; #endif /// Reference on the tink app instance. private ITinkApp TinkApp { get; } /// /// Holds the mail address candidate being entered by user. /// private string m_strMailAddress; /// /// Holds the password candidate being entered by user. /// private string m_strPassword; private bool m_bMailAndPasswordCandidatesOk = false; /// Holds a reference to the external trigger service. private Action OpenUrlInExternalBrowser { get; } /// /// Event which notifies clients about changed properties. /// public event PropertyChangedEventHandler PropertyChanged; /// /// /// /// Delegate to set new mail address and password to model. /// Mail address of user if login succeeded. /// Action to open an external browser. /// Interface to actuate methodes on GUI. /// Interface to navigate. public LoginPageViewModel( ITinkApp tinkApp, Action openUrlInExternalBrowser, #if !BACKSTYLE IViewService p_oViewService) #else IViewService p_oViewService, INavigation p_oNavigation) #endif { TinkApp = tinkApp ?? throw new ArgumentException("Can not instantiate map page view model- object. No tink app object available."); OpenUrlInExternalBrowser = openUrlInExternalBrowser ?? throw new ArgumentException("Can not instantiate login page view model- object. No user external browse service available."); m_oViewService = p_oViewService ?? throw new ArgumentException("Can not instantiate login page view model- object. No view available."); #if BACKSTYLE m_oNavigation = p_oNavigation ?? throw new ArgumentException("Can not instantiate login page view model- object. No navigation service available."); #endif m_strMailAddress = tinkApp.ActiveUser.Mail; m_strPassword = tinkApp.ActiveUser.Password; IsRegisterTargetsInfoVisible = new List { Model.Services.CopriApi.ServerUris.CopriServerUriList.TINK_LIVE, Model.Services.CopriApi.ServerUris.CopriServerUriList.TINK_DEVEL }.Contains(tinkApp.Uris.ActiveUri.AbsoluteUri); tinkApp.ActiveUser.StateChanged += OnStateChanged; } /// /// Login state changed. /// /// /// private void OnStateChanged(object p_oSender, EventArgs p_oEventArgs) { var l_oPropertyChanged = PropertyChanged; if (l_oPropertyChanged != null) { l_oPropertyChanged(this, new PropertyChangedEventArgs(nameof(IsLoggedOut))); l_oPropertyChanged(this, new PropertyChangedEventArgs(nameof(IsLoginRequestAllowed))); } } /// Gets a value indicating whether user is logged out or not. public bool IsLoggedOut { get { return !TinkApp.ActiveUser.IsLoggedIn; } } /// Gets a value indicating whether user can try to login. public bool IsLoginRequestAllowed { get { return !TinkApp.ActiveUser.IsLoggedIn && m_bMailAndPasswordCandidatesOk; } } /// Gets mail address of user. public string MailAddress { get { return m_strMailAddress; } set { m_strMailAddress = value; UpdateAndFireIfRequiredOnEnteringMailOrPwd(); } } /// Gets password user. public string Password { get { return m_strPassword; } set { m_strPassword = value; UpdateAndFireIfRequiredOnEnteringMailOrPwd(); } } /// Update on password or mailaddress set. private void UpdateAndFireIfRequiredOnEnteringMailOrPwd() { var l_bLastMailAndPasswordCandidatesOk = m_bMailAndPasswordCandidatesOk; m_bMailAndPasswordCandidatesOk = Validator.ValidateMailAndPasswordDelegate(MailAddress, Password).ValidElement == Elements.Account; if (m_bMailAndPasswordCandidatesOk != l_bLastMailAndPasswordCandidatesOk) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsLoginRequestAllowed))); } } /// /// Command object to bind login button to view model. /// public ICommand OnLoginRequest { get { return new Command(async () => await Login()); } } /// Processes request to register a new user. public ICommand OnRegisterRequest => new Command(async () => { if (CrossConnectivity.Current.IsConnected) { await m_oViewService.PushAsync(ViewTypes.RegisterPage); } else { await m_oViewService.DisplayAlert( AppResources.MessageTitleHint, AppResources.MessageLoginRegisterNoNet, AppResources.MessageAnswerOk); } }); /// Processes request to recover password. public ICommand OnPasswordForgottonRequest => new Command(async () => { if (CrossConnectivity.Current.IsConnected) { await m_oViewService.PushAsync(ViewTypes.PasswordForgottenPage); } else { await m_oViewService.DisplayAlert( AppResources.MessageTitleHint, AppResources.MessageLoginRecoverPassword, AppResources.MessageAnswerOk); } }); /// /// User request to log in. /// #if BACKSTYLE public async void Login() #else public async Task Login() #endif { try { Log.ForContext().Information("User taped login button."); try { TinkApp.ActiveUser.CheckIsPasswordValid(MailAddress, Password); // Do login. var l_oAccount = await TinkApp.GetConnector(CrossConnectivity.Current.IsConnected).Command.DoLogin(MailAddress, Password, TinkApp.ActiveUser.DeviceId); await TinkApp.ActiveUser.Login(l_oAccount); // Update map page filter because user might be of both groups TINK an Konrad. TinkApp.GroupFilterMapPage = GroupFilterMapPageHelper.CreateUpdated( TinkApp.GroupFilterMapPage, TinkApp.ActiveUser.DoFilter(TinkApp.FilterGroupSetting.DoFilter())); // Update settings page filter because user might be of both groups TINK an Konrad. TinkApp.FilterGroupSetting.DoFilter(TinkApp.ActiveUser.Group); // Persist new settings. TinkApp.Save(); TinkApp.UpdateConnector(); } catch (InvalidAuthorizationResponseException l_oException) { // Copri response is invalid. Log.ForContext().Error("Login failed (invalid. auth. response). {@l_oException}.", l_oException); await m_oViewService.DisplayAlert( AppResources.MessageLoginErrorTitle, l_oException.Message, AppResources.MessageAnswerOk); return; } catch (UnsupportedCopriVersionDetectedException) { await m_oViewService.DisplayAlert( AppResources.MessageLoginErrorTitle, string.Format(AppResources.MessageAppVersionIsOutdated, ContactPageViewModel.GetAppName(TinkApp.Uris.ActiveUri)), AppResources.MessageAnswerOk); return; } catch (Exception l_oException) { // Copri server is not reachable. if (l_oException is WebConnectFailureException) { Log.ForContext().Information("Login failed (web communication exception). {@l_oException}.", l_oException); await m_oViewService.DisplayAlert( AppResources.MessageLoginConnectionErrorTitle, string.Format("{0}\r\n{1}", l_oException.Message, WebConnectFailureException.GetHintToPossibleExceptionsReasons), AppResources.MessageAnswerOk); } else if (l_oException is UsernamePasswordInvalidException) { // Cookie is empty. Log.ForContext().Error("Login failed (empty cookie). {@l_oException}.", l_oException); await m_oViewService.DisplayAlert( AppResources.MessageLoginErrorTitle, string.Format(AppResources.MessageLoginConnectionErrorMessage, l_oException.Message), "OK"); } else { Log.ForContext().Error("Login failed. {@l_oException}.", l_oException); await m_oViewService.DisplayAlert( AppResources.MessageLoginErrorTitle, l_oException.Message, AppResources.MessageAnswerOk); } return; } // Display information that login succeeded. Log.ForContext().Information("Login succeeded. {@tinkApp.ActiveUser}.", TinkApp.ActiveUser); var title = TinkApp.ActiveUser.Group.Intersect(new List { Model.Connector.FilterHelper.FILTERTINKGENERAL, Model.Connector.FilterHelper.FILTERKONRAD }).Any() ? string.Format(AppResources.MessageLoginWelcomeTitleGroup, TinkApp.ActiveUser.GetUserGroupDisplayName()) : string.Format(AppResources.MessageLoginWelcomeTitle); await m_oViewService.DisplayAlert( title, string.Format(AppResources.MessageLoginWelcome, TinkApp.ActiveUser.Mail), AppResources.MessageAnswerOk); } catch (Exception p_oException) { Log.ForContext().Error("An unexpected error occurred displaying log out page. {@Exception}", p_oException); return; } try { if (!TinkApp.ActiveUser.Group.Contains(Model.Connector.FilterHelper.FILTERTINKGENERAL)) { // No need to show "Anleitung TINK Räder" because user can not use tink. #if USEFLYOUT m_oViewService.ShowPage(ViewTypes.MapPage); #else await m_oViewService.ShowPage("//MapPage"); #endif return; } // Swich to map page #if USEFLYOUT m_oViewService.ShowPage(ViewTypes.BikeInfoCarouselPage, AppResources.MarkingLoginInstructions); #else await m_oViewService.ShowPage("//MapPage"); #endif } catch (Exception p_oException) { Log.ForContext().Error("Ein unerwarteter Fehler ist auf der Seite Anleitung TINK Räder (nach Anmeldeseite) aufgetreten. {@Exception}", p_oException); return; } #if BACKSTYLE // Navigate back to map page. await m_oNavigation.PopToRootAsync(); #endif } /// Holds whether TINK/ Copri info is shown. public bool IsRegisterTargetsInfoVisible { get; private set; } /// Text providing info about TINK/ konrad registration. public FormattedString RegisterTargetsInfo { get { var l_oHint = new FormattedString(); l_oHint.Spans.Add(new Span { Text = AppResources.MarkingLoginInstructionsTinkKonradTitle }); l_oHint.Spans.Add(new Span { Text = AppResources.MarkingLoginInstructionsTinkKonradMessage }); return l_oHint; } } /// Opens login page. public void RegisterRequest(string url) { try { OpenUrlInExternalBrowser(url); } catch (Exception p_oException) { Log.Error("Ein unerwarteter Fehler ist auf der Login Seite beim Öffnen eines Browsers, Seite {url}, aufgetreten. {@Exception}", url, p_oException); return; } } } }