diff --git a/Fabulous.Avalonia.Samples.sln b/Fabulous.Avalonia.Samples.sln index f16edb874..354b3d321 100644 --- a/Fabulous.Avalonia.Samples.sln +++ b/Fabulous.Avalonia.Samples.sln @@ -48,6 +48,8 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "TestableApp.UnitTests", "sa EndProject Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Fabulous.Avalonia", "src\Fabulous.Avalonia\Fabulous.Avalonia.fsproj", "{31D47096-FBF7-4A74-A302-011B9608C32E}" EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Fabulous.Avalonia.Labs", "extensions\Fabulous.Avalonia.Labs\Fabulous.Avalonia.Labs.fsproj", "{992E1416-7747-4E03-98F5-F608C135C635}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -134,5 +136,9 @@ Global {31D47096-FBF7-4A74-A302-011B9608C32E}.Debug|Any CPU.Build.0 = Debug|Any CPU {31D47096-FBF7-4A74-A302-011B9608C32E}.Release|Any CPU.ActiveCfg = Release|Any CPU {31D47096-FBF7-4A74-A302-011B9608C32E}.Release|Any CPU.Build.0 = Release|Any CPU + {992E1416-7747-4E03-98F5-F608C135C635}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {992E1416-7747-4E03-98F5-F608C135C635}.Debug|Any CPU.Build.0 = Debug|Any CPU + {992E1416-7747-4E03-98F5-F608C135C635}.Release|Any CPU.ActiveCfg = Release|Any CPU + {992E1416-7747-4E03-98F5-F608C135C635}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/samples/Mvu/GitHubClient/App.fs b/samples/Mvu/GitHubClient/App.fs index 0d6571240..80674ce3a 100644 --- a/samples/Mvu/GitHubClient/App.fs +++ b/samples/Mvu/GitHubClient/App.fs @@ -1,9 +1,7 @@ namespace GitHubClient open System.Diagnostics -open System.IO -open Avalonia.Media -open Avalonia.Themes.Fluent +open Avalonia.Markup.Xaml.Styling open Fabulous open Fabulous.Avalonia open System.Net @@ -15,11 +13,11 @@ open System.Text.Json open type Fabulous.Avalonia.View module Models = - type RemoteData<'e, 't> = + type RemoteData<'T> = | NotAsked | Loading - | Content of 't - | Failure of 'e + | Content of 'T + | Failure of string type User = { login: string @@ -41,8 +39,6 @@ module URlConstants = [] let githubBaseUrl = "https://api.github.com/users/" -type GitHubError = | Non200Response - module GitHubService = let private fetchWitHeader (urlString: string) = let client = new HttpClient() @@ -63,47 +59,25 @@ module GitHubService = return match response.StatusCode with | HttpStatusCode.OK -> Ok deserialized - | _ -> Error Non200Response + | _ -> Error "User not found!" } - let getProfileImage (urlString: string) = - async { - let client = new HttpClient() - let! response = client.GetAsync(urlString) |> Async.AwaitTask - response.EnsureSuccessStatusCode() |> ignore - let! data = response.Content.ReadAsByteArrayAsync() |> Async.AwaitTask - - return - match response.StatusCode with - | HttpStatusCode.OK -> Ok(new MemoryStream(data)) - | _ -> Error Non200Response - } - - module App = type Msg = | UserNameChanged of string | SearchClicked | UserInfoLoaded of User - | UserInfoNotFound of GitHubError - | ProfileImageLoaded of Stream - | ProfileImageNotFound of GitHubError + | UserInfoNotFound of string | LoadingProgress of float - type CmdMsg = - | GetUserInfo of name: string - | GetProfileImage of url: string - type Model = { UserName: string - UserInfo: RemoteData - ProfileImage: RemoteData } + UserInfo: RemoteData } let init () = { UserName = "" - UserInfo = RemoteData.NotAsked - ProfileImage = RemoteData.NotAsked }, - [] + UserInfo = RemoteData.NotAsked }, + Cmd.none let getUserInfo userName = task { @@ -114,122 +88,90 @@ module App = | Error error -> return UserInfoNotFound error } - let getProfileImage url = - task { - let! response = GitHubService.getProfileImage url - - match response with - | Ok image -> return ProfileImageLoaded image - | Error error -> return ProfileImageNotFound error - } - - let mapCmdMsgToCmd cmdMsg = - match cmdMsg with - | GetUserInfo userName -> Cmd.OfTask.msg(getUserInfo userName) - | GetProfileImage url -> Cmd.OfTask.msg(getProfileImage url) - let update msg model = match msg with - | UserNameChanged userName -> { model with UserName = userName }, [] + | UserNameChanged userName -> { model with UserName = userName }, Cmd.none - | SearchClicked -> - { model with - UserInfo = RemoteData.Loading }, - [ GetUserInfo model.UserName ] + | SearchClicked -> { model with UserInfo = Loading }, Cmd.OfTask.msg(getUserInfo model.UserName) - | UserInfoLoaded user -> - { model with - UserInfo = RemoteData.Content(user) }, - [ GetProfileImage(user.avatar_url) ] + | UserInfoLoaded user -> { model with UserInfo = Content(user) }, Cmd.none | UserInfoNotFound _ -> { model with - UserInfo = RemoteData.Failure("User not found!") }, - [] + UserInfo = Failure("User not found!") }, + Cmd.none - | ProfileImageLoaded image -> - { model with - ProfileImage = RemoteData.Content(image) }, - [] + | LoadingProgress _ -> model, Cmd.none - | ProfileImageNotFound _ -> - { model with - ProfileImage = RemoteData.Failure("Profile image not found!") }, - [] + let program = + Program.statefulWithCmd init update + |> Program.withTrace(fun (format, args) -> Debug.WriteLine(format, box args)) + |> Program.withExceptionHandler(fun ex -> +#if DEBUG + printfn $"Exception: %s{ex.ToString()}" + false +#else + true +#endif + ) - | LoadingProgress _ -> model, [] + let content () = + Component("GitHubClient") { + let! model = Context.Mvu program - let content model = - Grid() { - (VStack() { - Image("avares://GitHubClient/Assets/github-icon.png") - .size(100., 100.) + Grid() { + VStack() { + match model.UserInfo with + | NotAsked -> () + | Loading -> ProgressBar(0., 1., 0.5, LoadingProgress) + | Content user -> + (VStack() { + AsyncImage(user.avatar_url) + .placeholderSource("avares://GitHubClient/Assets/github-icon.png") + .size(100., 100.) - TextBox(model.UserName, UserNameChanged) - Button("Search", SearchClicked) + TextBlock(user.login) - match model.UserInfo with - | NotAsked -> () - | Loading -> ProgressBar(0., 1., 0.5, LoadingProgress) - | Content user -> - (VStack() { - match model.ProfileImage with - | NotAsked -> () - | Loading -> () - | Content source -> Image(source).size(24., 24.) - | Failure _ -> - Image("avares://GitHubClient/Assets/github-icon.png", Stretch.Uniform) - .size(24., 24.) + if user.name.IsSome then + TextBlock(user.name.Value) - TextBlock(user.login) + if user.location.IsSome then + TextBlock(user.location.Value) - if user.name.IsSome then - TextBlock(user.name.Value) + if user.bio.IsSome then + TextBlock(user.bio.Value) - if user.location.IsSome then - TextBlock(user.location.Value) + TextBlock($"Public repos: {user.public_repos}") - if user.bio.IsSome then - TextBlock(user.bio.Value) + TextBlock($"Public gists: {user.public_gists}") - TextBlock($"Public repos: {user.public_repos}") + TextBlock($"Followers: {user.followers}") - TextBlock($"Public gists: {user.public_gists}") + TextBlock($"Following: {user.following}") - TextBlock($"Followers: {user.followers}") + TextBlock($"Created at: {user.created_at}") - TextBlock($"Following: {user.following}") + TextBlock($"Profile: {user.html_url}") + }) + .centerHorizontal() + | Failure s -> TextBlock(s) - TextBlock($"Created at: {user.created_at}") + TextBox(model.UserName, UserNameChanged) - TextBlock($"Profile: {user.html_url}") - }) - .centerHorizontal() - | Failure s -> TextBlock(s) - }) - .centerHorizontal() + Button("Search", SearchClicked).centerHorizontal() + } + } + |> _.centerVertical() } - let view model = + let view () = #if MOBILE - SingleViewApplication(content model) + SingleViewApplication(content()) #else - DesktopApplication(Window(content model)) + DesktopApplication(Window(content())) #endif let create () = - let theme () = FluentTheme() - - let program = - Program.statefulWithCmdMsg init update mapCmdMsgToCmd - |> Program.withTrace(fun (format, args) -> Debug.WriteLine(format, box args)) - |> Program.withExceptionHandler(fun ex -> -#if DEBUG - printfn $"Exception: %s{ex.ToString()}" - false -#else - true -#endif - ) - |> Program.withView view + let theme () = + StyleInclude(baseUri = null, Source = Uri("avares://GitHubClient/App.xaml")) - FabulousAppBuilder.Configure(theme, program) + FabulousAppBuilder.Configure(theme, view) diff --git a/samples/Mvu/GitHubClient/App.xaml b/samples/Mvu/GitHubClient/App.xaml new file mode 100644 index 000000000..e407ef2ed --- /dev/null +++ b/samples/Mvu/GitHubClient/App.xaml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/samples/Mvu/GitHubClient/GitHubClient.fsproj b/samples/Mvu/GitHubClient/GitHubClient.fsproj index 122cbbcf5..9194eb6ea 100644 --- a/samples/Mvu/GitHubClient/GitHubClient.fsproj +++ b/samples/Mvu/GitHubClient/GitHubClient.fsproj @@ -41,8 +41,12 @@ + + + + diff --git a/samples/Mvu/GitHubClient/Properties/launchSettings.json b/samples/Mvu/GitHubClient/Properties/launchSettings.json deleted file mode 100644 index c16206a81..000000000 --- a/samples/Mvu/GitHubClient/Properties/launchSettings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "profiles": { - "Windows Machine": { - "commandName": "MsixPackage", - "nativeDebugging": false - } - } -} \ No newline at end of file