using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Threading.Tasks; using Acr.Collections; using Plugin.Messaging; using Serilog; using ShareeBike.Model; using ShareeBike.Model.Connector; using ShareeBike.Model.Stations.StationNS; using ShareeBike.MultilingualResources; using ShareeBike.View; using ShareeBike.ViewModel.Bikes; using Xamarin.Essentials; using Xamarin.Forms; using ICommand = System.Windows.Input.ICommand; namespace ShareeBike.ViewModel.Contact { /// View model for contact page. public class ContactPageViewModel : INotifyPropertyChanged { public Xamarin.Forms.Command ShowSelectStationInfoText { get; private set; } /// /// Mail address for app related support. /// public const string APPSUPPORTMAILADDRESS = "hotline@sharee.bike"; /// Reference on view service to show modal notifications and to perform navigation. private IViewService ViewService { get; } /// Station selected by user. private IBikesViewModel SelectedBike { get; set; } /// Station selected by user. private IStation SelectedStation { get; set; } = new NullStation(); /// Holds the name of the app (sharee.bike, ...) string AppFlavorName { get; } /// Reference on the shareeBike app instance. private IShareeBikeApp ShareeBikeApp { get; } /// Holds a reference to the external trigger service. private Action OpenUrlInExternalBrowser { get; } Func CreateAttachment { get; } /// Provides a connector object. protected Func ConnectorFactory { get; } /// Delegate to retrieve connected state. protected Func IsConnectedDelegate { get; } /// Notifies view about changes. public event PropertyChangedEventHandler PropertyChanged; /// Constructs a contact page view model. /// Action to open an external browser. /// Mail address of active user. /// True if report level is verbose, false if not. /// Holds object to query location permissions. /// Holds object to query bluetooth state. /// Specifies on which platform code is run. /// Returns if mobile is connected to web or not. /// Connects system to copri. /// Service to control lock retrieve info. /// Stations to get station name from station id. /// Holds whether to poll or not and the period length is polling is on. /// Executes actions on GUI thread. /// Provides info about the smart device (phone, tablet, ...). /// Interface to actuate methods on GUI. public ContactPageViewModel( string appFlavorName, IShareeBikeApp shareeBikeApp, Func createAttachment, Action openUrlInExternalBrowser, IViewService viewService, Func isConnectedDelegate, Func connectorFactory) { AppFlavorName = !string.IsNullOrEmpty(appFlavorName) ? appFlavorName : throw new ArgumentException("Can not instantiate contact page view model- object. No app name centered."); ShareeBikeApp = shareeBikeApp ?? throw new ArgumentException("Can not instantiate settings page view model- object. No shareeBike app object available."); CreateAttachment = createAttachment ?? throw new ArgumentException("Can not instantiate contact page view model- object. No create attachment provider available."); ViewService = viewService ?? throw new ArgumentException("Can not instantiate contact page view model- object. No user view service available."); OpenUrlInExternalBrowser = openUrlInExternalBrowser ?? throw new ArgumentException("Can not instantiate contact page view model- object. No user external browse service available."); ConnectorFactory = connectorFactory ?? throw new ArgumentException("Can not instantiate bikes page view model- object. No connector available."); IsConnectedDelegate = isConnectedDelegate ?? throw new ArgumentException("Can not instantiate bikes page view model- object. No is connected delegate available."); ShowSelectStationInfoText = new Xamarin.Forms.Command(async () => { await ViewService.DisplayAlert( AppResources.MarkingLastSelectedStation, AppResources.MarkingContactNoStationInfoAvailableNoButton, AppResources.MessageAnswerOk); }); } public ICommand OnFAQClickedRequest => new Xamarin.Forms.Command(async () => await OpenFAQAsync()); /// Opens Help page, FAQ. public async Task OpenFAQAsync() { try { await ViewService.PushAsync(ViewTypes.HelpPage); } catch (Exception exception) { Log.ForContext().Error("Fehler beim Öffnen der Seite 'Hilfe' aufgetreten. {Exception}", exception); await ViewService.DisplayAlert( AppResources.ErrorPageNotLoadedTitle, $"{AppResources.ErrorPageNotLoaded}\r\n{exception.Message}", AppResources.MessageAnswerOk); return; } } /// Is invoked when page is shown. public async Task OnAppearing( IStation selectedStation) { if (SelectedStation?.Id == selectedStation?.Id) { // Nothing to do because either both are null or of same id. return; } SelectedStation = selectedStation; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedStationId))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedStationName))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(MailAddressText))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(OfficeHoursText))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(PhoneNumberText))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ProviderNameText))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsOperatorInfoAvaliable))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsDoPhoncallAvailable))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsSendMailAvailable))); await Task.CompletedTask; } /// Command object to bind mail button to view model. public ICommand OnMailToOperatorRequest => new Xamarin.Forms.Command(async () => await DoSendMailToOperator()); /// Command object to bind mail app related button to model. public ICommand OnMailAppRelatedRequest => new Xamarin.Forms.Command(async () => await DoSendMailAppRelated()); /// True if sending mail is possible. public bool IsSendMailAvailable => CrossMessaging.Current.EmailMessenger.CanSendEmail; /// cTrue if doing a phone call is possible. public bool IsDoPhoncallAvailable => CrossMessaging.Current.PhoneDialer.CanMakePhoneCall; /// Holds the mail address to mail to. public string MailAddressText => SelectedStation?.OperatorData?.MailAddressText ?? string.Empty; /// Holds the mail address to send mail to. public string PhoneNumberText => SelectedStation?.OperatorData?.PhoneNumberText ?? string.Empty; /// Holds the mail address to send mail to. public string OfficeHoursText => SelectedStation?.OperatorData?.Hours ?? string.Empty; /// Gets whether any operator support info is available. public bool IsOperatorInfoAvaliable => MailAddressText.Length > 0 || PhoneNumberText.Length > 0; /// Returns true if either mail was sent or if no mailer available. public async Task DoSendMailToOperator() { if (!IsSendMailAvailable) { await ViewService.DisplayAlert( String.Empty, AppResources.ErrorSupportmailMailingFailed, AppResources.MessageAnswerOk); return; } else { try { // Send operator related support mail to operator. await Email.ComposeAsync(new EmailMessage { To = new List { MailAddressText }, Cc = APPSUPPORTMAILADDRESS.ToUpper() != MailAddressText.ToUpper() // do not sent copy if same mail address ? new List { APPSUPPORTMAILADDRESS } : new List(), Subject = string.Format(AppResources.SupportmailSubjectOperatormail, AppFlavorName, SelectedStation?.Id), Body = ShareeBikeApp.ActiveUser.Mail != null ? $"{AppResources.SupportmailBodyText}\r\n\r\n\r\n\r\n{string.Format(AppResources.MarkingLoggedInStateInfoLoggedIn, ShareeBikeApp.ActiveUser.Mail)}" : $"{AppResources.SupportmailBodyText}\r\n\r\n\r\n\r\n{string.Format(AppResources.SupportmailBodyNotLoggedIn)}" }); return; } catch (Exception exception) { Log.Error("An unexpected error occurred sending mail to operator. {@Exception}", exception); await ViewService.DisplayAdvancedAlert( AppResources.MessageWaring, AppResources.ErrorSupportmailMailingFailed, exception.Message, AppResources.MessageAnswerOk); return; } } } /// Request to send a app related mail. public async Task DoSendMailAppRelated() { if (!IsSendMailAvailable) { await ViewService.DisplayAlert( String.Empty, AppResources.ErrorSupportmailMailingFailed, AppResources.MessageAnswerOk); return; } else { try { // Ask for permission to append diagnostics. await ViewService.DisplayAlert( AppResources.QuestionSupportmailAttachmentTitle, AppResources.QuestionSupportmailAttachment, AppResources.MessageAnswerOk); var message = new EmailMessage { To = new List { APPSUPPORTMAILADDRESS }, Subject = SelectedStation?.Id != null ? string.Format(AppResources.SupportmailSubjectAppmailWithStation, AppFlavorName, SelectedStation?.Id) : string.Format(AppResources.SupportmailSubjectAppmail, AppFlavorName), Body = ShareeBikeApp.ActiveUser.Mail != null ? $"{AppResources.SupportmailBodyText}\r\n\r\n\r\n\r\n{string.Format(AppResources.MarkingLoggedInStateInfoLoggedIn, ShareeBikeApp.ActiveUser.Mail)}" : $"{AppResources.SupportmailBodyText}\r\n\r\n\r\n\r\n{string.Format(AppResources.SupportmailBodyNotLoggedIn)}" }; // Send with attachment. var logFileName = string.Empty; try { logFileName = CreateAttachment(); } catch (Exception exception) { await ViewService.DisplayAdvancedAlert( AppResources.MessageWaring, AppResources.ErrorSupportmailCreateAttachment, exception.Message, AppResources.MessageAnswerOk); Log.ForContext().Error("An error occurred creating attachment for app mail. {@Exception)", exception); } if (!string.IsNullOrEmpty(logFileName)) { message.Attachments.Add(new Xamarin.Essentials.EmailAttachment(logFileName)); } // Send a shareeBike app related mail await Email.ComposeAsync(message); } catch (Exception exception) { Log.ForContext().Error("An unexpected error occurred sending mail. {@Exception}", exception); await ViewService.DisplayAdvancedAlert( AppResources.MessageWaring, AppResources.ErrorSupportmailMailingFailed, exception.Message, AppResources.MessageAnswerOk); return; } } } /// Command object to bind login button to view model. public ICommand OnSelectStationRequest => new Xamarin.Forms.Command(async () => await OpenSelectStationPageAsync()); /// Opens login page. public async Task OpenSelectStationPageAsync() { try { // Switch to map page await ViewService.PushAsync(ViewTypes.SelectStationPage); } catch (Exception p_oException) { Log.Error("Ein unerwarteter Fehler ist in der Klasse ContactPageViewModel aufgetreten. Kontext: Klick auf Hinweistext auf Station N- seite ohne Anmeldung. {@Exception}", p_oException); return; } } /// Command object to bind phone call button. public ICommand OnPhoneRequest => new Xamarin.Forms.Command(async () => await DoPhoneCall()); /// Request to do a phone call. public async Task DoPhoneCall() { if (!IsDoPhoncallAvailable) { await ViewService.DisplayAlert( String.Empty, AppResources.ErrorSupportmailPhoningFailed, AppResources.MessageAnswerOk); return; } else { try { // Make Phone Call CrossMessaging.Current.PhoneDialer.MakePhoneCall(PhoneNumberText); } catch (Exception exception) { Log.Error("An unexpected error occurred doing a phone call. {@Exception}", exception); await ViewService.DisplayAdvancedAlert( AppResources.MessageWaring, AppResources.ErrorSupportmailPhoningFailed, exception.Message, AppResources.MessageAnswerOk); return; } } } /// Text providing the id of the selected station. public string SelectedStationId => SelectedStation?.Id != null ? SelectedStation?.Id : string.Empty; public string SelectedStationName => SelectedStation?.StationName; /// Text providing the name of the operator of the selected station public string ProviderNameText => SelectedStation?.OperatorData?.Name; /// Text providing mail address and possible reasons to contact. public FormattedString MailAddressAndMotivationsText { get { var hint = new FormattedString(); hint.Spans.Add(new Span { Text = AppResources.MessageContactMail }); return hint; } } /// Invitation to rate app. public FormattedString PhoneContactText { get { var l_oHint = new FormattedString(); l_oHint.Spans.Add(new Span { Text = string.Format(AppResources.MessagePhoneMail, AppFlavorName) + $"{(OfficeHoursText.Length > 0 ? $" {OfficeHoursText}" : string.Empty)}" }); return l_oHint; } } } }