Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add examples for Notifications from async operations #222

Merged
merged 9 commits into from
Feb 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 137 additions & 57 deletions samples/Gallery/Pages/NotificationsPage.fs
Original file line number Diff line number Diff line change
Expand Up @@ -25,59 +25,125 @@ type NotificationViewModel(title, message) =

module NotificationsPage =
type Model =
{ NotificationManager: WindowNotificationManager }
{ NotificationManager: INotificationManager
NotificationPosition: NotificationPosition
Counter: int }

type Msg =
| ShowManagedNotification
| ShowCustomManagedNotification
| ShowNativeNotification
| ShowAsyncCompletedNotification
| ShowAsyncStatusNotifications
| NotifyInfo of string
| YesCommand
| NoCommand
| AttachedToVisualTreeChanged of VisualTreeAttachmentEventArgs
| AttachedToVisualTreeChanged of VisualTreeAttachmentEventArgs // event after which WindowNotificationManager is available
| ControlNotificationsShow
| TimedTick
| NotificationShowed
| PositionChanged of SelectionChangedEventArgs

type CmdMsg =
| StartTimer
| NotifyAsyncCompleted
| NotifyAsyncStatusUpdates of counter: string
| ShowNotification of notificationManager: INotificationManager * notification: INotification

let timerCmd () =
async {
do! Async.Sleep 1000
return TimedTick
}

let notifyOneAsync () =
async {
do! Async.Sleep 1000
return NotifyInfo "async operation completed"
}
|> Async.executeOnMainThread

type CmdMsg = | NoMsg
let notifyAsyncStatusUpdates message =
async { return NotifyInfo message } |> Async.executeOnMainThread

let showNotification (notificationManager: INotificationManager) notification =
let notificationManager = notificationManager :?> WindowNotificationManager
notificationManager.Show(notification)
NotificationShowed

let mapCmdMsgToCmd cmdMsg =
match cmdMsg with
| NoMsg -> Cmd.none
| NotifyAsyncCompleted -> Cmd.OfAsync.msg(notifyOneAsync())
| NotifyAsyncStatusUpdates message -> Cmd.OfAsync.msg(notifyAsyncStatusUpdates message)
| StartTimer -> Cmd.OfAsync.msg(timerCmd())
| ShowNotification(notificationManager, notification) -> Cmd.ofMsg(showNotification notificationManager notification)

let controlNotificationsRef = ViewRef<WindowNotificationManager>()

let init () = { NotificationManager = null }, []
let init () =
{ NotificationManager = null
Counter = 5
NotificationPosition = NotificationPosition.TopRight },
[]

let update msg model =
match msg with
| ShowManagedNotification ->

model.NotificationManager.Position <- NotificationPosition.BottomRight
| TimedTick ->
if model.Counter > 0 then
{ model with
Counter = model.Counter - 1 },
[ StartTimer
NotifyAsyncStatusUpdates($"async operation in progress {model.Counter}") ]
else
model, [ NotifyAsyncStatusUpdates("async operation completed") ]

model.NotificationManager.Show(Notification("Welcome", "Avalonia now supports Notifications.", NotificationType.Information))

model, []
| ShowManagedNotification ->
model,
[ ShowNotification(model.NotificationManager, Notification("Welcome", "Avalonia now supports Notifications.", NotificationType.Information)) ]
| ShowCustomManagedNotification ->
model.NotificationManager.Show(NotificationViewModel("Hey There!", "Did you know that Avalonia now supports Custom In-Window Notifications?"))

model, []
model,
[ ShowNotification(
model.NotificationManager,
NotificationViewModel("Hey There!", "Did you know that Avalonia now supports Custom In-Window Notifications?")
) ]
| ShowNativeNotification ->
model.NotificationManager.Show(Notification("Error", "Native Notifications are not quite ready. Coming soon.", NotificationType.Error))

model, []
model,
[ ShowNotification(
model.NotificationManager,
Notification("Error", "Native Notifications are not quite ready. Coming soon.", NotificationType.Error)
) ]
| ShowAsyncCompletedNotification -> model, [ NotifyAsyncCompleted ]
| ShowAsyncStatusNotifications -> model, [ StartTimer ]

| NotifyInfo message -> model, [ ShowNotification(model.NotificationManager, Notification(message, "", NotificationType.Information)) ]
| YesCommand ->
model.NotificationManager.Show(Notification("Avalonia Notifications", "Start adding notifications to your app today."))

model, []
model, [ ShowNotification(model.NotificationManager, Notification("Avalonia Notifications", "Start adding notifications to your app today.")) ]

| NoCommand ->
model.NotificationManager.Show(Notification("Avalonia Notifications", "Start adding notifications to your app today. To find out more visit..."))

model, []
model, [ ShowNotification(model.NotificationManager, Notification("Avalonia Notifications", "Start adding notifications to your app today.")) ]

| AttachedToVisualTreeChanged args -> { NotificationManager = FabApplication.Current.WindowNotificationManager }, []
(* WindowNotificationManager can't be used immediately after creating it,
so we need to wait for it to be attached to the visual tree.
See https://github.com/AvaloniaUI/Avalonia/issues/5442 *)
| AttachedToVisualTreeChanged args ->
{ model with
NotificationManager = FabApplication.Current.WindowNotificationManager },
[]

| ControlNotificationsShow ->
controlNotificationsRef.Value.Show(Notification("Control Notifications", "This notification is shown by the control itself."))
model, []
model,
[ ShowNotification(controlNotificationsRef.Value, Notification("Control Notifications", "This notification is shown by the control itself.")) ]

| NotificationShowed -> model, []

| PositionChanged args ->
let control = args.Source :?> ComboBox
let selectedItem = control.SelectedItem :?> ComboBoxItem
let position = Enum.Parse<NotificationPosition>(selectedItem.Content.ToString())

{ model with
NotificationPosition = position },
[]

let program =
Program.statefulWithCmdMsg init update mapCmdMsgToCmd
Expand All @@ -95,48 +161,62 @@ module NotificationsPage =
Component(program) {
let! model = Mvu.State

(Dock() {
TextBlock("TopLevel bound notification manager.")
.dock(Dock.Top)
.margin(2.)
.classes([ "h2" ])
.textWrapping(TextWrapping.Wrap)
(Grid() {
Dock() {
TextBlock("TopLevel bound notification manager.")
.dock(Dock.Top)
.margin(2.)
.classes([ "h2" ])
.textWrapping(TextWrapping.Wrap)

(VStack(4.) {
Button("Show Standard Managed Notification", ShowManagedNotification)
.dock(Dock.Top)

Button("Show Custom Managed Notification", ShowCustomManagedNotification)
})
.dock(Dock.Top)
.horizontalAlignment(HorizontalAlignment.Left)
.dock(Dock.Top)

TextBlock("Widget only notification manager.")
.dock(Dock.Top)
.margin(2.)
.classes([ "h2" ])
.textWrapping(TextWrapping.Wrap)
Button("Notify async operation completed", ShowAsyncCompletedNotification)
.dock(Dock.Top)

Button("Show Widget only notification", ControlNotificationsShow)
.dock(Dock.Top)
.horizontalAlignment(HorizontalAlignment.Left)
Button("Notify status updates from async operation", ShowAsyncStatusNotifications)
.dock(Dock.Top)

Border(
WindowNotificationManager(controlNotificationsRef)
.position(NotificationPosition.BottomRight)
.maxItems(3)
)
.padding(10)
.borderBrush(SolidColorBrush(Colors.Yellow))
TextBlock("Widget only notification manager.")
.dock(Dock.Top)
.margin(2.)
.classes([ "h2" ])
.textWrapping(TextWrapping.Wrap)

CustomNotification("Avalonia Notifications", "Start adding notifications to your app today.", YesCommand, NoCommand)
.dock(Dock.Top)
Button("Show Widget only notification", ControlNotificationsShow)
.dock(Dock.Top)
.horizontalAlignment(HorizontalAlignment.Left)

(ComboBox() {
ComboBoxItem(nameof(NotificationPosition.TopRight))
ComboBoxItem(nameof(NotificationPosition.TopLeft))
ComboBoxItem(nameof(NotificationPosition.BottomRight))
ComboBoxItem(nameof(NotificationPosition.BottomLeft))
})
.selectedIndex(0)
.dock(Dock.Top)
.onSelectionChanged(PositionChanged)

CustomNotification("Avalonia Notifications", "Start adding notifications to your app today.", YesCommand, NoCommand)
.dock(Dock.Top)

Border(
NotificationCard(false, "This is a notification card.")
.size(200., 100.)
)
.dock(Dock.Top)
.padding(10)
.borderBrush(SolidColorBrush(Colors.Blue))
}

// We can use the WindowNotificationManager a widget to be able to have a different WindowNotificationManager than FabApplication.Current.WindowNotificationManager
// Allowing you control ie the Position of a single notification
WindowNotificationManager(controlNotificationsRef)
.position(model.NotificationPosition)
.dock(Dock.Top)
.padding(10)
.borderBrush(SolidColorBrush(Colors.Blue))
.maxItems(3)

})
.onAttachedToVisualTree(AttachedToVisualTreeChanged)
Expand Down
13 changes: 13 additions & 0 deletions src/Fabulous.Avalonia/Async.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Fabulous.Avalonia

open Avalonia.Threading

[<RequireQualifiedAccess>]
module Async =
/// Execute the async computation on the device main thread
let executeOnMainThread (action: Async<'T>) : Async<'T> =
async {
return!
Dispatcher.UIThread.InvokeAsync(action = fun () -> Async.StartImmediateAsTask action)
|> Async.AwaitTask
}
1 change: 1 addition & 0 deletions src/Fabulous.Avalonia/Fabulous.Avalonia.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@
<Compile Include="Any.fs" />
<Compile Include="Program.fs" />
<Compile Include="AppBuilderExtensions.fs" />
<Compile Include="Async.fs" />
</ItemGroup>
<ItemGroup>
<!--
Expand Down
Loading