From 56e4cd1d21c7cb88759bff7dde43afe8cd7d0f3f Mon Sep 17 00:00:00 2001 From: Aleksey Nemiro Date: Sun, 4 Jun 2023 17:56:52 +0300 Subject: [PATCH 01/47] Updated packages. --- src/TapPlayer.Maui/TapPlayer.Maui.csproj | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/TapPlayer.Maui/TapPlayer.Maui.csproj b/src/TapPlayer.Maui/TapPlayer.Maui.csproj index 64184fd..f7e151f 100644 --- a/src/TapPlayer.Maui/TapPlayer.Maui.csproj +++ b/src/TapPlayer.Maui/TapPlayer.Maui.csproj @@ -97,11 +97,11 @@ - - - - - + + + + + From bc0b67dd6f478750434ea7918fbbebf2b5b8bf7b Mon Sep 17 00:00:00 2001 From: Aleksey Nemiro Date: Sun, 4 Jun 2023 18:34:49 +0300 Subject: [PATCH 02/47] Image.Behaviors is limited to Android only. Because in Windows the images are duplicated for some reason. --- src/TapPlayer.Maui/Components/TileView.xaml | 70 +++++++++++++++------ 1 file changed, 50 insertions(+), 20 deletions(-) diff --git a/src/TapPlayer.Maui/Components/TileView.xaml b/src/TapPlayer.Maui/Components/TileView.xaml index 97999f1..3ac7bb2 100644 --- a/src/TapPlayer.Maui/Components/TileView.xaml +++ b/src/TapPlayer.Maui/Components/TileView.xaml @@ -52,10 +52,16 @@ SemanticProperties.Description="{markup:Localization Key=NoSource}" > - + + + + + + + - + + + + + + + - + + + + + + + @@ -102,10 +120,16 @@ SemanticProperties.Description="{markup:Localization Key=IsPlaying}" > - + + + + + + + - + + + + + + + From cd7c4e003abb0d5b479baa9f85d93b619e79f3da Mon Sep 17 00:00:00 2001 From: Aleksey Nemiro Date: Sun, 4 Jun 2023 23:02:55 +0300 Subject: [PATCH 03/47] Using TileView instead of Button in the ProjectEdit page. --- .../Components/ProjectEdit.xaml | 2 ++ .../Components/ProjectEdit.xaml.cs | 33 ++----------------- src/TapPlayer.Maui/Components/TileView.xaml | 5 ++- .../ViewModels/ITileViewModel.cs | 7 ++-- .../ViewModels/ProjectEditViewModel.cs | 2 ++ .../ViewModels/TileViewModel.cs | 19 ++++------- 6 files changed, 20 insertions(+), 48 deletions(-) diff --git a/src/TapPlayer.Maui/Components/ProjectEdit.xaml b/src/TapPlayer.Maui/Components/ProjectEdit.xaml index ab460e4..7d82ca7 100644 --- a/src/TapPlayer.Maui/Components/ProjectEdit.xaml +++ b/src/TapPlayer.Maui/Components/ProjectEdit.xaml @@ -74,6 +74,8 @@ x:Name="TilesGrid" HorizontalOptions="Fill" VerticalOptions="Fill" + RowSpacing="2" + ColumnSpacing="2" /> diff --git a/src/TapPlayer.Maui/Components/ProjectEdit.xaml.cs b/src/TapPlayer.Maui/Components/ProjectEdit.xaml.cs index 823c85c..5cb0a4f 100644 --- a/src/TapPlayer.Maui/Components/ProjectEdit.xaml.cs +++ b/src/TapPlayer.Maui/Components/ProjectEdit.xaml.cs @@ -1,9 +1,7 @@ using CommunityToolkit.Mvvm.Messaging; using System.ComponentModel; using TapPlayer.Data.Enums; -using TapPlayer.Maui.Converters; using TapPlayer.Maui.Extensions; -using TapPlayer.Maui.Resources.Strings; using TapPlayer.Maui.ViewModels; namespace TapPlayer.Maui.Components; @@ -44,42 +42,17 @@ protected void GridSize_SelectedIndexChanged(object sender, EventArgs e) TilesGrid.DispatchInvalidateMeasure(); } - private Button CreateTill(GridCreateEventArgs e) + private IView CreateTill(GridCreateEventArgs e) { var tile = Model.Tiles.ElementAt(e.Index); - var button = new Button + var tileView = new TileView { BindingContext = tile, - CommandParameter = Model, HorizontalOptions = LayoutOptions.Fill, VerticalOptions = LayoutOptions.Fill, - LineBreakMode = LineBreakMode.NoWrap, }; - button.SetBinding( - Button.StyleProperty, - nameof(TileViewModel.Color), - converter: new ColorPaletteToButtonStyleConverter() - ); - - button.SetBinding( - Button.TextProperty, - nameof(TileViewModel.Name) - ); - - button.SetBinding( - Button.CommandProperty, - nameof(TileViewModel.EditCommand) - ); - - button.PropertyChanged += Tile_PropertyChanged; - - SemanticProperties.SetDescription( - button, - string.Format(CommonStrings.TileX, tile.Index + 1) - ); - - return button; + return tileView; } private void Tile_PropertyChanged(object sender, PropertyChangedEventArgs e) diff --git a/src/TapPlayer.Maui/Components/TileView.xaml b/src/TapPlayer.Maui/Components/TileView.xaml index 3ac7bb2..75c1042 100644 --- a/src/TapPlayer.Maui/Components/TileView.xaml +++ b/src/TapPlayer.Maui/Components/TileView.xaml @@ -30,7 +30,10 @@ - + diff --git a/src/TapPlayer.Maui/ViewModels/ITileViewModel.cs b/src/TapPlayer.Maui/ViewModels/ITileViewModel.cs index 729551c..974b509 100644 --- a/src/TapPlayer.Maui/ViewModels/ITileViewModel.cs +++ b/src/TapPlayer.Maui/ViewModels/ITileViewModel.cs @@ -1,5 +1,4 @@ -using System.Windows.Input; -using TapPlayer.Data.Enums; +using TapPlayer.Data.Enums; namespace TapPlayer.Maui.ViewModels; @@ -19,9 +18,7 @@ public interface ITileViewModel IMediaPlayerViewModel Player { get; set; } - ICommand EditCommand { get; } - - ICommand TapCommand { get; } + IAsyncCommand TapCommand { get; set; } Action StopAllExcludingBackground { get; } } diff --git a/src/TapPlayer.Maui/ViewModels/ProjectEditViewModel.cs b/src/TapPlayer.Maui/ViewModels/ProjectEditViewModel.cs index dae27da..b5acbb4 100644 --- a/src/TapPlayer.Maui/ViewModels/ProjectEditViewModel.cs +++ b/src/TapPlayer.Maui/ViewModels/ProjectEditViewModel.cs @@ -256,6 +256,7 @@ private async Task Load(Guid projectId) tile.Color = act?.Color ?? ColorPalette.Color1; tile.IsBackground = act?.IsBackground == true; tile.PlayType = act?.Play ?? PlayType.Once; + tile.TapCommand = TileEditCommand; Tiles.Add(tile); } @@ -388,6 +389,7 @@ private void InitDefault() tile.Name = string.Empty; tile.File = null; tile.Color = ColorPalette.Color1; + tile.TapCommand = TileEditCommand; Tiles.Add(tile); } diff --git a/src/TapPlayer.Maui/ViewModels/TileViewModel.cs b/src/TapPlayer.Maui/ViewModels/TileViewModel.cs index dac4bbc..948a340 100644 --- a/src/TapPlayer.Maui/ViewModels/TileViewModel.cs +++ b/src/TapPlayer.Maui/ViewModels/TileViewModel.cs @@ -1,5 +1,4 @@ -using System.Windows.Input; -using TapPlayer.Data.Enums; +using TapPlayer.Data.Enums; using TapPlayer.Maui.Services; namespace TapPlayer.Maui.ViewModels; @@ -107,9 +106,7 @@ public IMediaPlayerViewModel Player } } - public ICommand EditCommand { get; init; } - - public ICommand TapCommand { get; init; } + public IAsyncCommand TapCommand { get; set; } public Action StopAllExcludingBackground { get; set; } @@ -117,14 +114,10 @@ public TileViewModel(ITapPlayerService tapPlayerService) { _tapPlayerService = tapPlayerService; - TapCommand = new Command(Tap); - EditCommand = new Command(x => - { - x.TileEditCommand.Execute(this); - }); + TapCommand = new AsyncCommand(Tap); } - private void Tap() + private Task Tap(ITileViewModel model) { if (!IsBackground) { @@ -135,7 +128,7 @@ private void Tap() if (Player == null) { - return; + return Task.CompletedTask; } if (Player.IsPlaying) @@ -146,5 +139,7 @@ private void Tap() { Player.Play(); } + + return Task.CompletedTask; } } From 452d733c579c019b67dc21581ee234d9217d95c1 Mon Sep 17 00:00:00 2001 From: Aleksey Nemiro Date: Sun, 4 Jun 2023 23:04:46 +0300 Subject: [PATCH 04/47] Removed unused code. --- .../Components/ProjectEdit.xaml.cs | 16 ----- .../Extensions/ButtonExtensions.cs | 68 ------------------- 2 files changed, 84 deletions(-) delete mode 100644 src/TapPlayer.Maui/Extensions/ButtonExtensions.cs diff --git a/src/TapPlayer.Maui/Components/ProjectEdit.xaml.cs b/src/TapPlayer.Maui/Components/ProjectEdit.xaml.cs index 5cb0a4f..98a70fb 100644 --- a/src/TapPlayer.Maui/Components/ProjectEdit.xaml.cs +++ b/src/TapPlayer.Maui/Components/ProjectEdit.xaml.cs @@ -1,5 +1,4 @@ using CommunityToolkit.Mvvm.Messaging; -using System.ComponentModel; using TapPlayer.Data.Enums; using TapPlayer.Maui.Extensions; using TapPlayer.Maui.ViewModels; @@ -55,21 +54,6 @@ private IView CreateTill(GridCreateEventArgs e) return tileView; } - private void Tile_PropertyChanged(object sender, PropertyChangedEventArgs e) - { - if ( - e.PropertyName == nameof(Button.Text) - || e.PropertyName == nameof(Button.Width) - || e.PropertyName == nameof(Button.Height) - || e.PropertyName == nameof(Button.Padding) - ) - { - var button = (Button)sender; - - button.FitTextToSize(); - } - } - protected void Cancel_Clicked(object sender, EventArgs e) { Model.CancelCommand.Execute(null); diff --git a/src/TapPlayer.Maui/Extensions/ButtonExtensions.cs b/src/TapPlayer.Maui/Extensions/ButtonExtensions.cs deleted file mode 100644 index 4b07983..0000000 --- a/src/TapPlayer.Maui/Extensions/ButtonExtensions.cs +++ /dev/null @@ -1,68 +0,0 @@ -using Microsoft.Maui.Graphics.Skia; -using Font = Microsoft.Maui.Graphics.Font; - -namespace TapPlayer.Maui.Extensions; - -public static class ButtonExtensions -{ - public static void FitTextToSize(this Button button) - { - if (string.IsNullOrWhiteSpace(button.Text)) - { - return; - } - - double buttonContentWidth = button.Width - button.Padding.HorizontalThickness; - double buttonContentHeight = button.Height - button.Padding.VerticalThickness; - - if (Math.Min(buttonContentWidth, buttonContentHeight) <= 0) - { - return; - } - - double width = buttonContentWidth - (buttonContentWidth * 0.1); - double height = buttonContentHeight - (buttonContentHeight * 0.3); - - double ratio = Math.Min(width, height); - - if (ratio <= 0) - { - return; - } - - using var bmp = new SkiaBitmapExportContext((int)width, (int)height, (float)DeviceDisplay.MainDisplayInfo.Density); - var font = new Font(button.FontFamily); - var canvas = bmp.Canvas; - - float step = 2.0F; - float fontSize = (float)ratio; - - var size = canvas.GetStringSize( - button.Text, - font, - fontSize, - HorizontalAlignment.Left, - VerticalAlignment.Top - ); - - while (size.Width > width || size.Height > height) - { - fontSize -= step; - - if (fontSize < 8) - { - break; - } - - size = canvas.GetStringSize( - button.Text, - font, - fontSize, - HorizontalAlignment.Left, - VerticalAlignment.Top - ); - } - - button.FontSize = fontSize; - } -} From d3f41541b3ae70db79a123eea4bbabbb9383249b Mon Sep 17 00:00:00 2001 From: Aleksey Nemiro Date: Mon, 5 Jun 2023 10:45:01 +0300 Subject: [PATCH 05/47] Added IsPlayable property to ITileViewModel. --- src/TapPlayer.Maui/ViewModels/ITileViewModel.cs | 2 ++ .../ViewModels/ProjectEditViewModel.cs | 2 ++ src/TapPlayer.Maui/ViewModels/TileViewModel.cs | 14 ++++++++++++++ 3 files changed, 18 insertions(+) diff --git a/src/TapPlayer.Maui/ViewModels/ITileViewModel.cs b/src/TapPlayer.Maui/ViewModels/ITileViewModel.cs index 974b509..9cd0752 100644 --- a/src/TapPlayer.Maui/ViewModels/ITileViewModel.cs +++ b/src/TapPlayer.Maui/ViewModels/ITileViewModel.cs @@ -18,6 +18,8 @@ public interface ITileViewModel IMediaPlayerViewModel Player { get; set; } + bool IsPlayable { get; set; } + IAsyncCommand TapCommand { get; set; } Action StopAllExcludingBackground { get; } diff --git a/src/TapPlayer.Maui/ViewModels/ProjectEditViewModel.cs b/src/TapPlayer.Maui/ViewModels/ProjectEditViewModel.cs index b5acbb4..df009b9 100644 --- a/src/TapPlayer.Maui/ViewModels/ProjectEditViewModel.cs +++ b/src/TapPlayer.Maui/ViewModels/ProjectEditViewModel.cs @@ -257,6 +257,7 @@ private async Task Load(Guid projectId) tile.IsBackground = act?.IsBackground == true; tile.PlayType = act?.Play ?? PlayType.Once; tile.TapCommand = TileEditCommand; + tile.IsPlayable = false; Tiles.Add(tile); } @@ -390,6 +391,7 @@ private void InitDefault() tile.File = null; tile.Color = ColorPalette.Color1; tile.TapCommand = TileEditCommand; + tile.IsPlayable = false; Tiles.Add(tile); } diff --git a/src/TapPlayer.Maui/ViewModels/TileViewModel.cs b/src/TapPlayer.Maui/ViewModels/TileViewModel.cs index 948a340..0b4108c 100644 --- a/src/TapPlayer.Maui/ViewModels/TileViewModel.cs +++ b/src/TapPlayer.Maui/ViewModels/TileViewModel.cs @@ -14,6 +14,7 @@ public class TileViewModel : ViewModelBase, ITileViewModel private bool _isBackground; private ColorPalette _color; private IMediaPlayerViewModel _player; + private bool _isPlayable = true; public int Index { @@ -106,6 +107,19 @@ public IMediaPlayerViewModel Player } } + public bool IsPlayable + { + get + { + return _isPlayable; + } + set + { + _isPlayable = value; + OnProprtyChanged(); + } + } + public IAsyncCommand TapCommand { get; set; } public Action StopAllExcludingBackground { get; set; } From db10636b5dec48803aafeedd3717e5f81a270abc Mon Sep 17 00:00:00 2001 From: Aleksey Nemiro Date: Mon, 5 Jun 2023 10:45:24 +0300 Subject: [PATCH 06/47] Created TileViewModelToMediaElementSourceConverter. --- src/TapPlayer.Maui/Components/TileView.xaml | 8 ++++-- ...eViewModelToMediaElementSourceConverter.cs | 28 +++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 src/TapPlayer.Maui/Converters/TileViewModelToMediaElementSourceConverter.cs diff --git a/src/TapPlayer.Maui/Components/TileView.xaml b/src/TapPlayer.Maui/Components/TileView.xaml index 75c1042..09460b6 100644 --- a/src/TapPlayer.Maui/Components/TileView.xaml +++ b/src/TapPlayer.Maui/Components/TileView.xaml @@ -13,7 +13,7 @@ - + @@ -176,7 +176,11 @@ diff --git a/src/TapPlayer.Maui/Converters/TileViewModelToMediaElementSourceConverter.cs b/src/TapPlayer.Maui/Converters/TileViewModelToMediaElementSourceConverter.cs new file mode 100644 index 0000000..a1e637a --- /dev/null +++ b/src/TapPlayer.Maui/Converters/TileViewModelToMediaElementSourceConverter.cs @@ -0,0 +1,28 @@ +using System.Globalization; +using TapPlayer.Maui.ViewModels; + +namespace TapPlayer.Maui.Converters; + +internal class TileViewModelToMediaElementSourceConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + var tile = (ITileViewModel)value; + + if ( + tile == null + || string.IsNullOrWhiteSpace(tile.File?.FullPath) + || !tile.IsPlayable + ) + { + return null; + } + + return tile.File.FullPath; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } +} From 8bfc9dd33057f8e2c4df2200e038e6e072ecba48 Mon Sep 17 00:00:00 2001 From: Aleksey Nemiro Date: Mon, 5 Jun 2023 10:46:00 +0300 Subject: [PATCH 07/47] ReduceValueConverter - Minor improvements. --- .../Converters/ReduceValueConverter.cs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/TapPlayer.Maui/Converters/ReduceValueConverter.cs b/src/TapPlayer.Maui/Converters/ReduceValueConverter.cs index d1ced82..952cedd 100644 --- a/src/TapPlayer.Maui/Converters/ReduceValueConverter.cs +++ b/src/TapPlayer.Maui/Converters/ReduceValueConverter.cs @@ -6,24 +6,15 @@ internal class ReduceValueConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - if (value == null) - { - return value; - } - - if (System.Convert.ToInt32(value, CultureInfo.InvariantCulture) < 0) - { - return value; - } - - var result = System.Convert.ToInt32(value, CultureInfo.InvariantCulture) - - System.Convert.ToInt32(parameter, CultureInfo.InvariantCulture); + var valueInt = System.Convert.ToInt32(value, CultureInfo.InvariantCulture); + var parameterInt = System.Convert.ToInt32(parameter, CultureInfo.InvariantCulture); + var result = valueInt - parameterInt; return result; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { - return System.Convert.ToDouble(value, CultureInfo.InvariantCulture); + return System.Convert.ToInt32(value, CultureInfo.InvariantCulture); } -} \ No newline at end of file +} From 3767385ecd7a1bb2128405191336cad1692b40f9 Mon Sep 17 00:00:00 2001 From: Aleksey Nemiro Date: Tue, 6 Jun 2023 09:52:42 +0300 Subject: [PATCH 08/47] Added and implemented IDispatcherService. --- src/TapPlayer.Maui/AppBuilder.cs | 1 + src/TapPlayer.Maui/Services/DispatcherService.cs | 9 +++++++++ src/TapPlayer.Maui/Services/IDispatcherService.cs | 6 ++++++ 3 files changed, 16 insertions(+) create mode 100644 src/TapPlayer.Maui/Services/DispatcherService.cs create mode 100644 src/TapPlayer.Maui/Services/IDispatcherService.cs diff --git a/src/TapPlayer.Maui/AppBuilder.cs b/src/TapPlayer.Maui/AppBuilder.cs index fa161d4..8dd05ca 100644 --- a/src/TapPlayer.Maui/AppBuilder.cs +++ b/src/TapPlayer.Maui/AppBuilder.cs @@ -43,6 +43,7 @@ public static MauiAppBuilder CreateBuilder() builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); diff --git a/src/TapPlayer.Maui/Services/DispatcherService.cs b/src/TapPlayer.Maui/Services/DispatcherService.cs new file mode 100644 index 0000000..542151c --- /dev/null +++ b/src/TapPlayer.Maui/Services/DispatcherService.cs @@ -0,0 +1,9 @@ +namespace TapPlayer.Maui.Services; + +public class DispatcherService : IDispatcherService +{ + public void Dispatch(Action action) + { + Application.Current.MainPage.Dispatcher.Dispatch(action); + } +} diff --git a/src/TapPlayer.Maui/Services/IDispatcherService.cs b/src/TapPlayer.Maui/Services/IDispatcherService.cs new file mode 100644 index 0000000..994d772 --- /dev/null +++ b/src/TapPlayer.Maui/Services/IDispatcherService.cs @@ -0,0 +1,6 @@ +namespace TapPlayer.Maui.Services; + +public interface IDispatcherService +{ + void Dispatch(Action action); +} From 55764b8e8f4f7c01b888b7d96ccb1e232cfd20de Mon Sep 17 00:00:00 2001 From: Aleksey Nemiro Date: Tue, 6 Jun 2023 09:54:21 +0300 Subject: [PATCH 09/47] TapPlayerService - Using IDispatcherService to solve the problem with calls in the wrong threads. --- .../Services/TapPlayerService.cs | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/TapPlayer.Maui/Services/TapPlayerService.cs b/src/TapPlayer.Maui/Services/TapPlayerService.cs index c98717a..783c203 100644 --- a/src/TapPlayer.Maui/Services/TapPlayerService.cs +++ b/src/TapPlayer.Maui/Services/TapPlayerService.cs @@ -5,8 +5,14 @@ namespace TapPlayer.Maui.Services; public class TapPlayerService : ITapPlayerService { + private readonly IDispatcherService _dispatcherService; private readonly ObservableCollection _tiles = new ObservableCollection(); + public TapPlayerService(IDispatcherService dispatcherService) + { + _dispatcherService = dispatcherService; + } + public void Set(ObservableCollection tiles) { Clear(); @@ -27,7 +33,7 @@ public void StopAllExcludingBackground(Func additionalCond foreach (var tile in activeTiles) { - tile.Player.Stop(); + _dispatcherService.Dispatch(tile.Player.Stop); } } @@ -39,7 +45,7 @@ public void StopAll() foreach (var tile in activeTiles) { - tile.Player.Stop(); + _dispatcherService.Dispatch(tile.Player.Stop); } } @@ -47,12 +53,15 @@ public void DisposeAll() { foreach (var tile in _tiles) { - if (tile.Player != null) + _dispatcherService.Dispatch(() => { - tile.Player.Stop(); - tile.Player.Dispose(); - tile.Player = null; - } + if (tile.Player != null) + { + tile.Player.Stop(); + tile.Player.Dispose(); + tile.Player = null; + } + }); } } From 09cd3a610e33a3c49eb0c08bfdcd8e9a026edc23 Mon Sep 17 00:00:00 2001 From: Aleksey Nemiro Date: Tue, 6 Jun 2023 11:40:39 +0300 Subject: [PATCH 10/47] Show loading indicator when tiles are generated. --- .../Components/ProjectEdit.xaml | 151 +++++++++--------- .../Components/ProjectEdit.xaml.cs | 88 ++++++++-- .../Components/TileView.xaml.cs | 15 +- .../Extensions/GridExtensions.cs | 3 +- src/TapPlayer.Maui/MainPage.xaml | 7 +- src/TapPlayer.Maui/MainPage.xaml.cs | 39 ++++- .../ViewModels/IMainPageViewModel.cs | 3 + .../ViewModels/IProjectEditViewModel.cs | 4 +- .../ViewModels/MainPageViewModel.cs | 10 +- .../ViewModels/ProjectEditViewModel.cs | 30 +++- 10 files changed, 234 insertions(+), 116 deletions(-) diff --git a/src/TapPlayer.Maui/Components/ProjectEdit.xaml b/src/TapPlayer.Maui/Components/ProjectEdit.xaml index 7d82ca7..e4a88d1 100644 --- a/src/TapPlayer.Maui/Components/ProjectEdit.xaml +++ b/src/TapPlayer.Maui/Components/ProjectEdit.xaml @@ -7,7 +7,6 @@ xmlns:markup="clr-namespace:TapPlayer.Maui.MarkupExtensions" x:Class="TapPlayer.Maui.Components.ProjectEdit" x:Name="Me" - BindingContextChanged="ProjectEdit_BindingContextChanged" > @@ -16,86 +15,94 @@ - - + - - + - - + + - - + + - + - -