Skip to content

Commit

Permalink
fix xf sample, add TemplatedTaskLoader to sample
Browse files Browse the repository at this point in the history
  • Loading branch information
roubachof committed Jun 22, 2022
1 parent 9972710 commit 7684782
Show file tree
Hide file tree
Showing 21 changed files with 633 additions and 249 deletions.
92 changes: 91 additions & 1 deletion README.md
@@ -1,6 +1,11 @@
# TaskLoaderView 2.0: Let's burn IsBusy=true!

<img src="Docs/tlv_icon_tos.png" width="200" />

<img src="Docs/maui_logo.png" height="200" />

<img src="Docs/tlv_icon_tos.png" height="200" />



The `TaskLoaderView` is a UI component that handles all your UI loading state (Loading, Error, Result, Notification), and removes all the pain of async loading from your view models (try catch / async void / IsBusy / HasErrors / base view models / ...) thanks to its brother the `TaskLoaderNotifier`.

Expand All @@ -22,6 +27,91 @@ It has been tested on **Android**, **iOS** and **UWP** platforms through the `Re

It uses the Sharpnado's [TaskMonitor](https://github.com/roubachof/Sharpnado.TaskMonitor).

## 2.5.0 MAUI support \o/ and TemplatedTaskLoader

Version 2.5.0 now supports .Net MAUI.

New `TemplatedTaskLoader`: it does the same job as the `TaskLoaderView` but using `ControlTemplate` instead of a absolute layout of views.

```xml
<cv:TemplatedTaskLoader x:Name="LoaderView"
Grid.Row="1"
Style="{StaticResource TemplatedLoaderLongLoading}"
TaskLoaderNotifier="{Binding Loader}">
<cv:TemplatedTaskLoader.ResultControlTemplate>
<ControlTemplate>
...
</ControlTemplate>
</cv:TemplatedTaskLoader.ResultControlTemplate>
<cv:TemplatedTaskLoader.ErrorControlTemplate>
<ControlTemplate>
...
</ControlTemplate>
</cv:TemplatedTaskLoader.ErrorControlTemplate>
<cv:TemplatedTaskLoader.LoadingControlTemplate>
<ControlTemplate>
...
</ControlTemplate>
</cv:TemplatedTaskLoader.LoadingControlTemplate>
</cv:TemplatedTaskLoader>

...

<Style x:Key="TemplatedLoaderLongLoading"
TargetType="customViews:TemplatedTaskLoader">
<Setter Property="LoadingControlTemplate" Value="{StaticResource LottieRocketControlTemplate}" />
<Setter Property="ErrorControlTemplate" Value="{StaticResource ErrorViewControlTemplate}" />
</Style>

<ControlTemplate x:Key="LottieRocketControlTemplate">
<forms:AnimationView HorizontalOptions="Center"
VerticalOptions="Center"
HeightRequest="200"
WidthRequest="200"
Animation="delivery_truck_animation.json"
IsAnimating="{Binding Source={RelativeSource AncestorType={x:Type customViews:TemplatedTaskLoader}},
Path=TaskLoaderNotifier.ShowLoader}"
RepeatMode="Infinite" />

<ControlTemplate x:Key="ErrorViewControlTemplate">
<StackLayout HorizontalOptions="Center"
VerticalOptions="Center"
BindingContext="{Binding Source={RelativeSource AncestorType={x:Type customViews:TemplatedTaskLoader}},
Path=TaskLoaderNotifier}"
IsVisible="False"
Orientation="Vertical"
Spacing="10">
<Frame Style="{StaticResource FrameCircle}"
WidthRequest="{StaticResource SizeTaskLoaderIcon}"
HeightRequest="{StaticResource SizeTaskLoaderIcon}"
Margin="0,0,0,10"
BackgroundColor="{StaticResource ColorPrimary}">
<Image HorizontalOptions="Center"
VerticalOptions="Center"
Source="{Binding Error,
Converter={converters:ExceptionToImageSourceConverter}}" />
</Frame>
<Label Style="{StaticResource TextBodySecondary}"
WidthRequest="300"
Margin="0,0,0,20"
HorizontalTextAlignment="Center"
LineBreakMode="WordWrap"
MaxLines="2"
Text="{Binding Error,
Converter={converters:ExceptionToErrorMessageConverter}}" />
<sho:Shadows CornerRadius="10"
Shades="{StaticResource ShadowAccentBottom}">
<Button Style="{StaticResource ButtonAccent}"
HorizontalOptions="Center"
VerticalOptions="End"
Command="{Binding ReloadCommand}"
Text="{x:Static loc:GlobalResources.Common_Retry}" />
</sho:Shadows>
</StackLayout>
</ControlTemplate>
</ControlTemplate>
```

## 2.4.0 BREAKING CHANGES

The task source given to `TaskLoaderNotifier` is now a `Func<bool, Task>` (or a `Func<bool, Task<T>>`) instead of a `Func<Task>`.
Expand Down
5 changes: 5 additions & 0 deletions Retronado.Maui/Retronado.Maui.sln
Expand Up @@ -9,6 +9,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sharpnado.Maui.TaskLoaderVi
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Maui.Skeleton", "..\Maui.Skeleton\Maui.Skeleton\Maui.Skeleton.csproj", "{755E904F-41CD-4232-95D2-64F75DCD3B45}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{A75EB1DA-83CD-49D3-A562-4604A1614ADF}"
ProjectSection(SolutionItems) = preProject
..\README.md = ..\README.md
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down
Expand Up @@ -4,10 +4,11 @@
using Sample.Services;

using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace Sample.Converters
{
public class ExceptionToErrorMessageConverter : IValueConverter
public class ExceptionToErrorMessageConverter : IValueConverter, IMarkupExtension
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Expand All @@ -26,5 +27,10 @@ public object ConvertBack(object value, Type targetType, object parameter, Cultu
// One-Way converter only
throw new NotImplementedException();
}

public object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
}
Expand Up @@ -5,10 +5,11 @@
using Sample.Services;

using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace Sample.Converters
{
public class ExceptionToImageSourceConverter : IValueConverter
public class ExceptionToImageSourceConverter : IValueConverter, IMarkupExtension
{
private static ImageResourceExtension imageResourceExtension;

Expand Down Expand Up @@ -48,5 +49,10 @@ public object ConvertBack(object value, Type targetType, object parameter, Cultu
// One-Way converter only
throw new NotImplementedException();
}

public object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
}
124 changes: 62 additions & 62 deletions Retronado/Sample/Styles/Global.xaml
@@ -1,62 +1,62 @@
<?xml version="1.0" encoding="UTF-8" ?>
<?xaml-comp compile="true" ?>

<ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:converters="clr-namespace:Sample.Converters;assembly=Sample"
xmlns:customViews="clr-namespace:Sharpnado.Presentation.Forms.CustomViews;assembly=Sharpnado.TaskLoaderView"
xmlns:localization="clr-namespace:Sample.Localization;assembly=Sample"
xmlns:views="clr-namespace:Sample.Views;assembly=Sample">

<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Colors.xaml" />
<ResourceDictionary Source="Text.xaml" />
</ResourceDictionary.MergedDictionaries>

<Style ApplyToDerivedTypes="True" TargetType="ContentPage">
<Setter Property="Padding" Value="0" />
<Setter Property="BackgroundColor" Value="{StaticResource TosWindows}" />
</Style>

<Style ApplyToDerivedTypes="True" TargetType="Grid">
<Setter Property="Padding" Value="0" />
<Setter Property="ColumnSpacing" Value="0" />
<Setter Property="RowSpacing" Value="0" />
</Style>

<Style ApplyToDerivedTypes="True" TargetType="Button">
<Setter Property="FontSize" Value="16" />
<Setter Property="FontFamily" Value="{StaticResource FontArcadeClassic}" />
</Style>

<OnPlatform x:Key="PickerMargin" x:TypeArguments="Thickness">
<OnPlatform.Platforms>
<On Platform="Android" Value="0,8,0,5" />
<On Platform="iOS" Value="0" />
<On Platform="UWP" Value="0,-13,0,13" />
</OnPlatform.Platforms>
</OnPlatform>

<Style ApplyToDerivedTypes="True" TargetType="NavigationPage">
<Setter Property="BarBackgroundColor" Value="{StaticResource NavigationBarColor}" />
<Setter Property="BarTextColor" Value="{StaticResource BarTextColor}" />
</Style>

<DataTemplate x:Key="GameDataTemplate">
<views:GameViewCell />
</DataTemplate>

<converters:ExceptionToImageSourceConverter x:Key="ExceptionToImageSourceConverter" />
<converters:ExceptionToErrorMessageConverter x:Key="ExceptionToErrorMessageConverter" />
<converters:CyclicLoadingLottieConverter x:Key="CyclicLoadingLottieConverter" />
<converters:ExceptionToLottieConverter x:Key="ExceptionToLottieConverter" />
<converters:StringToImageSourceResourceConverter x:Key="StringToImageSourceResourceConverter" />
<converters:ListCountToVisibilityConverter x:Key="ListCountToVisibilityConverter" />

<Style TargetType="customViews:TaskLoaderView">
<Setter Property="AccentColor" Value="{StaticResource PrimaryColor}" />
<Setter Property="RetryButtonText" Value="{localization:Translate ErrorButton_Retry}" />
<Setter Property="TextColor" Value="{StaticResource OnSurfaceColor}" />
</Style>

</ResourceDictionary>
<?xml version="1.0" encoding="UTF-8" ?>
<?xaml-comp compile="true" ?>

<ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:converters="clr-namespace:Sample.Converters;assembly=Sample"
xmlns:localization="clr-namespace:Sample.Localization;assembly=Sample"
xmlns:views="clr-namespace:Sample.Views;assembly=Sample"
xmlns:taskLoaderView="clr-namespace:Sharpnado.TaskLoaderView;assembly=Sharpnado.TaskLoaderView">

<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Colors.xaml" />
<ResourceDictionary Source="Text.xaml" />
</ResourceDictionary.MergedDictionaries>

<Style ApplyToDerivedTypes="True" TargetType="ContentPage">
<Setter Property="Padding" Value="0" />
<Setter Property="BackgroundColor" Value="{StaticResource TosWindows}" />
</Style>

<Style ApplyToDerivedTypes="True" TargetType="Grid">
<Setter Property="Padding" Value="0" />
<Setter Property="ColumnSpacing" Value="0" />
<Setter Property="RowSpacing" Value="0" />
</Style>

<Style ApplyToDerivedTypes="True" TargetType="Button">
<Setter Property="FontSize" Value="16" />
<Setter Property="FontFamily" Value="{StaticResource FontArcadeClassic}" />
</Style>

<OnPlatform x:Key="PickerMargin" x:TypeArguments="Thickness">
<OnPlatform.Platforms>
<On Platform="Android" Value="0,8,0,5" />
<On Platform="iOS" Value="0" />
<On Platform="UWP" Value="0,-13,0,13" />
</OnPlatform.Platforms>
</OnPlatform>

<Style ApplyToDerivedTypes="True" TargetType="NavigationPage">
<Setter Property="BarBackgroundColor" Value="{StaticResource NavigationBarColor}" />
<Setter Property="BarTextColor" Value="{StaticResource BarTextColor}" />
</Style>

<DataTemplate x:Key="GameDataTemplate">
<views:GameViewCell />
</DataTemplate>

<converters:ExceptionToImageSourceConverter x:Key="ExceptionToImageSourceConverter" />
<converters:ExceptionToErrorMessageConverter x:Key="ExceptionToErrorMessageConverter" />
<converters:CyclicLoadingLottieConverter x:Key="CyclicLoadingLottieConverter" />
<converters:ExceptionToLottieConverter x:Key="ExceptionToLottieConverter" />
<converters:StringToImageSourceResourceConverter x:Key="StringToImageSourceResourceConverter" />
<converters:ListCountToVisibilityConverter x:Key="ListCountToVisibilityConverter" />

<Style TargetType="taskLoaderView:TaskLoaderView">
<Setter Property="AccentColor" Value="{StaticResource PrimaryColor}" />
<Setter Property="RetryButtonText" Value="{localization:Translate ErrorButton_Retry}" />
<Setter Property="TextColor" Value="{StaticResource OnSurfaceColor}" />
</Style>

</ResourceDictionary>
23 changes: 17 additions & 6 deletions Retronado/Sample/ViewModels/CommandsPageViewModel.cs
@@ -1,7 +1,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;

using System.Windows.Input;
using Sample.Domain;
using Sample.Navigation;
using Sample.Services;
Expand All @@ -23,9 +23,10 @@ public CommandsPageViewModel(INavigationService navigationService, IRetroGamingS
BuyGameCommand = new TaskLoaderCommand(BuyGame);
PlayTheGameCommand = new TaskLoaderCommand(PlayTheGame);

CompositeNotifier = new CompositeTaskLoaderNotifier(
BuyGameCommand.Notifier,
PlayTheGameCommand.Notifier);
CompositeNotifier = CompositeTaskLoaderNotifier.ForCommands()
.WithLoaders(Loader)
.WithCommands(BuyGameCommand, PlayTheGameCommand)
.Build();
}

public CompositeTaskLoaderNotifier CompositeNotifier { get; }
Expand All @@ -45,13 +46,23 @@ public override void OnNavigated(object parameter)
// For testing the TaskMonitorConfiguration.ConsiderCanceledAsFaulted = true setting
// cts.Cancel();

Loader.Load(_ => GetRandomGame(cts.Token));
Loader.Load(isRefreshing => GetRandomGame(cts.Token, isRefreshing));
}

private async Task<Game> GetRandomGame(CancellationToken token)
private async Task<Game> GetRandomGame(CancellationToken token, bool isRefreshing)
{
await Task.Delay(TimeSpan.FromSeconds(2), token);

if (isRefreshing)
{
throw new InvalidOperationException();
}

if (DateTime.Now.Second % 2 == 0)
{
throw new InvalidOperationException();
}

return await _retroGamingService.GetRandomGame(true);
}

Expand Down
3 changes: 3 additions & 0 deletions Retronado/Sample/ViewModels/ErrorEmulatorViewModel.cs
@@ -1,6 +1,9 @@
using System;
using System.Collections.Generic;

#if NET6_0_OR_GREATER
using ResourceGenerator.Resources;
#endif
using Sample.Infrastructure;
using Sample.Localization;

Expand Down
5 changes: 4 additions & 1 deletion Retronado/Sample/ViewModels/LoadOnDemandViewModel.cs
@@ -1,4 +1,7 @@
using Sample.Domain;
using System;
using System.Threading;
using System.Threading.Tasks;
using Sample.Domain;
using Sample.Services;
using Sharpnado.TaskLoaderView;

Expand Down

0 comments on commit 7684782

Please sign in to comment.