Skip to content

Commit

Permalink
Use MVVM for FlyoutMenuPage
Browse files Browse the repository at this point in the history
  • Loading branch information
BurkusCat committed Oct 16, 2023
1 parent e1c4618 commit b186b6a
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 59 deletions.
2 changes: 2 additions & 0 deletions samples/DemoApp/MauiProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public static MauiAppBuilder RegisterViewModels(this MauiAppBuilder mauiAppBuild
// flyouts
mauiAppBuilder.Services.AddTransient<ContactsViewModel>();
mauiAppBuilder.Services.AddTransient<DemoFlyoutViewModel>();
mauiAppBuilder.Services.AddTransient<FlyoutMenuViewModel>();
mauiAppBuilder.Services.AddTransient<RemindersViewModel>();
mauiAppBuilder.Services.AddTransient<TodoViewModel>();

Expand All @@ -73,6 +74,7 @@ public static MauiAppBuilder RegisterViews(this MauiAppBuilder mauiAppBuilder)
// flyouts
mauiAppBuilder.Services.AddTransient<ContactsPage>();
mauiAppBuilder.Services.AddTransient<DemoFlyoutPage>();
mauiAppBuilder.Services.AddTransient<FlyoutMenuPage>();
mauiAppBuilder.Services.AddTransient<RemindersPage>();
mauiAppBuilder.Services.AddTransient<TodoPage>();

Expand Down
24 changes: 10 additions & 14 deletions samples/DemoApp/ViewModels/Flyouts/ContactsViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using DemoApp.Views;

Expand All @@ -10,17 +9,7 @@ public partial class ContactsViewModel : BaseViewModel
#region Properties

[ObservableProperty]
private ObservableCollection<string> contactNames;

#endregion Properties

#region Constructors

public ContactsViewModel(
INavigationService navigationService)
: base(navigationService)
{
var mockContactNames = new List<string>()
private List<string> contactNames = new List<string>()
{
"Ronan",
"Claire",
Expand All @@ -30,7 +19,14 @@ public ContactsViewModel(
"Veronica",
};

contactNames = new ObservableCollection<string>(mockContactNames);
#endregion Properties

#region Constructors

public ContactsViewModel(
INavigationService navigationService)
: base(navigationService)
{
}

#endregion Constructors
Expand Down
69 changes: 69 additions & 0 deletions samples/DemoApp/ViewModels/Flyouts/FlyoutMenuViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using DemoApp.Models;
using DemoApp.Properties;
using DemoApp.Views;

namespace DemoApp.ViewModels;

public partial class FlyoutMenuViewModel : BaseViewModel
{
#region Properties

[ObservableProperty]
private List<FlyoutPageItem> menuPages = new List<FlyoutPageItem>
{
new FlyoutPageItem
{
Title = Resources.Contacts_Title,
IconSource = "contacts.png",
TargetType = typeof(ContactsPage),
},
new FlyoutPageItem
{
Title = Resources.Todo_Title,
IconSource = "todo.png",
TargetType = typeof(TodoPage),
},
new FlyoutPageItem
{
Title = Resources.Reminders_Title,
IconSource = "reminders.png",
TargetType = typeof(RemindersPage),
},
new FlyoutPageItem
{
Title = Resources.DemoTabs_Title,
IconSource = "tabs.png",
TargetType = typeof(DemoTabsPage),
},
};

#endregion Properties

#region Constructors

public FlyoutMenuViewModel(
INavigationService navigationService)
: base(navigationService)
{
}

#endregion Constructors

#region Commands

/// <summary>
/// Switch the flyout detail to the selected page.
/// </summary>
[RelayCommand]
private void SwitchFlyoutDetailPage(FlyoutPageItem flyoutPageItem)
{
if (flyoutPageItem != null)
{
navigationService.SwitchFlyoutDetail(flyoutPageItem.TargetType);
}
}

#endregion Commands
}
6 changes: 5 additions & 1 deletion samples/DemoApp/ViewModels/HomeViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,11 @@ private async Task AddMultiplePages()
[RelayCommand]
private async Task GoToFlyoutPageDemo()
{
await navigationService.Navigate($"/{nameof(DemoFlyoutPage)}");
// hack: flyout page must be the root page so I've made it a modal
var navigationParameters = new NavigationParameters();
navigationParameters.UseModalNavigation = true;

await navigationService.Navigate($"{nameof(DemoFlyoutPage)}", navigationParameters);
}

#endregion Commands
Expand Down
36 changes: 11 additions & 25 deletions samples/DemoApp/Views/Flyouts/FlyoutMenuPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,22 @@
xmlns:properties="clr-namespace:DemoApp.Properties"
xmlns:views="clr-namespace:DemoApp.Views;assembly=DemoApp"
xmlns:vm="clr-namespace:DemoApp.ViewModels"
x:Name="page"
Title="{x:Static properties:Resources.DemoFlyout_Title}"
Padding="0,40,0,0"
x:DataType="vm:FlyoutMenuViewModel"
BackgroundColor="{AppThemeBinding Dark={StaticResource Gray600},
Light={StaticResource Gray200}}">
<CollectionView
x:Name="collectionView"
SelectionMode="Single">
<CollectionView.ItemsSource>
<x:Array Type="{x:Type models:FlyoutPageItem}">
<models:FlyoutPageItem
Title="{x:Static properties:Resources.Contacts_Title}"
IconSource="contacts.png"
TargetType="{x:Type views:ContactsPage}" />
<models:FlyoutPageItem
Title="{x:Static properties:Resources.Todo_Title}"
IconSource="todo.png"
TargetType="{x:Type views:TodoPage}" />
<models:FlyoutPageItem
Title="{x:Static properties:Resources.Reminders_Title}"
IconSource="reminders.png"
TargetType="{x:Type views:RemindersPage}" />
<models:FlyoutPageItem
Title="{x:Static properties:Resources.DemoTabs_Title}"
IconSource="tabs.png"
TargetType="{x:Type views:DemoTabsPage}" />
</x:Array>
</CollectionView.ItemsSource>
Light={StaticResource Gray200}}"
BindingContext="{burkus:ResolveBindingContext x:TypeArguments=vm:FlyoutMenuViewModel}">
<CollectionView ItemsSource="{Binding MenuPages}">
<CollectionView.ItemTemplate>
<DataTemplate>
<DataTemplate x:DataType="models:FlyoutPageItem">
<Grid Padding="5,10">
<Grid.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding BindingContext.SwitchFlyoutDetailPageCommand, Source={x:Reference page}}"
CommandParameter="{Binding .}" />
</Grid.GestureRecognizers>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30" />
<ColumnDefinition Width="*" />
Expand Down
20 changes: 1 addition & 19 deletions samples/DemoApp/Views/Flyouts/FlyoutMenuPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,27 +1,9 @@
using DemoApp.Models;

namespace DemoApp.Views;
namespace DemoApp.Views;

public partial class FlyoutMenuPage : ContentPage
{
private readonly INavigationService navigationService;

public FlyoutMenuPage()
{
navigationService = ServiceResolver.Resolve<INavigationService>();

InitializeComponent();
collectionView.SelectionChanged += OnSelectionChanged;
}

private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var item = collectionView.SelectedItem as FlyoutPageItem;

if (item != null)
{
navigationService.SwitchFlyoutDetail(item.TargetType);
collectionView.SelectedItem = null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using DemoApp.Models;
using DemoApp.ViewModels;
using DemoApp.Views;

namespace DemoApp.UnitTests.ViewModels;

public class FlyoutMenuViewModelTests
{
private readonly INavigationService mockNavigationService;

public FlyoutMenuViewModelTests()
{
mockNavigationService = Substitute.For<INavigationService>();
}

public FlyoutMenuViewModel ViewModel => new FlyoutMenuViewModel(
mockNavigationService);

[Fact]
public void Constructor_WhenInitialized_SetsMenuPageList()
{
// Arrange
// Act
var viewModel = ViewModel;

// Assert
Assert.Equal(4, viewModel.MenuPages.Count);
}

[Fact]
public void SwitchFlyoutDetailPageCommand_WhenCalled_SwitchesToSelectedDetailPage()
{
// Arrange
var viewModel = ViewModel;
var selectedItem = new FlyoutPageItem
{
Title = "MockItem",
IconSource = "test.png",
TargetType = typeof(TodoPage),
};

// Act
viewModel.SwitchFlyoutDetailPageCommand.Execute(selectedItem);

// Assert
mockNavigationService.Received().SwitchFlyoutDetail(typeof(TodoPage));
}
}

0 comments on commit b186b6a

Please sign in to comment.