This commit is contained in:
Brandon Minnick 2024-10-10 14:29:22 -07:00
Родитель 8963a8720d
Коммит a0f2a831b7
51 изменённых файлов: 2013 добавлений и 1 удалений

6
.gitignore поставляемый
Просмотреть файл

@ -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="&#xf04b;"/>
<FontImage x:Key="ExchangeIcon"
FontFamily="{StaticResource FaSolidFont}"
Glyph="&#xf362;" />
<FontImage x:Key="CalendarIcon"
FontFamily="{StaticResource FaRegularFont}"
Glyph="&#xf073;" />
<FontImage x:Key="BookIcon"
FontFamily="{StaticResource FaSolidFont}"
Glyph="&#xf02d;" />
<FontImage x:Key="FlagIcon"
FontFamily="{StaticResource FaSolidFont}"
Glyph="&#xf024;" />
<FontImage x:Key="CommentIcon"
FontFamily="{StaticResource FaSolidFont}"
Glyph="&#xf4ad;" />
<FontImage x:Key="SendIcon"
FontFamily="{StaticResource FaSolidFont}"
Glyph="&#xf1d8;" />
<FontImage x:Key="SortIcon"
FontFamily="{StaticResource FaSolidFont}"
Glyph="&#xf338;" />
<FontImage x:Key="UndoIcon"
FontFamily="{StaticResource FaSolidFont}"
Glyph="&#xf0e2;" />
<FontImage x:Key="CubeIcon"
FontFamily="{StaticResource FaSolidFont}"
Glyph="&#xf1b3;" />
<FontImage x:Key="ToolsIcon"
FontFamily="{StaticResource FaSolidFont}"
Glyph="&#xf7d9;" />
<FontImage x:Key="WrenchIcon"
FontFamily="{StaticResource FaSolidFont}"
Glyph="&#xf0ad;" />
<FontImage x:Key="GlobeIcon"
FontFamily="{StaticResource FaSolidFont}"
Glyph="&#xf57d;" />
<FontImage x:Key="RulerIcon"
FontFamily="{StaticResource FaSolidFont}"
Glyph="&#xf5ae;" />
<FontImage x:Key="CheckerIcon"
FontFamily="{StaticResource FaSolidFont}"
Glyph="&#xf11e;" />
<FontImage x:Key="RefreshIcon"
FontFamily="{StaticResource FaSolidFont}"
Glyph="&#xf021;" />
<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&lt;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
&lt;ContentPage.Resources>
&lt;converters:TaskResultConverter x:Key="TaskResultConverter" />
&lt;/ContentPage.Resources>
&lt;StackLayout Spacing="8">
&lt;Label Text="{Binding DownloadTextCommand.ExecutionTask.Status, TargetNullValue='Task status: ', FallbackValue='Task status: ', StringFormat='Task status: {0}' }" />
&lt;Label Text="{Binding DownloadTextCommand.ExecutionTask, Converter={StaticResource TaskResultConverter}, StringFormat='Result: {0}' }" />
&lt;Button Text="Click me!"
Command="{Binding DownloadTextCommand}" />
&lt;ActivityIndicator HorizontalOptions="Center"
IsVisible="{Binding DownloadTextCommand.IsRunning, Mode=OneWay}"
IsRunning="{Binding DownloadTextCommand.IsRunning, Mode=OneWay}" />
&lt;/StackLayout>
```
</controls:InteractiveSample.XamlCode>
<controls:InteractiveSample.CSharpCode>
```csharp
public MyViewModel()
{
DownloadTextCommand = new AsyncRelayCommand(DownloadTextAsync);
}
public IAsyncRelayCommand DownloadTextCommand { get; }
private async Task&lt;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
&lt;StackLayout Spacing="8">
&lt;Label Text="{Binding Username, Mode=OneWay}" />
&lt;Button Text="Click to request the username!"
Command="{Binding RequestCurrentUsernameCommand}" />
&lt;Button Text="Click to reset the local username!"
Command="{Binding ResetCurrentUsernameCommand}" />
&lt;/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
&lt;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&lt;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
&lt;StackLayout Spacing="8">
&lt;!--Sender module-->
&lt;Frame BorderColor="#40FFFFFF"
CornerRadius="4"
Padding="8">
&lt;StackLayout Spacing="8">
&lt;Label Text="{Binding SenderViewModel.Username, Mode=OneWay}" />
&lt;Button Text="Click to send a message!"
Command="{Binding SenderViewModel.SendUserMessageCommand}" />
&lt;/StackLayout>
&lt;/Frame>
&lt;!--Receiver module-->
&lt;Frame BorderColor="#40FFFFFF"
CornerRadius="4"
Padding="8">
&lt;StackLayout Spacing="8">
&lt;Label Text="{Binding ReceiverViewModel.Username, Mode=OneWay}" />
&lt;/StackLayout>
&lt;/Frame>
&lt;/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
&lt;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
&lt;UsernameChangedMessage>(this, m => Username = m.Value);
}
}
// A sample message with a username value
public sealed class UsernameChangedMessage : ValueChangedMessage&lt;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
&lt;StackLayout Spacing="8">
&lt;!--Simple property sample-->
&lt;Entry Placeholder="Type here to update the text below"
Text="{Binding Name, Mode=TwoWay}" />
&lt;Label Text="{Binding Name, Mode=OneWay}" />
&lt;/StackLayout>
```
</controls:InteractiveSample.XamlCode>
<controls:InteractiveSample.CSharpCode>
```csharp
private string name;
/// &lt;summary>
/// Gets or sets the name to display.
/// &lt;/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&lt;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
&lt;StackLayout Spacing="8">
&lt;Button Text="Click me to load a Task to await"
Command="{Binding ReloadTaskCommand}" />
&lt;Label Text="{Binding MyTask.Status, Mode=OneWay}" />
&lt;/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
&lt;!--Feed-->
&lt;Grid RowDefinitions="60, 8, *">
&lt;!--Header with topic selector and refresh button-->
&lt;Frame Padding="10,0,10,0">
&lt;Grid ColumnDefinitions="*, Auto">
&lt;Picker ItemsSource="{Binding Subreddits, Mode=OneWay}"
SelectedItem="{Binding SelectedSubreddit, Mode=TwoWay}"
VerticalOptions="Center"
HorizontalOptions="StartAndExpand">
&lt;Picker.Behaviors>
&lt;toolkit:EventToCommandBehavior EventName="SelectedIndexChanged"
Command="{Binding LoadPostsCommand}" />
&lt;/Picker.Behaviors>
&lt;/Picker>
&lt;Button Text="Refresh"
Grid.Column="1"
Command="{Binding LoadPostsCommand}" />
&lt;/Grid>
&lt;/Frame>
&lt;!--Items list-->
&lt;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}}">
&lt;CollectionView.ItemTemplate>
&lt;DataTemplate x:DataType="models:Post">
&lt;Frame BorderColor="Black">
&lt;Grid ColumnSpacing="8"
Padding="16"
ColumnDefinitions="*, Auto">
&lt;Label Text="{Binding Title}"
FontSize="15"
LineBreakMode="WordWrap"
VerticalOptions="Center" />
&lt;Image Grid.Column="1"
Source="{Binding Thumbnail}"
Aspect="AspectFit"
HorizontalOptions="End"
WidthRequest="{Binding Thumbnail, Converter={StaticResource IsSelfPostToWidthRequestConverter}}" />
&lt;/Grid>
&lt;/Frame>
&lt;/DataTemplate>
&lt;/CollectionView.ItemTemplate>
&lt;/CollectionView>
&lt;!--Loading bar-->
&lt;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}" />
&lt;/Grid>
&lt;!--Post-->
&lt;Grid RowDefinitions="Auto, *">
&lt;!--Self text-->
&lt;Frame Grid.Row="1">
&lt;ScrollView>
&lt;Label Text="{Binding Post.SelfText, Mode=OneWay}"
LineBreakMode="WordWrap"
Margin="16" />
&lt;/ScrollView>
&lt;/Frame>
&lt;!--Header-->
&lt;Frame>
&lt;Grid Grid.Row="0"
ColumnSpacing="8"
Padding="16"
ColumnDefinitions="*, Auto">
&lt;Label Text="{Binding Post.Title, Mode=OneWay}"
FontSize="16"
FontAttributes="Bold"
LineBreakMode="WordWrap"
VerticalOptions="Center" />
&lt;Image Grid.Column="1"
Source="{Binding Post.Thumbnail, Mode=OneWay}"
Aspect="AspectFit"
HorizontalOptions="End"
WidthRequest="{Binding Post.Thumbnail, Converter={StaticResource IsSelfPostToWidthRequestConverter}}" />
&lt;/Grid>
&lt;/Frame>
&lt;/Grid>
```
</controls:InteractiveSample.XamlCode>
<controls:InteractiveSample.CSharpCode>
```csharp
/// &lt;summary>
/// A viewmodel for a subreddit widget.
/// &lt;/summary>
public sealed class SubredditWidgetViewModel : ObservableRecipient
{
/// &lt;summary>
/// Gets the &lt;see cref="IRedditService"/> instance to use.
/// &lt;/summary>
private readonly IRedditService RedditService = Ioc.Default.GetRequiredService&lt;IRedditService>();
/// &lt;summary>
/// Gets the &lt;see cref="ISettingsService"/> instance to use.
/// &lt;/summary>
private readonly ISettingsService SettingsService = Ioc.Default.GetRequiredService&lt;ISettingsService>();
/// &lt;summary>
/// An &lt;see cref="AsyncLock"/> instance to avoid concurrent requests.
/// &lt;/summary>
private readonly AsyncLock LoadingLock = new AsyncLock();
/// &lt;summary>
/// Creates a new &lt;see cref="SubredditWidgetViewModel"/> instance.
/// &lt;/summary>
public SubredditWidgetViewModel()
{
LoadPostsCommand = new AsyncRelayCommand(LoadPostsAsync);
selectedSubreddit = SettingsService.GetValue&lt;string>(nameof(SelectedSubreddit)) ?? Subreddits[0];
}
/// &lt;summary>
/// Gets the &lt;see cref="IAsyncRelayCommand"/> instance responsible for loading posts.
/// &lt;/summary>
public IAsyncRelayCommand LoadPostsCommand { get; }
/// &lt;summary>
/// Gets the collection of loaded posts.
/// &lt;/summary>
public ObservableCollection&lt;Post> Posts { get; } = new ObservableCollection&lt;Post>();
/// &lt;summary>
/// Gets the collection of available subreddits to pick from.
/// &lt;/summary>
public IReadOnlyList&lt;string> Subreddits { get; } = new[]
{
"microsoft",
"windows",
"surface",
"windowsphone",
"dotnet",
"csharp"
};
private string selectedSubreddit;
/// &lt;summary>
/// Gets or sets the currently selected subreddit.
/// &lt;/summary>
public string SelectedSubreddit
{
get => selectedSubreddit;
set
{
SetProperty(ref selectedSubreddit, value);
SettingsService.SetValue(nameof(SelectedSubreddit), value);
}
}
private Post selectedPost;
/// &lt;summary>
/// Gets or sets the currently selected subreddit.
/// &lt;/summary>
public Post SelectedPost
{
get => selectedPost;
set => SetProperty(ref selectedPost, value, true);
}
/// &lt;summary>
/// Loads the posts from a specified subreddit.
/// &lt;/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!
}
}
}
}
/// &lt;summary>
/// A viewmodel for a post widget.
/// &lt;/summary>
public sealed class PostWidgetViewModel : ObservableRecipient, IRecipient&lt;PropertyChangedMessage&lt;Post>>
{
private Post post;
/// &lt;summary>
/// Gets the currently selected post, if any.
/// &lt;/summary>
public Post Post
{
get => post;
private set => SetProperty(ref post, value);
}
/// &lt;inheritdoc/>
public void Receive(PropertyChangedMessage&lt;Post> message)
{
if (message.Sender.GetType() == typeof(SubredditWidgetViewModel) &amp;&amp;
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
&lt;StackLayout Spacing="8">
&lt;Label Text="{Binding Counter}"/>
&lt;Button Text="Click me!"
Command="{Binding IncrementCounterCommand}" />
&lt;/StackLayout>
```
</controls:InteractiveSample.XamlCode>
<controls:InteractiveSample.CSharpCode>
```csharp
public class MyViewModel : ObservableObject
{
public MyViewModel()
{
IncrementCounterCommand = new RelayCommand(IncrementCounter);
}
/// &lt;summary>
/// Gets the &lt;see cref="ICommand"/> responsible for incrementing &lt;see cref="Counter"/>.
/// &lt;/summary>
public ICommand IncrementCounterCommand { get; }
private int counter;
/// &lt;summary>
/// Gets the current value of the counter.
/// &lt;/summary>
public int Counter
{
get => counter;
private set => SetProperty(ref counter, value);
}
/// &lt;summary>
/// Increments &lt;see cref="Counter"/>.
/// &lt;/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);
}
}
}