Add MAUI App
This commit is contained in:
Родитель
8963a8720d
Коммит
a0f2a831b7
|
@ -348,3 +348,9 @@ MigrationBackup/
|
|||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
**/.DS_Store
|
||||
**/.meteor
|
|
@ -1,6 +1,6 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<LangVersion>10.0</LangVersion>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
</Project>
|
|
@ -0,0 +1,27 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{2F02FAF9-B9BD-405B-8987-51540C89ED18}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
Directory.Build.props = Directory.Build.props
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MvvmSample.Core", "MvvmSample.Core\MvvmSample.Core.csproj", "{770388CB-6068-4201-89B6-CA131B1DC59D}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MvvmSampleMAUI", "MvvmSampleMAUI\MvvmSampleMAUI.csproj", "{056A4F3A-23D4-4028-BAD1-07E0BAF8521B}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{770388CB-6068-4201-89B6-CA131B1DC59D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{770388CB-6068-4201-89B6-CA131B1DC59D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{770388CB-6068-4201-89B6-CA131B1DC59D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{770388CB-6068-4201-89B6-CA131B1DC59D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{056A4F3A-23D4-4028-BAD1-07E0BAF8521B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{056A4F3A-23D4-4028-BAD1-07E0BAF8521B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{056A4F3A-23D4-4028-BAD1-07E0BAF8521B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{056A4F3A-23D4-4028-BAD1-07E0BAF8521B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -0,0 +1,221 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
|
||||
xmlns:md="clr-namespace:Xam.Forms.Markdown;assembly=Xam.Forms.MarkdownView"
|
||||
x:Class="MvvmSampleXF.App">
|
||||
<Application.Resources>
|
||||
<Color x:Key="PrimaryColor">#3750D1</Color>
|
||||
<Color x:Key="FrameBackgroundColorDark">#1AFFFFFF</Color>
|
||||
<Color x:Key="BackgroundColorDark">#121212</Color>
|
||||
<Color x:Key="BackgroundColorLight">#EFF2F5</Color>
|
||||
|
||||
<Color x:Key="TextPrimaryColorDark">#FFFFFF</Color>
|
||||
<Color x:Key="TextPrimaryColorLight">#323130</Color>
|
||||
|
||||
<md:DarkMarkdownTheme x:Key="DarkMarkdownTheme" />
|
||||
<md:LightMarkdownTheme x:Key="LightMarkdownTheme" />
|
||||
|
||||
<OnPlatform x:Key="ShellForegroundColorLight"
|
||||
x:TypeArguments="Color">
|
||||
<On Platform="Android"
|
||||
Value="{StaticResource TextPrimaryColorDark}" />
|
||||
<On Platform="iOS"
|
||||
Value="{StaticResource TextPrimaryColorLight}" />
|
||||
</OnPlatform>
|
||||
|
||||
<Style x:Key="BaseStyle"
|
||||
TargetType="Element"
|
||||
ApplyToDerivedTypes="True">
|
||||
<Setter Property="Shell.BackgroundColor"
|
||||
Value="{AppThemeBinding {StaticResource PrimaryColor}}" />
|
||||
<Setter Property="Shell.ForegroundColor"
|
||||
Value="{AppThemeBinding Dark={StaticResource TextPrimaryColorDark}, Light={StaticResource ShellForegroundColorLight}}" />
|
||||
<Setter Property="Shell.TabBarBackgroundColor"
|
||||
Value="{AppThemeBinding Dark={StaticResource BackgroundColorDark}, Light={StaticResource BackgroundColorLight}}" />
|
||||
<Setter Property="Shell.NavBarHasShadow"
|
||||
Value="false" />
|
||||
<Setter Property="Shell.UnselectedColor"
|
||||
Value="Gray" />
|
||||
<Setter Property="Shell.TabBarTitleColor"
|
||||
Value="{StaticResource PrimaryColor}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="TabBar"
|
||||
ApplyToDerivedTypes="True"
|
||||
BasedOn="{StaticResource BaseStyle}" />
|
||||
|
||||
<Style TargetType="FlyoutItem"
|
||||
ApplyToDerivedTypes="True"
|
||||
BasedOn="{StaticResource BaseStyle}">
|
||||
</Style>
|
||||
|
||||
|
||||
<Style BasedOn="{StaticResource BaseStyle}"
|
||||
ApplyToDerivedTypes="True"
|
||||
TargetType="Tab" />
|
||||
|
||||
<Style TargetType="Shell"
|
||||
ApplyToDerivedTypes="True">
|
||||
<Setter Property="FlyoutBackgroundColor"
|
||||
Value="{AppThemeBinding Dark={StaticResource BackgroundColorDark}, Light={StaticResource PrimaryColor}}" />
|
||||
</Style>
|
||||
|
||||
|
||||
<Style TargetType="Label"
|
||||
Class="FlyoutItemLabelStyle">
|
||||
<Setter Property="TextColor"
|
||||
Value="{StaticResource TextPrimaryColorDark}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Layout"
|
||||
Class="FlyoutItemLayoutStyle"
|
||||
ApplyToDerivedTypes="True">
|
||||
<Setter Property="BackgroundColor"
|
||||
Value="{AppThemeBinding Dark={StaticResource BackgroundColorDark}, Light={StaticResource PrimaryColor}}" />
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="BackgroundColor"
|
||||
Value="{AppThemeBinding Dark={StaticResource FrameBackgroundColorDark}, Light={StaticResource PrimaryColor}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Selected">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="BackgroundColor"
|
||||
Value="{AppThemeBinding Dark={StaticResource PrimaryColor}, Light={StaticResource FrameBackgroundColorDark}}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="md:MarkdownView">
|
||||
<Setter Property="Theme"
|
||||
Value="{AppThemeBinding Dark={StaticResource DarkMarkdownTheme}, Light={StaticResource LightMarkdownTheme}}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Page"
|
||||
ApplyToDerivedTypes="True"
|
||||
BasedOn="{StaticResource BaseStyle}">
|
||||
<Setter Property="BackgroundColor"
|
||||
Value="{AppThemeBinding Dark={StaticResource BackgroundColorDark}, Light={StaticResource BackgroundColorLight}}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Label"
|
||||
ApplyToDerivedTypes="True">
|
||||
<Setter Property="TextColor"
|
||||
Value="{AppThemeBinding Dark={StaticResource TextPrimaryColorDark}, Light={StaticResource TextPrimaryColorLight}}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Frame"
|
||||
ApplyToDerivedTypes="True">
|
||||
<Setter Property="BackgroundColor"
|
||||
Value="{AppThemeBinding Dark={StaticResource FrameBackgroundColorDark}, Light={StaticResource BackgroundColorLight}}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="toolkit:TabViewItem">
|
||||
<Setter Property="TextColor"
|
||||
Value="{AppThemeBinding Dark={StaticResource TextPrimaryColorDark}, Light={StaticResource TextPrimaryColorLight}}" />
|
||||
</Style>
|
||||
|
||||
|
||||
<Style TargetType="Picker">
|
||||
<Setter Property="TextColor"
|
||||
Value="{AppThemeBinding Dark={StaticResource TextPrimaryColorDark}, Light={StaticResource TextPrimaryColorLight}}" />
|
||||
</Style>
|
||||
|
||||
<OnPlatform x:Key="FaSolidFont"
|
||||
x:TypeArguments="x:String">
|
||||
<On Platform="iOS"
|
||||
Value="FontAwesome5Free-Solid" />
|
||||
<On Platform="Android"
|
||||
Value="FontAwesomeSolid.otf#Regular" />
|
||||
</OnPlatform>
|
||||
|
||||
<OnPlatform x:Key="FaRegularFont"
|
||||
x:TypeArguments="x:String">
|
||||
<On Platform="iOS"
|
||||
Value="FontAwesome5Free-Regular" />
|
||||
<On Platform="Android"
|
||||
Value="FontAwesomeRegular.otf#Regular" />
|
||||
</OnPlatform>
|
||||
|
||||
<OnPlatform x:Key="FaBrandsFont"
|
||||
x:TypeArguments="x:String">
|
||||
<On Platform="iOS"
|
||||
Value="FontAwesome5Brands-Regular" />
|
||||
<On Platform="Android"
|
||||
Value="FontAwesomeBrands.otf#Regular" />
|
||||
</OnPlatform>
|
||||
|
||||
<FontImage x:Key="PlayIcon"
|
||||
FontFamily="{StaticResource FaSolidFont}"
|
||||
Glyph=""/>
|
||||
|
||||
<FontImage x:Key="ExchangeIcon"
|
||||
FontFamily="{StaticResource FaSolidFont}"
|
||||
Glyph="" />
|
||||
|
||||
<FontImage x:Key="CalendarIcon"
|
||||
FontFamily="{StaticResource FaRegularFont}"
|
||||
Glyph="" />
|
||||
|
||||
<FontImage x:Key="BookIcon"
|
||||
FontFamily="{StaticResource FaSolidFont}"
|
||||
Glyph="" />
|
||||
|
||||
<FontImage x:Key="FlagIcon"
|
||||
FontFamily="{StaticResource FaSolidFont}"
|
||||
Glyph="" />
|
||||
|
||||
<FontImage x:Key="CommentIcon"
|
||||
FontFamily="{StaticResource FaSolidFont}"
|
||||
Glyph="" />
|
||||
|
||||
<FontImage x:Key="SendIcon"
|
||||
FontFamily="{StaticResource FaSolidFont}"
|
||||
Glyph="" />
|
||||
|
||||
<FontImage x:Key="SortIcon"
|
||||
FontFamily="{StaticResource FaSolidFont}"
|
||||
Glyph="" />
|
||||
|
||||
<FontImage x:Key="UndoIcon"
|
||||
FontFamily="{StaticResource FaSolidFont}"
|
||||
Glyph="" />
|
||||
|
||||
<FontImage x:Key="CubeIcon"
|
||||
FontFamily="{StaticResource FaSolidFont}"
|
||||
Glyph="" />
|
||||
|
||||
<FontImage x:Key="ToolsIcon"
|
||||
FontFamily="{StaticResource FaSolidFont}"
|
||||
Glyph="" />
|
||||
|
||||
<FontImage x:Key="WrenchIcon"
|
||||
FontFamily="{StaticResource FaSolidFont}"
|
||||
Glyph="" />
|
||||
|
||||
<FontImage x:Key="GlobeIcon"
|
||||
FontFamily="{StaticResource FaSolidFont}"
|
||||
Glyph="" />
|
||||
|
||||
<FontImage x:Key="RulerIcon"
|
||||
FontFamily="{StaticResource FaSolidFont}"
|
||||
Glyph="" />
|
||||
|
||||
<FontImage x:Key="CheckerIcon"
|
||||
FontFamily="{StaticResource FaSolidFont}"
|
||||
Glyph="" />
|
||||
|
||||
<FontImage x:Key="RefreshIcon"
|
||||
FontFamily="{StaticResource FaSolidFont}"
|
||||
Glyph="" />
|
||||
<toolkit:InvertedBoolConverter x:Key="InvertedBoolConverter" />
|
||||
<toolkit:IsNotNullOrEmptyConverter x:Key="IsNotNullOrEmptyConverter" />
|
||||
</Application.Resources>
|
||||
</Application>
|
|
@ -0,0 +1,11 @@
|
|||
namespace MvvmSampleMAUI;
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
public App()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
MainPage = new AppShell();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:controls="clr-namespace:MvvmSampleXF.Controls"
|
||||
xmlns:views="clr-namespace:MvvmSampleXF.Views"
|
||||
x:Class="MvvmSampleXF.AppShell">
|
||||
<Shell.FlyoutHeader>
|
||||
<controls:FlyoutHeader />
|
||||
</Shell.FlyoutHeader>
|
||||
<ShellContent Title="Introduction"
|
||||
Icon="{StaticResource PlayIcon}"
|
||||
ContentTemplate="{DataTemplate views:IntroductionPage}" />
|
||||
|
||||
<ShellContent Title="ObservableObject"
|
||||
Icon="{StaticResource SortIcon}"
|
||||
ContentTemplate="{DataTemplate views:ObservableObjectPage}" />
|
||||
|
||||
<FlyoutItem Title="Commands"
|
||||
Icon="{StaticResource CalendarIcon}">
|
||||
<ShellContent Title="Introduction"
|
||||
Icon="{StaticResource BookIcon}"
|
||||
ContentTemplate="{DataTemplate views:RelayCommandPage}" />
|
||||
|
||||
<ShellContent Title="Async Commands"
|
||||
Icon="{StaticResource FlagIcon}"
|
||||
ContentTemplate="{DataTemplate views:AsyncRelayCommandPage}" />
|
||||
</FlyoutItem>
|
||||
|
||||
<FlyoutItem Title="Messenger"
|
||||
Icon="{StaticResource CommentIcon}">
|
||||
<ShellContent Title="Introduction"
|
||||
Icon="{StaticResource BookIcon}"
|
||||
ContentTemplate="{DataTemplate views:MessengerPage}" />
|
||||
<ShellContent Title="Sending Messages"
|
||||
Icon="{StaticResource SendIcon}"
|
||||
ContentTemplate="{DataTemplate views:MessengerSendPage}" />
|
||||
<ShellContent Title="Request Messages"
|
||||
Icon="{StaticResource ExchangeIcon}"
|
||||
ContentTemplate="{DataTemplate views:MessengerRequestPage}" />
|
||||
</FlyoutItem>
|
||||
|
||||
<ShellContent Title="Inversion of Control"
|
||||
Icon="{StaticResource UndoIcon}"
|
||||
ContentTemplate="{DataTemplate views:IoCPage}" />
|
||||
|
||||
<ShellContent Title="ViewModel Setup"
|
||||
Icon="{StaticResource CubeIcon}"
|
||||
ContentTemplate="{DataTemplate views:SettingUpTheViewModelsPage}" />
|
||||
|
||||
<FlyoutItem Title="Creating a Service"
|
||||
Icon="{StaticResource ToolsIcon}">
|
||||
<ShellContent Title="Settings Service"
|
||||
Icon="{StaticResource WrenchIcon}"
|
||||
ContentTemplate="{DataTemplate views:SettingsServicePage}" />
|
||||
<ShellContent Title="Reddit Service"
|
||||
Icon="{StaticResource GlobeIcon}"
|
||||
ContentTemplate="{DataTemplate views:RedditServicePage}" />
|
||||
</FlyoutItem>
|
||||
|
||||
<ShellContent Title="Building the UI"
|
||||
Icon="{StaticResource RulerIcon}"
|
||||
ContentTemplate="{DataTemplate views:BuildingTheUIPage}" />
|
||||
|
||||
<ShellContent Title="The Final Result!"
|
||||
Icon="{StaticResource CheckerIcon}"
|
||||
ContentTemplate="{DataTemplate views:RedditBrowserPage}" />
|
||||
</Shell>
|
|
@ -0,0 +1,10 @@
|
|||
namespace MvvmSampleMAUI;
|
||||
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class AppShell : Shell
|
||||
{
|
||||
public AppShell()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="MvvmSampleXF.Controls.FlyoutHeader">
|
||||
<Grid BackgroundColor="{AppThemeBinding Dark=Black, Light=White}">
|
||||
<Image Aspect="AspectFill"
|
||||
Source="headerBg"
|
||||
Opacity="0.6" />
|
||||
</Grid>
|
||||
</ContentView>
|
|
@ -0,0 +1,10 @@
|
|||
namespace MvvmSampleMAUI.Controls;
|
||||
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class FlyoutHeader : ContentView
|
||||
{
|
||||
public FlyoutHeader()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
|
||||
xmlns:md="clr-namespace:Xam.Forms.Markdown;assembly=Xam.Forms.MarkdownView"
|
||||
x:Class="MvvmSampleXF.Controls.InteractiveSample">
|
||||
<ContentView.Resources>
|
||||
<ControlTemplate x:Key="InteractiveSampleTemplate">
|
||||
<Frame CornerRadius="4"
|
||||
BackgroundColor="Transparent"
|
||||
BorderColor="{StaticResource PrimaryColor}">
|
||||
<toolkit:TabView TabIndicatorColor="{StaticResource PrimaryColor}"
|
||||
TabIndicatorPlacement="Bottom">
|
||||
<toolkit:TabViewItem Text="INTERACTIVE SAMPLE"
|
||||
FontAttributes="Bold">
|
||||
<ContentPresenter Padding="12"
|
||||
Content="{TemplateBinding Content}"
|
||||
BindingContext="{TemplateBinding BindingContext}"/>
|
||||
</toolkit:TabViewItem>
|
||||
<toolkit:TabViewItem Text="See XAML">
|
||||
<ScrollView>
|
||||
<md:MarkdownView Padding="8"
|
||||
Markdown="{TemplateBinding XamlCode}" />
|
||||
</ScrollView>
|
||||
</toolkit:TabViewItem>
|
||||
<toolkit:TabViewItem Text="See C#">
|
||||
<ScrollView>
|
||||
<md:MarkdownView Padding="8"
|
||||
Markdown="{TemplateBinding CSharpCode}" />
|
||||
</ScrollView>
|
||||
</toolkit:TabViewItem>
|
||||
</toolkit:TabView>
|
||||
</Frame>
|
||||
</ControlTemplate>
|
||||
</ContentView.Resources>
|
||||
</ContentView>
|
|
@ -0,0 +1,25 @@
|
|||
namespace MvvmSampleMAUI.Controls;
|
||||
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class InteractiveSample : ContentView
|
||||
{
|
||||
public static readonly BindableProperty CSharpCodeProperty = BindableProperty.Create(nameof(CSharpCode), typeof(string), typeof(InteractiveSample), string.Empty);
|
||||
public static readonly BindableProperty XamlCodeProperty = BindableProperty.Create(nameof(XamlCode), typeof(string), typeof(InteractiveSample), string.Empty);
|
||||
|
||||
public InteractiveSample()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public string CSharpCode
|
||||
{
|
||||
get => (string)GetValue(CSharpCodeProperty);
|
||||
set => SetValue(CSharpCodeProperty, value);
|
||||
}
|
||||
|
||||
public string XamlCode
|
||||
{
|
||||
get => (string)GetValue(XamlCodeProperty);
|
||||
set => SetValue(XamlCodeProperty, value);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
using System.Globalization;
|
||||
namespace MvvmSampleMAUI.Converters;
|
||||
|
||||
public class IsSelfPostToWidthRequestConverter : IValueConverter
|
||||
{
|
||||
public double WidthRequest { get; set; }
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is string str)
|
||||
{
|
||||
return str.Equals("self", StringComparison.OrdinalIgnoreCase) ? 0 : WidthRequest;
|
||||
}
|
||||
|
||||
return 0d;
|
||||
}
|
||||
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) => throw new NotImplementedException();
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
using System.Globalization;
|
||||
namespace MvvmSampleMAUI.Converters;
|
||||
|
||||
public class TaskResultConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is Task<string> task)
|
||||
{
|
||||
return task.Status == TaskStatus.RanToCompletion ? task.Result : default;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) => throw new NotImplementedException();
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
using CommunityToolkit.Maui;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.Extensions.Http.Resilience;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MvvmSample.Core.Services;
|
||||
using MvvmSample.Core.ViewModels;
|
||||
using MvvmSample.Core.ViewModels.Widgets;
|
||||
using MvvmSampleMAUI.Services;
|
||||
using MvvmSampleMAUI.Views;
|
||||
using Polly;
|
||||
using Refit;
|
||||
|
||||
namespace MvvmSampleMAUI;
|
||||
|
||||
public static class MauiProgram
|
||||
{
|
||||
public static MauiApp CreateMauiApp()
|
||||
{
|
||||
var builder = MauiApp.CreateBuilder();
|
||||
builder
|
||||
.UseMauiApp<App>()
|
||||
.UseMauiCommunityToolkit()
|
||||
.ConfigureFonts(fonts =>
|
||||
{
|
||||
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
|
||||
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
|
||||
});
|
||||
|
||||
#if DEBUG
|
||||
builder.Logging.AddDebug();
|
||||
#endif
|
||||
builder.Services.AddSingleton<IFilesService, FileService>()
|
||||
.AddSingleton<ISettingsService, SettingsService>()
|
||||
.AddSingleton(RestService.For<IRedditService>("https://www.reddit.com/"))
|
||||
.AddTransientWithShellRoute<AsyncRelayCommandPage, AsyncRelayCommandPageViewModel>()
|
||||
.AddTransientWithShellRoute<BuildingTheUIPage, SamplePageViewModel>()
|
||||
.AddTransientWithShellRoute<IntroductionPage, ObservableObjectPageViewModel>()
|
||||
.AddTransientWithShellRoute<IoCPage, IocPageViewModel>()
|
||||
.AddTransientWithShellRoute<MessengerPage, MessengerPageViewModel>()
|
||||
.AddTransientWithShellRoute<MessengerRequestPage, MessengerPageViewModel>()
|
||||
.AddTransientWithShellRoute<MessengerSendPage, MessengerPageViewModel>()
|
||||
.AddTransientWithShellRoute<ObservableObjectPage, ObservableObjectPageViewModel>()
|
||||
.AddTransientWithShellRoute<PuttingThingsTogetherPage, SamplePageViewModel>()
|
||||
.AddTransientWithShellRoute<RedditBrowserPage>()
|
||||
.AddTransientWithShellRoute<RedditServicePage, SamplePageViewModel>()
|
||||
.AddTransientWithShellRoute<RelayCommandPage, RelayCommandPageViewModel>()
|
||||
.AddTransientWithShellRoute<SettingsServicePage, SamplePageViewModel>()
|
||||
.AddTransientWithShellRoute<MessengerSendPage, MessengerPageViewModel>()
|
||||
.AddTransientWithShellRoute<MessengerSendPage, MessengerPageViewModel>()
|
||||
.AddTransientWithShellRoute<MessengerSendPage, MessengerPageViewModel>()
|
||||
.AddTransientWithShellRoute<MessengerSendPage, MessengerPageViewModel>()
|
||||
.AddTransientWithShellRoute<MessengerSendPage, MessengerPageViewModel>();
|
||||
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
static IServiceCollection AddTransientWithShellRoute<TPage>(this IServiceCollection services) where TPage : ContentPage
|
||||
{
|
||||
Routing.RegisterRoute(AppShell.GetPageRoute<TPage>(), typeof(TPage));
|
||||
return services.AddTransient<TPage>();
|
||||
}
|
||||
|
||||
static IServiceCollection AddTransientWithShellRoute<TPage, TViewModel>(this IServiceCollection services) where TPage : BaseContentPage<TViewModel>
|
||||
where TViewModel : ObservableObject
|
||||
{
|
||||
return services.AddTransientWithShellRoute<TPage, TViewModel>(AppShell.GetPageRoute<TPage>());
|
||||
}
|
||||
|
||||
sealed class MobileHttpRetryStrategyOptions : HttpRetryStrategyOptions
|
||||
{
|
||||
public MobileHttpRetryStrategyOptions()
|
||||
{
|
||||
BackoffType = DelayBackoffType.Exponential;
|
||||
MaxRetryAttempts = 3;
|
||||
UseJitter = true;
|
||||
Delay = TimeSpan.FromSeconds(2);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net8.0-android;net8.0-ios;net8.0-maccatalyst</TargetFrameworks>
|
||||
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net8.0-windows10.0.19041.0</TargetFrameworks>
|
||||
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
|
||||
<!-- <TargetFrameworks>$(TargetFrameworks);net8.0-tizen</TargetFrameworks> -->
|
||||
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>MvvmSampleMAUI</RootNamespace>
|
||||
<UseMaui>true</UseMaui>
|
||||
<SingleProject>true</SingleProject>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<WarningsAsErrors>nullable</WarningsAsErrors>
|
||||
|
||||
<!-- Display name -->
|
||||
<ApplicationTitle>MvvmSampleMAUI</ApplicationTitle>
|
||||
|
||||
<!-- App Identifier -->
|
||||
<ApplicationId>com.microsoft.MvvmSample</ApplicationId>
|
||||
|
||||
<!-- Versions -->
|
||||
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>1</ApplicationVersion>
|
||||
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">11.0</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">13.1</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">21.0</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
|
||||
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- App Icon -->
|
||||
<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4"/>
|
||||
|
||||
<!-- Splash Screen -->
|
||||
<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#512BD4" BaseSize="128,128"/>
|
||||
|
||||
<!-- Images -->
|
||||
<MauiImage Include="Resources\Images\*"/>
|
||||
<MauiImage Update="Resources\Images\dotnet_bot.png" Resize="True" BaseSize="300,185"/>
|
||||
|
||||
<!-- Custom Fonts -->
|
||||
<MauiFont Include="Resources\Fonts\*"/>
|
||||
|
||||
<!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
|
||||
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Maui" Version="9.1.0" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.3.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="8.10.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.0"/>
|
||||
<PackageReference Include="Indiko.Maui.Controls.Markdown" Version="1.0.17"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MvvmSample.Core\MvvmSample.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.5.002.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MvvmSampleMAUI", "MvvmSampleMAUI.csproj", "{29A3122D-D9B1-4A31-B0F3-622B0D97E66C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{29A3122D-D9B1-4A31-B0F3-622B0D97E66C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{29A3122D-D9B1-4A31-B0F3-622B0D97E66C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{29A3122D-D9B1-4A31-B0F3-622B0D97E66C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{29A3122D-D9B1-4A31-B0F3-622B0D97E66C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {C0E44466-2824-4633-8595-21FA2B608433}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -0,0 +1,31 @@
|
|||
using System.Reflection;
|
||||
using MvvmSample.Core.Services;
|
||||
|
||||
namespace MvvmSampleMAUI.Services;
|
||||
|
||||
public sealed class FileService : IFilesService
|
||||
{
|
||||
public string InstallationPath => Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||
|
||||
public Task<Stream> OpenForReadAsync(string path)
|
||||
{
|
||||
if (GetType() is not Type type)
|
||||
{
|
||||
throw new InvalidOperationException("Could not retrieve type");
|
||||
}
|
||||
|
||||
return GetEmbeddedFileStreamAsync(type, path);
|
||||
}
|
||||
|
||||
|
||||
static async Task<Stream> GetEmbeddedFileStreamAsync(Type assemblyType, string fileName)
|
||||
{
|
||||
var manifestName = assemblyType.GetTypeInfo().Assembly
|
||||
.GetManifestResourceNames()
|
||||
.FirstOrDefault(n => n.EndsWith(fileName.Replace(" ", "_").Replace("\\", ".").Replace("/", "."), StringComparison.OrdinalIgnoreCase))
|
||||
?? throw new InvalidOperationException($"Failed to find resource [{fileName}]");
|
||||
|
||||
return await Task.FromResult(assemblyType.GetTypeInfo().Assembly.GetManifestResourceStream(manifestName) ?? Stream.Null)
|
||||
.ConfigureAwait(ConfigureAwaitOptions.ForceYielding | ConfigureAwaitOptions.None);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using MvvmSample.Core.Services;
|
||||
|
||||
namespace MvvmSampleMAUI.Services;
|
||||
|
||||
public sealed class SettingsService(IPreferences preferences) : ISettingsService
|
||||
{
|
||||
readonly IPreferences _preferences = preferences;
|
||||
|
||||
public T? GetValue<T>(string key) => _preferences.Get<T?>(key, default);
|
||||
|
||||
public void SetValue<T>(string key, T? value) => _preferences.Set(key, value);
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:vm="clr-namespace:MvvmSample.Core.ViewModels;assembly=MvvmSample.Core"
|
||||
xmlns:md="clr-namespace:Xam.Forms.Markdown;assembly=Xam.Forms.MarkdownView"
|
||||
xmlns:controls="clr-namespace:MvvmSampleXF.Controls"
|
||||
xmlns:converters="clr-namespace:MvvmSampleXF.Converters"
|
||||
x:Class="MvvmSampleXF.Views.AsyncRelayCommandPage"
|
||||
x:DataType="vm:AsyncRelayCommandPageViewModel"
|
||||
Title="Commands">
|
||||
<ContentPage.Resources>
|
||||
<converters:TaskResultConverter x:Key="TaskResultConverter" />
|
||||
</ContentPage.Resources>
|
||||
<ScrollView Padding="16">
|
||||
<StackLayout Padding="16">
|
||||
<md:MarkdownView Markdown="{Binding Path=Texts[AsyncRelayCommand and AsyncRelayCommand<T>], Mode=OneWay}" />
|
||||
<md:MarkdownView Markdown="{Binding Path=Texts[How they work], Mode=OneWay}" />
|
||||
<md:MarkdownView Markdown="{Binding Path=Texts[Working with asynchronous commands], Mode=OneWay}" />
|
||||
|
||||
<controls:InteractiveSample ControlTemplate="{StaticResource InteractiveSampleTemplate}"
|
||||
HeightRequest="400">
|
||||
<StackLayout Spacing="8">
|
||||
<Label Text="{Binding DownloadTextCommand.ExecutionTask.Status, TargetNullValue='Task status: ', FallbackValue='Task status: ', StringFormat='Task status: {0}' }" />
|
||||
<Label Text="{Binding DownloadTextCommand.ExecutionTask, Converter={StaticResource TaskResultConverter}, StringFormat='Result: {0}' }" />
|
||||
<Button Text="Click me!"
|
||||
Command="{Binding DownloadTextCommand}" />
|
||||
<ActivityIndicator HorizontalOptions="Center"
|
||||
IsVisible="{Binding DownloadTextCommand.IsRunning, Mode=OneWay}"
|
||||
IsRunning="{Binding DownloadTextCommand.IsRunning, Mode=OneWay}" />
|
||||
</StackLayout>
|
||||
<controls:InteractiveSample.XamlCode>
|
||||
```xml
|
||||
<ContentPage.Resources>
|
||||
<converters:TaskResultConverter x:Key="TaskResultConverter" />
|
||||
</ContentPage.Resources>
|
||||
<StackLayout Spacing="8">
|
||||
<Label Text="{Binding DownloadTextCommand.ExecutionTask.Status, TargetNullValue='Task status: ', FallbackValue='Task status: ', StringFormat='Task status: {0}' }" />
|
||||
<Label Text="{Binding DownloadTextCommand.ExecutionTask, Converter={StaticResource TaskResultConverter}, StringFormat='Result: {0}' }" />
|
||||
<Button Text="Click me!"
|
||||
Command="{Binding DownloadTextCommand}" />
|
||||
<ActivityIndicator HorizontalOptions="Center"
|
||||
IsVisible="{Binding DownloadTextCommand.IsRunning, Mode=OneWay}"
|
||||
IsRunning="{Binding DownloadTextCommand.IsRunning, Mode=OneWay}" />
|
||||
</StackLayout>
|
||||
|
||||
```
|
||||
</controls:InteractiveSample.XamlCode>
|
||||
<controls:InteractiveSample.CSharpCode>
|
||||
```csharp
|
||||
public MyViewModel()
|
||||
{
|
||||
DownloadTextCommand = new AsyncRelayCommand(DownloadTextAsync);
|
||||
}
|
||||
|
||||
public IAsyncRelayCommand DownloadTextCommand { get; }
|
||||
|
||||
private async Task<string> DownloadTextAsync()
|
||||
{
|
||||
await Task.Delay(3000); // Simulate a web request
|
||||
|
||||
return "Hello world!";
|
||||
}
|
||||
|
||||
```
|
||||
</controls:InteractiveSample.CSharpCode>
|
||||
</controls:InteractiveSample>
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
</ContentPage>
|
|
@ -0,0 +1,20 @@
|
|||
using MvvmSample.Core.ViewModels;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
|
||||
namespace MvvmSampleMAUI.Views;
|
||||
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class AsyncRelayCommandPage : BaseContentPage<AsyncRelayCommandPageViewModel>
|
||||
{
|
||||
public AsyncRelayCommandPage(AsyncRelayCommandPageViewModel viewModel) : base(viewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
|
||||
BindingContext.LoadDocsCommand.Execute("AsyncRelayCommand");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
namespace MvvmSampleMAUI.Views;
|
||||
|
||||
public abstract class BaseContentPage<TViewModel> : ContentPage where TViewModel : ObservableObject
|
||||
{
|
||||
protected BaseContentPage(TViewModel viewModel)
|
||||
{
|
||||
base.BindingContext = viewModel;
|
||||
}
|
||||
|
||||
protected new TViewModel BindingContext => (TViewModel)base.BindingContext;
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:vm="clr-namespace:MvvmSample.Core.ViewModels;assembly=MvvmSample.Core"
|
||||
xmlns:md="clr-namespace:Xam.Forms.Markdown;assembly=Xam.Forms.MarkdownView"
|
||||
x:Class="MvvmSampleXF.Views.BuildingTheUIPage"
|
||||
x:DataType="vm:SamplePageViewModel"
|
||||
Title="Building the UI">
|
||||
<ScrollView Padding="16">
|
||||
<StackLayout Padding="16">
|
||||
<md:MarkdownView Markdown="{Binding Path=Texts[Building the UI], Mode=OneWay}" />
|
||||
<md:MarkdownView Markdown="{Binding Path=Texts[Good to go! 🚀], Mode=OneWay}" />
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
</ContentPage>
|
|
@ -0,0 +1,19 @@
|
|||
using MvvmSample.Core.ViewModels;
|
||||
|
||||
namespace MvvmSampleMAUI.Views;
|
||||
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class BuildingTheUIPage : BaseContentPage<SamplePageViewModel>
|
||||
{
|
||||
public BuildingTheUIPage(SamplePageViewModel viewModel) : base(viewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
|
||||
BindingContext.LoadDocsCommand.Execute("PuttingThingsTogether");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:vm="clr-namespace:MvvmSample.Core.ViewModels;assembly=MvvmSample.Core"
|
||||
xmlns:md="clr-namespace:Xam.Forms.Markdown;assembly=Xam.Forms.MarkdownView"
|
||||
x:Class="MvvmSampleXF.Views.IntroductionPage"
|
||||
x:DataType="vm:ObservableObjectPageViewModel"
|
||||
Title="Introduction">
|
||||
<ScrollView Padding="16">
|
||||
<StackLayout Padding="16">
|
||||
<md:MarkdownView Markdown="{Binding Path=Texts[Introduction to the MVVM package], Mode=OneWay}" />
|
||||
<md:MarkdownView Markdown="{Binding Path=Texts[When should I use this package?], Mode=OneWay}" />
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
</ContentPage>
|
|
@ -0,0 +1,20 @@
|
|||
using MvvmSample.Core.ViewModels;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
|
||||
namespace MvvmSampleMAUI.Views;
|
||||
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class IntroductionPage : BaseContentPage<ObservableObjectPageViewModel>
|
||||
{
|
||||
public IntroductionPage(ObservableObjectPageViewModel viewModel) : base(viewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
|
||||
BindingContext.LoadDocsCommand.Execute("Introduction");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:vm="clr-namespace:MvvmSample.Core.ViewModels;assembly=MvvmSample.Core"
|
||||
xmlns:md="clr-namespace:Xam.Forms.Markdown;assembly=Xam.Forms.MarkdownView"
|
||||
x:Class="MvvmSampleXF.Views.IoCPage"
|
||||
x:DataType="vm:IocPageViewModel"
|
||||
Title="Inversion of Control">
|
||||
<ScrollView Padding="16">
|
||||
<StackLayout Padding="16">
|
||||
<md:MarkdownView Markdown="{Binding Path=Texts[Ioc (Inversion of control)], Mode=OneWay}" />
|
||||
<md:MarkdownView Markdown="{Binding Path=Texts[Configure and resolve services], Mode=OneWay}" />
|
||||
<md:MarkdownView Markdown="{Binding Path=Texts[Constructor injection], Mode=OneWay}" />
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
</ContentPage>
|
|
@ -0,0 +1,22 @@
|
|||
using MvvmSample.Core.ViewModels;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
|
||||
namespace MvvmSampleMAUI.Views;
|
||||
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class IoCPage : BaseContentPage<IocPageViewModel>
|
||||
{
|
||||
public IoCPage(IocPageViewModel viewModel) : base(viewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public IocPageViewModel ViewModel => (IocPageViewModel)BindingContext;
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
|
||||
ViewModel.LoadDocsCommand.Execute("IoC");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:vm="clr-namespace:MvvmSample.Core.ViewModels;assembly=MvvmSample.Core"
|
||||
xmlns:md="clr-namespace:Xam.Forms.Markdown;assembly=Xam.Forms.MarkdownView"
|
||||
x:Class="MvvmSampleXF.Views.MessengerPage"
|
||||
x:DataType="vm:MessengerPageViewModel"
|
||||
Title="Messenger">
|
||||
<ScrollView Padding="16">
|
||||
<StackLayout Padding="16">
|
||||
<md:MarkdownView Markdown="{Binding Path=Texts[Messenger], Mode=OneWay}" />
|
||||
<md:MarkdownView Markdown="{Binding Path=Texts[How it works], Mode=OneWay}" />
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
</ContentPage>
|
|
@ -0,0 +1,20 @@
|
|||
using MvvmSample.Core.ViewModels;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
|
||||
namespace MvvmSampleMAUI.Views;
|
||||
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class MessengerPage : BaseContentPage<MessengerPageViewModel>
|
||||
{
|
||||
public MessengerPage(MessengerPageViewModel viewModel) : base(viewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
|
||||
BindingContext.LoadDocsCommand.Execute("Messenger");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:vm="clr-namespace:MvvmSample.Core.ViewModels;assembly=MvvmSample.Core"
|
||||
xmlns:md="clr-namespace:Xam.Forms.Markdown;assembly=Xam.Forms.MarkdownView"
|
||||
xmlns:controls="clr-namespace:MvvmSampleXF.Controls"
|
||||
x:Class="MvvmSampleXF.Views.MessengerRequestPage"
|
||||
Title="Messenger">
|
||||
<ScrollView Padding="16">
|
||||
<StackLayout Padding="16">
|
||||
<md:MarkdownView Markdown="{Binding Path=Texts[Sending and receiving messages], Mode=OneWay}" />
|
||||
|
||||
<controls:InteractiveSample ControlTemplate="{StaticResource InteractiveSampleTemplate}"
|
||||
HeightRequest="500">
|
||||
<StackLayout Spacing="8">
|
||||
|
||||
<Label Text="{Binding Username, Mode=OneWay}" />
|
||||
<Button Text="Click to request the username!"
|
||||
Command="{Binding RequestCurrentUsernameCommand}" />
|
||||
<Button Text="Click to reset the local username!"
|
||||
Command="{Binding ResetCurrentUsernameCommand}" />
|
||||
</StackLayout>
|
||||
<controls:InteractiveSample.XamlCode>
|
||||
```xml
|
||||
<StackLayout Spacing="8">
|
||||
|
||||
<Label Text="{Binding Username, Mode=OneWay}" />
|
||||
<Button Text="Click to request the username!"
|
||||
Command="{Binding RequestCurrentUsernameCommand}" />
|
||||
<Button Text="Click to reset the local username!"
|
||||
Command="{Binding ResetCurrentUsernameCommand}" />
|
||||
</StackLayout>
|
||||
|
||||
```
|
||||
</controls:InteractiveSample.XamlCode>
|
||||
<controls:InteractiveSample.CSharpCode>
|
||||
```csharp
|
||||
public class UserSenderViewModel : ObservableRecipient
|
||||
{
|
||||
public UserSenderViewModel()
|
||||
{
|
||||
SendUserMessageCommand = new RelayCommand(SendUserMessage);
|
||||
}
|
||||
|
||||
public ICommand SendUserMessageCommand { get; }
|
||||
|
||||
private string username = "Bob";
|
||||
|
||||
public string Username
|
||||
{
|
||||
get => username;
|
||||
private set => SetProperty(ref username, value);
|
||||
}
|
||||
|
||||
protected override void OnActivated()
|
||||
{
|
||||
Messenger.Register
|
||||
<CurrentUsernameRequestMessage>(this, m => m.Reply(Username));
|
||||
}
|
||||
|
||||
public void SendUserMessage()
|
||||
{
|
||||
Username = Username == "Bob" ? "Alice" : "Bob";
|
||||
|
||||
Messenger.Send(new UsernameChangedMessage(Username));
|
||||
}
|
||||
}
|
||||
|
||||
// A sample request message to get the current username
|
||||
public sealed class CurrentUsernameRequestMessage : RequestMessage<string>
|
||||
{
|
||||
}
|
||||
```
|
||||
</controls:InteractiveSample.CSharpCode>
|
||||
</controls:InteractiveSample>
|
||||
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
</ContentPage>
|
|
@ -0,0 +1,29 @@
|
|||
using MvvmSample.Core.ViewModels;
|
||||
|
||||
namespace MvvmSampleMAUI.Views;
|
||||
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class MessengerRequestPage : BaseContentPage<MessengerPageViewModel>
|
||||
{
|
||||
public MessengerRequestPage(MessengerPageViewModel viewModel) : base(viewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
|
||||
BindingContext.LoadDocsCommand.Execute("Messenger");
|
||||
BindingContext.SenderViewModel.IsActive = true;
|
||||
BindingContext.ReceiverViewModel.IsActive = true;
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
|
||||
ViewModel.SenderViewModel.IsActive = false;
|
||||
ViewModel.ReceiverViewModel.IsActive = false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:vm="clr-namespace:MvvmSample.Core.ViewModels;assembly=MvvmSample.Core"
|
||||
xmlns:md="clr-namespace:Xam.Forms.Markdown;assembly=Xam.Forms.MarkdownView"
|
||||
xmlns:controls="clr-namespace:MvvmSampleXF.Controls"
|
||||
x:Class="MvvmSampleXF.Views.MessengerSendPage"
|
||||
x:DataType="vm:MessengerPageViewModel"
|
||||
Title="Messenger">
|
||||
<ScrollView Padding="16">
|
||||
<StackLayout Padding="16">
|
||||
<md:MarkdownView Markdown="{Binding Path=Texts[Sending and receiving messages], Mode=OneWay}" />
|
||||
|
||||
<controls:InteractiveSample ControlTemplate="{StaticResource InteractiveSampleTemplate}"
|
||||
HeightRequest="600">
|
||||
<StackLayout Spacing="8">
|
||||
|
||||
<!--Sender module-->
|
||||
<Frame BorderColor="#40FFFFFF"
|
||||
CornerRadius="4"
|
||||
Padding="8">
|
||||
<StackLayout Spacing="8">
|
||||
<Label Text="{Binding SenderViewModel.Username, Mode=OneWay}" />
|
||||
<Button Text="Click to send a message!"
|
||||
Command="{Binding SenderViewModel.SendUserMessageCommand}" />
|
||||
</StackLayout>
|
||||
</Frame>
|
||||
|
||||
<!--Receiver module-->
|
||||
<Frame BorderColor="#40FFFFFF"
|
||||
CornerRadius="4"
|
||||
Padding="8">
|
||||
<StackLayout Spacing="8">
|
||||
<Label Text="{Binding ReceiverViewModel.Username, Mode=OneWay}" />
|
||||
</StackLayout>
|
||||
</Frame>
|
||||
</StackLayout>
|
||||
<controls:InteractiveSample.XamlCode>
|
||||
```xml
|
||||
<StackLayout Spacing="8">
|
||||
|
||||
<!--Sender module-->
|
||||
<Frame BorderColor="#40FFFFFF"
|
||||
CornerRadius="4"
|
||||
Padding="8">
|
||||
<StackLayout Spacing="8">
|
||||
<Label Text="{Binding SenderViewModel.Username, Mode=OneWay}" />
|
||||
<Button Text="Click to send a message!"
|
||||
Command="{Binding SenderViewModel.SendUserMessageCommand}" />
|
||||
</StackLayout>
|
||||
</Frame>
|
||||
|
||||
<!--Receiver module-->
|
||||
<Frame BorderColor="#40FFFFFF"
|
||||
CornerRadius="4"
|
||||
Padding="8">
|
||||
<StackLayout Spacing="8">
|
||||
<Label Text="{Binding ReceiverViewModel.Username, Mode=OneWay}" />
|
||||
</StackLayout>
|
||||
</Frame>
|
||||
</StackLayout>
|
||||
|
||||
```
|
||||
</controls:InteractiveSample.XamlCode>
|
||||
<controls:InteractiveSample.CSharpCode>
|
||||
```csharp
|
||||
public class MessengerPageViewModel : SamplePageViewModel
|
||||
{
|
||||
public MessengerPageViewModel()
|
||||
{
|
||||
RequestCurrentUsernameCommand = new RelayCommand(RequestCurrentUsername);
|
||||
ResetCurrentUsernameCommand = new RelayCommand(ResetCurrentUsername);
|
||||
}
|
||||
|
||||
public ICommand RequestCurrentUsernameCommand { get; }
|
||||
public ICommand ResetCurrentUsernameCommand { get; }
|
||||
|
||||
public UserSenderViewModel SenderViewModel { get; } = new UserSenderViewModel();
|
||||
|
||||
public UserReceiverViewModel ReceiverViewModel { get; } = new UserReceiverViewModel();
|
||||
|
||||
// Simple viewmodel for a module sending a username message
|
||||
public class UserSenderViewModel : ObservableRecipient
|
||||
{
|
||||
public UserSenderViewModel()
|
||||
{
|
||||
SendUserMessageCommand = new RelayCommand(SendUserMessage);
|
||||
}
|
||||
|
||||
public ICommand SendUserMessageCommand { get; }
|
||||
|
||||
private string username = "Bob";
|
||||
|
||||
public string Username
|
||||
{
|
||||
get => username;
|
||||
private set => SetProperty(ref username, value);
|
||||
}
|
||||
|
||||
protected override void OnActivated()
|
||||
{
|
||||
Messenger.Register
|
||||
<CurrentUsernameRequestMessage>(this, m => m.Reply(Username));
|
||||
}
|
||||
|
||||
public void SendUserMessage()
|
||||
{
|
||||
Username = Username == "Bob" ? "Alice" : "Bob";
|
||||
|
||||
Messenger.Send(new UsernameChangedMessage(Username));
|
||||
}
|
||||
}
|
||||
|
||||
// Simple viewmodel for a module receiving a username message
|
||||
public class UserReceiverViewModel : ObservableRecipient
|
||||
{
|
||||
private string username = "";
|
||||
|
||||
public string Username
|
||||
{
|
||||
get => username;
|
||||
private set => SetProperty(ref username, value);
|
||||
}
|
||||
|
||||
protected override void OnActivated()
|
||||
{
|
||||
Messenger.Register
|
||||
<UsernameChangedMessage>(this, m => Username = m.Value);
|
||||
}
|
||||
}
|
||||
|
||||
// A sample message with a username value
|
||||
public sealed class UsernameChangedMessage : ValueChangedMessage<string>
|
||||
{
|
||||
public UsernameChangedMessage(string value) : base(value)
|
||||
{
|
||||
}
|
||||
}
|
||||
```
|
||||
</controls:InteractiveSample.CSharpCode>
|
||||
</controls:InteractiveSample>
|
||||
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
</ContentPage>
|
|
@ -0,0 +1,29 @@
|
|||
using MvvmSample.Core.ViewModels;
|
||||
|
||||
namespace MvvmSampleMAUI.Views;
|
||||
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class MessengerSendPage : BaseContentPage<MessengerPageViewModel>
|
||||
{
|
||||
public MessengerSendPage(MessengerPageViewModel viewModel) : base(viewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
|
||||
BindingContext.LoadDocsCommand.Execute("Messenger");
|
||||
BindingContext.SenderViewModel.IsActive = true;
|
||||
BindingContext.ReceiverViewModel.IsActive = true;
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
|
||||
BindingContext.SenderViewModel.IsActive = false;
|
||||
BindingContext.ReceiverViewModel.IsActive = false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:vm="clr-namespace:MvvmSample.Core.ViewModels;assembly=MvvmSample.Core"
|
||||
xmlns:md="clr-namespace:Xam.Forms.Markdown;assembly=Xam.Forms.MarkdownView"
|
||||
xmlns:controls="clr-namespace:MvvmSampleXF.Controls"
|
||||
x:Class="MvvmSampleXF.Views.ObservableObjectPage"
|
||||
x:DataType="vm:ObservableObjectPageViewModel"
|
||||
Title="ObservableObject">
|
||||
<ScrollView Padding="16">
|
||||
<StackLayout Padding="16">
|
||||
<md:MarkdownView Markdown="{Binding Path=Texts[ObservableObject], Mode=OneWay}" />
|
||||
<md:MarkdownView Markdown="{Binding Path=Texts[How it works], Mode=OneWay}" />
|
||||
<md:MarkdownView Markdown="{Binding Path=Texts[Simple property], Mode=OneWay}" />
|
||||
|
||||
<controls:InteractiveSample ControlTemplate="{StaticResource InteractiveSampleTemplate}"
|
||||
HeightRequest="300">
|
||||
<StackLayout Spacing="8">
|
||||
<!--Simple property sample-->
|
||||
<Entry Placeholder="Type here to update the text below"
|
||||
Text="{Binding Name, Mode=TwoWay}" />
|
||||
<Label Text="{Binding Name, Mode=OneWay}" />
|
||||
</StackLayout>
|
||||
<controls:InteractiveSample.XamlCode>
|
||||
```xml
|
||||
<StackLayout Spacing="8">
|
||||
<!--Simple property sample-->
|
||||
<Entry Placeholder="Type here to update the text below"
|
||||
Text="{Binding Name, Mode=TwoWay}" />
|
||||
<Label Text="{Binding Name, Mode=OneWay}" />
|
||||
</StackLayout>
|
||||
|
||||
```
|
||||
</controls:InteractiveSample.XamlCode>
|
||||
<controls:InteractiveSample.CSharpCode>
|
||||
```csharp
|
||||
private string name;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name to display.
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get => name;
|
||||
set => SetProperty(ref name, value);
|
||||
}
|
||||
|
||||
```
|
||||
</controls:InteractiveSample.CSharpCode>
|
||||
</controls:InteractiveSample>
|
||||
|
||||
|
||||
|
||||
<md:MarkdownView Markdown="{Binding Path=Texts[Wrapping a non-observable model], Mode=OneWay}" />
|
||||
<md:MarkdownView Markdown="{Binding Path=Texts[Handling `Task<T>` properties], Mode=OneWay}" />
|
||||
|
||||
<controls:InteractiveSample ControlTemplate="{StaticResource InteractiveSampleTemplate}"
|
||||
HeightRequest="400">
|
||||
<StackLayout Spacing="8">
|
||||
<Button Text="Click me to load a Task to await"
|
||||
Command="{Binding ReloadTaskCommand}" />
|
||||
<Label Text="{Binding MyTask.Status, Mode=OneWay}" />
|
||||
</StackLayout>
|
||||
<controls:InteractiveSample.XamlCode>
|
||||
```xml
|
||||
<StackLayout Spacing="8">
|
||||
<Button Text="Click me to load a Task to await"
|
||||
Command="{Binding ReloadTaskCommand}" />
|
||||
<Label Text="{Binding MyTask.Status, Mode=OneWay}" />
|
||||
</StackLayout>
|
||||
|
||||
```
|
||||
</controls:InteractiveSample.XamlCode>
|
||||
<controls:InteractiveSample.CSharpCode>
|
||||
```csharp
|
||||
public ObservableObjectPageViewModel()
|
||||
{
|
||||
ReloadTaskCommand = new RelayCommand(ReloadTask);
|
||||
}
|
||||
|
||||
public ICommand ReloadTaskCommand { get; }
|
||||
|
||||
private string name;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get => name;
|
||||
set => SetProperty(ref name, value);
|
||||
}
|
||||
|
||||
private TaskNotifier myTask;
|
||||
|
||||
public Task MyTask
|
||||
{
|
||||
get => myTask;
|
||||
private set => SetPropertyAndNotifyOnCompletion(ref myTask, value);
|
||||
}
|
||||
|
||||
public void ReloadTask()
|
||||
{
|
||||
MyTask = Task.Delay(3000);
|
||||
}
|
||||
|
||||
```
|
||||
</controls:InteractiveSample.CSharpCode>
|
||||
</controls:InteractiveSample>
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
</ContentPage>
|
|
@ -0,0 +1,19 @@
|
|||
using MvvmSample.Core.ViewModels;
|
||||
|
||||
namespace MvvmSampleMAUI.Views;
|
||||
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class ObservableObjectPage : BaseContentPage<ObservableObjectPageViewModel>
|
||||
{
|
||||
public ObservableObjectPage(ObservableObjectPageViewModel viewModel) : base(viewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
|
||||
BindingContext.LoadDocsCommand.Execute("ObservableObject");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:vm="clr-namespace:MvvmSample.Core.ViewModels;assembly=MvvmSample.Core"
|
||||
xmlns:md="clr-namespace:Xam.Forms.Markdown;assembly=Xam.Forms.MarkdownView"
|
||||
x:Class="MvvmSampleXF.Views.PuttingThingsTogetherPage"
|
||||
x:DataType="vm:SamplePageViewModel">
|
||||
<ScrollView Padding="16">
|
||||
<StackLayout Spacing="16">
|
||||
<md:MarkdownView Markdown="{Binding Path=Texts[Putting things together], Mode=OneWay}" />
|
||||
<md:MarkdownView Markdown="{Binding Path=Texts[What do we want to build], Mode=OneWay}" />
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
</ContentPage>
|
|
@ -0,0 +1,20 @@
|
|||
using MvvmSample.Core.ViewModels;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
|
||||
namespace MvvmSampleMAUI.Views;
|
||||
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class PuttingThingsTogetherPage : BaseContentPage<SamplePageViewModel>
|
||||
{
|
||||
public PuttingThingsTogetherPage(SamplePageViewModel viewModel) : base(viewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
|
||||
BindingContext.LoadDocsCommand.Execute("PuttingThingsTogether");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,254 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:controls="clr-namespace:MvvmSampleXF.Controls"
|
||||
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
|
||||
xmlns:widgets="clr-namespace:MvvmSampleXF.Views.Widgets"
|
||||
x:Class="MvvmSampleXF.Views.RedditBrowserPage"
|
||||
Title="The Final Result!">
|
||||
<Grid Padding="16">
|
||||
<controls:InteractiveSample ControlTemplate="{StaticResource InteractiveSampleTemplate}">
|
||||
<toolkit:TabView x:Name="RedditTabView"
|
||||
TabIndicatorPlacement="Bottom"
|
||||
TabIndicatorColor="Orange"
|
||||
IsSwipeEnabled="False">
|
||||
<toolkit:TabViewItem Text="Subreddit">
|
||||
<widgets:SubredditWidget x:Name="SubredditWidget" />
|
||||
</toolkit:TabViewItem>
|
||||
<toolkit:TabViewItem Text="Post">
|
||||
<widgets:PostWidget x:Name="PostWidget" />
|
||||
</toolkit:TabViewItem>
|
||||
</toolkit:TabView>
|
||||
<controls:InteractiveSample.XamlCode>
|
||||
|
||||
```xml
|
||||
<!--Feed-->
|
||||
<Grid RowDefinitions="60, 8, *">
|
||||
|
||||
<!--Header with topic selector and refresh button-->
|
||||
<Frame Padding="10,0,10,0">
|
||||
<Grid ColumnDefinitions="*, Auto">
|
||||
<Picker ItemsSource="{Binding Subreddits, Mode=OneWay}"
|
||||
SelectedItem="{Binding SelectedSubreddit, Mode=TwoWay}"
|
||||
VerticalOptions="Center"
|
||||
HorizontalOptions="StartAndExpand">
|
||||
<Picker.Behaviors>
|
||||
<toolkit:EventToCommandBehavior EventName="SelectedIndexChanged"
|
||||
Command="{Binding LoadPostsCommand}" />
|
||||
</Picker.Behaviors>
|
||||
</Picker>
|
||||
<Button Text="Refresh"
|
||||
Grid.Column="1"
|
||||
Command="{Binding LoadPostsCommand}" />
|
||||
</Grid>
|
||||
</Frame>
|
||||
|
||||
<!--Items list-->
|
||||
<CollectionView Grid.Row="2"
|
||||
ItemsSource="{Binding Posts}"
|
||||
SelectedItem="{Binding SelectedPost, Mode=TwoWay}"
|
||||
SelectionMode="Single"
|
||||
SelectionChanged="CollectionView_SelectionChanged"
|
||||
IsVisible="{Binding LoadPostsCommand.IsRunning, Mode=OneWay, Converter={StaticResource InvertedBoolConverter}}">
|
||||
<CollectionView.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:Post">
|
||||
<Frame BorderColor="Black">
|
||||
<Grid ColumnSpacing="8"
|
||||
Padding="16"
|
||||
ColumnDefinitions="*, Auto">
|
||||
<Label Text="{Binding Title}"
|
||||
FontSize="15"
|
||||
LineBreakMode="WordWrap"
|
||||
VerticalOptions="Center" />
|
||||
<Image Grid.Column="1"
|
||||
Source="{Binding Thumbnail}"
|
||||
Aspect="AspectFit"
|
||||
HorizontalOptions="End"
|
||||
WidthRequest="{Binding Thumbnail, Converter={StaticResource IsSelfPostToWidthRequestConverter}}" />
|
||||
</Grid>
|
||||
</Frame>
|
||||
</DataTemplate>
|
||||
</CollectionView.ItemTemplate>
|
||||
</CollectionView>
|
||||
|
||||
<!--Loading bar-->
|
||||
<ActivityIndicator Grid.Row="1"
|
||||
Grid.RowSpan="2"
|
||||
Margin="0, 10, 0, 0"
|
||||
VerticalOptions="Start"
|
||||
IsRunning="{Binding LoadPostsCommand.IsRunning, Mode=OneWay}"
|
||||
IsVisible="{Binding LoadPostsCommand.IsRunning, Mode=OneWay}" />
|
||||
</Grid>
|
||||
|
||||
<!--Post-->
|
||||
<Grid RowDefinitions="Auto, *">
|
||||
|
||||
<!--Self text-->
|
||||
<Frame Grid.Row="1">
|
||||
<ScrollView>
|
||||
<Label Text="{Binding Post.SelfText, Mode=OneWay}"
|
||||
LineBreakMode="WordWrap"
|
||||
Margin="16" />
|
||||
</ScrollView>
|
||||
</Frame>
|
||||
|
||||
<!--Header-->
|
||||
<Frame>
|
||||
<Grid Grid.Row="0"
|
||||
ColumnSpacing="8"
|
||||
Padding="16"
|
||||
ColumnDefinitions="*, Auto">
|
||||
<Label Text="{Binding Post.Title, Mode=OneWay}"
|
||||
FontSize="16"
|
||||
FontAttributes="Bold"
|
||||
LineBreakMode="WordWrap"
|
||||
VerticalOptions="Center" />
|
||||
<Image Grid.Column="1"
|
||||
Source="{Binding Post.Thumbnail, Mode=OneWay}"
|
||||
Aspect="AspectFit"
|
||||
HorizontalOptions="End"
|
||||
WidthRequest="{Binding Post.Thumbnail, Converter={StaticResource IsSelfPostToWidthRequestConverter}}" />
|
||||
</Grid>
|
||||
</Frame>
|
||||
</Grid>
|
||||
```
|
||||
</controls:InteractiveSample.XamlCode>
|
||||
<controls:InteractiveSample.CSharpCode>
|
||||
```csharp
|
||||
/// <summary>
|
||||
/// A viewmodel for a subreddit widget.
|
||||
/// </summary>
|
||||
public sealed class SubredditWidgetViewModel : ObservableRecipient
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IRedditService"/> instance to use.
|
||||
/// </summary>
|
||||
private readonly IRedditService RedditService = Ioc.Default.GetRequiredService<IRedditService>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="ISettingsService"/> instance to use.
|
||||
/// </summary>
|
||||
private readonly ISettingsService SettingsService = Ioc.Default.GetRequiredService<ISettingsService>();
|
||||
|
||||
/// <summary>
|
||||
/// An <see cref="AsyncLock"/> instance to avoid concurrent requests.
|
||||
/// </summary>
|
||||
private readonly AsyncLock LoadingLock = new AsyncLock();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="SubredditWidgetViewModel"/> instance.
|
||||
/// </summary>
|
||||
public SubredditWidgetViewModel()
|
||||
{
|
||||
LoadPostsCommand = new AsyncRelayCommand(LoadPostsAsync);
|
||||
|
||||
selectedSubreddit = SettingsService.GetValue<string>(nameof(SelectedSubreddit)) ?? Subreddits[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IAsyncRelayCommand"/> instance responsible for loading posts.
|
||||
/// </summary>
|
||||
public IAsyncRelayCommand LoadPostsCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of loaded posts.
|
||||
/// </summary>
|
||||
public ObservableCollection<Post> Posts { get; } = new ObservableCollection<Post>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of available subreddits to pick from.
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> Subreddits { get; } = new[]
|
||||
{
|
||||
"microsoft",
|
||||
"windows",
|
||||
"surface",
|
||||
"windowsphone",
|
||||
"dotnet",
|
||||
"csharp"
|
||||
};
|
||||
|
||||
private string selectedSubreddit;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the currently selected subreddit.
|
||||
/// </summary>
|
||||
public string SelectedSubreddit
|
||||
{
|
||||
get => selectedSubreddit;
|
||||
set
|
||||
{
|
||||
SetProperty(ref selectedSubreddit, value);
|
||||
|
||||
SettingsService.SetValue(nameof(SelectedSubreddit), value);
|
||||
}
|
||||
}
|
||||
|
||||
private Post selectedPost;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the currently selected subreddit.
|
||||
/// </summary>
|
||||
public Post SelectedPost
|
||||
{
|
||||
get => selectedPost;
|
||||
set => SetProperty(ref selectedPost, value, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the posts from a specified subreddit.
|
||||
/// </summary>
|
||||
private async Task LoadPostsAsync()
|
||||
{
|
||||
using (await LoadingLock.LockAsync())
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await RedditService.GetSubredditPostsAsync(SelectedSubreddit);
|
||||
|
||||
Posts.Clear();
|
||||
|
||||
foreach (var item in response.Data.Items)
|
||||
{
|
||||
Posts.Add(item.Data);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Whoops!
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A viewmodel for a post widget.
|
||||
/// </summary>
|
||||
public sealed class PostWidgetViewModel : ObservableRecipient, IRecipient<PropertyChangedMessage<Post>>
|
||||
{
|
||||
private Post post;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the currently selected post, if any.
|
||||
/// </summary>
|
||||
public Post Post
|
||||
{
|
||||
get => post;
|
||||
private set => SetProperty(ref post, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Receive(PropertyChangedMessage<Post> message)
|
||||
{
|
||||
if (message.Sender.GetType() == typeof(SubredditWidgetViewModel) &&
|
||||
message.PropertyName == nameof(SubredditWidgetViewModel.SelectedPost))
|
||||
{
|
||||
Post = message.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</controls:InteractiveSample.CSharpCode>
|
||||
</controls:InteractiveSample>
|
||||
</Grid>
|
||||
</ContentPage>
|
|
@ -0,0 +1,38 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace MvvmSampleMAUI.Views;
|
||||
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class RedditBrowserPage : ContentPage
|
||||
{
|
||||
public RedditBrowserPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
SubredditWidget.PostSelected += HandleSubredditWidgetPostSelected;
|
||||
PostWidget.OnAppearing();
|
||||
SubredditWidget.OnAppearing();
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
SubredditWidget.PostSelected -= HandleSubredditWidgetPostSelected;
|
||||
PostWidget.OnDisappearing();
|
||||
}
|
||||
|
||||
void HandleSubredditWidgetPostSelected(object sender, EventArgs e)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
||||
//Ugly workaround for https://github.com/xamarin/XamarinCommunityToolkit/issues/595
|
||||
// MethodInfo dynMethod = RedditTabView.GetType().GetMethod("UpdateSelectedIndex", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
// dynMethod?.Invoke(RedditTabView, [1, false]);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:vm="clr-namespace:MvvmSample.Core.ViewModels;assembly=MvvmSample.Core"
|
||||
xmlns:md="clr-namespace:Xam.Forms.Markdown;assembly=Xam.Forms.MarkdownView"
|
||||
x:DataType="vm:SamplePageViewModel"
|
||||
x:Class="MvvmSampleXF.Views.RedditServicePage"
|
||||
Title="Creating a Service">
|
||||
<ScrollView Padding="16">
|
||||
<StackLayout Spacing="16">
|
||||
<md:MarkdownView x:Name="Markdown"
|
||||
Markdown="{Binding Path=Texts[Building the Reddit service], Mode=OneWay}" />
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
</ContentPage>
|
|
@ -0,0 +1,18 @@
|
|||
using MvvmSample.Core.ViewModels;
|
||||
|
||||
namespace MvvmSampleMAUI.Views;
|
||||
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class RedditServicePage : BaseContentPage<SamplePageViewModel>
|
||||
{
|
||||
public RedditServicePage(SamplePageViewModel viewModel) : base(viewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
BindingContext.LoadDocsCommand.Execute("PuttingThingsTogether");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:md="clr-namespace:Xam.Forms.Markdown;assembly=Xam.Forms.MarkdownView"
|
||||
xmlns:vm="clr-namespace:MvvmSample.Core.ViewModels;assembly=MvvmSample.Core"
|
||||
xmlns:controls="clr-namespace:MvvmSampleXF.Controls"
|
||||
x:Class="MvvmSampleXF.Views.RelayCommandPage"
|
||||
x:DataType="vm:RelayCommandPageViewModel"
|
||||
Title="Commands">
|
||||
<ScrollView Padding="16">
|
||||
<StackLayout Spacing="16">
|
||||
<md:MarkdownView Markdown="{Binding Path=Texts[RelayCommand], Mode=OneWay}" />
|
||||
<md:MarkdownView Markdown="{Binding Path=Texts[How they work], Mode=OneWay}" />
|
||||
<md:MarkdownView Markdown="{Binding Path=Texts[Working with `ICommand`], Mode=OneWay}" />
|
||||
|
||||
<controls:InteractiveSample ControlTemplate="{StaticResource InteractiveSampleTemplate}"
|
||||
HeightRequest="400">
|
||||
<StackLayout Spacing="8">
|
||||
<Label Text="{Binding Counter}"/>
|
||||
<Button Text="Click me!"
|
||||
Command="{Binding IncrementCounterCommand}" />
|
||||
</StackLayout>
|
||||
<controls:InteractiveSample.XamlCode>
|
||||
```xml
|
||||
<StackLayout Spacing="8">
|
||||
<Label Text="{Binding Counter}"/>
|
||||
<Button Text="Click me!"
|
||||
Command="{Binding IncrementCounterCommand}" />
|
||||
</StackLayout>
|
||||
```
|
||||
</controls:InteractiveSample.XamlCode>
|
||||
<controls:InteractiveSample.CSharpCode>
|
||||
```csharp
|
||||
public class MyViewModel : ObservableObject
|
||||
{
|
||||
public MyViewModel()
|
||||
{
|
||||
IncrementCounterCommand = new RelayCommand(IncrementCounter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="ICommand"/> responsible for incrementing <see cref="Counter"/>.
|
||||
/// </summary>
|
||||
public ICommand IncrementCounterCommand { get; }
|
||||
|
||||
private int counter;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current value of the counter.
|
||||
/// </summary>
|
||||
public int Counter
|
||||
{
|
||||
get => counter;
|
||||
private set => SetProperty(ref counter, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increments <see cref="Counter"/>.
|
||||
/// </summary>
|
||||
private void IncrementCounter() => Counter++;
|
||||
}
|
||||
```
|
||||
</controls:InteractiveSample.CSharpCode>
|
||||
</controls:InteractiveSample>
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
</ContentPage>
|
|
@ -0,0 +1,19 @@
|
|||
using MvvmSample.Core.ViewModels;
|
||||
|
||||
namespace MvvmSampleMAUI.Views;
|
||||
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class RelayCommandPage : BaseContentPage<RelayCommandPageViewModel>
|
||||
{
|
||||
public RelayCommandPage(RelayCommandPageViewModel viewModel) : base(viewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
|
||||
BindingContext.LoadDocsCommand.Execute("RelayCommand");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="MvvmSampleXF.Views.SettingUpTheViewModelsPage"
|
||||
xmlns:vm="clr-namespace:MvvmSample.Core.ViewModels;assembly=MvvmSample.Core"
|
||||
xmlns:md="clr-namespace:Xam.Forms.Markdown;assembly=Xam.Forms.MarkdownView"
|
||||
x:DataType="vm:SamplePageViewModel"
|
||||
Title="ViewModel Setup">
|
||||
<ScrollView Padding="16">
|
||||
<StackLayout Spacing="16">
|
||||
<md:MarkdownView x:Name="Markdown"
|
||||
Markdown="{Binding Path=Texts[Building the settings service], Mode=OneWay}"/>
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
</ContentPage>
|
|
@ -0,0 +1,19 @@
|
|||
using MvvmSample.Core.ViewModels;
|
||||
|
||||
namespace MvvmSampleMAUI.Views;
|
||||
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class SettingUpTheViewModelsPage : BaseContentPage<SamplePageViewModel>
|
||||
{
|
||||
public SettingUpTheViewModelsPage(SamplePageViewModel viewModel) : base(viewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
|
||||
BindingContext.LoadDocsCommand.Execute("PuttingThingsTogether");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="MvvmSampleXF.Views.SettingsServicePage"
|
||||
xmlns:vm="clr-namespace:MvvmSample.Core.ViewModels;assembly=MvvmSample.Core"
|
||||
xmlns:md="clr-namespace:Xam.Forms.Markdown;assembly=Xam.Forms.MarkdownView"
|
||||
x:DataType="vm:SamplePageViewModel"
|
||||
Title="Creating a Service">
|
||||
<ScrollView Padding="16">
|
||||
<StackLayout Spacing="16">
|
||||
<md:MarkdownView x:Name="Markdown"
|
||||
Markdown="{Binding Path=Texts[Building the settings service], Mode=OneWay}" />
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
</ContentPage>
|
|
@ -0,0 +1,19 @@
|
|||
using MvvmSample.Core.ViewModels;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
namespace MvvmSampleMAUI.Views;
|
||||
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class SettingsServicePage : BaseContentPage<SamplePageViewModel>
|
||||
{
|
||||
public SettingsServicePage(SamplePageViewModel viewModel) : base(viewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
|
||||
BindingContext.LoadDocsCommand.Execute("PuttingThingsTogether");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:widgets="clr-namespace:MvvmSample.Core.ViewModels.Widgets;assembly=MvvmSample.Core"
|
||||
xmlns:converters="clr-namespace:MvvmSampleXF.Converters"
|
||||
x:Class="MvvmSampleXF.Views.Widgets.PostWidget">
|
||||
<ContentView.Resources>
|
||||
<converters:IsSelfPostToWidthRequestConverter WidthRequest="160"
|
||||
x:Key="IsSelfPostToWidthRequestConverter" />
|
||||
</ContentView.Resources>
|
||||
<!--Post-->
|
||||
<Grid RowDefinitions="Auto, *">
|
||||
|
||||
<!--Self text-->
|
||||
<Frame Grid.Row="1">
|
||||
<ScrollView>
|
||||
<Label Text="{Binding Post.SelfText, Mode=OneWay}"
|
||||
LineBreakMode="WordWrap"
|
||||
Margin="16" />
|
||||
</ScrollView>
|
||||
</Frame>
|
||||
|
||||
<!--Header-->
|
||||
<Frame>
|
||||
<Grid Grid.Row="0"
|
||||
ColumnSpacing="8"
|
||||
Padding="16"
|
||||
ColumnDefinitions="*, Auto">
|
||||
<Label Text="{Binding Post.Title, Mode=OneWay}"
|
||||
FontSize="16"
|
||||
FontAttributes="Bold"
|
||||
LineBreakMode="WordWrap"
|
||||
VerticalOptions="Center" />
|
||||
<Image Grid.Column="1"
|
||||
Source="{Binding Post.Thumbnail, Mode=OneWay}"
|
||||
Aspect="AspectFit"
|
||||
HorizontalOptions="End"
|
||||
WidthRequest="{Binding Post.Thumbnail, Converter={StaticResource IsSelfPostToWidthRequestConverter}}" />
|
||||
</Grid>
|
||||
</Frame>
|
||||
</Grid>
|
||||
</ContentView>
|
|
@ -0,0 +1,31 @@
|
|||
using MvvmSample.Core.ViewModels.Widgets;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Xaml;
|
||||
|
||||
namespace MvvmSampleMAUI.Views.Widgets
|
||||
{
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class PostWidget : ContentView
|
||||
{
|
||||
public PostWidget()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
BindingContext = Ioc.Default.GetRequiredService<PostWidgetViewModel>();
|
||||
}
|
||||
|
||||
public PostWidgetViewModel ViewModel => (PostWidgetViewModel)BindingContext;
|
||||
|
||||
public void OnAppearing()
|
||||
{
|
||||
ViewModel.IsActive = true;
|
||||
}
|
||||
|
||||
public void OnDisappearing()
|
||||
{
|
||||
ViewModel.IsActive = false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
|
||||
xmlns:models="clr-namespace:MvvmSample.Core.Models;assembly=MvvmSample.Core"
|
||||
xmlns:widgets="clr-namespace:MvvmSample.Core.ViewModels.Widgets;assembly=MvvmSample.Core"
|
||||
xmlns:converters="clr-namespace:MvvmSampleXF.Converters"
|
||||
x:Class="MvvmSampleXF.Views.Widgets.SubredditWidget">
|
||||
<ContentView.Resources>
|
||||
<converters:IsSelfPostToWidthRequestConverter WidthRequest="120"
|
||||
x:Key="IsSelfPostToWidthRequestConverter" />
|
||||
</ContentView.Resources>
|
||||
<!--Feed-->
|
||||
<Grid RowDefinitions="60, 8, *">
|
||||
|
||||
<!--Header with topic selector and refresh button-->
|
||||
<Frame Padding="10,0,10,0">
|
||||
<Grid ColumnDefinitions="*, Auto">
|
||||
<Picker ItemsSource="{Binding Subreddits, Mode=OneWay}"
|
||||
SelectedItem="{Binding SelectedSubreddit, Mode=TwoWay}"
|
||||
VerticalOptions="Center"
|
||||
HorizontalOptions="FillAndExpand"
|
||||
Title="Subreddit">
|
||||
<Picker.Behaviors>
|
||||
<toolkit:EventToCommandBehavior EventName="SelectedIndexChanged"
|
||||
Command="{Binding LoadPostsCommand}" />
|
||||
</Picker.Behaviors>
|
||||
</Picker>
|
||||
<Button Text="Refresh"
|
||||
Grid.Column="1"
|
||||
Command="{Binding LoadPostsCommand}" />
|
||||
</Grid>
|
||||
</Frame>
|
||||
|
||||
<!--Items list-->
|
||||
<CollectionView Grid.Row="2"
|
||||
ItemsSource="{Binding Posts}"
|
||||
SelectedItem="{Binding SelectedPost, Mode=TwoWay}"
|
||||
SelectionMode="Single"
|
||||
SelectionChanged="CollectionView_SelectionChanged"
|
||||
IsVisible="{Binding LoadPostsCommand.IsRunning, Mode=OneWay, Converter={StaticResource InvertedBoolConverter}}">
|
||||
<CollectionView.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:Post">
|
||||
<Frame BorderColor="Black">
|
||||
<Grid ColumnSpacing="8"
|
||||
Padding="16"
|
||||
ColumnDefinitions="*, Auto">
|
||||
<Label Text="{Binding Title}"
|
||||
FontSize="15"
|
||||
LineBreakMode="WordWrap"
|
||||
VerticalOptions="Center" />
|
||||
<Image Grid.Column="1"
|
||||
Source="{Binding Thumbnail}"
|
||||
Aspect="AspectFit"
|
||||
HorizontalOptions="End"
|
||||
WidthRequest="{Binding Thumbnail, Converter={StaticResource IsSelfPostToWidthRequestConverter}}" />
|
||||
</Grid>
|
||||
</Frame>
|
||||
</DataTemplate>
|
||||
</CollectionView.ItemTemplate>
|
||||
</CollectionView>
|
||||
|
||||
<!--Loading bar-->
|
||||
<ActivityIndicator Grid.Row="1"
|
||||
Grid.RowSpan="2"
|
||||
Margin="0, 10, 0, 0"
|
||||
VerticalOptions="Start"
|
||||
IsRunning="{Binding LoadPostsCommand.IsRunning, Mode=OneWay}"
|
||||
IsVisible="{Binding LoadPostsCommand.IsRunning, Mode=OneWay}" />
|
||||
</Grid>
|
||||
</ContentView>
|
|
@ -0,0 +1,33 @@
|
|||
using MvvmSample.Core.ViewModels.Widgets;
|
||||
using System;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Xaml;
|
||||
|
||||
namespace MvvmSampleMAUI.Views.Widgets
|
||||
{
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class SubredditWidget : ContentView
|
||||
{
|
||||
public event EventHandler PostSelected;
|
||||
public SubredditWidget()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
BindingContext = Ioc.Default.GetRequiredService<SubredditWidgetViewModel>();
|
||||
}
|
||||
|
||||
public SubredditWidgetViewModel ViewModel => (SubredditWidgetViewModel)BindingContext;
|
||||
|
||||
private void CollectionView_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
PostSelected?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public void OnAppearing()
|
||||
{
|
||||
ViewModel.LoadPostsCommand.Execute(null);
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче