using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using Tango.FSE.Common.Notifications;
using Tango.Core;
using System.Collections.Concurrent;
using System.Windows.Media.Imaging;
using Tango.SharedUI.Helpers;
using System.Timers;
using Tango.Core.Commands;
using Tango.SharedUI;
using System.Reflection;
using Tango.Core.DI;
using System.ComponentModel;
using System.Windows.Data;
using MaterialDesignThemes.Wpf;
using Tango.FSE.Common.Core;
using Tango.FSE.Common.BugReporting;
using Tango.FSE.Common.FSEApplication;
using Tango.FSE.Common.DemoMode;
using Tango.FSE.Common;
using System.Threading;
namespace Tango.FSE.UI.Notifications
{
///
/// Represents the default FSE notification provider.
///
///
///
public class DefaultNotificationProvider : ExtendedObject, INotificationProvider
{
private HashSet _reportedExceptions;
private ConcurrentQueue> _pendingMessageBoxes;
private ConcurrentQueue> _pendingDialogs;
private List _appButtons;
[TangoInject(TangoInjectMode.WhenAvailable)]
public IBugReporter BugReporter { get; set; }
[TangoInject(TangoInjectMode.WhenAvailable)]
private IDemoModeManager DemoModeManager { get; set; }
private bool _notificationsVisible;
///
/// Gets or sets a value indicating whether to allow notifications visibility.
///
public bool NotificationsVisible
{
get { return _notificationsVisible; }
set { _notificationsVisible = value; RaisePropertyChangedAuto(); }
}
///
/// Gets the collection of notification items.
///
public ObservableCollection NotificationItems { get; private set; }
///
/// Gets the notification items view.
///
public ICollectionView NotificationItemsView { get; private set; }
///
/// Gets the collection of task items.
///
public ObservableCollection TaskItems { get; private set; }
///
/// Gets a value indicating whether there are any task items pending.
///
public bool HasTaskItems
{
get { return TaskItems.Count > 0; }
}
///
/// Gets the collection of snack bar items.
///
public ObservableCollection SnackbarItems { get; private set; }
///
/// Gets or sets a value indicating whether this instance has snack bar items.
///
public bool HasSnackbarItems
{
get { return SnackbarItems.Count > 0; }
}
private bool _holsSnackbarItemsTimeout;
///
/// Gets or sets a value indicating whether to delay the snackbar items closing timeout.
///
public bool HoldSnackbarItemsTimeout
{
get { return _holsSnackbarItemsTimeout; }
set
{
_holsSnackbarItemsTimeout = value;
RaisePropertyChangedAuto();
SnackbarItems.ToList().ForEach(x => x.HoldTimeout = value);
}
}
///
/// Initializes a new instance of the class.
///
public DefaultNotificationProvider()
{
NotificationsVisible = true;
_reportedExceptions = new HashSet();
NotificationItems = new ObservableCollection();
TaskItems = new ObservableCollection();
_pendingMessageBoxes = new ConcurrentQueue>();
_pendingDialogs = new ConcurrentQueue>();
_appButtons = new List();
SnackbarItems = new ObservableCollection();
PopNotificationCommand = new RelayCommand((x) => PopNotification(x));
NotificationItems.EnableCrossThreadOperations();
NotificationItemsView = CollectionViewSource.GetDefaultView(NotificationItems);
NotificationItemsView.SortDescriptions.Add(new SortDescription(nameof(NotificationItem.Priority), ListSortDirection.Descending));
}
private MessageBoxVM _currentMessageBox;
///
/// Gets the current message box if any.
///
public MessageBoxVM CurrentMessageBox
{
get { return _currentMessageBox; }
private set
{
_currentMessageBox = value;
RaisePropertyChangedAuto();
RaisePropertyChanged(nameof(HasMessageBox));
}
}
///
/// Gets a value indicating whether a message box is available.
///
public bool HasMessageBox
{
get
{
return CurrentMessageBox != null;
}
}
private InputBoxVM _currentInputBox;
///
/// Gets the current input box.
///
public InputBoxVM CurrentInputBox
{
get { return _currentInputBox; }
private set
{
_currentInputBox = value;
RaisePropertyChangedAuto();
RaisePropertyChanged(nameof(HasInputBox));
}
}
///
/// Gets a value indicating whether this instance has input box.
///
public bool HasInputBox
{
get
{
return CurrentInputBox != null;
}
}
private FrameworkElement _currentDialog;
///
/// Gets the current dialog if any.
///
public FrameworkElement CurrentDialog
{
get { return _currentDialog; }
private set
{
_currentDialog = value;
RaisePropertyChangedAuto();
RaisePropertyChanged(nameof(HasDialog));
}
}
private AppButton _currentAppButton;
///
/// Gets the current app button.
///
public AppButton CurrentAppButton
{
get { return _currentAppButton; }
private set { _currentAppButton = value; RaisePropertyChangedAuto(); }
}
///
/// Gets a value indicating whether a dialog is available.
///
public bool HasDialog
{
get
{
return CurrentDialog != null;
}
}
///
/// Shows an error message box.
///
/// The message.
///
public Task ShowError(string message)
{
return ShowMessageBox(new MessageBoxVM()
{
Message = message,
Title = "Error",
Type = MessageType.Error,
});
}
///
/// Shows an information message box.
///
/// The message.
///
public Task ShowInfo(string message)
{
return ShowMessageBox(new MessageBoxVM()
{
Message = message,
Title = "Information",
Type = MessageType.Info,
});
}
///
/// Shows warning message box.
///
/// The message.
///
public Task ShowWarning(string message)
{
return ShowMessageBox(new MessageBoxVM()
{
Message = message,
Title = "Warning",
Type = MessageType.Warning,
});
}
///
/// Shows a question message box.
///
/// The message.
///
public Task ShowQuestion(string message, String okText = null, String cancelText = null)
{
return ShowMessageBox(new MessageBoxVM()
{
Message = message,
Title = "Confirm",
HasCancel = true,
Type = MessageType.Question,
OKText = okText != null ? okText : "YES",
CancelText = cancelText != null ? cancelText : "NO"
});
}
///
/// Shows a warning question message box.
///
/// The message.
public Task ShowWarningQuestion(String message, String okText = null, String cancelText = null)
{
return ShowMessageBox(new MessageBoxVM()
{
Message = message,
Title = "Warning",
HasCancel = true,
Type = MessageType.Warning,
OKText = okText != null ? okText : "YES",
CancelText = cancelText != null ? cancelText : "NO"
});
}
///
/// Shows a success message box.
///
/// The message.
///
public Task ShowSuccess(string message)
{
return ShowMessageBox(new MessageBoxVM()
{
Message = message,
Title = "Success",
Type = MessageType.Success,
});
}
///
/// Shows the message box.
///
/// The view model.
///
private Task ShowMessageBox(MessageBoxVM vm)
{
ReleaseGlobalBusyMessage();
LogManager.Log($"Displaying MessagBox '{vm.Message}'.");
TaskCompletionSource source = new TaskCompletionSource();
vm.Accepted += () => { OnMessageBoxClosed(); source.SetResult(true); };
vm.Canceled += () => { OnMessageBoxClosed(); source.SetResult(false); };
if (CurrentMessageBox == null)
{
CurrentMessageBox = vm;
}
else
{
_pendingMessageBoxes.Enqueue(new PendingNotification(vm, source));
}
return source.Task;
}
public Task ShowInputBox(string title, string message, PackIconKind icon = PackIconKind.InformationOutline, string defaultInput = null, string inputHint = null, int? maxChars = null, string okText = null, string cancelText = null, Func validationFunc = null)
{
TaskCompletionSource source = new TaskCompletionSource();
InputBoxVM vm = new InputBoxVM();
vm.Title = title;
vm.Message = message;
vm.Icon = icon;
vm.Input = defaultInput;
vm.InputHint = inputHint;
vm.ValidationFunction = validationFunc;
if (maxChars != null)
{
vm.MaxCharacters = maxChars.Value;
}
if (okText != null)
{
vm.OKText = okText;
}
if (cancelText != null)
{
vm.CancelText = cancelText;
}
vm.Accepted += () =>
{
CurrentInputBox = null;
source.SetResult(new InputBoxResult() { Confirmed = true, Input = vm.Input });
};
vm.Canceled += () =>
{
CurrentInputBox = null;
source.SetResult(new InputBoxResult() { Confirmed = false, Input = vm.Input });
};
CurrentInputBox = vm;
return source.Task;
}
///
/// Called when the message box has been closed.
///
private void OnMessageBoxClosed()
{
LogManager.Log("MessageBox closed.");
CurrentMessageBox = null;
if (_pendingMessageBoxes.Count > 0)
{
PendingNotification p = null;
if (_pendingMessageBoxes.TryDequeue(out p))
{
CurrentMessageBox = p.Item;
}
}
}
///
/// Inserts the notification item to the bottom of the notifications collection.
///
/// The item.
///
public NotificationItem PushNotification(NotificationItem item)
{
LogManager.Log($"Pushing NotificationItem '{item.GetType().Name}'.");
item.RemoveAction = () => { PopNotification(item); };
NotificationItems.Insert(0, item);
RaisePropertyChanged(nameof(HasNotificationItems));
return item;
}
///
/// Pushes the notification.
///
///
///
public NotificationItem PushNotification() where T : NotificationItem
{
return PushNotification(Activator.CreateInstance());
}
///
/// Removed the specified notification item.
///
/// The item.
public void PopNotification(NotificationItem item)
{
LogManager.Log($"Popping out NotificationItem '{item.GetType().Name}'.");
NotificationItems.Remove(item);
RaisePropertyChanged(nameof(HasNotificationItems));
}
///
/// Gets a value indicating whether this instance has notification items.
///
public bool HasNotificationItems
{
get
{
return NotificationItems.Count > 0;
}
}
///
/// Gets the pop notification command.
///
public RelayCommand PopNotificationCommand { get; private set; }
///
/// Displays the specified dialog in a modal design.
///
///
/// The data context.
/// The view.
///
public async Task ShowDialog(T datacontext, FrameworkElement view) where T : DialogViewVM
{
LogManager.Log($"Invoking dialog '{view.GetType().Name}'...");
view.DataContext = datacontext;
TangoIOC.Default.Inject(datacontext);
view.Loaded += (_, __) =>
{
view.DataContext = datacontext;
datacontext.OnShow();
};
TaskCompletionSource source = new TaskCompletionSource();
bool completed = false;
datacontext.Accepted += () =>
{
if (!completed)
{
completed = true;
LogManager.Log($"Dialog '{view.GetType().Name}' accepted.");
OnDialogClosed();
source.SetResult(datacontext);
}
};
datacontext.Canceled += () =>
{
if (!completed)
{
completed = true;
LogManager.Log($"Dialog '{view.GetType().Name}' canceled.");
OnDialogClosed();
source.SetResult(datacontext);
}
};
if (CurrentDialog == null)
{
CurrentDialog = view;
}
else
{
_pendingDialogs.Enqueue(new PendingNotification(new DialogAndView(datacontext, view), source));
}
var result = await source.Task;
return result as T;
}
///
/// Called when [dialog closed].
///
private void OnDialogClosed()
{
CurrentDialog = null;
if (_pendingDialogs.Count > 0)
{
PendingNotification p = null;
if (_pendingDialogs.TryDequeue(out p))
{
CurrentDialog = p.Item.View;
}
}
}
///
/// Displays the specified dialog in a modal design.
/// The notification provider will try to locate the view automatically using conventions.
///
///
/// The data context.
///
public Task ShowDialog(T datacontext) where T : DialogViewVM
{
var callingAssembly = datacontext.GetType().Assembly;
String viewName = datacontext.GetType().FullName.Replace("VM", "");
var viewType = callingAssembly.GetType(viewName);
if (viewType == null)
{
throw new NullReferenceException("View type for " + datacontext.GetType().Name + " could not be found!");
}
var view = Activator.CreateInstance(viewType) as FrameworkElement;
if (view == null)
{
throw new NullReferenceException("The view " + viewType.ToString() + " is not of type framework element.");
}
return ShowDialog(datacontext, view);
}
///
/// Displays the specified dialog in a modal design.
/// The data context instance will be automatically created.
/// The notification provider will try to locate the view automatically using conventions.
///
///
///
public Task ShowDialog() where T : DialogViewVM
{
return ShowDialog(Activator.CreateInstance());
}
///
/// Sets the global busy message.
///
/// The message.
public void SetGlobalBusyMessage(string message)
{
GlobalBusyMessage = message;
IsInGlobalBusyState = true;
RaisePropertyChanged(nameof(IsInGlobalBusyState));
RaisePropertyChanged(nameof(GlobalBusyMessage));
}
///
/// Releases the global busy message.
///
public void ReleaseGlobalBusyMessage()
{
GlobalBusyMessage = null;
IsInGlobalBusyState = false;
RaisePropertyChanged(nameof(IsInGlobalBusyState));
RaisePropertyChanged(nameof(GlobalBusyMessage));
}
///
/// Gets the current global busy message.
///
public string GlobalBusyMessage { get; private set; }
///
/// Gets a value indicating whether this instance is in global busy state.
///
public bool IsInGlobalBusyState { get; private set; }
private AppBarItem _currentAppBarItem;
///
/// Gets the current application bar item.
///
public AppBarItem CurrentAppBarItem
{
get { return _currentAppBarItem; }
set { _currentAppBarItem = value; RaisePropertyChangedAuto(); RaisePropertyChanged(nameof(HasAppBarItem)); }
}
///
/// Gets a value indicating whether this instance has application bar item.
///
public bool HasAppBarItem
{
get { return CurrentAppBarItem != null; }
}
///
/// Pushes the application bar item.
///
/// The application bar item.
///
public AppBarItem PushAppBarItem(AppBarItem appBarItem)
{
LogManager.Log($"Pushing AppBarItem '{appBarItem.GetType().Name}'.");
CurrentAppBarItem = appBarItem;
appBarItem.RemoveAction = () => PopAppBarItem(appBarItem);
return appBarItem;
}
///
/// Pushes the application bar item.
///
///
///
public AppBarItem PushAppBarItem() where T : AppBarItem
{
return PushAppBarItem(Activator.CreateInstance());
}
///
/// Pops the application bar item.
///
/// The application bar item.
public void PopAppBarItem(AppBarItem appBarItem)
{
LogManager.Log($"Popping out AppBarItem '{appBarItem.GetType().Name}'.");
CurrentAppBarItem = null;
}
///
/// Pushes the task item.
///
/// The task bar item.
///
public TaskItem PushTaskItem(TaskItem taskBarItem)
{
InvokeUI(() =>
{
TaskItems.Add(taskBarItem);
RaisePropertyChanged(nameof(HasTaskItems));
});
return taskBarItem;
}
///
/// Handles the Push Task Item event.
///
///
///
public TaskItem PushTaskItem() where T : TaskItem
{
return PushTaskItem(Activator.CreateInstance());
}
///
/// Pushes the task item with the specified message.
///
/// The message.
/// Indicates whether the cancel button will be enabled.
/// Specify the action to execute when cancel button is pressed.
///
public TaskItem PushTaskItem(String message, bool canCancel = false, Action cancelAction = null)
{
if (canCancel || cancelAction != null)
{
return PushTaskItem(new TaskItem(message, cancelAction));
}
else
{
return PushTaskItem(new TaskItem(message));
}
}
///
/// Pops the task item.
///
///
public void PopTaskItem(TaskItem taskBarItem)
{
InvokeUI(() =>
{
TaskItems.Remove(taskBarItem);
RaisePropertyChanged(nameof(HasTaskItems));
});
}
///
/// Pushes the app button.
///
/// The app button.
public void PushAppButton(AppButton appButton)
{
_appButtons.Insert(0, appButton);
CurrentAppButton = appButton;
}
///
/// Pops the app button.
///
/// The app button.
public void PopAppButton(AppButton appButton)
{
_appButtons.RemoveAll(x => x == appButton);
CurrentAppButton = _appButtons.FirstOrDefault();
}
///
/// Pushes the snack bar item.
///
/// The snack bar item.
///
public SnackbarItem PushSnackbarItem(SnackbarItem snackbarItem)
{
InvokeUI(() =>
{
SnackbarItems.Insert(0, snackbarItem);
RaisePropertyChanged(nameof(HasSnackbarItems));
});
return snackbarItem;
}
///
/// Pushes the snack bar item.
///
/// The type.
/// The title.
/// if set to true [can close].
/// The message.
/// The timeout.
/// The close action.
/// The press action.
///
public SnackbarItem PushSnackbarItem(MessageType type, String title, bool canClose, String message = null, TimeSpan? timeout = null, Action closeAction = null, Action pressAction = null)
{
if (pressAction == null && (type == MessageType.Error || type == MessageType.Warning))
{
pressAction = () =>
{
if (type == MessageType.Error)
{
ShowError((title.EndsWith(".") ? title : title + ".") + "\n" + message);
}
else if (type == MessageType.Warning)
{
ShowError((title.EndsWith(".") ? title : title + ".") + "\n" + message);
}
};
}
return PushSnackbarItem(new SnackbarItem(type, title, canClose, message, timeout, closeAction, pressAction));
}
///
/// Display a new error SnackBar. Pressing the snack bar will display the issue reporting dialog.
///
/// The exception.
/// The title.
/// The message.
/// The message type.
///
public SnackbarItem PushErrorReportingSnackbar(Exception exception, String title, String message, MessageType messageType = MessageType.BugReport)
{
String exceptionText = exception.ToString();
if (!_reportedExceptions.Contains(exceptionText))
{
_reportedExceptions.Add(exceptionText);
return PushSnackbarItem(messageType, title, true, $"{message}\nTap to report this issue.", null, null, () =>
{
BugReporter.ShowBugReportDialog(new Bug()
{
Type = BugType.FSE,
Description = message,
Exception = exception,
Title = title + " - " + message,
});
});
}
else
{
return PushSnackbarItem(messageType == MessageType.BugReport ? MessageType.Error : messageType, title, true, $"{message}.", TimeSpan.FromSeconds(10), null, () =>
{
ShowInfo("This error has been reported to the development team.");
});
}
}
///
/// Displays a indeterminate progress Snackbar. Should use and .
///
/// The title.
/// The message.
///
public SnackbarItem PushProgressSnackbar(String title, String message)
{
SnackbarItem item = new SnackbarItem(MessageType.Busy, title, false, message, null, () => { }, () => { });
item.IsProgress = true;
PushSnackbarItem(item);
return item;
}
///
/// Pops the snack bar item.
///
/// The snack bar item.
public void PopSnackbarItem(SnackbarItem snackbarItem)
{
InvokeUI(() =>
{
SnackbarItems.Remove(snackbarItem);
RaisePropertyChanged(nameof(HasSnackbarItems));
});
}
///
/// Called when is ready and user is logged-in. (happens every time a user logs-in)
///
///
public void OnApplicationReady(IFSEApplicationManager applicationManager)
{
if (applicationManager.DemoMode)
{
DemoModeManager.InsertCommand(() =>
{
ShowInfo("This is a standard information message.");
}, "Emulate Info Message", "Emulates a standard information message box.");
DemoModeManager.InsertCommand(() =>
{
ShowWarning("Something bad is about to happen.");
}, "Emulate Warning Message", "Emulates a standard warning message box.");
DemoModeManager.InsertCommand(() =>
{
ShowWarningQuestion("Something bad is about to happen. Are you sure?");
}, "Emulate Warning Question Message", "Emulates a standard warning question message box.");
DemoModeManager.InsertCommand(() =>
{
ShowError("Something bad happened.");
}, "Emulate Error Message", "Emulates a standard error message box.");
DemoModeManager.InsertCommand(() =>
{
ShowSuccess("The operation completed successfully.");
}, "Emulate Success Message", "Emulates a standard success message box.");
DemoModeManager.InsertCommand(() =>
{
PushSnackbarItem(MessageType.Info, "Some Information", true, "This is a standard information message.\nTap to see more details.", TimeSpan.FromSeconds(5), null, () =>
{
ShowInfo("This is a standard information message.");
});
}, "Emulate Info Snackbar", "Creates an information snackbar with timeout and press action.");
DemoModeManager.InsertCommand(() =>
{
PushSnackbarItem(MessageType.Warning, "Some Warning", true, "This is a standard warning message.\nTap to see more details.", TimeSpan.FromSeconds(5), null, () =>
{
ShowWarning("This is a standard warning message.");
});
}, "Emulate Warning Snackbar", "Creates a warning snackbar with timeout and press action.");
DemoModeManager.InsertCommand(() =>
{
PushSnackbarItem(MessageType.Error, "Fake Error Occurred", false, "This is a standard error message.\nTap to see more details.", null, null, () =>
{
ShowError("This is a standard warning message.");
});
}, "Emulate Error Snackbar", "Creates an error snackbar with no timeout and no ability to close.");
DemoModeManager.InsertCommand(() =>
{
PushSnackbarItem(MessageType.Success, "Operation Completed", true, "This is a standard success message.\nTap to close", TimeSpan.FromSeconds(5));
}, "Emulate Success Snackbar", "Creates a success snackbar with timeout and close press action.");
DemoModeManager.InsertCommand(() =>
{
var snackbar = PushProgressSnackbar("Background Operation", "The application is doing stuff in the background...");
Task.Factory.StartNew(() =>
{
for (int i = 0; i < 100; i++)
{
snackbar.Message = $"The application is doing stuff in the background ({i}/100)...";
Thread.Sleep(50);
}
snackbar.ProgressCompleted("Operation completed.", TimeSpan.FromSeconds(2));
});
}, "Emulate Progress Snackbar", "Creates a success snackbar with timeout and close press action.");
DemoModeManager.InsertCommand(() =>
{
throw new TimeoutException("This is a fake exception.");
}, "Emulate Bug Reporting Error", "Creates a snackbar notification with a fake error and bug reporting option.");
}
}
}
}