From 26676d769ad1edc7401159322c71a27a74ec8783 Mon Sep 17 00:00:00 2001 From: Pavel Anpin <6060545+anpin@users.noreply.github.com> Date: Mon, 28 Mar 2022 12:20:28 +0400 Subject: [PATCH 01/28] Added MAUI targets, but haven't tested yet due to build issue in VS2022 --- APES.UI.XF.sln | 95 +++++++- .../APES.UI.MAUI.Sample.csproj | 68 ++++++ .../APES.UI.MAUI.Sample.sln | 27 +++ samples/APES.UI.MAUI.Sample/App.xaml | 35 +++ samples/APES.UI.MAUI.Sample/App.xaml.cs | 11 + samples/APES.UI.MAUI.Sample/MainPage.xaml | 45 ++++ samples/APES.UI.MAUI.Sample/MainPage.xaml.cs | 9 + samples/APES.UI.MAUI.Sample/MauiProgram.cs | 17 ++ samples/APES.UI.MAUI.Sample/NavPage.xaml | 6 + samples/APES.UI.MAUI.Sample/NavPage.xaml.cs | 8 + .../Platforms/Android/AndroidManifest.xml | 6 + .../Platforms/Android/MainActivity.cs | 10 + .../Platforms/Android/MainApplication.cs | 15 ++ .../Android/Resources/values/colors.xml | 6 + .../Platforms/MacCatalyst/AppDelegate.cs | 9 + .../Platforms/MacCatalyst/Info.plist | 30 +++ .../Platforms/MacCatalyst/Program.cs | 15 ++ .../Platforms/Windows/App.xaml | 8 + .../Platforms/Windows/App.xaml.cs | 24 ++ .../Platforms/Windows/Package.appxmanifest | 33 +++ .../Platforms/Windows/app.manifest | 15 ++ .../Platforms/iOS/AppDelegate.cs | 9 + .../Platforms/iOS/Info.plist | 32 +++ .../Platforms/iOS/Program.cs | 15 ++ .../Resources/Fonts/OpenSans-Regular.ttf | Bin 0 -> 107140 bytes .../Resources/Images/dotnet_bot.svg | 93 ++++++++ .../Resources/Raw/AboutAssets.txt | 14 ++ .../APES.UI.MAUI.Sample/Resources/appicon.svg | 4 + .../Resources/appiconfg.svg | 8 + .../APES.UI.Sample.Shared.csproj | 16 ++ .../AsyncCommand.cs | 0 .../IAsyncCommand.cs | 0 .../IErrorHandler.cs | 0 .../TaskExtensions.cs | 0 .../ViewModels/MainViewModel.cs | 36 ++- .../Resources/Resource.designer.cs | 2 +- .../APES.UI.XF.Sample.csproj | 1 + samples/APES.UI.XF.Sample/MainPage.xaml | 2 +- src/APES.UI.XF.csproj | 53 +++-- src/Droid/ContextMenuContainerRenderer.cs | 206 +++++++++++++++--- src/Shared/AssemblyConfiguration.cs | 6 +- src/Shared/ContextMenuContainer.cs | 7 +- src/Shared/ContextMenuContainerExtensions.cs | 1 - src/Shared/ContextMenuItem.cs | 7 +- src/UWP/ContextMenuContainerRenderer.cs | 29 ++- ...eImageSourceToBitmapIconSourceConverter.cs | 7 + src/UWP/GenericBoolConverter.cs | 5 +- src/iOS/ContextMenuContainerRenderer.cs | 10 + src/iOS/ContextMenuDelegate.cs | 10 + 49 files changed, 997 insertions(+), 68 deletions(-) create mode 100644 samples/APES.UI.MAUI.Sample/APES.UI.MAUI.Sample.csproj create mode 100644 samples/APES.UI.MAUI.Sample/APES.UI.MAUI.Sample.sln create mode 100644 samples/APES.UI.MAUI.Sample/App.xaml create mode 100644 samples/APES.UI.MAUI.Sample/App.xaml.cs create mode 100644 samples/APES.UI.MAUI.Sample/MainPage.xaml create mode 100644 samples/APES.UI.MAUI.Sample/MainPage.xaml.cs create mode 100644 samples/APES.UI.MAUI.Sample/MauiProgram.cs create mode 100644 samples/APES.UI.MAUI.Sample/NavPage.xaml create mode 100644 samples/APES.UI.MAUI.Sample/NavPage.xaml.cs create mode 100644 samples/APES.UI.MAUI.Sample/Platforms/Android/AndroidManifest.xml create mode 100644 samples/APES.UI.MAUI.Sample/Platforms/Android/MainActivity.cs create mode 100644 samples/APES.UI.MAUI.Sample/Platforms/Android/MainApplication.cs create mode 100644 samples/APES.UI.MAUI.Sample/Platforms/Android/Resources/values/colors.xml create mode 100644 samples/APES.UI.MAUI.Sample/Platforms/MacCatalyst/AppDelegate.cs create mode 100644 samples/APES.UI.MAUI.Sample/Platforms/MacCatalyst/Info.plist create mode 100644 samples/APES.UI.MAUI.Sample/Platforms/MacCatalyst/Program.cs create mode 100644 samples/APES.UI.MAUI.Sample/Platforms/Windows/App.xaml create mode 100644 samples/APES.UI.MAUI.Sample/Platforms/Windows/App.xaml.cs create mode 100644 samples/APES.UI.MAUI.Sample/Platforms/Windows/Package.appxmanifest create mode 100644 samples/APES.UI.MAUI.Sample/Platforms/Windows/app.manifest create mode 100644 samples/APES.UI.MAUI.Sample/Platforms/iOS/AppDelegate.cs create mode 100644 samples/APES.UI.MAUI.Sample/Platforms/iOS/Info.plist create mode 100644 samples/APES.UI.MAUI.Sample/Platforms/iOS/Program.cs create mode 100644 samples/APES.UI.MAUI.Sample/Resources/Fonts/OpenSans-Regular.ttf create mode 100644 samples/APES.UI.MAUI.Sample/Resources/Images/dotnet_bot.svg create mode 100644 samples/APES.UI.MAUI.Sample/Resources/Raw/AboutAssets.txt create mode 100644 samples/APES.UI.MAUI.Sample/Resources/appicon.svg create mode 100644 samples/APES.UI.MAUI.Sample/Resources/appiconfg.svg create mode 100644 samples/APES.UI.Samples.Shared/APES.UI.Sample.Shared.csproj rename samples/{APES.UI.XF.Sample => APES.UI.Samples.Shared}/AsyncCommand.cs (100%) rename samples/{APES.UI.XF.Sample => APES.UI.Samples.Shared}/IAsyncCommand.cs (100%) rename samples/{APES.UI.XF.Sample => APES.UI.Samples.Shared}/IErrorHandler.cs (100%) rename samples/{APES.UI.XF.Sample => APES.UI.Samples.Shared}/TaskExtensions.cs (100%) rename samples/{APES.UI.XF.Sample => APES.UI.Samples.Shared}/ViewModels/MainViewModel.cs (80%) diff --git a/APES.UI.XF.sln b/APES.UI.XF.sln index 2e495bb..02be5b2 100644 --- a/APES.UI.XF.sln +++ b/APES.UI.XF.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.6.30114.105 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32210.308 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8D069BC7-B62E-4349-9DE0-0BF140EE3FF9}" EndProject @@ -23,6 +23,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "APES.UI.XF.Sample.Mac", "sa EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "APES.UI.XF.Sample.UWP", "samples\APES.UI.XF.Sample.UWP\APES.UI.XF.Sample.UWP.csproj", "{BC47ADDA-6CF9-44E5-ADCF-494F7A198E44}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "APES.UI.MAUI.Sample", "samples\APES.UI.MAUI.Sample\APES.UI.MAUI.Sample.csproj", "{6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "APES.UI.Sample.Shared", "samples\APES.UI.Samples.Shared\APES.UI.Sample.Shared.csproj", "{7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -360,6 +364,91 @@ Global {BC47ADDA-6CF9-44E5-ADCF-494F7A198E44}.Release|x86.ActiveCfg = Release|x86 {BC47ADDA-6CF9-44E5-ADCF-494F7A198E44}.Release|x86.Build.0 = Release|x86 {BC47ADDA-6CF9-44E5-ADCF-494F7A198E44}.Release|x86.Deploy.0 = Release|x86 + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.Debug|ARM.ActiveCfg = Debug|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.Debug|ARM.Build.0 = Debug|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.Debug|ARM64.Build.0 = Debug|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.Debug|iPhone.Build.0 = Debug|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.Debug|x64.ActiveCfg = Debug|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.Debug|x64.Build.0 = Debug|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.Debug|x86.ActiveCfg = Debug|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.Debug|x86.Build.0 = Debug|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.MonoDroid10|Any CPU.ActiveCfg = Debug|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.MonoDroid10|Any CPU.Build.0 = Debug|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.MonoDroid10|ARM.ActiveCfg = Debug|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.MonoDroid10|ARM.Build.0 = Debug|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.MonoDroid10|ARM64.ActiveCfg = Debug|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.MonoDroid10|ARM64.Build.0 = Debug|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.MonoDroid10|iPhone.ActiveCfg = Debug|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.MonoDroid10|iPhone.Build.0 = Debug|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.MonoDroid10|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.MonoDroid10|iPhoneSimulator.Build.0 = Debug|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.MonoDroid10|x64.ActiveCfg = Debug|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.MonoDroid10|x64.Build.0 = Debug|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.MonoDroid10|x86.ActiveCfg = Debug|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.MonoDroid10|x86.Build.0 = Debug|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.Release|Any CPU.Build.0 = Release|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.Release|ARM.ActiveCfg = Release|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.Release|ARM.Build.0 = Release|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.Release|ARM64.ActiveCfg = Release|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.Release|ARM64.Build.0 = Release|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.Release|iPhone.ActiveCfg = Release|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.Release|iPhone.Build.0 = Release|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.Release|x64.ActiveCfg = Release|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.Release|x64.Build.0 = Release|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.Release|x86.ActiveCfg = Release|Any CPU + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8}.Release|x86.Build.0 = Release|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.Debug|ARM.ActiveCfg = Debug|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.Debug|ARM.Build.0 = Debug|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.Debug|ARM64.Build.0 = Debug|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.Debug|iPhone.Build.0 = Debug|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.Debug|x64.ActiveCfg = Debug|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.Debug|x64.Build.0 = Debug|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.Debug|x86.ActiveCfg = Debug|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.Debug|x86.Build.0 = Debug|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.MonoDroid10|Any CPU.ActiveCfg = Debug|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.MonoDroid10|Any CPU.Build.0 = Debug|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.MonoDroid10|ARM.ActiveCfg = Debug|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.MonoDroid10|ARM.Build.0 = Debug|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.MonoDroid10|ARM64.ActiveCfg = Debug|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.MonoDroid10|ARM64.Build.0 = Debug|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.MonoDroid10|iPhone.ActiveCfg = Debug|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.MonoDroid10|iPhone.Build.0 = Debug|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.MonoDroid10|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.MonoDroid10|iPhoneSimulator.Build.0 = Debug|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.MonoDroid10|x64.ActiveCfg = Debug|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.MonoDroid10|x64.Build.0 = Debug|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.MonoDroid10|x86.ActiveCfg = Debug|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.MonoDroid10|x86.Build.0 = Debug|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.Release|Any CPU.Build.0 = Release|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.Release|ARM.ActiveCfg = Release|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.Release|ARM.Build.0 = Release|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.Release|ARM64.ActiveCfg = Release|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.Release|ARM64.Build.0 = Release|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.Release|iPhone.ActiveCfg = Release|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.Release|iPhone.Build.0 = Release|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.Release|x64.ActiveCfg = Release|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.Release|x64.Build.0 = Release|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.Release|x86.ActiveCfg = Release|Any CPU + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -372,6 +461,8 @@ Global {53559169-EC01-4D52-9348-092783E9E5F0} = {8D069BC7-B62E-4349-9DE0-0BF140EE3FF9} {F3219BAF-5883-4769-B32E-076DDFE71694} = {44E54204-4497-43C1-AAF9-43AFFD5FB5F2} {BC47ADDA-6CF9-44E5-ADCF-494F7A198E44} = {44E54204-4497-43C1-AAF9-43AFFD5FB5F2} + {6D3BEB7F-E3F6-41EF-8BDC-DF56EC39EBE8} = {44E54204-4497-43C1-AAF9-43AFFD5FB5F2} + {7861BCE8-7C35-4EDC-B1D5-AEA7B99D4E7E} = {44E54204-4497-43C1-AAF9-43AFFD5FB5F2} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1F776336-20B3-431D-B3CD-8D07AA121CDA} diff --git a/samples/APES.UI.MAUI.Sample/APES.UI.MAUI.Sample.csproj b/samples/APES.UI.MAUI.Sample/APES.UI.MAUI.Sample.csproj new file mode 100644 index 0000000..97f7def --- /dev/null +++ b/samples/APES.UI.MAUI.Sample/APES.UI.MAUI.Sample.csproj @@ -0,0 +1,68 @@ + + + + net6.0-android;net6.0-ios;net6.0-maccatalyst + $(TargetFrameworks);net6.0-windows10.0.19041 + Exe + APES.UI.MAUI.Sample + true + true + enable + true + + + APES.UI.MAUI.Sample + + + com.companyname.apes.ui.maui.sample + 381762DE-A6AE-4986-8C37-3A067AE03B2E + + + 1.0 + 1 + + + True + + 14.2 + 14.0 + 21.0 + 10.0.17763.0 + 10.0.17763.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + WinExe + win10-x64 + + + diff --git a/samples/APES.UI.MAUI.Sample/APES.UI.MAUI.Sample.sln b/samples/APES.UI.MAUI.Sample/APES.UI.MAUI.Sample.sln new file mode 100644 index 0000000..7139903 --- /dev/null +++ b/samples/APES.UI.MAUI.Sample/APES.UI.MAUI.Sample.sln @@ -0,0 +1,27 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31611.283 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "APES.UI.MAUI.Sample", "APES.UI.MAUI.Sample.csproj", "{E58C314D-6977-4ED6-9871-B180BDD37544}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E58C314D-6977-4ED6-9871-B180BDD37544}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E58C314D-6977-4ED6-9871-B180BDD37544}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E58C314D-6977-4ED6-9871-B180BDD37544}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {E58C314D-6977-4ED6-9871-B180BDD37544}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E58C314D-6977-4ED6-9871-B180BDD37544}.Release|Any CPU.Build.0 = Release|Any CPU + {E58C314D-6977-4ED6-9871-B180BDD37544}.Release|Any CPU.Deploy.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {61F7FB11-1E47-470C-91E2-47F8143E1572} + EndGlobalSection +EndGlobal diff --git a/samples/APES.UI.MAUI.Sample/App.xaml b/samples/APES.UI.MAUI.Sample/App.xaml new file mode 100644 index 0000000..a209a59 --- /dev/null +++ b/samples/APES.UI.MAUI.Sample/App.xaml @@ -0,0 +1,35 @@ + + + + + + #512bdf + White + + + White + Black + + + + + + + + + + diff --git a/samples/APES.UI.MAUI.Sample/App.xaml.cs b/samples/APES.UI.MAUI.Sample/App.xaml.cs new file mode 100644 index 0000000..dc684a0 --- /dev/null +++ b/samples/APES.UI.MAUI.Sample/App.xaml.cs @@ -0,0 +1,11 @@ +namespace APES.UI.MAUI.Sample; + +public partial class App : Application +{ + public App() + { + InitializeComponent(); + APES.UI.XF.ContextMenuContainer.Init(); + MainPage = new NavPage(new MainPage()); + } +} diff --git a/samples/APES.UI.MAUI.Sample/MainPage.xaml b/samples/APES.UI.MAUI.Sample/MainPage.xaml new file mode 100644 index 0000000..5785258 --- /dev/null +++ b/samples/APES.UI.MAUI.Sample/MainPage.xaml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/APES.UI.MAUI.Sample/MainPage.xaml.cs b/samples/APES.UI.MAUI.Sample/MainPage.xaml.cs new file mode 100644 index 0000000..2267813 --- /dev/null +++ b/samples/APES.UI.MAUI.Sample/MainPage.xaml.cs @@ -0,0 +1,9 @@ +namespace APES.UI.MAUI.Sample; +public partial class MainPage : ContentPage + { + public MainPage() + { + InitializeComponent(); + } + } + diff --git a/samples/APES.UI.MAUI.Sample/MauiProgram.cs b/samples/APES.UI.MAUI.Sample/MauiProgram.cs new file mode 100644 index 0000000..4ab0ca6 --- /dev/null +++ b/samples/APES.UI.MAUI.Sample/MauiProgram.cs @@ -0,0 +1,17 @@ +namespace APES.UI.MAUI.Sample; + +public static class MauiProgram +{ + public static MauiApp CreateMauiApp() + { + var builder = MauiApp.CreateBuilder(); + builder + .UseMauiApp() + .ConfigureFonts(fonts => + { + fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); + }); + + return builder.Build(); + } +} diff --git a/samples/APES.UI.MAUI.Sample/NavPage.xaml b/samples/APES.UI.MAUI.Sample/NavPage.xaml new file mode 100644 index 0000000..2ef4391 --- /dev/null +++ b/samples/APES.UI.MAUI.Sample/NavPage.xaml @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/samples/APES.UI.MAUI.Sample/NavPage.xaml.cs b/samples/APES.UI.MAUI.Sample/NavPage.xaml.cs new file mode 100644 index 0000000..bb542ec --- /dev/null +++ b/samples/APES.UI.MAUI.Sample/NavPage.xaml.cs @@ -0,0 +1,8 @@ +namespace APES.UI.MAUI.Sample; + public partial class NavPage : NavigationPage + { + public NavPage(Page root) : base(root) + { + InitializeComponent(); + } + } diff --git a/samples/APES.UI.MAUI.Sample/Platforms/Android/AndroidManifest.xml b/samples/APES.UI.MAUI.Sample/Platforms/Android/AndroidManifest.xml new file mode 100644 index 0000000..7570ff6 --- /dev/null +++ b/samples/APES.UI.MAUI.Sample/Platforms/Android/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/APES.UI.MAUI.Sample/Platforms/Android/MainActivity.cs b/samples/APES.UI.MAUI.Sample/Platforms/Android/MainActivity.cs new file mode 100644 index 0000000..3869160 --- /dev/null +++ b/samples/APES.UI.MAUI.Sample/Platforms/Android/MainActivity.cs @@ -0,0 +1,10 @@ +using Android.App; +using Android.Content.PM; +using Android.OS; + +namespace APES.UI.MAUI.Sample; + +[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize)] +public class MainActivity : MauiAppCompatActivity +{ +} diff --git a/samples/APES.UI.MAUI.Sample/Platforms/Android/MainApplication.cs b/samples/APES.UI.MAUI.Sample/Platforms/Android/MainApplication.cs new file mode 100644 index 0000000..fae3c02 --- /dev/null +++ b/samples/APES.UI.MAUI.Sample/Platforms/Android/MainApplication.cs @@ -0,0 +1,15 @@ +using Android.App; +using Android.Runtime; + +namespace APES.UI.MAUI.Sample; + +[Application] +public class MainApplication : MauiApplication +{ + public MainApplication(IntPtr handle, JniHandleOwnership ownership) + : base(handle, ownership) + { + } + + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); +} diff --git a/samples/APES.UI.MAUI.Sample/Platforms/Android/Resources/values/colors.xml b/samples/APES.UI.MAUI.Sample/Platforms/Android/Resources/values/colors.xml new file mode 100644 index 0000000..c04d749 --- /dev/null +++ b/samples/APES.UI.MAUI.Sample/Platforms/Android/Resources/values/colors.xml @@ -0,0 +1,6 @@ + + + #512BD4 + #2B0B98 + #2B0B98 + \ No newline at end of file diff --git a/samples/APES.UI.MAUI.Sample/Platforms/MacCatalyst/AppDelegate.cs b/samples/APES.UI.MAUI.Sample/Platforms/MacCatalyst/AppDelegate.cs new file mode 100644 index 0000000..c8198fd --- /dev/null +++ b/samples/APES.UI.MAUI.Sample/Platforms/MacCatalyst/AppDelegate.cs @@ -0,0 +1,9 @@ +using Foundation; + +namespace APES.UI.MAUI.Sample; + +[Register("AppDelegate")] +public class AppDelegate : MauiUIApplicationDelegate +{ + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); +} diff --git a/samples/APES.UI.MAUI.Sample/Platforms/MacCatalyst/Info.plist b/samples/APES.UI.MAUI.Sample/Platforms/MacCatalyst/Info.plist new file mode 100644 index 0000000..c96dd0a --- /dev/null +++ b/samples/APES.UI.MAUI.Sample/Platforms/MacCatalyst/Info.plist @@ -0,0 +1,30 @@ + + + + + UIDeviceFamily + + 1 + 2 + + UIRequiredDeviceCapabilities + + arm64 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + XSAppIconAssets + Assets.xcassets/appicon.appiconset + + diff --git a/samples/APES.UI.MAUI.Sample/Platforms/MacCatalyst/Program.cs b/samples/APES.UI.MAUI.Sample/Platforms/MacCatalyst/Program.cs new file mode 100644 index 0000000..33351e8 --- /dev/null +++ b/samples/APES.UI.MAUI.Sample/Platforms/MacCatalyst/Program.cs @@ -0,0 +1,15 @@ +using ObjCRuntime; +using UIKit; + +namespace APES.UI.MAUI.Sample; + +public class Program +{ + // This is the main entry point of the application. + static void Main(string[] args) + { + // if you want to use a different Application Delegate class from "AppDelegate" + // you can specify it here. + UIApplication.Main(args, null, typeof(AppDelegate)); + } +} diff --git a/samples/APES.UI.MAUI.Sample/Platforms/Windows/App.xaml b/samples/APES.UI.MAUI.Sample/Platforms/Windows/App.xaml new file mode 100644 index 0000000..8ba4ac8 --- /dev/null +++ b/samples/APES.UI.MAUI.Sample/Platforms/Windows/App.xaml @@ -0,0 +1,8 @@ + + + diff --git a/samples/APES.UI.MAUI.Sample/Platforms/Windows/App.xaml.cs b/samples/APES.UI.MAUI.Sample/Platforms/Windows/App.xaml.cs new file mode 100644 index 0000000..58ea73d --- /dev/null +++ b/samples/APES.UI.MAUI.Sample/Platforms/Windows/App.xaml.cs @@ -0,0 +1,24 @@ +using Microsoft.UI.Xaml; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace APES.UI.MAUI.Sample.WinUI; + +/// +/// Provides application-specific behavior to supplement the default Application class. +/// +public partial class App : MauiWinUIApplication +{ + /// + /// Initializes the singleton application object. This is the first line of authored code + /// executed, and as such is the logical equivalent of main() or WinMain(). + /// + public App() + { + this.InitializeComponent(); + } + + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); +} + diff --git a/samples/APES.UI.MAUI.Sample/Platforms/Windows/Package.appxmanifest b/samples/APES.UI.MAUI.Sample/Platforms/Windows/Package.appxmanifest new file mode 100644 index 0000000..0cb469c --- /dev/null +++ b/samples/APES.UI.MAUI.Sample/Platforms/Windows/Package.appxmanifest @@ -0,0 +1,33 @@ + + + + + + + User Name + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/APES.UI.MAUI.Sample/Platforms/Windows/app.manifest b/samples/APES.UI.MAUI.Sample/Platforms/Windows/app.manifest new file mode 100644 index 0000000..6116417 --- /dev/null +++ b/samples/APES.UI.MAUI.Sample/Platforms/Windows/app.manifest @@ -0,0 +1,15 @@ + + + + + + + + true/PM + PerMonitorV2, PerMonitor + + + diff --git a/samples/APES.UI.MAUI.Sample/Platforms/iOS/AppDelegate.cs b/samples/APES.UI.MAUI.Sample/Platforms/iOS/AppDelegate.cs new file mode 100644 index 0000000..c8198fd --- /dev/null +++ b/samples/APES.UI.MAUI.Sample/Platforms/iOS/AppDelegate.cs @@ -0,0 +1,9 @@ +using Foundation; + +namespace APES.UI.MAUI.Sample; + +[Register("AppDelegate")] +public class AppDelegate : MauiUIApplicationDelegate +{ + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); +} diff --git a/samples/APES.UI.MAUI.Sample/Platforms/iOS/Info.plist b/samples/APES.UI.MAUI.Sample/Platforms/iOS/Info.plist new file mode 100644 index 0000000..0004a4f --- /dev/null +++ b/samples/APES.UI.MAUI.Sample/Platforms/iOS/Info.plist @@ -0,0 +1,32 @@ + + + + + LSRequiresIPhoneOS + + UIDeviceFamily + + 1 + 2 + + UIRequiredDeviceCapabilities + + arm64 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + XSAppIconAssets + Assets.xcassets/appicon.appiconset + + diff --git a/samples/APES.UI.MAUI.Sample/Platforms/iOS/Program.cs b/samples/APES.UI.MAUI.Sample/Platforms/iOS/Program.cs new file mode 100644 index 0000000..33351e8 --- /dev/null +++ b/samples/APES.UI.MAUI.Sample/Platforms/iOS/Program.cs @@ -0,0 +1,15 @@ +using ObjCRuntime; +using UIKit; + +namespace APES.UI.MAUI.Sample; + +public class Program +{ + // This is the main entry point of the application. + static void Main(string[] args) + { + // if you want to use a different Application Delegate class from "AppDelegate" + // you can specify it here. + UIApplication.Main(args, null, typeof(AppDelegate)); + } +} diff --git a/samples/APES.UI.MAUI.Sample/Resources/Fonts/OpenSans-Regular.ttf b/samples/APES.UI.MAUI.Sample/Resources/Fonts/OpenSans-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..af6165d2c9a2f6e1172ff681ed2196d98ecddae5 GIT binary patch literal 107140 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 SF|m(Ds1)$5#B|{I4E_g}TqM8% literal 0 HcmV?d00001 diff --git a/samples/APES.UI.MAUI.Sample/Resources/Images/dotnet_bot.svg b/samples/APES.UI.MAUI.Sample/Resources/Images/dotnet_bot.svg new file mode 100644 index 0000000..abfaff2 --- /dev/null +++ b/samples/APES.UI.MAUI.Sample/Resources/Images/dotnet_bot.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/APES.UI.MAUI.Sample/Resources/Raw/AboutAssets.txt b/samples/APES.UI.MAUI.Sample/Resources/Raw/AboutAssets.txt new file mode 100644 index 0000000..3f7a940 --- /dev/null +++ b/samples/APES.UI.MAUI.Sample/Resources/Raw/AboutAssets.txt @@ -0,0 +1,14 @@ +Any raw assets you want to be deployed with your application can be placed in +this directory (and child directories) and given a Build Action of "MauiAsset": + + + +These files will be deployed with you package and will be accessible using Essentials: + + async Task LoadMauiAsset() + { + using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt"); + using var reader = new StreamReader(stream); + + var contents = reader.ReadToEnd(); + } diff --git a/samples/APES.UI.MAUI.Sample/Resources/appicon.svg b/samples/APES.UI.MAUI.Sample/Resources/appicon.svg new file mode 100644 index 0000000..9d63b65 --- /dev/null +++ b/samples/APES.UI.MAUI.Sample/Resources/appicon.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/samples/APES.UI.MAUI.Sample/Resources/appiconfg.svg b/samples/APES.UI.MAUI.Sample/Resources/appiconfg.svg new file mode 100644 index 0000000..21dfb25 --- /dev/null +++ b/samples/APES.UI.MAUI.Sample/Resources/appiconfg.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/samples/APES.UI.Samples.Shared/APES.UI.Sample.Shared.csproj b/samples/APES.UI.Samples.Shared/APES.UI.Sample.Shared.csproj new file mode 100644 index 0000000..bbbf2de --- /dev/null +++ b/samples/APES.UI.Samples.Shared/APES.UI.Sample.Shared.csproj @@ -0,0 +1,16 @@ + + + netstandard2.0;net6.0 + enable + enable + latest + portable + + + $(DefineConstants);MAUI + true + + + + + diff --git a/samples/APES.UI.XF.Sample/AsyncCommand.cs b/samples/APES.UI.Samples.Shared/AsyncCommand.cs similarity index 100% rename from samples/APES.UI.XF.Sample/AsyncCommand.cs rename to samples/APES.UI.Samples.Shared/AsyncCommand.cs diff --git a/samples/APES.UI.XF.Sample/IAsyncCommand.cs b/samples/APES.UI.Samples.Shared/IAsyncCommand.cs similarity index 100% rename from samples/APES.UI.XF.Sample/IAsyncCommand.cs rename to samples/APES.UI.Samples.Shared/IAsyncCommand.cs diff --git a/samples/APES.UI.XF.Sample/IErrorHandler.cs b/samples/APES.UI.Samples.Shared/IErrorHandler.cs similarity index 100% rename from samples/APES.UI.XF.Sample/IErrorHandler.cs rename to samples/APES.UI.Samples.Shared/IErrorHandler.cs diff --git a/samples/APES.UI.XF.Sample/TaskExtensions.cs b/samples/APES.UI.Samples.Shared/TaskExtensions.cs similarity index 100% rename from samples/APES.UI.XF.Sample/TaskExtensions.cs rename to samples/APES.UI.Samples.Shared/TaskExtensions.cs diff --git a/samples/APES.UI.XF.Sample/ViewModels/MainViewModel.cs b/samples/APES.UI.Samples.Shared/ViewModels/MainViewModel.cs similarity index 80% rename from samples/APES.UI.XF.Sample/ViewModels/MainViewModel.cs rename to samples/APES.UI.Samples.Shared/ViewModels/MainViewModel.cs index aa251e5..6e6e599 100644 --- a/samples/APES.UI.XF.Sample/ViewModels/MainViewModel.cs +++ b/samples/APES.UI.Samples.Shared/ViewModels/MainViewModel.cs @@ -4,12 +4,18 @@ using System.Threading.Tasks; using System.ComponentModel; using System.Windows.Input; +#if MAUI +using Microsoft.Maui; +using Microsoft.Maui.Controls; +using Microsoft.Maui.Essentials; +#else using Xamarin.Forms; +#endif namespace APES.UI.XF.Sample.ViewModels { public class MainViewModel : INotifyPropertyChanged { - public event PropertyChangedEventHandler PropertyChanged; + public event PropertyChangedEventHandler? PropertyChanged; protected void NotifyPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] String propertyName = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); @@ -21,7 +27,7 @@ protected bool SetField(ref T field, T value, [System.Runtime.CompilerService NotifyPropertyChanged(propertyName); return true; } - string text; + string text = ""; public string Text { get => text; @@ -49,6 +55,7 @@ public MainViewModel() DestructiveCommand = new Command(DestructiveHandler); ConstructiveCommand = new Command(ConstructiveHandler); NeverEndingCommand = new AsyncCommand(NeverendingTask); +#if !MAUI switch (Device.RuntimePlatform) { case Device.Android: @@ -68,6 +75,31 @@ public MainViewModel() settingsIconSource = @"Assets\outline_settings_black_24.png"; break; } +#else + if (DeviceInfo.Platform == DevicePlatform.Android) + { + logoIconSource = "logo.png"; + deleteIconSource = "outline_delete_24.xml"; + settingsIconSource = "outline_settings_black_24.png"; + } + else if (DeviceInfo.Platform == DevicePlatform.iOS || + DeviceInfo.Platform == DevicePlatform.macOS || + DeviceInfo.Platform == DevicePlatform.MacCatalyst) + { + + logoIconSource = "logo.png"; + deleteIconSource = "outline_delete_black_24.png"; + settingsIconSource = "outline_settings_black_24.png"; + } + + else if (DeviceInfo.Platform == DevicePlatform.WinUI) + { + logoIconSource = @"Assets\logo.png"; + deleteIconSource = @"Assets\outline_delete_black_24.png"; + settingsIconSource = @"Assets\outline_settings_black_24.png"; + } +#endif + FillAllImageActions(); } void OnFirstCommandExecuted(string s) diff --git a/samples/APES.UI.XF.Sample.Android/Resources/Resource.designer.cs b/samples/APES.UI.XF.Sample.Android/Resources/Resource.designer.cs index 635fa4d..c5f1476 100644 --- a/samples/APES.UI.XF.Sample.Android/Resources/Resource.designer.cs +++ b/samples/APES.UI.XF.Sample.Android/Resources/Resource.designer.cs @@ -14,7 +14,7 @@ namespace APES.UI.XF.Sample.Droid { - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "12.2.99.0")] public partial class Resource { diff --git a/samples/APES.UI.XF.Sample/APES.UI.XF.Sample.csproj b/samples/APES.UI.XF.Sample/APES.UI.XF.Sample.csproj index 15b2915..b47f286 100644 --- a/samples/APES.UI.XF.Sample/APES.UI.XF.Sample.csproj +++ b/samples/APES.UI.XF.Sample/APES.UI.XF.Sample.csproj @@ -21,6 +21,7 @@ + diff --git a/samples/APES.UI.XF.Sample/MainPage.xaml b/samples/APES.UI.XF.Sample/MainPage.xaml index 9288b94..9844f35 100644 --- a/samples/APES.UI.XF.Sample/MainPage.xaml +++ b/samples/APES.UI.XF.Sample/MainPage.xaml @@ -2,7 +2,7 @@ + - netstandard2.0;xamarin.ios10;xamarin.mac20;monoandroid10.0; + netstandard2.0;xamarin.ios10;xamarin.mac20;monoandroid10.0;net6.0;net6.0-android;net6.0-ios;net6.0-maccatalyst;net6.0-macos + $(TargetFrameworks);net6.0-windows10.0.19041 $(TargetFrameworks);uap10.0.17763 - + + + @@ -109,8 +115,22 @@ - - + + + + + + + + + + + + + + + + @@ -141,9 +161,4 @@ --> - - - Code - - diff --git a/src/Droid/ContextMenuContainerRenderer.cs b/src/Droid/ContextMenuContainerRenderer.cs index 36c4821..624bdf0 100644 --- a/src/Droid/ContextMenuContainerRenderer.cs +++ b/src/Droid/ContextMenuContainerRenderer.cs @@ -12,24 +12,76 @@ using AndroidX.AppCompat.Widget; using DrawableWrapperX = AndroidX.AppCompat.Graphics.Drawable.DrawableWrapper; using Java.Lang.Reflect; +#if MAUI +using Java.Interop; +using Microsoft.Maui.Controls.Compatibility.Platform.Android; +using Microsoft.Maui; +using Microsoft.Maui.Controls; +using Microsoft.Maui.Controls.Internals; +using Microsoft.Maui.Handlers; +#else using Xamarin.Forms; using Xamarin.Forms.Internals; using Xamarin.Forms.Platform.Android; +#endif using APES.UI.XF; using APES.UI.XF.Droid; using Path = System.IO.Path; using AColor = Android.Graphics.Color; +using AView = Android.Views.View; +#if !MAUI [assembly: ExportRenderer(typeof(ContextMenuContainer), typeof(ContextMenuContainerRenderer))] +#endif + namespace APES.UI.XF.Droid { [Preserve(AllMembers = true)] - public class ContextMenuContainerRenderer : ViewRenderer + public class ContextMenuContainerRenderer +#if MAUI + : ViewHandler +#else + : ViewRenderer +#endif { PopupMenu? contextMenu; +#if MAUI + public ContextMenuContainerRenderer(IPropertyMapper mapper, CommandMapper? commandMapper) : base(mapper, + commandMapper) + { + + } + + protected override AView CreatePlatformView() + { + var v = new AView(Context); + v.SetOnTouchListener(new TouchListner(() => enabled, OpenContextMenu)); + return v; + } + + public override void SetVirtualView(IView view) + { + if (VirtualView is ContextMenuContainer old) + { + old.BindingContextChanged -= Element_BindingContextChanged; + old.MenuItems.CollectionChanged -= MenuItems_CollectionChanged; + + } + + if (view is ContextMenuContainer newElement) + { + newElement.BindingContextChanged += Element_BindingContextChanged; + newElement.MenuItems.CollectionChanged += MenuItems_CollectionChanged; + + } + + base.SetVirtualView(view); + } +#else public ContextMenuContainerRenderer(Context context) : base(context) { } + protected override void OnElementChanged(ElementChangedEventArgs e) { base.OnElementChanged(e); @@ -45,9 +97,14 @@ protected override void OnElementChanged(ElementChangedEventArgs= 21 ? - Context?.GetDrawable(id) : - Context?.GetDrawable(name); + Drawable? drawable = (int) Build.VERSION.SdkInt >= 21 + ? Context?.GetDrawable(id) + : Context?.GetDrawable(name); if (drawable != null) { var wrapper = new DrawableWrapperX(drawable); @@ -99,9 +160,15 @@ void AddMenuItem(ContextMenuItem item) } } } + void FillMenuItems() { +#if MAUI + if (VirtualView is ContextMenuContainer element) +#else if (Element is ContextMenuContainer element) +#endif + { if (element.MenuItems.Count > 0) { @@ -112,6 +179,7 @@ void FillMenuItems() } } } + void RefillMenuItems() { if (contextMenu == null) @@ -120,9 +188,14 @@ void RefillMenuItems() contextMenu.Menu.Clear(); FillMenuItems(); } + PopupMenu? GetContextMenu() { +#if MAUI + if (contextMenu != null && VirtualView is ContextMenuContainer element) +#else if (contextMenu != null && Element is ContextMenuContainer element) +#endif { if (element.MenuItems.Count != contextMenu.Menu.Size()) { @@ -140,9 +213,12 @@ void RefillMenuItems() } } } + return contextMenu; } - private void MenuItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + + private void MenuItems_CollectionChanged(object sender, + System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { RefillMenuItems(); } @@ -151,50 +227,105 @@ private void Element_BindingContextChanged(object sender, EventArgs e) { RefillMenuItems(); } + private void ContextMenu_MenuItemClick(object sender, PopupMenu.MenuItemClickEventArgs e) { - - var item = ((ContextMenuContainer)Element).MenuItems.FirstOrDefault(x => x.Text == e.Item.TitleFormatted?.ToString()); +#if MAUI + var virt = ((ContextMenuContainer) VirtualView); +#else + var virt = ((ContextMenuContainer)Element); +#endif + var item = virt.MenuItems.FirstOrDefault(x => x.Text == e.Item.TitleFormatted?.ToString()); item?.OnItemTapped(); } +#if MAUI + bool enabled => VirtualView is ContextMenuContainer element && element.MenuItems.Count > 0; +#else bool enabled => Element is ContextMenuContainer element && element.MenuItems.Count > 0; - MyTimer timer; - bool timerFired = false; - - public override bool DispatchTouchEvent(MotionEvent e) +#endif + const int LongPressWaitTime = 1500; +#if MAUI + class TouchListner : Java.Lang.Object, AView.IOnTouchListener { - bool result; - Logger.Debug("ContextMEnuContainer DispatchTouchEvent fired {0}", e.Action); - if (enabled && e.Action == MotionEventActions.Down) - { - //You can change the timespan of the long press - timerFired = false; - timer = new MyTimer(TimeSpan.FromMilliseconds(1500), () => - { - timerFired = true; - OpenContextMenu(); - }); - timer.Start(); - } - if (timerFired) - { - result = true; - } - else if (e.Action == MotionEventActions.Up || e.Action == MotionEventActions.Cancel) + readonly Func _enabled; + readonly Action _onHold; + + MyTimer timer; + bool timerFired = false; + + public TouchListner(Func enabled, Action onHold) { - timer?.Stop(); - result = base.DispatchTouchEvent(e); + _enabled = enabled; + _onHold = onHold; } - else + public bool OnTouch(AView? v, MotionEvent? e) { - result = base.DispatchTouchEvent(e); - if(!result && enabled) + bool result = false; + Logger.Debug("TouchListner OnTouch fired {0}", e.Action); + if (_enabled() && e.Action == MotionEventActions.Down) + { + //You can change the timespan of the long press + timerFired = false; + timer = new MyTimer(TimeSpan.FromMilliseconds(LongPressWaitTime), () => + { + timerFired = true; + _onHold.Invoke(); + }); + timer.Start(); + } + + if (timerFired) { result = true; } + else if (e.Action == MotionEventActions.Up || e.Action == MotionEventActions.Cancel) + { + timer?.Stop(); + result = false; + } + return result; } - return result; } +#else + MyTimer timer; + bool timerFired = false; + public override bool DispatchTouchEvent(MotionEvent e) + { + bool result; + Logger.Debug("ContextMenuContainer DispatchTouchEvent fired {0}", e.Action); + if (enabled && e.Action == MotionEventActions.Down) + { + //You can change the timespan of the long press + timerFired = false; + timer = new MyTimer(TimeSpan.FromMilliseconds(LongPressWaitTime), () => + { + timerFired = true; + OpenContextMenu(); + }); + timer.Start(); + } + + if (timerFired) + { + result = true; + } + else if (e.Action == MotionEventActions.Up || e.Action == MotionEventActions.Cancel) + { + timer?.Stop(); + result = base.DispatchTouchEvent(e); + } + else + { + result = base.DispatchTouchEvent(e); + if (!result && enabled) + { + result = true; + } + } + + return result; + } +#endif void OpenContextMenu() { if (GetContextMenu() == null) @@ -203,8 +334,10 @@ void OpenContextMenu() FillMenuItems(); } + contextMenu?.Show(); } + class MyTimer { private readonly TimeSpan timespan; @@ -218,6 +351,7 @@ public MyTimer(TimeSpan timespan, Action callback) this.callback = callback; this.cancellation = new CancellationTokenSource(); } + public void Start() { CancellationTokenSource cts = this.cancellation; // safe copy diff --git a/src/Shared/AssemblyConfiguration.cs b/src/Shared/AssemblyConfiguration.cs index 92b73f8..1720602 100644 --- a/src/Shared/AssemblyConfiguration.cs +++ b/src/Shared/AssemblyConfiguration.cs @@ -1,6 +1,10 @@ using System.Runtime.CompilerServices; +#if MAUI +using Microsoft.Maui; +using Microsoft.Maui.Controls; +#else using Xamarin.Forms; - +#endif [assembly: XmlnsDefinition("http://apes.ge", "APES.UI.XF")] [assembly: XmlnsPrefix("http://apes.ge", "apes")] [assembly: InternalsVisibleTo("APES.UI.XF.Tests")] \ No newline at end of file diff --git a/src/Shared/ContextMenuContainer.cs b/src/Shared/ContextMenuContainer.cs index 1cf76bc..43a5d7a 100644 --- a/src/Shared/ContextMenuContainer.cs +++ b/src/Shared/ContextMenuContainer.cs @@ -1,6 +1,11 @@ using System; -using Xamarin.Forms; using System.Collections.Generic; +#if MAUI +using Microsoft.Maui; +using Microsoft.Maui.Controls; +#else +using Xamarin.Forms; +#endif namespace APES.UI.XF { public class ContextMenuContainer : ContentView diff --git a/src/Shared/ContextMenuContainerExtensions.cs b/src/Shared/ContextMenuContainerExtensions.cs index 0312f2b..02799db 100644 --- a/src/Shared/ContextMenuContainerExtensions.cs +++ b/src/Shared/ContextMenuContainerExtensions.cs @@ -1,5 +1,4 @@ using System; -using Xamarin.Forms; using System.Collections.Generic; namespace APES.UI.XF { diff --git a/src/Shared/ContextMenuItem.cs b/src/Shared/ContextMenuItem.cs index f3f731a..a59ba8c 100644 --- a/src/Shared/ContextMenuItem.cs +++ b/src/Shared/ContextMenuItem.cs @@ -1,8 +1,13 @@ using System; using System.Collections.Generic; using System.Text; -using Xamarin.Forms; using System.Windows.Input; +#if MAUI +using Microsoft.Maui; +using Microsoft.Maui.Controls; +#else +using Xamarin.Forms; +#endif namespace APES.UI.XF { public partial class ContextMenuItem : Element diff --git a/src/UWP/ContextMenuContainerRenderer.cs b/src/UWP/ContextMenuContainerRenderer.cs index d3315c1..0f64c14 100644 --- a/src/UWP/ContextMenuContainerRenderer.cs +++ b/src/UWP/ContextMenuContainerRenderer.cs @@ -6,6 +6,29 @@ using System.Threading; using System.Collections.Generic; using System.Collections.Specialized; +#if MAUI +using Microsoft.Maui; +using Microsoft.Maui.Controls; +using Microsoft.Maui.Controls.Platform; +using Microsoft.Maui.Controls.Compatibility; +using Microsoft.Maui.Controls.Compatibility.Platform.UWP; +using Microsoft.Maui.Controls.Internals; +using Microsoft.Maui.Handlers; +using Microsoft.UI.Input; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Automation.Peers; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Media; +using Microsoft.UI.Xaml.Media.Animation; +using WColors = Microsoft.UI.Colors; +using WBinding = Microsoft.UI.Xaml.Data.Binding; +using MenuFlyoutItem = Microsoft.UI.Xaml.Controls.MenuFlyoutItem; +using Setter = Microsoft.UI.Xaml.Setter; +using SolidColorBrush = Microsoft.UI.Xaml.Media.SolidColorBrush; +using Style = Microsoft.UI.Xaml.Style; +#else using Windows.UI.Input; using Windows.UI.Xaml; using Windows.UI.Xaml.Automation.Peers; @@ -16,10 +39,12 @@ using Windows.UI.Xaml.Media.Animation; using Xamarin.Forms.Internals; using Xamarin.Forms.Platform.UWP; -using APES.UI.XF; -using APES.UI.XF.UWP; using WColors = Windows.UI.Colors; using WBinding = Windows.UI.Xaml.Data.Binding; +#endif +using APES.UI.XF; +using APES.UI.XF.UWP; + [assembly: ExportRenderer(typeof(ContextMenuContainer), typeof(ContextMenuContainerRenderer))] namespace APES.UI.XF.UWP { diff --git a/src/UWP/FileImageSourceToBitmapIconSourceConverter.cs b/src/UWP/FileImageSourceToBitmapIconSourceConverter.cs index f3368b2..6e72741 100644 --- a/src/UWP/FileImageSourceToBitmapIconSourceConverter.cs +++ b/src/UWP/FileImageSourceToBitmapIconSourceConverter.cs @@ -1,4 +1,10 @@ using System; +#if MAUI +using Microsoft.Maui.Controls; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using IValueConverter = Microsoft.UI.Xaml.Data.IValueConverter; +#else using Xamarin.Forms.Platform.UWP; using Windows.UI.Xaml.Data; using Windows.UI.Xaml.Controls; @@ -6,6 +12,7 @@ using Xamarin.Forms.PlatformConfiguration.WindowsSpecific; using IOPath = System.IO.Path; using FileImageSource = Xamarin.Forms.FileImageSource; +#endif namespace APES.UI.XF.UWP { class FileImageSourceToBitmapIconSourceConverter : IValueConverter diff --git a/src/UWP/GenericBoolConverter.cs b/src/UWP/GenericBoolConverter.cs index 2433893..f21af50 100644 --- a/src/UWP/GenericBoolConverter.cs +++ b/src/UWP/GenericBoolConverter.cs @@ -1,9 +1,12 @@ using System; +#if MAUI +using Microsoft.UI.Xaml.Data; +#else using Xamarin.Forms.Platform.UWP; using Windows.UI.Xaml; using Windows.UI.Xaml.Data; using Windows.UI.Xaml.Controls; - +#endif namespace APES.UI.XF.UWP { class GenericBoolConverter : IValueConverter diff --git a/src/iOS/ContextMenuContainerRenderer.cs b/src/iOS/ContextMenuContainerRenderer.cs index f76aba7..94374cc 100644 --- a/src/iOS/ContextMenuContainerRenderer.cs +++ b/src/iOS/ContextMenuContainerRenderer.cs @@ -2,9 +2,19 @@ using System.Collections.Generic; using System.Text; using UIKit; +#if MAUI +using Microsoft.Maui; +using Microsoft.Maui.Controls; +using Microsoft.Maui.Controls.Platform; +using Microsoft.Maui.Controls.Compatibility; +using Microsoft.Maui.Controls.Compatibility.Platform.iOS; +using Microsoft.Maui.Controls.Internals; +using Microsoft.Maui.Handlers; +#else using Xamarin.Forms; using Xamarin.Forms.Internals; using Xamarin.Forms.Platform.iOS; +#endif using APES.UI.XF; using APES.UI.XF.iOS; [assembly: ExportRenderer(typeof(ContextMenuContainer), typeof(ContextMenuContainerRenderer))] diff --git a/src/iOS/ContextMenuDelegate.cs b/src/iOS/ContextMenuDelegate.cs index 67136bf..d22897b 100644 --- a/src/iOS/ContextMenuDelegate.cs +++ b/src/iOS/ContextMenuDelegate.cs @@ -4,8 +4,18 @@ using System.Text; using UIKit; using Foundation; +#if MAUI +using Microsoft.Maui; +using Microsoft.Maui.Controls; +using Microsoft.Maui.Controls.Platform; +using Microsoft.Maui.Controls.Compatibility; +using Microsoft.Maui.Controls.Compatibility.Platform.iOS; +using Microsoft.Maui.Controls.Internals; +using Microsoft.Maui.Handlers; +#else using Xamarin.Forms; using Xamarin.Forms.Platform.iOS; +#endif using CoreGraphics; namespace APES.UI.XF.iOS From 1fa29cf5a7c26c4d3d7708e4a640c12f258539eb Mon Sep 17 00:00:00 2001 From: Pavel Anpin <6060545+anpin@users.noreply.github.com> Date: Tue, 29 Mar 2022 10:35:52 +0400 Subject: [PATCH 02/28] ContextMenu works on windows and android, but content inside fails to render in some cases --- .../APES.UI.MAUI.Sample.csproj | 20 +- samples/APES.UI.MAUI.Sample/MainPage.xaml | 4 +- samples/APES.UI.MAUI.Sample/MauiProgram.cs | 6 +- .../Platforms/Windows/App.xaml.cs | 7 + .../Resources/Images/logo.png | Bin 0 -> 4740 bytes .../Images/outline_delete_black_24.png | Bin 0 -> 244 bytes .../Images/outline_settings_black_24.png | Bin 0 -> 1517 bytes .../ViewModels/MainViewModel.cs | 8 +- src/APES.UI.XF.csproj | 9 +- src/Droid/ContextMenuContainerRenderer.cs | 224 +++++------------- src/Shared/AssemblyConfiguration.cs | 3 +- src/Shared/ContextMenuContainerExtensions.cs | 30 +++ 12 files changed, 124 insertions(+), 187 deletions(-) create mode 100644 samples/APES.UI.MAUI.Sample/Resources/Images/logo.png create mode 100644 samples/APES.UI.MAUI.Sample/Resources/Images/outline_delete_black_24.png create mode 100644 samples/APES.UI.MAUI.Sample/Resources/Images/outline_settings_black_24.png diff --git a/samples/APES.UI.MAUI.Sample/APES.UI.MAUI.Sample.csproj b/samples/APES.UI.MAUI.Sample/APES.UI.MAUI.Sample.csproj index 97f7def..f9d3885 100644 --- a/samples/APES.UI.MAUI.Sample/APES.UI.MAUI.Sample.csproj +++ b/samples/APES.UI.MAUI.Sample/APES.UI.MAUI.Sample.csproj @@ -2,7 +2,7 @@ net6.0-android;net6.0-ios;net6.0-maccatalyst - $(TargetFrameworks);net6.0-windows10.0.19041 + $(TargetFrameworks);net6.0-windows10.0.18362 Exe APES.UI.MAUI.Sample true @@ -14,7 +14,7 @@ APES.UI.MAUI.Sample - com.companyname.apes.ui.maui.sample + com.apes.ui.maui.sample 381762DE-A6AE-4986-8C37-3A067AE03B2E @@ -27,8 +27,8 @@ 14.2 14.0 21.0 - 10.0.17763.0 - 10.0.17763.0 + 10.0.18362.0 + 10.0.18362.0 @@ -51,18 +51,24 @@ - + + + + + 6.0.200-preview.14.1092 + - - + + WinExe win10-x64 + __WINDOWS__ diff --git a/samples/APES.UI.MAUI.Sample/MainPage.xaml b/samples/APES.UI.MAUI.Sample/MainPage.xaml index 5785258..9a52b4c 100644 --- a/samples/APES.UI.MAUI.Sample/MainPage.xaml +++ b/samples/APES.UI.MAUI.Sample/MainPage.xaml @@ -19,7 +19,7 @@ - @@ -28,7 +28,7 @@ - + diff --git a/samples/APES.UI.MAUI.Sample/MauiProgram.cs b/samples/APES.UI.MAUI.Sample/MauiProgram.cs index 4ab0ca6..0cd5977 100644 --- a/samples/APES.UI.MAUI.Sample/MauiProgram.cs +++ b/samples/APES.UI.MAUI.Sample/MauiProgram.cs @@ -1,4 +1,5 @@ -namespace APES.UI.MAUI.Sample; +using APES.UI.XF; +namespace APES.UI.MAUI.Sample; public static class MauiProgram { @@ -10,7 +11,8 @@ public static MauiApp CreateMauiApp() .ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); - }); + }) + .ConfigureContextMenuContainer(); return builder.Build(); } diff --git a/samples/APES.UI.MAUI.Sample/Platforms/Windows/App.xaml.cs b/samples/APES.UI.MAUI.Sample/Platforms/Windows/App.xaml.cs index 58ea73d..c04f978 100644 --- a/samples/APES.UI.MAUI.Sample/Platforms/Windows/App.xaml.cs +++ b/samples/APES.UI.MAUI.Sample/Platforms/Windows/App.xaml.cs @@ -16,9 +16,16 @@ public partial class App : MauiWinUIApplication /// public App() { + this.UnhandledException += App_UnhandledException; this.InitializeComponent(); } + private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e) + { + APES.UI.XF.Logger.Error(e.Exception); + e.Handled = true; + } + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); } diff --git a/samples/APES.UI.MAUI.Sample/Resources/Images/logo.png b/samples/APES.UI.MAUI.Sample/Resources/Images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..8a4ee54f9efd6103777dd2a6f428956287581f85 GIT binary patch literal 4740 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 Qg@f$yboFyt=akR{05gA9!vFvP literal 0 HcmV?d00001 diff --git a/samples/APES.UI.MAUI.Sample/Resources/Images/outline_settings_black_24.png b/samples/APES.UI.MAUI.Sample/Resources/Images/outline_settings_black_24.png new file mode 100644 index 0000000000000000000000000000000000000000..df851556647c53340db0279740f5f5fd12ddb7c1 GIT binary patch literal 1517 zcmdT^`#Tc~0Nq4h%_GbskK7`kjE3&qwh5bOXe4SZ=D932nOGJJna5qoJ7JmFzNtw> zRvvK?*JB~BD6f>qkhf&_>d(06`_4H(oS)A3o$sVzT#xl=cfZC%+%@$Y^ml*TE1RU}nwG@hSb(GV;Smk<6Wh-;3-FRvhxW8+?ds#yC%s zjM6Al=-)aYe`{FVrKCM` zm!gLUwKKWB%<;v#0`JB?CIsAxR0i`8*476cNmG?^JW%L)FXh;27!SZ1_>$E6PrGrd zY?ODG&kC@`_x%)ha}ZwoJ<(+gslihJ)76Gc3>Qc+0#(5xC?i0J=;lO6%_K=Pu1k8p zP-I+@9i_Q~2n7Cp5 zvx%=p)$W0D?t#gd8tM z$BrAy*Ky4M6743g9DTWn%uzmT4Jrr{ox$tHTV-_x?yBbJ`f0z~lYOo8#t)yHc`a-_ zAj6RnPvS6H)9Z1_@mt=IWulH6y2o4pgNcpls_=}2k^hoC_byZSg zsXC;*&MXt+iuVHjeNfAFn%Y4jC2w*zKJ`N7J}~4o8`;9Lvhc~)(2)}i^BcI9Cu=s` zXa%gXNQQ7aWn$f$zD+nZN!1v5ECJ)$RV)$3!)_J6pA)m4H^M+|hahUK{uE7h!EIGJ6cy2>Ot6GVHeSN=I&Mm8xy>!T%h}DPMpo(VysM}-AAhVF+|M@^q`QHA z+bWZ>iPh2mMV>C6CN2yx+@Mby@(ELX3`=DqBx~wXluZKEjzYzv4bt;Ss8rK8#*Unq eZ{_!XY%KVGNgy+o<#xw^owlQ`EBcv@U;KZD<$M7E literal 0 HcmV?d00001 diff --git a/samples/APES.UI.Samples.Shared/ViewModels/MainViewModel.cs b/samples/APES.UI.Samples.Shared/ViewModels/MainViewModel.cs index 6e6e599..98e99c9 100644 --- a/samples/APES.UI.Samples.Shared/ViewModels/MainViewModel.cs +++ b/samples/APES.UI.Samples.Shared/ViewModels/MainViewModel.cs @@ -27,7 +27,7 @@ protected bool SetField(ref T field, T value, [System.Runtime.CompilerService NotifyPropertyChanged(propertyName); return true; } - string text = ""; + string text = "Default text"; public string Text { get => text; @@ -94,9 +94,9 @@ public MainViewModel() else if (DeviceInfo.Platform == DevicePlatform.WinUI) { - logoIconSource = @"Assets\logo.png"; - deleteIconSource = @"Assets\outline_delete_black_24.png"; - settingsIconSource = @"Assets\outline_settings_black_24.png"; + logoIconSource = @"logo.png"; + deleteIconSource = @"outline_delete_black_24.png"; + settingsIconSource = @"outline_settings_black_24.png"; } #endif diff --git a/src/APES.UI.XF.csproj b/src/APES.UI.XF.csproj index 6e6b90b..2a10719 100644 --- a/src/APES.UI.XF.csproj +++ b/src/APES.UI.XF.csproj @@ -2,9 +2,9 @@ - netstandard2.0;xamarin.ios10;xamarin.mac20;monoandroid10.0;net6.0;net6.0-android;net6.0-ios;net6.0-maccatalyst;net6.0-macos - $(TargetFrameworks);net6.0-windows10.0.19041 - $(TargetFrameworks);uap10.0.17763 + netstandard2.0;xamarin.ios10;xamarin.mac20;monoandroid10.0;net6.0;net6.0-android;net6.0-ios;net6.0-maccatalyst + + $(TargetFrameworks);net6.0-windows10.0.17763.0;uap10.0.17763 - + + diff --git a/src/Droid/ContextMenuContainerRenderer.cs b/src/Droid/ContextMenuContainerRenderer.cs index 624bdf0..7de8182 100644 --- a/src/Droid/ContextMenuContainerRenderer.cs +++ b/src/Droid/ContextMenuContainerRenderer.cs @@ -13,76 +13,38 @@ using DrawableWrapperX = AndroidX.AppCompat.Graphics.Drawable.DrawableWrapper; using Java.Lang.Reflect; #if MAUI -using Java.Interop; -using Microsoft.Maui.Controls.Compatibility.Platform.Android; using Microsoft.Maui; +//using Microsoft.Maui.Platform; using Microsoft.Maui.Controls; +using Microsoft.Maui.Controls.Platform; +using Microsoft.Maui.Controls.Compatibility; +using Microsoft.Maui.Controls.Compatibility.Platform.Android; using Microsoft.Maui.Controls.Internals; using Microsoft.Maui.Handlers; +using XView = Microsoft.Maui.Controls.View; #else using Xamarin.Forms; using Xamarin.Forms.Internals; using Xamarin.Forms.Platform.Android; +using XView = Xamarin.Forms.View; #endif using APES.UI.XF; using APES.UI.XF.Droid; using Path = System.IO.Path; using AColor = Android.Graphics.Color; -using AView = Android.Views.View; -#if !MAUI -[assembly: ExportRenderer(typeof(ContextMenuContainer), typeof(ContextMenuContainerRenderer))] -#endif +[assembly: ExportRenderer(typeof(ContextMenuContainer), typeof(ContextMenuContainerRenderer))] namespace APES.UI.XF.Droid { [Preserve(AllMembers = true)] - public class ContextMenuContainerRenderer -#if MAUI - : ViewHandler -#else - : ViewRenderer -#endif + public class ContextMenuContainerRenderer : ViewRenderer { PopupMenu? contextMenu; - -#if MAUI - public ContextMenuContainerRenderer(IPropertyMapper mapper, CommandMapper? commandMapper) : base(mapper, - commandMapper) - { - - } - - protected override AView CreatePlatformView() - { - var v = new AView(Context); - v.SetOnTouchListener(new TouchListner(() => enabled, OpenContextMenu)); - return v; - } - - public override void SetVirtualView(IView view) - { - if (VirtualView is ContextMenuContainer old) - { - old.BindingContextChanged -= Element_BindingContextChanged; - old.MenuItems.CollectionChanged -= MenuItems_CollectionChanged; - - } - - if (view is ContextMenuContainer newElement) - { - newElement.BindingContextChanged += Element_BindingContextChanged; - newElement.MenuItems.CollectionChanged += MenuItems_CollectionChanged; - - } - - base.SetVirtualView(view); - } -#else + IVisualElementRenderer? childRenderer; public ContextMenuContainerRenderer(Context context) : base(context) { } - - protected override void OnElementChanged(ElementChangedEventArgs e) + protected override void OnElementChanged(ElementChangedEventArgs e) { base.OnElementChanged(e); if (e.OldElement is ContextMenuContainer old) @@ -91,20 +53,25 @@ protected override void OnElementChanged(ElementChangedEventArgs= 21 - ? Context?.GetDrawable(id) - : Context?.GetDrawable(name); + Drawable? drawable = (int)Build.VERSION.SdkInt >= 21 ? + Context?.GetDrawable(id) : + Context?.GetDrawable(name); if (drawable != null) { var wrapper = new DrawableWrapperX(drawable); @@ -160,15 +123,9 @@ void AddMenuItem(ContextMenuItem item) } } } - void FillMenuItems() { -#if MAUI - if (VirtualView is ContextMenuContainer element) -#else if (Element is ContextMenuContainer element) -#endif - { if (element.MenuItems.Count > 0) { @@ -179,7 +136,6 @@ void FillMenuItems() } } } - void RefillMenuItems() { if (contextMenu == null) @@ -188,14 +144,9 @@ void RefillMenuItems() contextMenu.Menu.Clear(); FillMenuItems(); } - PopupMenu? GetContextMenu() { -#if MAUI - if (contextMenu != null && VirtualView is ContextMenuContainer element) -#else if (contextMenu != null && Element is ContextMenuContainer element) -#endif { if (element.MenuItems.Count != contextMenu.Menu.Size()) { @@ -213,12 +164,9 @@ void RefillMenuItems() } } } - return contextMenu; } - - private void MenuItems_CollectionChanged(object sender, - System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + private void MenuItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { RefillMenuItems(); } @@ -227,105 +175,50 @@ private void Element_BindingContextChanged(object sender, EventArgs e) { RefillMenuItems(); } - private void ContextMenu_MenuItemClick(object sender, PopupMenu.MenuItemClickEventArgs e) { -#if MAUI - var virt = ((ContextMenuContainer) VirtualView); -#else - var virt = ((ContextMenuContainer)Element); -#endif - var item = virt.MenuItems.FirstOrDefault(x => x.Text == e.Item.TitleFormatted?.ToString()); + + var item = ((ContextMenuContainer)Element).MenuItems.FirstOrDefault(x => x.Text == e.Item.TitleFormatted?.ToString()); item?.OnItemTapped(); } -#if MAUI - bool enabled => VirtualView is ContextMenuContainer element && element.MenuItems.Count > 0; -#else bool enabled => Element is ContextMenuContainer element && element.MenuItems.Count > 0; -#endif - const int LongPressWaitTime = 1500; -#if MAUI - class TouchListner : Java.Lang.Object, AView.IOnTouchListener - { - readonly Func _enabled; - readonly Action _onHold; - - MyTimer timer; - bool timerFired = false; + MyTimer? timer; + bool timerFired = false; - public TouchListner(Func enabled, Action onHold) + public override bool DispatchTouchEvent(MotionEvent e) + { + bool result; + Logger.Debug("ContextMEnuContainer DispatchTouchEvent fired {0}", e.Action); + if (enabled && e.Action == MotionEventActions.Down) { - _enabled = enabled; - _onHold = onHold; + //You can change the timespan of the long press + timerFired = false; + timer = new MyTimer(TimeSpan.FromMilliseconds(1500), () => + { + timerFired = true; + OpenContextMenu(); + }); + timer.Start(); } - public bool OnTouch(AView? v, MotionEvent? e) + if (timerFired) { - bool result = false; - Logger.Debug("TouchListner OnTouch fired {0}", e.Action); - if (_enabled() && e.Action == MotionEventActions.Down) - { - //You can change the timespan of the long press - timerFired = false; - timer = new MyTimer(TimeSpan.FromMilliseconds(LongPressWaitTime), () => - { - timerFired = true; - _onHold.Invoke(); - }); - timer.Start(); - } - - if (timerFired) - { - result = true; - } - else if (e.Action == MotionEventActions.Up || e.Action == MotionEventActions.Cancel) - { - timer?.Stop(); - result = false; - } - return result; + result = true; } - } -#else - MyTimer timer; - bool timerFired = false; - public override bool DispatchTouchEvent(MotionEvent e) - { - bool result; - Logger.Debug("ContextMenuContainer DispatchTouchEvent fired {0}", e.Action); - if (enabled && e.Action == MotionEventActions.Down) - { - //You can change the timespan of the long press - timerFired = false; - timer = new MyTimer(TimeSpan.FromMilliseconds(LongPressWaitTime), () => - { - timerFired = true; - OpenContextMenu(); - }); - timer.Start(); - } - - if (timerFired) + else if (e.Action == MotionEventActions.Up || e.Action == MotionEventActions.Cancel) + { + timer?.Stop(); + result = base.DispatchTouchEvent(e); + } + else + { + result = base.DispatchTouchEvent(e); + if (!result && enabled) { result = true; } - else if (e.Action == MotionEventActions.Up || e.Action == MotionEventActions.Cancel) - { - timer?.Stop(); - result = base.DispatchTouchEvent(e); - } - else - { - result = base.DispatchTouchEvent(e); - if (!result && enabled) - { - result = true; - } - } - - return result; } -#endif + return result; + } void OpenContextMenu() { if (GetContextMenu() == null) @@ -334,10 +227,8 @@ void OpenContextMenu() FillMenuItems(); } - contextMenu?.Show(); } - class MyTimer { private readonly TimeSpan timespan; @@ -351,7 +242,6 @@ public MyTimer(TimeSpan timespan, Action callback) this.callback = callback; this.cancellation = new CancellationTokenSource(); } - public void Start() { CancellationTokenSource cts = this.cancellation; // safe copy diff --git a/src/Shared/AssemblyConfiguration.cs b/src/Shared/AssemblyConfiguration.cs index 1720602..de4a350 100644 --- a/src/Shared/AssemblyConfiguration.cs +++ b/src/Shared/AssemblyConfiguration.cs @@ -7,4 +7,5 @@ #endif [assembly: XmlnsDefinition("http://apes.ge", "APES.UI.XF")] [assembly: XmlnsPrefix("http://apes.ge", "apes")] -[assembly: InternalsVisibleTo("APES.UI.XF.Tests")] \ No newline at end of file +[assembly: InternalsVisibleTo("APES.UI.XF.Tests")] +[assembly: InternalsVisibleTo("APES.UI.MAUI.Sample")] \ No newline at end of file diff --git a/src/Shared/ContextMenuContainerExtensions.cs b/src/Shared/ContextMenuContainerExtensions.cs index 02799db..a5eb789 100644 --- a/src/Shared/ContextMenuContainerExtensions.cs +++ b/src/Shared/ContextMenuContainerExtensions.cs @@ -1,9 +1,39 @@ using System; using System.Collections.Generic; +#if MAUI +using Microsoft.Maui.Hosting; +using Microsoft.Maui.Controls.Compatibility; +#if __ANDROID__ +using APES.UI.XF.Droid; +#elif __IOS__ || __MACCATALYST__ +using APES.UI.XF.iOS; +#elif NET6_0_WINDOWS10_0_17763_0 +using APES.UI.XF.UWP; +#endif +#endif + namespace APES.UI.XF { public static class ContextMenuContainerExtensions { public static bool HasMenuOptions(this ContextMenuContainer container) => container.MenuItems.Count > 0; +#if MAUI && (__ANDROID__ || __IOS__ || __MACCATALYST__ || NET6_0_WINDOWS10_0_17763_0) + public static MauiAppBuilder ConfigureContextMenuContainer(this MauiAppBuilder mauiAppBuilder) + { + + return mauiAppBuilder.ConfigureMauiHandlers(handlers => + { + +#if __ANDROID__ + handlers.AddCompatibilityRenderer(); + +#elif __IOS__ || __MACCATALYST__ + handlers.AddCompatibilityRenderer(); +#elif NET6_0_WINDOWS10_0_17763_0 + handlers.AddCompatibilityRenderer(); +#endif + }); + } +#endif } } \ No newline at end of file From 887e671fb3a8669742cd5ac5cdd72baf056b6a48 Mon Sep 17 00:00:00 2001 From: Pavel Anpin Date: Tue, 29 Mar 2022 03:57:05 -0700 Subject: [PATCH 03/28] Works on iOS, but on catalyst throws exception when opening menu possibly due to icon file path --- .../APES.UI.MAUI.Sample.csproj | 28 +++++++--- .../ViewModels/MainViewModel.cs | 38 ++++++------- src/Shared/ContextMenuContainerExtensions.cs | 3 +- src/iOS/ContextMenuContainerRenderer.cs | 54 ++++++++++++++++++- 4 files changed, 94 insertions(+), 29 deletions(-) diff --git a/samples/APES.UI.MAUI.Sample/APES.UI.MAUI.Sample.csproj b/samples/APES.UI.MAUI.Sample/APES.UI.MAUI.Sample.csproj index f9d3885..a393f07 100644 --- a/samples/APES.UI.MAUI.Sample/APES.UI.MAUI.Sample.csproj +++ b/samples/APES.UI.MAUI.Sample/APES.UI.MAUI.Sample.csproj @@ -25,12 +25,31 @@ True 14.2 + ios-arm64 14.0 21.0 10.0.18362.0 10.0.18362.0 - + + WinExe + win10-x64 + __WINDOWS__ + + + true + full + false + True + + + SdkOnly + -v -v -v -v + true + true + true + true + @@ -64,11 +83,4 @@ - - - WinExe - win10-x64 - __WINDOWS__ - - diff --git a/samples/APES.UI.Samples.Shared/ViewModels/MainViewModel.cs b/samples/APES.UI.Samples.Shared/ViewModels/MainViewModel.cs index 98e99c9..decbc80 100644 --- a/samples/APES.UI.Samples.Shared/ViewModels/MainViewModel.cs +++ b/samples/APES.UI.Samples.Shared/ViewModels/MainViewModel.cs @@ -76,28 +76,28 @@ public MainViewModel() break; } #else - if (DeviceInfo.Platform == DevicePlatform.Android) - { - logoIconSource = "logo.png"; - deleteIconSource = "outline_delete_24.xml"; - settingsIconSource = "outline_settings_black_24.png"; - } - else if (DeviceInfo.Platform == DevicePlatform.iOS || - DeviceInfo.Platform == DevicePlatform.macOS || - DeviceInfo.Platform == DevicePlatform.MacCatalyst) - { - + // + // { + // logoIconSource = "logo.png"; + // deleteIconSource = "outline_delete_24.xml"; + // settingsIconSource = "outline_settings_black_24.png"; + // } + // else if (DeviceInfo.Platform == DevicePlatform.iOS || + // DeviceInfo.Platform == DevicePlatform.macOS || + // DeviceInfo.Platform == DevicePlatform.MacCatalyst) + // { + // + // logoIconSource = "logo.png"; + // deleteIconSource = "outline_delete_black_24.png"; + // settingsIconSource = "outline_settings_black_24.png"; + // } + // + // else if (DeviceInfo.Platform == DevicePlatform.WinUI) + // { logoIconSource = "logo.png"; deleteIconSource = "outline_delete_black_24.png"; settingsIconSource = "outline_settings_black_24.png"; - } - - else if (DeviceInfo.Platform == DevicePlatform.WinUI) - { - logoIconSource = @"logo.png"; - deleteIconSource = @"outline_delete_black_24.png"; - settingsIconSource = @"outline_settings_black_24.png"; - } + // } #endif FillAllImageActions(); diff --git a/src/Shared/ContextMenuContainerExtensions.cs b/src/Shared/ContextMenuContainerExtensions.cs index a5eb789..78c8145 100644 --- a/src/Shared/ContextMenuContainerExtensions.cs +++ b/src/Shared/ContextMenuContainerExtensions.cs @@ -28,7 +28,8 @@ public static MauiAppBuilder ConfigureContextMenuContainer(this MauiAppBuilder m handlers.AddCompatibilityRenderer(); #elif __IOS__ || __MACCATALYST__ - handlers.AddCompatibilityRenderer(); + //handlers.AddCompatibilityRenderer(); + handlers.AddHandler(); #elif NET6_0_WINDOWS10_0_17763_0 handlers.AddCompatibilityRenderer(); #endif diff --git a/src/iOS/ContextMenuContainerRenderer.cs b/src/iOS/ContextMenuContainerRenderer.cs index 94374cc..b27e5fc 100644 --- a/src/iOS/ContextMenuContainerRenderer.cs +++ b/src/iOS/ContextMenuContainerRenderer.cs @@ -7,9 +7,12 @@ using Microsoft.Maui.Controls; using Microsoft.Maui.Controls.Platform; using Microsoft.Maui.Controls.Compatibility; +using Microsoft.Maui.Controls.Compatibility.Platform; using Microsoft.Maui.Controls.Compatibility.Platform.iOS; using Microsoft.Maui.Controls.Internals; using Microsoft.Maui.Handlers; +using Microsoft.Maui.Platform; +using ContentView = Microsoft.Maui.Platform.ContentView; #else using Xamarin.Forms; using Xamarin.Forms.Internals; @@ -17,14 +20,51 @@ #endif using APES.UI.XF; using APES.UI.XF.iOS; +using Microsoft.Maui.Platform; +#if !MAUI [assembly: ExportRenderer(typeof(ContextMenuContainer), typeof(ContextMenuContainerRenderer))] +#endif namespace APES.UI.XF.iOS { [Preserve(AllMembers = true)] + #if MAUI + class ContextMenuContainerRenderer : ContentViewHandler //ViewHandler +#else class ContextMenuContainerRenderer : ViewRenderer +#endif { ContextMenuDelegate? contextMenuDelegate; UIContextMenuInteraction? contextMenu; + #if MAUI + //TODO: not sure if this is really needed, will test when debug is available for MAUI + bool wasSetOnce = false; + public override void SetVirtualView(IView view) + { + if (wasSetOnce) + { + var old = VirtualView as ContextMenuContainer; + old.BindingContextChanged -= Element_BindingContextChanged; + old.MenuItems.CollectionChanged -= MenuItems_CollectionChanged; + } + + base.SetVirtualView(view); + + if (VirtualView is ContextMenuContainer newElement) + { + newElement.BindingContextChanged += Element_BindingContextChanged; + newElement.MenuItems.CollectionChanged += MenuItems_CollectionChanged; + + //PlatformView.AddSubview(newElement.Content.ToContainerView(MauiContext)); + //SetContent(); + //PlatformView.View = newElement; + RefillMenuItems(); + wasSetOnce = true; + } + } + + UIView Control => PlatformView; + UITraitCollection TraitCollection => UITraitCollection.CurrentTraitCollection; +#else protected override void OnElementChanged(ElementChangedEventArgs e) { base.OnElementChanged(e); @@ -36,11 +76,12 @@ protected override void OnElementChanged(ElementChangedEventArgs constructInteraction(((ContextMenuContainer)VirtualView).MenuItems); + private void MenuItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + RefillMenuItems(); + } + + private void Element_BindingContextChanged(object sender, EventArgs e) + { + RefillMenuItems(); + } } } From c0eeee42314e41be82a2222b14fdcf395cf71fb2 Mon Sep 17 00:00:00 2001 From: Pavel Anpin Date: Wed, 30 Mar 2022 12:39:59 +0400 Subject: [PATCH 04/28] Fixed issue on xamarin.ios target --- src/iOS/ContextMenuContainerRenderer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/iOS/ContextMenuContainerRenderer.cs b/src/iOS/ContextMenuContainerRenderer.cs index b27e5fc..b80fd4c 100644 --- a/src/iOS/ContextMenuContainerRenderer.cs +++ b/src/iOS/ContextMenuContainerRenderer.cs @@ -20,7 +20,6 @@ #endif using APES.UI.XF; using APES.UI.XF.iOS; -using Microsoft.Maui.Platform; #if !MAUI [assembly: ExportRenderer(typeof(ContextMenuContainer), typeof(ContextMenuContainerRenderer))] #endif @@ -81,6 +80,7 @@ protected override void OnElementChanged(ElementChangedEventArgs Element; #endif void deconstructIntercation() { From 9a6ed29e008cb91d87572dd296306c59950c8958 Mon Sep 17 00:00:00 2001 From: Pavel Anpin Date: Thu, 28 Apr 2022 15:00:31 +0400 Subject: [PATCH 05/28] feat: fixed Windows targets --- src/APES.UI.XF.csproj | 4 ++++ src/Shared/ContextMenuContainerExtensions.cs | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/APES.UI.XF.csproj b/src/APES.UI.XF.csproj index 2a10719..ee4a4a5 100644 --- a/src/APES.UI.XF.csproj +++ b/src/APES.UI.XF.csproj @@ -69,6 +69,10 @@ $(DefineConstants);MAUI true + + + $(DefineConstants);__WINDOWS__ + diff --git a/src/Shared/ContextMenuContainerExtensions.cs b/src/Shared/ContextMenuContainerExtensions.cs index 78c8145..6c3fad7 100644 --- a/src/Shared/ContextMenuContainerExtensions.cs +++ b/src/Shared/ContextMenuContainerExtensions.cs @@ -7,7 +7,7 @@ using APES.UI.XF.Droid; #elif __IOS__ || __MACCATALYST__ using APES.UI.XF.iOS; -#elif NET6_0_WINDOWS10_0_17763_0 +#elif __WINDOWS__ using APES.UI.XF.UWP; #endif #endif @@ -17,7 +17,7 @@ namespace APES.UI.XF public static class ContextMenuContainerExtensions { public static bool HasMenuOptions(this ContextMenuContainer container) => container.MenuItems.Count > 0; -#if MAUI && (__ANDROID__ || __IOS__ || __MACCATALYST__ || NET6_0_WINDOWS10_0_17763_0) +#if MAUI && (__ANDROID__ || __IOS__ || __MACCATALYST__ || __WINDOWS__) public static MauiAppBuilder ConfigureContextMenuContainer(this MauiAppBuilder mauiAppBuilder) { @@ -30,7 +30,7 @@ public static MauiAppBuilder ConfigureContextMenuContainer(this MauiAppBuilder m #elif __IOS__ || __MACCATALYST__ //handlers.AddCompatibilityRenderer(); handlers.AddHandler(); -#elif NET6_0_WINDOWS10_0_17763_0 +#elif __WINDOWS__ handlers.AddCompatibilityRenderer(); #endif }); From 13d037594af30c84eb5eef812c62eafdb65ae8ab Mon Sep 17 00:00:00 2001 From: Pavel Anpin Date: Thu, 12 May 2022 12:46:03 +0400 Subject: [PATCH 06/28] dep: WinUI bump to 1.0.3 --- src/APES.UI.XF.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/APES.UI.XF.csproj b/src/APES.UI.XF.csproj index ee4a4a5..ef0c7f8 100644 --- a/src/APES.UI.XF.csproj +++ b/src/APES.UI.XF.csproj @@ -98,7 +98,7 @@ - + From 64538a216b0b5d3b9902840d01bd00b8ccb61418 Mon Sep 17 00:00:00 2001 From: Pavel Anpin Date: Thu, 12 May 2022 12:46:49 +0400 Subject: [PATCH 07/28] feat: fixed compilation for net6.0 target --- src/Shared/ContextMenuContainerExtensions.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Shared/ContextMenuContainerExtensions.cs b/src/Shared/ContextMenuContainerExtensions.cs index 6c3fad7..6815b11 100644 --- a/src/Shared/ContextMenuContainerExtensions.cs +++ b/src/Shared/ContextMenuContainerExtensions.cs @@ -3,6 +3,7 @@ #if MAUI using Microsoft.Maui.Hosting; using Microsoft.Maui.Controls.Compatibility; +using Microsoft.Maui.Controls.Compatibility.Hosting; #if __ANDROID__ using APES.UI.XF.Droid; #elif __IOS__ || __MACCATALYST__ @@ -17,7 +18,7 @@ namespace APES.UI.XF public static class ContextMenuContainerExtensions { public static bool HasMenuOptions(this ContextMenuContainer container) => container.MenuItems.Count > 0; -#if MAUI && (__ANDROID__ || __IOS__ || __MACCATALYST__ || __WINDOWS__) +#if MAUI public static MauiAppBuilder ConfigureContextMenuContainer(this MauiAppBuilder mauiAppBuilder) { From d6b3e0bd2f68cbea6bad92302cfbae4fdefe7e0d Mon Sep 17 00:00:00 2001 From: Pavel Anpin Date: Mon, 16 May 2022 16:26:28 +0400 Subject: [PATCH 08/28] feat: using compatability handlers by default --- src/Shared/ContextMenuContainerExtensions.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Shared/ContextMenuContainerExtensions.cs b/src/Shared/ContextMenuContainerExtensions.cs index 78c8145..9db347e 100644 --- a/src/Shared/ContextMenuContainerExtensions.cs +++ b/src/Shared/ContextMenuContainerExtensions.cs @@ -5,10 +5,12 @@ using Microsoft.Maui.Controls.Compatibility; #if __ANDROID__ using APES.UI.XF.Droid; +using Microsoft.Maui.Controls.Compatibility.Hosting; #elif __IOS__ || __MACCATALYST__ using APES.UI.XF.iOS; #elif NET6_0_WINDOWS10_0_17763_0 using APES.UI.XF.UWP; +using Microsoft.Maui.Controls.Compatibility.Hosting; #endif #endif @@ -17,10 +19,13 @@ namespace APES.UI.XF public static class ContextMenuContainerExtensions { public static bool HasMenuOptions(this ContextMenuContainer container) => container.MenuItems.Count > 0; -#if MAUI && (__ANDROID__ || __IOS__ || __MACCATALYST__ || NET6_0_WINDOWS10_0_17763_0) +#if MAUI public static MauiAppBuilder ConfigureContextMenuContainer(this MauiAppBuilder mauiAppBuilder) { - + +#if __ANDROID__ || NET6_0_WINDOWS10_0_17763_0 + mauiAppBuilder.UseMauiCompatibility(); +#endif return mauiAppBuilder.ConfigureMauiHandlers(handlers => { @@ -28,8 +33,7 @@ public static MauiAppBuilder ConfigureContextMenuContainer(this MauiAppBuilder m handlers.AddCompatibilityRenderer(); #elif __IOS__ || __MACCATALYST__ - //handlers.AddCompatibilityRenderer(); - handlers.AddHandler(); + handlers.AddHandler(); #elif NET6_0_WINDOWS10_0_17763_0 handlers.AddCompatibilityRenderer(); #endif @@ -37,4 +41,4 @@ public static MauiAppBuilder ConfigureContextMenuContainer(this MauiAppBuilder m } #endif } -} \ No newline at end of file +} From 1c98e4ad03b0717a3ec7a6553ba0d3f304a05c70 Mon Sep 17 00:00:00 2001 From: Pavel Anpin Date: Mon, 6 Jun 2022 11:24:30 +0400 Subject: [PATCH 09/28] feat: removed xamarin targets temporarly --- src/APES.UI.XF.csproj | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/APES.UI.XF.csproj b/src/APES.UI.XF.csproj index 2a10719..fb5b63f 100644 --- a/src/APES.UI.XF.csproj +++ b/src/APES.UI.XF.csproj @@ -1,8 +1,8 @@ - + - netstandard2.0;xamarin.ios10;xamarin.mac20;monoandroid10.0;net6.0;net6.0-android;net6.0-ios;net6.0-maccatalyst + net6.0;net6.0-android;net6.0-ios;net6.0-maccatalyst $(TargetFrameworks);net6.0-windows10.0.17763.0;uap10.0.17763 @@ -49,6 +49,12 @@ true enable + + 14.1 + 14.0 + 31.0 + 10.0.19041.0 + 10.0.19041.0 @@ -94,7 +100,7 @@ - + From ae21b0313e507bd878db3a9393a63fbc432cafeb Mon Sep 17 00:00:00 2001 From: Pavel Anpin Date: Tue, 7 Jun 2022 17:58:39 +0400 Subject: [PATCH 10/28] feat: dotfiles --- .editorconfig | 191 ++++++++++++++++++++++++++++++++++++++++ .stylecop.json | 20 +++++ .stylecop.ruleset | 217 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 428 insertions(+) create mode 100644 .editorconfig create mode 100644 .stylecop.json create mode 100644 .stylecop.ruleset diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..63dc37a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,191 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +charset = utf-8 +trim_trailing_whitespace = false +insert_final_newline = true +end_of_line = lf + +# Visual Studio Solution Files +[*.sln] +indent_style = tab + +# Markdown Files +[*.md] +trim_trailing_whitespace = false + +# Batch Files +[*.{cmd,bat}] +end_of_line = crlf + +# .NET Source files +[*.{cs,csx,cake,vb}] +indent_size = 4 + +dotnet_style_qualification_for_field = false:warning +dotnet_style_qualification_for_property = false:warning +dotnet_style_qualification_for_method = false:warning +dotnet_style_qualification_for_event = false:warning + +dotnet_style_predefined_type_for_locals_parameters_members = true:warning +dotnet_style_predefined_type_for_member_access = true:warning + +dotnet_style_require_accessibility_modifiers = always:warning +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async +dotnet_style_readonly_field = true:warning + +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion + +dotnet_style_object_initializer = true:warning +dotnet_style_collection_initializer = true:warning +dotnet_style_explicit_tuple_names = true:warning +dotnet_style_prefer_inferred_tuple_names = true:warning +dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning +dotnet_style_prefer_auto_properties = true:warning +dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_return = true:suggestion +dotnet_style_prefer_compound_assignment = true:warning + +dotnet_style_coalesce_expression = true:warning +dotnet_style_null_propagation = true:warning +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning + +file_header_template = unset + +csharp_style_var_for_built_in_types = false:suggestion +csharp_style_var_when_type_is_apparent = true:warning +csharp_style_var_elsewhere = true:silent + +csharp_style_expression_bodied_methods = true:warning +csharp_style_expression_bodied_constructors = true:warning +csharp_style_expression_bodied_operators = true:warning +csharp_style_expression_bodied_properties = true:warning +csharp_style_expression_bodied_indexers = true:warning +csharp_style_expression_bodied_accessors = true:warning +csharp_style_expression_bodied_lambdas = true:warning +csharp_style_expression_bodied_local_functions = true:warning + +csharp_style_pattern_matching_over_is_with_cast_check = true:warning +csharp_style_pattern_matching_over_as_with_null_check = true:warning +csharp_style_prefer_switch_expression = true:warning +csharp_style_prefer_pattern_matching = true:warning +csharp_style_prefer_not_pattern = true:warning + +csharp_style_inlined_variable_declaration = true:warning +csharp_prefer_simple_default_expression = true:warning +csharp_style_pattern_local_over_anonymous_function = true:warning +csharp_style_deconstructed_variable_declaration = true:warning +csharp_style_prefer_index_operator = true:warning +csharp_style_prefer_range_operator = true:warning +csharp_style_implicit_object_creation_when_type_is_apparent = true:warning + +csharp_style_throw_expression = true:warning +csharp_style_conditional_delegate_call = true:warning + +csharp_prefer_braces = when_multiline:warning +csharp_prefer_simple_using_statement = true:warning + +csharp_using_directive_placement = outside_namespace +csharp_style_namespace_declarations = file_scoped + +csharp_prefer_static_local_function = true:warning + +csharp_style_unused_value_expression_statement_preference = discard_variable:silent +csharp_style_unused_value_assignment_preference = discard_variable:suggestion +dotnet_code_quality_unused_parameters = all:suggestion +dotnet_remove_unnecessary_suppression_exclusions = none + +dotnet_sort_system_directives_first = true +dotnet_separate_import_directive_groups = false + +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +csharp_indent_case_contents = true +csharp_indent_switch_labels = true +csharp_indent_labels = one_less_than_current +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents_when_block = false + +csharp_space_after_cast = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_around_binary_operators = before_and_after +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_after_comma = true +csharp_space_before_comma = false +csharp_space_after_dot = false +csharp_space_before_dot = false +csharp_space_after_semicolon_in_for_statement = true +csharp_space_before_semicolon_in_for_statement = false +csharp_space_around_declaration_statements = false +csharp_space_before_open_square_brackets = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_square_brackets = false + +csharp_preserve_single_line_statements = false +csharp_preserve_single_line_blocks = true + + +dotnet_naming_style.camel_case.capitalization = camel_case +dotnet_naming_style.pascal_case.capitalization = pascal_case +dotnet_naming_style.under_scored.capitalization = camel_case +dotnet_naming_style.under_scored.required_prefix = _ +dotnet_naming_style.pascal_case_with_prefix_i.capitalization = pascal_case +dotnet_naming_style.pascal_case_with_prefix_i.required_prefix = I + +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.required_modifiers = const +dotnet_naming_rule.constant_fields_must_be_pascal_case.severity = warning +dotnet_naming_rule.constant_fields_must_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_must_be_pascal_case.style = pascal_case + +dotnet_naming_symbols.public_fields.applicable_accessibilities = public, internal +dotnet_naming_symbols.public_fields.applicable_kinds = field +dotnet_naming_rule.public_fields_must_be_pascal_case.severity = warning +dotnet_naming_rule.public_fields_must_be_pascal_case.symbols = public_fields +dotnet_naming_rule.public_fields_must_be_pascal_case.style = pascal_case + +dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected, protected_internal +dotnet_naming_symbols.private_fields.applicable_kinds = field +dotnet_naming_rule.private_fields_must_be_camel_case.severity = warning +dotnet_naming_rule.private_fields_must_be_camel_case.symbols = private_fields +dotnet_naming_rule.private_fields_must_be_camel_case.style = under_scored + +dotnet_naming_symbols.public_members.applicable_accessibilities = public, internal, protected, protected_internal +dotnet_naming_symbols.public_members.applicable_kinds = method, property, event, delegate +dotnet_naming_rule.public_members_must_be_capitalized.severity = warning +dotnet_naming_rule.public_members_must_be_capitalized.symbols = public_members +dotnet_naming_rule.public_members_must_be_capitalized.style = pascal_case + +dotnet_naming_symbols.parameters.applicable_kinds = parameter +dotnet_naming_rule.parameters_must_be_camel_case.severity = warning +dotnet_naming_rule.parameters_must_be_camel_case.symbols = parameters +dotnet_naming_rule.parameters_must_be_camel_case.style = camel_case + +dotnet_naming_symbols.non_interface_types.applicable_kinds = class, struct, enum, delegate +dotnet_naming_rule.non_interface_types_must_be_pascal_case.severity = warning +dotnet_naming_rule.non_interface_types_must_be_pascal_case.symbols = non_interface_types +dotnet_naming_rule.non_interface_types_must_be_pascal_case.style = pascal_case + +dotnet_naming_symbols.interface_types.applicable_kinds = interface +dotnet_naming_rule.interface_types_must_be_prefixed_with_i.severity = warning +dotnet_naming_rule.interface_types_must_be_prefixed_with_i.symbols = interface_types +dotnet_naming_rule.interface_types_must_be_prefixed_with_i.style = pascal_case_with_prefix_i diff --git a/.stylecop.json b/.stylecop.json new file mode 100644 index 0000000..16ed402 --- /dev/null +++ b/.stylecop.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + "settings": { + "indentation": { + "indentationSize": 4, + "tabSize": 4, + "useTabs": false + }, + "orderingRules": { + "usingDirectivesPlacement": "outsideNamespace", + "blankLinesBetweenUsingGroups": "allow", + "systemUsingDirectivesFirst": true + }, + "documentationRules": { + "xmlHeader": false, + "companyName": "Brightlink AV LTD", + "copyrightText": "Copyright © {companyName} All rights reserved." + } + } +} diff --git a/.stylecop.ruleset b/.stylecop.ruleset new file mode 100644 index 0000000..fe5980e --- /dev/null +++ b/.stylecop.ruleset @@ -0,0 +1,217 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From dc8640a03ef93844acc6e26f0b891bd6df8484c5 Mon Sep 17 00:00:00 2001 From: Pavel Anpin Date: Wed, 8 Jun 2022 08:17:47 +0400 Subject: [PATCH 11/28] feat: added MAUI Android handler --- .../APES.UI.MAUI.Sample.csproj | 8 +- .../ViewModels/MainViewModel.cs | 3 +- src/APES.UI.XF.csproj | 27 +- src/Droid/ContextMenuContainerRenderer.cs | 407 ++++++++++++------ src/Shared/ContextMenuContainer.cs | 2 +- src/Shared/ContextMenuContainerExtensions.cs | 9 +- src/Shared/ContextMenuContainerRenderer.cs | 56 +++ src/iOS/ContextMenuContainerRenderer.cs | 57 +-- 8 files changed, 363 insertions(+), 206 deletions(-) create mode 100644 src/Shared/ContextMenuContainerRenderer.cs diff --git a/samples/APES.UI.MAUI.Sample/APES.UI.MAUI.Sample.csproj b/samples/APES.UI.MAUI.Sample/APES.UI.MAUI.Sample.csproj index a393f07..2123d0a 100644 --- a/samples/APES.UI.MAUI.Sample/APES.UI.MAUI.Sample.csproj +++ b/samples/APES.UI.MAUI.Sample/APES.UI.MAUI.Sample.csproj @@ -1,4 +1,4 @@ - + net6.0-android;net6.0-ios;net6.0-maccatalyst @@ -70,13 +70,13 @@ - + - + diff --git a/samples/APES.UI.Samples.Shared/ViewModels/MainViewModel.cs b/samples/APES.UI.Samples.Shared/ViewModels/MainViewModel.cs index decbc80..efadc93 100644 --- a/samples/APES.UI.Samples.Shared/ViewModels/MainViewModel.cs +++ b/samples/APES.UI.Samples.Shared/ViewModels/MainViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Text; using System.Threading.Tasks; @@ -7,7 +7,6 @@ #if MAUI using Microsoft.Maui; using Microsoft.Maui.Controls; -using Microsoft.Maui.Essentials; #else using Xamarin.Forms; #endif diff --git a/src/APES.UI.XF.csproj b/src/APES.UI.XF.csproj index ef0c7f8..7e18b25 100644 --- a/src/APES.UI.XF.csproj +++ b/src/APES.UI.XF.csproj @@ -1,23 +1,25 @@ - + - netstandard2.0;xamarin.ios10;xamarin.mac20;monoandroid10.0;net6.0;net6.0-android;net6.0-ios;net6.0-maccatalyst + + netstandard2.0;monoandroid10.0;net6.0;net6.0-android;net6.0-ios;net6.0-maccatalyst - $(TargetFrameworks);net6.0-windows10.0.17763.0;uap10.0.17763 + $(TargetFrameworks);net6.0-windows10.0.17763.0;uap10.0.17763 + 14.2 + 14.0 + 21.0 + 10.0.17763.0 + 10.0.17763.0 - APES.UI.XF APES.UI.XF ContextMenuContainer True $(AssemblyName) ($(TargetFramework)) - 1.0.8.0 - 1.0.8.0 + 1.1.0.0 + 1.1.0.0 1.1.0.0 1.1.0.0 true @@ -88,7 +90,7 @@ - + - + - + @@ -135,6 +137,7 @@ + diff --git a/src/Droid/ContextMenuContainerRenderer.cs b/src/Droid/ContextMenuContainerRenderer.cs index 7de8182..54e9c53 100644 --- a/src/Droid/ContextMenuContainerRenderer.cs +++ b/src/Droid/ContextMenuContainerRenderer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Threading; using Android.App; @@ -9,41 +9,82 @@ using Android.Text; using Android.Text.Style; using Android.Graphics.Drawables; +using Android.Runtime; +using Android.Util; using AndroidX.AppCompat.Widget; using DrawableWrapperX = AndroidX.AppCompat.Graphics.Drawable.DrawableWrapper; using Java.Lang.Reflect; #if MAUI -using Microsoft.Maui; -//using Microsoft.Maui.Platform; +using Microsoft.Maui.Platform; using Microsoft.Maui.Controls; -using Microsoft.Maui.Controls.Platform; -using Microsoft.Maui.Controls.Compatibility; -using Microsoft.Maui.Controls.Compatibility.Platform.Android; -using Microsoft.Maui.Controls.Internals; using Microsoft.Maui.Handlers; -using XView = Microsoft.Maui.Controls.View; +using JetBrains.Annotations; #else using Xamarin.Forms; using Xamarin.Forms.Internals; using Xamarin.Forms.Platform.Android; +using APES.UI.XF.Droid; using XView = Xamarin.Forms.View; +using PreserveAttribute = Xamarin.Forms.Internals.PreserveAttribute; #endif using APES.UI.XF; -using APES.UI.XF.Droid; using Path = System.IO.Path; using AColor = Android.Graphics.Color; - +#if MAUI +namespace APES.UI.XF +#else [assembly: ExportRenderer(typeof(ContextMenuContainer), typeof(ContextMenuContainerRenderer))] namespace APES.UI.XF.Droid +#endif { +#if MAUI + sealed partial class ContextMenuContainerHandler : ContentViewHandler +#else [Preserve(AllMembers = true)] - public class ContextMenuContainerRenderer : ViewRenderer + class ContextMenuContainerRenderer : ViewRenderer +#endif + { - PopupMenu? contextMenu; - IVisualElementRenderer? childRenderer; +#if MAUI + //private IContentView Element => VirtualView; + + void constructInteraction(ContextMenuContainer menuItems) + { + ((ContainerViewGroup)PlatformView).SetupMenu(menuItems); + //deconstructIntercation(); + //if (menuItems?.Count > 0) + //{ + // foreach (var item in menuItems) + // { + // AddMenuItem(item); + // } + //} + } + + protected override ContentViewGroup CreatePlatformView() + { + if (VirtualView == null) + { + throw new InvalidOperationException($"{nameof(VirtualView)} must be set to create a ContentViewGroup"); + } + if (VirtualView is not ContextMenuContainer) + { + throw new InvalidOperationException($"{nameof(VirtualView)} must be of type ContextMenuContainer, but was {VirtualView.GetType()} "); + } + + var viewGroup = new ContainerViewGroup(Context); + //{ + // CrossPlatformMeasure = VirtualView.CrossPlatformMeasure, + // CrossPlatformArrange = VirtualView.CrossPlatformArrange + //}; + return viewGroup; + } +#else public ContextMenuContainerRenderer(Context context) : base(context) { + } + protected override void OnElementChanged(ElementChangedEventArgs e) { base.OnElementChanged(e); @@ -54,181 +95,261 @@ protected override void OnElementChanged(ElementChangedEventArgs e) } if (e.NewElement is ContextMenuContainer newElement) { - #if MAUI - if(Control == null) - { - //var m = newElement.Content .ToHandler(this.MauiContext); - childRenderer = newElement.Content.GetRenderer() ?? Platform.CreateRendererWithContext(newElement.Content, Context); - //Platform.SetRenderer(newElement.Content, childRenderer); - SetNativeControl(childRenderer.View); - } - #endif newElement.BindingContextChanged += Element_BindingContextChanged; newElement.MenuItems.CollectionChanged += MenuItems_CollectionChanged; } } + void constructInteraction(ContextMenuContainer container) + { + deconstructIntercation(); + if (container.MenuItems?.Count > 0) + { + foreach (var item in container.MenuItems) + { + AddMenuItem(item); + } + } + } - - void ConstructNativeMenu() + void RefillMenuItems() { - var child = GetChildAt(0); - if (child == null) - return; - contextMenu = new PopupMenu(Context, child); - contextMenu.MenuItemClick += ContextMenu_MenuItemClick; - Field field = contextMenu.Class.GetDeclaredField("mPopup"); - field.Accessible = true; - Java.Lang.Object? menuPopupHelper = field.Get(contextMenu); - Method? setForceIcons = menuPopupHelper?.Class.GetDeclaredMethod("setForceShowIcon", Java.Lang.Boolean.Type); - setForceIcons?.Invoke(menuPopupHelper, true); + if (Element is ContextMenuContainer container) + { + constructInteraction(container); + } } - void DeconstructNativeMenu() + void MenuItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { - if (contextMenu == null) - return; - contextMenu.MenuItemClick -= ContextMenu_MenuItemClick; - contextMenu.Dispose(); - contextMenu = null; + RefillMenuItems(); } - void AddMenuItem(ContextMenuItem item) + + void Element_BindingContextChanged(object sender, EventArgs e) { - if (contextMenu == null) - return; - var title = new SpannableString(item.Text); - if (item.IsDestructive) - title.SetSpan(new ForegroundColorSpan(AColor.Red), 0, title.Length(), 0); - var contextAction = contextMenu.Menu.Add(title); - if (contextAction == null) + RefillMenuItems(); + } +#endif + + +#if MAUI + + class ContainerViewGroup : ContentViewGroup + { + + + private ContextMenuContainer? Element; +#endif + + PopupMenu? contextMenu; + MyTimer? timer; + bool timerFired = false; + + private bool enabled => Element is ContextMenuContainer element && element.MenuItems.Count > 0; +#if MAUI + public ContainerViewGroup([NotNull] Context context) : base(context) { - Logger.Error("We couldn't create IMenuItem with title {0}", item.Text); - return; + } - contextAction.SetEnabled(item.IsEnabled); - if (item.Icon != null) + + public ContainerViewGroup(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer) { - var name = Path.GetFileNameWithoutExtension(item.Icon.File); - var id = Context.GetDrawableId(name); - if (id != 0) + } + + public ContainerViewGroup([NotNull] Context context, [NotNull] IAttributeSet attrs) : base(context, attrs) + { + } + + public ContainerViewGroup([NotNull] Context context, [NotNull] IAttributeSet attrs, int defStyleAttr) : + base(context, attrs, defStyleAttr) + { + } + + public ContainerViewGroup([NotNull] Context context, [NotNull] IAttributeSet attrs, int defStyleAttr, + int defStyleRes) : base(context, attrs, defStyleAttr, defStyleRes) + { + } + + public void SetupMenu(ContextMenuContainer? container) + { + deconstructIntercation(); + Element = container; + } +#endif + + + void deconstructIntercation() + { + if (Element != null && contextMenu != null) { - Drawable? drawable = (int)Build.VERSION.SdkInt >= 21 ? - Context?.GetDrawable(id) : - Context?.GetDrawable(name); - if (drawable != null) - { - var wrapper = new DrawableWrapperX(drawable); - if (item.IsDestructive) - wrapper.SetTint(AColor.Red); - contextAction.SetIcon(wrapper); - } + contextMenu.Dismiss(); + contextMenu.Menu.Clear(); + //contextMenuDelegate?.Dispose(); + //contextMenu?.Dispose(); } } - } - void FillMenuItems() - { - if (Element is ContextMenuContainer element) + public override bool DispatchTouchEvent(MotionEvent e) { - if (element.MenuItems.Count > 0) + bool result; + Logger.Debug("ContextMenuContainer DispatchTouchEvent fired {0}", e.Action); + if (enabled && e.Action == MotionEventActions.Down) { - foreach (var item in element.MenuItems) + //You can change the timespan of the long press + timerFired = false; + timer = new MyTimer(TimeSpan.FromMilliseconds(1500), () => { - AddMenuItem(item); - } + timerFired = true; + OpenContextMenu(); + }); + timer.Start(); } - } - } - void RefillMenuItems() - { - if (contextMenu == null) - return; - contextMenu.Dismiss(); - contextMenu.Menu.Clear(); - FillMenuItems(); - } - PopupMenu? GetContextMenu() - { - if (contextMenu != null && Element is ContextMenuContainer element) - { - if (element.MenuItems.Count != contextMenu.Menu.Size()) + + if (timerFired) { - DeconstructNativeMenu(); + result = true; + } + else if (e.Action == MotionEventActions.Up || e.Action == MotionEventActions.Cancel) + { + timer?.Stop(); + result = base.DispatchTouchEvent(e); } else { - for (int i = 0; i < contextMenu.Menu.Size(); i++) + result = base.DispatchTouchEvent(e); + if (!result && enabled) { - if (!element.MenuItems[i].Text.Equals(contextMenu.Menu.GetItem(i)?.TitleFormatted?.ToString())) - { - DeconstructNativeMenu(); - break; - } + result = true; } } + + return result; } - return contextMenu; - } - private void MenuItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) - { - RefillMenuItems(); - } - private void Element_BindingContextChanged(object sender, EventArgs e) - { - RefillMenuItems(); - } - private void ContextMenu_MenuItemClick(object sender, PopupMenu.MenuItemClickEventArgs e) - { - var item = ((ContextMenuContainer)Element).MenuItems.FirstOrDefault(x => x.Text == e.Item.TitleFormatted?.ToString()); - item?.OnItemTapped(); - } - bool enabled => Element is ContextMenuContainer element && element.MenuItems.Count > 0; - MyTimer? timer; - bool timerFired = false; - public override bool DispatchTouchEvent(MotionEvent e) - { - bool result; - Logger.Debug("ContextMEnuContainer DispatchTouchEvent fired {0}", e.Action); - if (enabled && e.Action == MotionEventActions.Down) + void OpenContextMenu() { - //You can change the timespan of the long press - timerFired = false; - timer = new MyTimer(TimeSpan.FromMilliseconds(1500), () => + if (GetContextMenu() == null) { - timerFired = true; - OpenContextMenu(); - }); - timer.Start(); + ConstructNativeMenu(); + FillMenuItems(); + + } + + contextMenu?.Show(); } - if (timerFired) + + void ConstructNativeMenu() { - result = true; + var child = GetChildAt(0); + if (child == null) + return; + contextMenu = new PopupMenu(Context, child); + contextMenu.MenuItemClick += ContextMenu_MenuItemClick; + Field field = contextMenu.Class.GetDeclaredField("mPopup"); + field.Accessible = true; + Java.Lang.Object? menuPopupHelper = field.Get(contextMenu); + Method? setForceIcons = + menuPopupHelper?.Class.GetDeclaredMethod("setForceShowIcon", Java.Lang.Boolean.Type); + setForceIcons?.Invoke(menuPopupHelper, true); } - else if (e.Action == MotionEventActions.Up || e.Action == MotionEventActions.Cancel) + + void DeconstructNativeMenu() { - timer?.Stop(); - result = base.DispatchTouchEvent(e); + if (contextMenu == null) + return; + contextMenu.MenuItemClick -= ContextMenu_MenuItemClick; + contextMenu.Dispose(); + contextMenu = null; } - else + + + void AddMenuItem(ContextMenuItem item) { - result = base.DispatchTouchEvent(e); - if (!result && enabled) + if (contextMenu == null) + return; + var title = new SpannableString(item.Text); + if (item.IsDestructive) + title.SetSpan(new ForegroundColorSpan(AColor.Red), 0, title.Length(), 0); + var contextAction = contextMenu.Menu.Add(title); + if (contextAction == null) { - result = true; + Logger.Error("We couldn't create IMenuItem with title {0}", item.Text); + return; + } + + contextAction.SetEnabled(item.IsEnabled); + if (item.Icon != null) + { + var name = Path.GetFileNameWithoutExtension(item.Icon.File); + var id = Context.GetDrawableId(name); + if (id != 0) + { +#if MAUI + Drawable? drawable = Context?.GetDrawable(id); +#else + Drawable? drawable = (int)Build.VERSION.SdkInt >= 21 + ? Context?.GetDrawable(id) + : Context?.GetDrawable(name); +#endif + if (drawable != null) + { + var wrapper = new DrawableWrapperX(drawable); + if (item.IsDestructive) + wrapper.SetTint(AColor.Red); + contextAction.SetIcon(wrapper); + } + } } } - return result; - } - void OpenContextMenu() - { - if (GetContextMenu() == null) + + void FillMenuItems() { - ConstructNativeMenu(); - FillMenuItems(); + if (Element is ContextMenuContainer element) + { + if (element.MenuItems.Count > 0) + { + foreach (var item in element.MenuItems) + { + AddMenuItem(item); + } + } + } + } + PopupMenu? GetContextMenu() + { + if (contextMenu != null && Element is ContextMenuContainer element) + { + if (element.MenuItems.Count != contextMenu.Menu.Size()) + { + DeconstructNativeMenu(); + } + else + { + for (int i = 0; i < contextMenu.Menu.Size(); i++) + { + if (!element.MenuItems[i].Text + .Equals(contextMenu.Menu.GetItem(i)?.TitleFormatted?.ToString())) + { + DeconstructNativeMenu(); + break; + } + } + } + } + + return contextMenu; } - contextMenu?.Show(); - } + + private void ContextMenu_MenuItemClick(object sender, PopupMenu.MenuItemClickEventArgs e) + { + + var item = ((ContextMenuContainer)Element).MenuItems.FirstOrDefault(x => + x.Text == e.Item.TitleFormatted?.ToString()); + item?.OnItemTapped(); + } +#if MAUI + } +#endif class MyTimer { private readonly TimeSpan timespan; diff --git a/src/Shared/ContextMenuContainer.cs b/src/Shared/ContextMenuContainer.cs index 43a5d7a..007a1df 100644 --- a/src/Shared/ContextMenuContainer.cs +++ b/src/Shared/ContextMenuContainer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; #if MAUI using Microsoft.Maui; diff --git a/src/Shared/ContextMenuContainerExtensions.cs b/src/Shared/ContextMenuContainerExtensions.cs index 6815b11..87dc7f1 100644 --- a/src/Shared/ContextMenuContainerExtensions.cs +++ b/src/Shared/ContextMenuContainerExtensions.cs @@ -5,7 +5,7 @@ using Microsoft.Maui.Controls.Compatibility; using Microsoft.Maui.Controls.Compatibility.Hosting; #if __ANDROID__ -using APES.UI.XF.Droid; +//using APES.UI.XF.Droid; #elif __IOS__ || __MACCATALYST__ using APES.UI.XF.iOS; #elif __WINDOWS__ @@ -26,11 +26,10 @@ public static MauiAppBuilder ConfigureContextMenuContainer(this MauiAppBuilder m { #if __ANDROID__ - handlers.AddCompatibilityRenderer(); + handlers.AddHandler(); #elif __IOS__ || __MACCATALYST__ - //handlers.AddCompatibilityRenderer(); - handlers.AddHandler(); + handlers.AddHandler(); #elif __WINDOWS__ handlers.AddCompatibilityRenderer(); #endif @@ -38,4 +37,4 @@ public static MauiAppBuilder ConfigureContextMenuContainer(this MauiAppBuilder m } #endif } -} \ No newline at end of file +} diff --git a/src/Shared/ContextMenuContainerRenderer.cs b/src/Shared/ContextMenuContainerRenderer.cs new file mode 100644 index 0000000..8342694 --- /dev/null +++ b/src/Shared/ContextMenuContainerRenderer.cs @@ -0,0 +1,56 @@ +#if MAUI && (__ANDROID__ || __IOS__ || __MACCATALYST__)// || __WINDOWS__) +using System; +using Microsoft.Maui; +using Microsoft.Maui.Controls.Internals; +using Microsoft.Maui.Handlers; +namespace APES.UI.XF; + + [Preserve(AllMembers = true)] + sealed partial class ContextMenuContainerHandler: ContentViewHandler + { + //TODO: not sure if this is really needed, will test when debug is available for MAUI + bool wasSetOnce = false; + public override void SetVirtualView(IView view) + { + if (wasSetOnce) + { + var old = VirtualView as ContextMenuContainer; + old.BindingContextChanged -= Element_BindingContextChanged; + old.MenuItems.CollectionChanged -= MenuItems_CollectionChanged; + } + + base.SetVirtualView(view); + + if (VirtualView is ContextMenuContainer newElement) + { + newElement.BindingContextChanged += Element_BindingContextChanged; + newElement.MenuItems.CollectionChanged += MenuItems_CollectionChanged; + + //PlatformView.AddSubview(newElement.Content.ToContainerView(MauiContext)); + //SetContent(); + //PlatformView.View = newElement; + RefillMenuItems(); + wasSetOnce = true; + } + } + + + void RefillMenuItems() + { + if (VirtualView is ContextMenuContainer container) + { + constructInteraction(container); + } + } + + void MenuItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + RefillMenuItems(); + } + + void Element_BindingContextChanged(object sender, EventArgs e) + { + RefillMenuItems(); + } + } +#endif diff --git a/src/iOS/ContextMenuContainerRenderer.cs b/src/iOS/ContextMenuContainerRenderer.cs index b80fd4c..1d13b89 100644 --- a/src/iOS/ContextMenuContainerRenderer.cs +++ b/src/iOS/ContextMenuContainerRenderer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Text; using UIKit; @@ -20,47 +20,25 @@ #endif using APES.UI.XF; using APES.UI.XF.iOS; -#if !MAUI +#if MAUI +namespace APES.UI.XF + +#else [assembly: ExportRenderer(typeof(ContextMenuContainer), typeof(ContextMenuContainerRenderer))] -#endif namespace APES.UI.XF.iOS +#endif { - [Preserve(AllMembers = true)] - #if MAUI - class ContextMenuContainerRenderer : ContentViewHandler //ViewHandler + +#if MAUI + sealed partial class ContextMenuContainerHandler : ContentViewHandler //ViewHandler #else + [Preserve(AllMembers = true) class ContextMenuContainerRenderer : ViewRenderer #endif { ContextMenuDelegate? contextMenuDelegate; UIContextMenuInteraction? contextMenu; - #if MAUI - //TODO: not sure if this is really needed, will test when debug is available for MAUI - bool wasSetOnce = false; - public override void SetVirtualView(IView view) - { - if (wasSetOnce) - { - var old = VirtualView as ContextMenuContainer; - old.BindingContextChanged -= Element_BindingContextChanged; - old.MenuItems.CollectionChanged -= MenuItems_CollectionChanged; - } - - base.SetVirtualView(view); - - if (VirtualView is ContextMenuContainer newElement) - { - newElement.BindingContextChanged += Element_BindingContextChanged; - newElement.MenuItems.CollectionChanged += MenuItems_CollectionChanged; - - //PlatformView.AddSubview(newElement.Content.ToContainerView(MauiContext)); - //SetContent(); - //PlatformView.View = newElement; - RefillMenuItems(); - wasSetOnce = true; - } - } - + #if MAUI UIView Control => PlatformView; UITraitCollection TraitCollection => UITraitCollection.CurrentTraitCollection; #else @@ -91,26 +69,27 @@ void deconstructIntercation() //contextMenu?.Dispose(); } } - void constructInteraction(ContextMenuItems menuItems) + void constructInteraction(ContextMenuContainer container) { deconstructIntercation(); - if (menuItems?.Count > 0) + if (container.MenuItems?.Count > 0) { - contextMenuDelegate = new ContextMenuDelegate(menuItems, () => TraitCollection.UserInterfaceStyle); + contextMenuDelegate = new ContextMenuDelegate(container.MenuItems, () => TraitCollection.UserInterfaceStyle); contextMenu = new UIContextMenuInteraction(contextMenuDelegate); Control.AddInteraction(contextMenu); } } - +#if !MAUI void RefillMenuItems() => constructInteraction(((ContextMenuContainer)VirtualView).MenuItems); - private void MenuItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + void MenuItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { RefillMenuItems(); } - private void Element_BindingContextChanged(object sender, EventArgs e) + void Element_BindingContextChanged(object sender, EventArgs e) { RefillMenuItems(); } +#endif } } From d832ec60b1d324abda320dacde90a05b4dad4c94 Mon Sep 17 00:00:00 2001 From: Pavel Anpin Date: Fri, 10 Jun 2022 11:41:50 +0400 Subject: [PATCH 12/28] feat: ported all MAUI specific code from legacy renderers to handlers --- Directory.Build.props | 12 ++ src/Droid/ContextMenuContainerRenderer.cs | 211 +++++++++++-------- src/Shared/ContextMenuContainerExtensions.cs | 16 +- src/Shared/ContextMenuContainerRenderer.cs | 54 +---- src/UWP/ContextMenuContainerRenderer.cs | 57 +++-- src/iOS/ContextMenuContainerRenderer.cs | 110 ++++++---- 6 files changed, 249 insertions(+), 211 deletions(-) create mode 100644 Directory.Build.props diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..661a7f9 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,12 @@ + + + $(MSBuildThisFileDirectory).stylecop.ruleset + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + diff --git a/src/Droid/ContextMenuContainerRenderer.cs b/src/Droid/ContextMenuContainerRenderer.cs index 54e9c53..7b0143d 100644 --- a/src/Droid/ContextMenuContainerRenderer.cs +++ b/src/Droid/ContextMenuContainerRenderer.cs @@ -14,11 +14,129 @@ using AndroidX.AppCompat.Widget; using DrawableWrapperX = AndroidX.AppCompat.Graphics.Drawable.DrawableWrapper; using Java.Lang.Reflect; +using APES.UI.XF; +using Path = System.IO.Path; +using AColor = Android.Graphics.Color; #if MAUI +using Microsoft.Maui; using Microsoft.Maui.Platform; using Microsoft.Maui.Controls; using Microsoft.Maui.Handlers; using JetBrains.Annotations; + +namespace APES.UI.XF; +sealed partial class ContextMenuContainerRenderer : ContentViewHandler +{ + //TODO: not sure if this is really needed, will test when debug is available for MAUI + bool wasSetOnce = false; + public override void SetVirtualView(IView view) + { + if (wasSetOnce) + { + var old = VirtualView as ContextMenuContainer; + old.BindingContextChanged -= Element_BindingContextChanged; + old.MenuItems.CollectionChanged -= MenuItems_CollectionChanged; + } + + base.SetVirtualView(view); + + if (VirtualView is ContextMenuContainer newElement) + { + newElement.BindingContextChanged += Element_BindingContextChanged; + newElement.MenuItems.CollectionChanged += MenuItems_CollectionChanged; + + //PlatformView.AddSubview(newElement.Content.ToContainerView(MauiContext)); + //SetContent(); + //PlatformView.View = newElement; + RefillMenuItems(); + wasSetOnce = true; + } + } + + + void RefillMenuItems() + { + if (VirtualView is ContextMenuContainer container) + { + constructInteraction(container); + } + } + + void MenuItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + RefillMenuItems(); + } + + void Element_BindingContextChanged(object sender, EventArgs e) + { + RefillMenuItems(); + } + + void constructInteraction(ContextMenuContainer menuItems) + { + ((ContainerViewGroup)PlatformView).SetupMenu(menuItems); + //deconstructIntercation(); + //if (menuItems?.Count > 0) + //{ + // foreach (var item in menuItems) + // { + // AddMenuItem(item); + // } + //} + } + + protected override ContentViewGroup CreatePlatformView() + { + if (VirtualView == null) + { + throw new InvalidOperationException($"{nameof(VirtualView)} must be set to create a ContentViewGroup"); + } + if (VirtualView is not ContextMenuContainer) + { + throw new InvalidOperationException($"{nameof(VirtualView)} must be of type ContextMenuContainer, but was {VirtualView.GetType()} "); + } + + var viewGroup = new ContainerViewGroup(Context); + //{ + // CrossPlatformMeasure = VirtualView.CrossPlatformMeasure, + // CrossPlatformArrange = VirtualView.CrossPlatformArrange + //}; + return viewGroup; + } + + class ContainerViewGroup : ContentViewGroup + { + + + private ContextMenuContainer? Element; + public ContainerViewGroup([NotNull] Context context) : base(context) + { + + } + + public ContainerViewGroup(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer) + { + } + + public ContainerViewGroup([NotNull] Context context, [NotNull] IAttributeSet attrs) : base(context, attrs) + { + } + + public ContainerViewGroup([NotNull] Context context, [NotNull] IAttributeSet attrs, int defStyleAttr) : + base(context, attrs, defStyleAttr) + { + } + + public ContainerViewGroup([NotNull] Context context, [NotNull] IAttributeSet attrs, int defStyleAttr, + int defStyleRes) : base(context, attrs, defStyleAttr, defStyleRes) + { + } + + public void SetupMenu(ContextMenuContainer? container) + { + deconstructIntercation(); + Element = container; + } #else using Xamarin.Forms; using Xamarin.Forms.Internals; @@ -26,60 +144,14 @@ using APES.UI.XF.Droid; using XView = Xamarin.Forms.View; using PreserveAttribute = Xamarin.Forms.Internals.PreserveAttribute; -#endif -using APES.UI.XF; -using Path = System.IO.Path; -using AColor = Android.Graphics.Color; -#if MAUI -namespace APES.UI.XF -#else [assembly: ExportRenderer(typeof(ContextMenuContainer), typeof(ContextMenuContainerRenderer))] namespace APES.UI.XF.Droid -#endif { -#if MAUI - sealed partial class ContextMenuContainerHandler : ContentViewHandler -#else [Preserve(AllMembers = true)] class ContextMenuContainerRenderer : ViewRenderer -#endif { -#if MAUI - //private IContentView Element => VirtualView; - - void constructInteraction(ContextMenuContainer menuItems) - { - ((ContainerViewGroup)PlatformView).SetupMenu(menuItems); - //deconstructIntercation(); - //if (menuItems?.Count > 0) - //{ - // foreach (var item in menuItems) - // { - // AddMenuItem(item); - // } - //} - } - - protected override ContentViewGroup CreatePlatformView() - { - if (VirtualView == null) - { - throw new InvalidOperationException($"{nameof(VirtualView)} must be set to create a ContentViewGroup"); - } - if (VirtualView is not ContextMenuContainer) - { - throw new InvalidOperationException($"{nameof(VirtualView)} must be of type ContextMenuContainer, but was {VirtualView.GetType()} "); - } - - var viewGroup = new ContainerViewGroup(Context); - //{ - // CrossPlatformMeasure = VirtualView.CrossPlatformMeasure, - // CrossPlatformArrange = VirtualView.CrossPlatformArrange - //}; - return viewGroup; - } -#else + public ContextMenuContainerRenderer(Context context) : base(context) { @@ -129,52 +201,11 @@ void Element_BindingContextChanged(object sender, EventArgs e) } #endif - -#if MAUI - - class ContainerViewGroup : ContentViewGroup - { - - - private ContextMenuContainer? Element; -#endif - PopupMenu? contextMenu; MyTimer? timer; bool timerFired = false; private bool enabled => Element is ContextMenuContainer element && element.MenuItems.Count > 0; -#if MAUI - public ContainerViewGroup([NotNull] Context context) : base(context) - { - - } - - public ContainerViewGroup(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer) - { - } - - public ContainerViewGroup([NotNull] Context context, [NotNull] IAttributeSet attrs) : base(context, attrs) - { - } - - public ContainerViewGroup([NotNull] Context context, [NotNull] IAttributeSet attrs, int defStyleAttr) : - base(context, attrs, defStyleAttr) - { - } - - public ContainerViewGroup([NotNull] Context context, [NotNull] IAttributeSet attrs, int defStyleAttr, - int defStyleRes) : base(context, attrs, defStyleAttr, defStyleRes) - { - } - - public void SetupMenu(ContextMenuContainer? container) - { - deconstructIntercation(); - Element = container; - } -#endif - void deconstructIntercation() { @@ -182,8 +213,6 @@ void deconstructIntercation() { contextMenu.Dismiss(); contextMenu.Menu.Clear(); - //contextMenuDelegate?.Dispose(); - //contextMenu?.Dispose(); } } public override bool DispatchTouchEvent(MotionEvent e) @@ -381,4 +410,6 @@ public void Stop() } } } +#if !MAUI } +#endif diff --git a/src/Shared/ContextMenuContainerExtensions.cs b/src/Shared/ContextMenuContainerExtensions.cs index 87dc7f1..56b99f7 100644 --- a/src/Shared/ContextMenuContainerExtensions.cs +++ b/src/Shared/ContextMenuContainerExtensions.cs @@ -4,13 +4,6 @@ using Microsoft.Maui.Hosting; using Microsoft.Maui.Controls.Compatibility; using Microsoft.Maui.Controls.Compatibility.Hosting; -#if __ANDROID__ -//using APES.UI.XF.Droid; -#elif __IOS__ || __MACCATALYST__ -using APES.UI.XF.iOS; -#elif __WINDOWS__ -using APES.UI.XF.UWP; -#endif #endif namespace APES.UI.XF @@ -24,15 +17,8 @@ public static MauiAppBuilder ConfigureContextMenuContainer(this MauiAppBuilder m return mauiAppBuilder.ConfigureMauiHandlers(handlers => { + handlers.AddHandler(); -#if __ANDROID__ - handlers.AddHandler(); - -#elif __IOS__ || __MACCATALYST__ - handlers.AddHandler(); -#elif __WINDOWS__ - handlers.AddCompatibilityRenderer(); -#endif }); } #endif diff --git a/src/Shared/ContextMenuContainerRenderer.cs b/src/Shared/ContextMenuContainerRenderer.cs index 8342694..ffffb10 100644 --- a/src/Shared/ContextMenuContainerRenderer.cs +++ b/src/Shared/ContextMenuContainerRenderer.cs @@ -1,56 +1,12 @@ -#if MAUI && (__ANDROID__ || __IOS__ || __MACCATALYST__)// || __WINDOWS__) +#if MAUI using System; using Microsoft.Maui; using Microsoft.Maui.Controls.Internals; using Microsoft.Maui.Handlers; namespace APES.UI.XF; - [Preserve(AllMembers = true)] - sealed partial class ContextMenuContainerHandler: ContentViewHandler - { - //TODO: not sure if this is really needed, will test when debug is available for MAUI - bool wasSetOnce = false; - public override void SetVirtualView(IView view) - { - if (wasSetOnce) - { - var old = VirtualView as ContextMenuContainer; - old.BindingContextChanged -= Element_BindingContextChanged; - old.MenuItems.CollectionChanged -= MenuItems_CollectionChanged; - } - - base.SetVirtualView(view); - - if (VirtualView is ContextMenuContainer newElement) - { - newElement.BindingContextChanged += Element_BindingContextChanged; - newElement.MenuItems.CollectionChanged += MenuItems_CollectionChanged; - - //PlatformView.AddSubview(newElement.Content.ToContainerView(MauiContext)); - //SetContent(); - //PlatformView.View = newElement; - RefillMenuItems(); - wasSetOnce = true; - } - } - - - void RefillMenuItems() - { - if (VirtualView is ContextMenuContainer container) - { - constructInteraction(container); - } - } - - void MenuItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) - { - RefillMenuItems(); - } - - void Element_BindingContextChanged(object sender, EventArgs e) - { - RefillMenuItems(); - } - } +sealed partial class ContextMenuContainerRenderer : ContentViewHandler +{ + +} #endif diff --git a/src/UWP/ContextMenuContainerRenderer.cs b/src/UWP/ContextMenuContainerRenderer.cs index 0f64c14..667a9b4 100644 --- a/src/UWP/ContextMenuContainerRenderer.cs +++ b/src/UWP/ContextMenuContainerRenderer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.ComponentModel; using System.Numerics; @@ -8,10 +8,7 @@ using System.Collections.Specialized; #if MAUI using Microsoft.Maui; -using Microsoft.Maui.Controls; -using Microsoft.Maui.Controls.Platform; -using Microsoft.Maui.Controls.Compatibility; -using Microsoft.Maui.Controls.Compatibility.Platform.UWP; +using Microsoft.Maui.Platform; using Microsoft.Maui.Controls.Internals; using Microsoft.Maui.Handlers; using Microsoft.UI.Input; @@ -28,6 +25,26 @@ using Setter = Microsoft.UI.Xaml.Setter; using SolidColorBrush = Microsoft.UI.Xaml.Media.SolidColorBrush; using Style = Microsoft.UI.Xaml.Style; +using APES.UI.XF.UWP; + +namespace APES.UI.XF; +[Preserve(AllMembers = true)] +sealed partial class ContextMenuContainerRenderer : ContentViewHandler +{ + private ContextMenuContainer Element => (ContextMenuContainer)VirtualView; + + protected override ContentPanel CreatePlatformView() + { + var result =base.CreatePlatformView(); + result.PointerReleased += PlatformViewPointerReleased; + return result; + } + + public override void SetVirtualView(IView view) + { + base.SetVirtualView(view); + + } #else using Windows.UI.Input; using Windows.UI.Xaml; @@ -41,17 +58,18 @@ using Xamarin.Forms.Platform.UWP; using WColors = Windows.UI.Colors; using WBinding = Windows.UI.Xaml.Data.Binding; -#endif using APES.UI.XF; using APES.UI.XF.UWP; [assembly: ExportRenderer(typeof(ContextMenuContainer), typeof(ContextMenuContainerRenderer))] namespace APES.UI.XF.UWP + { [Preserve(AllMembers = true)] public class ContextMenuContainerRenderer : ViewRenderer { - FrameworkElement? content; + + FrameworkElement? PlatformView; public ContextMenuContainerRenderer() { AutoPackage = false; @@ -73,8 +91,7 @@ protected override void OnElementChanged(ElementChangedEventArgs BoolToStytleConverter { get; } = new(DestructiveStyle, NondDestructiveStyle); } +#if !MAUI } +#endif diff --git a/src/iOS/ContextMenuContainerRenderer.cs b/src/iOS/ContextMenuContainerRenderer.cs index 1d13b89..057c408 100644 --- a/src/iOS/ContextMenuContainerRenderer.cs +++ b/src/iOS/ContextMenuContainerRenderer.cs @@ -1,47 +1,74 @@ using System; -using System.Collections.Generic; -using System.Text; using UIKit; #if MAUI using Microsoft.Maui; -using Microsoft.Maui.Controls; -using Microsoft.Maui.Controls.Platform; -using Microsoft.Maui.Controls.Compatibility; -using Microsoft.Maui.Controls.Compatibility.Platform; -using Microsoft.Maui.Controls.Compatibility.Platform.iOS; -using Microsoft.Maui.Controls.Internals; using Microsoft.Maui.Handlers; -using Microsoft.Maui.Platform; -using ContentView = Microsoft.Maui.Platform.ContentView; +using APES.UI.XF.iOS; + +namespace APES.UI.XF; + +sealed partial class ContextMenuContainerRenderer : ContentViewHandler +{ +UIView Control => PlatformView; +UITraitCollection TraitCollection => UITraitCollection.CurrentTraitCollection; + +//TODO: not sure if this is really needed, will test when debug is available for MAUI +bool wasSetOnce = false; +public override void SetVirtualView(IView view) +{ + if (wasSetOnce) + { + var old = VirtualView as ContextMenuContainer; + old.BindingContextChanged -= Element_BindingContextChanged; + old.MenuItems.CollectionChanged -= MenuItems_CollectionChanged; + } + + base.SetVirtualView(view); + + if (VirtualView is ContextMenuContainer newElement) + { + newElement.BindingContextChanged += Element_BindingContextChanged; + newElement.MenuItems.CollectionChanged += MenuItems_CollectionChanged; + + //PlatformView.AddSubview(newElement.Content.ToContainerView(MauiContext)); + //SetContent(); + //PlatformView.View = newElement; + RefillMenuItems(); + wasSetOnce = true; + } +} + + +void RefillMenuItems() +{ + if (VirtualView is ContextMenuContainer container) + { + constructInteraction(container); + } +} + +void MenuItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) +{ + RefillMenuItems(); +} + +void Element_BindingContextChanged(object sender, EventArgs e) +{ + RefillMenuItems(); +} #else using Xamarin.Forms; using Xamarin.Forms.Internals; using Xamarin.Forms.Platform.iOS; -#endif using APES.UI.XF; using APES.UI.XF.iOS; -#if MAUI -namespace APES.UI.XF -#else [assembly: ExportRenderer(typeof(ContextMenuContainer), typeof(ContextMenuContainerRenderer))] namespace APES.UI.XF.iOS -#endif { - -#if MAUI - sealed partial class ContextMenuContainerHandler : ContentViewHandler //ViewHandler -#else [Preserve(AllMembers = true) class ContextMenuContainerRenderer : ViewRenderer -#endif { - ContextMenuDelegate? contextMenuDelegate; - UIContextMenuInteraction? contextMenu; - #if MAUI - UIView Control => PlatformView; - UITraitCollection TraitCollection => UITraitCollection.CurrentTraitCollection; -#else protected override void OnElementChanged(ElementChangedEventArgs e) { base.OnElementChanged(e); @@ -59,8 +86,23 @@ protected override void OnElementChanged(ElementChangedEventArgs Element; + + void RefillMenuItems() => constructInteraction(((ContextMenuContainer)VirtualView).MenuItems); + void MenuItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + RefillMenuItems(); + } + + void Element_BindingContextChanged(object sender, EventArgs e) + { + RefillMenuItems(); + } #endif - void deconstructIntercation() + + ContextMenuDelegate? contextMenuDelegate; + UIContextMenuInteraction? contextMenu; + + void deconstructIntercation() { if (Control != null && contextMenu != null) { @@ -79,17 +121,7 @@ void constructInteraction(ContextMenuContainer container) Control.AddInteraction(contextMenu); } } -#if !MAUI - void RefillMenuItems() => constructInteraction(((ContextMenuContainer)VirtualView).MenuItems); - void MenuItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) - { - RefillMenuItems(); - } - - void Element_BindingContextChanged(object sender, EventArgs e) - { - RefillMenuItems(); - } -#endif } +#if !MAUI } +#endif From cbb97f3cb13130387ef2da8bc4e812e9407f7eca Mon Sep 17 00:00:00 2001 From: Pavel Anpin Date: Fri, 10 Jun 2022 12:31:39 +0400 Subject: [PATCH 13/28] feat: fixed issues for Apple --- src/APES.UI.XF.csproj | 6 +++--- src/Shared/ContextMenuContainerExtensions.cs | 2 -- src/iOS/ContextMenuContainerRenderer.cs | 6 +++--- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/APES.UI.XF.csproj b/src/APES.UI.XF.csproj index 7e18b25..fc28953 100644 --- a/src/APES.UI.XF.csproj +++ b/src/APES.UI.XF.csproj @@ -1,10 +1,10 @@ - + - netstandard2.0;monoandroid10.0;net6.0;net6.0-android;net6.0-ios;net6.0-maccatalyst - + + netstandard2.0;xamarin.ios10;xamarin.mac20;monoandroid10.0;net6.0;net6.0-android;net6.0-ios;net6.0-maccatalyst $(TargetFrameworks);net6.0-windows10.0.17763.0;uap10.0.17763 14.2 14.0 diff --git a/src/Shared/ContextMenuContainerExtensions.cs b/src/Shared/ContextMenuContainerExtensions.cs index 56b99f7..06b27a9 100644 --- a/src/Shared/ContextMenuContainerExtensions.cs +++ b/src/Shared/ContextMenuContainerExtensions.cs @@ -2,8 +2,6 @@ using System.Collections.Generic; #if MAUI using Microsoft.Maui.Hosting; -using Microsoft.Maui.Controls.Compatibility; -using Microsoft.Maui.Controls.Compatibility.Hosting; #endif namespace APES.UI.XF diff --git a/src/iOS/ContextMenuContainerRenderer.cs b/src/iOS/ContextMenuContainerRenderer.cs index 057c408..c673ee9 100644 --- a/src/iOS/ContextMenuContainerRenderer.cs +++ b/src/iOS/ContextMenuContainerRenderer.cs @@ -66,7 +66,7 @@ void Element_BindingContextChanged(object sender, EventArgs e) [assembly: ExportRenderer(typeof(ContextMenuContainer), typeof(ContextMenuContainerRenderer))] namespace APES.UI.XF.iOS { - [Preserve(AllMembers = true) + [Preserve(AllMembers = true)] class ContextMenuContainerRenderer : ViewRenderer { protected override void OnElementChanged(ElementChangedEventArgs e) @@ -82,12 +82,12 @@ protected override void OnElementChanged(ElementChangedEventArgs Element; - void RefillMenuItems() => constructInteraction(((ContextMenuContainer)VirtualView).MenuItems); + void RefillMenuItems() => constructInteraction(VirtualView); void MenuItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { RefillMenuItems(); From 81b5bca57d9f9482db969857c492c05be38f5ab4 Mon Sep 17 00:00:00 2001 From: Pavel Anpin Date: Fri, 10 Jun 2022 16:25:07 +0400 Subject: [PATCH 14/28] feat: enforced analyzer styles, fixed warnings and obsolete calls --- .stylecop.json | 3 +- .stylecop.ruleset | 8 +- Directory.Build.props | 20 +- .../APES.UI.MAUI.Sample.csproj | 6 +- .../APES.UI.MAUI.Sample.sln | 27 - samples/APES.UI.MAUI.Sample/App.xaml.cs | 7 +- samples/APES.UI.MAUI.Sample/MauiProgram.cs | 2 +- .../Platforms/Android/MainActivity.cs | 1 - .../Platforms/MacCatalyst/Program.cs | 13 +- .../Platforms/Windows/App.xaml.cs | 8 +- .../Platforms/iOS/Program.cs | 13 +- .../APES.UI.Samples.Shared/AsyncCommand.cs | 25 +- .../APES.UI.Samples.Shared/IAsyncCommand.cs | 3 +- .../APES.UI.Samples.Shared/IErrorHandler.cs | 3 +- .../APES.UI.Samples.Shared/TaskExtensions.cs | 5 +- .../ViewModels/MainViewModel.cs | 47 +- samples/APES.UI.XF.Sample/App.xaml.cs | 6 +- samples/APES.UI.XF.Sample/MainPage.xaml.cs | 13 +- samples/APES.UI.XF.Sample/NavPage.xaml.cs | 13 +- src/APES.UI.XF.csproj | 19 +- src/APES.UI.XF.csproj.DotSettings | 4 + src/Droid/ContextMenuContainerRenderer.cs | 470 ++++++++++-------- src/Mac/ContextContainerNativeView.cs | 250 +++++----- src/Mac/ContextMenuContainerRenderer.cs | 24 +- src/Mac/ImageHandler.cs | 24 +- src/Shared/AssemblyConfiguration.cs | 12 +- src/Shared/ContextMenuContainer.cs | 100 ++-- src/Shared/ContextMenuContainerExtensions.cs | 21 +- src/Shared/ContextMenuContainerRenderer.cs | 12 - src/Shared/ContextMenuItem.cs | 50 +- src/Shared/ContextMenuItems.cs | 8 +- src/Shared/Logger.cs | 25 +- src/UWP/ContextMenuContainerRenderer.cs | 239 +++++---- ...eImageSourceToBitmapIconSourceConverter.cs | 56 +-- src/UWP/GenericBoolConverter.cs | 34 +- src/UWP/Initialize.cs | 16 - src/iOS/ContextMenuContainerRenderer.cs | 154 +++--- src/iOS/ContextMenuDelegate.cs | 82 +-- tests/APES.UI.XF.Tests/ContextMenuItemTest.cs | 33 +- 39 files changed, 931 insertions(+), 925 deletions(-) delete mode 100644 samples/APES.UI.MAUI.Sample/APES.UI.MAUI.Sample.sln create mode 100644 src/APES.UI.XF.csproj.DotSettings delete mode 100644 src/Shared/ContextMenuContainerRenderer.cs delete mode 100644 src/UWP/Initialize.cs diff --git a/.stylecop.json b/.stylecop.json index 16ed402..858aa27 100644 --- a/.stylecop.json +++ b/.stylecop.json @@ -13,8 +13,7 @@ }, "documentationRules": { "xmlHeader": false, - "companyName": "Brightlink AV LTD", - "copyrightText": "Copyright © {companyName} All rights reserved." + "copyrightText": "MIT License\nCopyright (c) 2021 Pavel Anpin" } } } diff --git a/.stylecop.ruleset b/.stylecop.ruleset index fe5980e..1a598b2 100644 --- a/.stylecop.ruleset +++ b/.stylecop.ruleset @@ -1,7 +1,7 @@ - + @@ -17,8 +17,8 @@ - - + + @@ -120,7 +120,7 @@ - + diff --git a/Directory.Build.props b/Directory.Build.props index 661a7f9..d58aee7 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,12 +1,12 @@ - - $(MSBuildThisFileDirectory).stylecop.ruleset - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - + + + + + + + + + + diff --git a/samples/APES.UI.MAUI.Sample/APES.UI.MAUI.Sample.csproj b/samples/APES.UI.MAUI.Sample/APES.UI.MAUI.Sample.csproj index 2123d0a..37a6996 100644 --- a/samples/APES.UI.MAUI.Sample/APES.UI.MAUI.Sample.csproj +++ b/samples/APES.UI.MAUI.Sample/APES.UI.MAUI.Sample.csproj @@ -2,7 +2,7 @@ net6.0-android;net6.0-ios;net6.0-maccatalyst - $(TargetFrameworks);net6.0-windows10.0.18362 + $(TargetFrameworks);net6.0-windows10.0.17763 Exe APES.UI.MAUI.Sample true @@ -28,8 +28,8 @@ ios-arm64 14.0 21.0 - 10.0.18362.0 - 10.0.18362.0 + 10.0.17763.0 + 10.0.17763.0 WinExe diff --git a/samples/APES.UI.MAUI.Sample/APES.UI.MAUI.Sample.sln b/samples/APES.UI.MAUI.Sample/APES.UI.MAUI.Sample.sln deleted file mode 100644 index 7139903..0000000 --- a/samples/APES.UI.MAUI.Sample/APES.UI.MAUI.Sample.sln +++ /dev/null @@ -1,27 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31611.283 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "APES.UI.MAUI.Sample", "APES.UI.MAUI.Sample.csproj", "{E58C314D-6977-4ED6-9871-B180BDD37544}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {E58C314D-6977-4ED6-9871-B180BDD37544}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E58C314D-6977-4ED6-9871-B180BDD37544}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E58C314D-6977-4ED6-9871-B180BDD37544}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {E58C314D-6977-4ED6-9871-B180BDD37544}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E58C314D-6977-4ED6-9871-B180BDD37544}.Release|Any CPU.Build.0 = Release|Any CPU - {E58C314D-6977-4ED6-9871-B180BDD37544}.Release|Any CPU.Deploy.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {61F7FB11-1E47-470C-91E2-47F8143E1572} - EndGlobalSection -EndGlobal diff --git a/samples/APES.UI.MAUI.Sample/App.xaml.cs b/samples/APES.UI.MAUI.Sample/App.xaml.cs index dc684a0..4dca69d 100644 --- a/samples/APES.UI.MAUI.Sample/App.xaml.cs +++ b/samples/APES.UI.MAUI.Sample/App.xaml.cs @@ -1,11 +1,14 @@ -namespace APES.UI.MAUI.Sample; +// MIT License +// Copyright (c) 2021 Pavel Anpin + +namespace APES.UI.MAUI.Sample; public partial class App : Application { public App() { InitializeComponent(); - APES.UI.XF.ContextMenuContainer.Init(); + XF.ContextMenuContainer.Init(); MainPage = new NavPage(new MainPage()); } } diff --git a/samples/APES.UI.MAUI.Sample/MauiProgram.cs b/samples/APES.UI.MAUI.Sample/MauiProgram.cs index 0cd5977..534c88d 100644 --- a/samples/APES.UI.MAUI.Sample/MauiProgram.cs +++ b/samples/APES.UI.MAUI.Sample/MauiProgram.cs @@ -1,4 +1,4 @@ -using APES.UI.XF; +using APES.UI.XF; namespace APES.UI.MAUI.Sample; public static class MauiProgram diff --git a/samples/APES.UI.MAUI.Sample/Platforms/Android/MainActivity.cs b/samples/APES.UI.MAUI.Sample/Platforms/Android/MainActivity.cs index 3869160..e1c967b 100644 --- a/samples/APES.UI.MAUI.Sample/Platforms/Android/MainActivity.cs +++ b/samples/APES.UI.MAUI.Sample/Platforms/Android/MainActivity.cs @@ -1,6 +1,5 @@ using Android.App; using Android.Content.PM; -using Android.OS; namespace APES.UI.MAUI.Sample; diff --git a/samples/APES.UI.MAUI.Sample/Platforms/MacCatalyst/Program.cs b/samples/APES.UI.MAUI.Sample/Platforms/MacCatalyst/Program.cs index 33351e8..6befbc6 100644 --- a/samples/APES.UI.MAUI.Sample/Platforms/MacCatalyst/Program.cs +++ b/samples/APES.UI.MAUI.Sample/Platforms/MacCatalyst/Program.cs @@ -1,15 +1,12 @@ -using ObjCRuntime; -using UIKit; +using UIKit; namespace APES.UI.MAUI.Sample; public class Program { // This is the main entry point of the application. - static void Main(string[] args) - { - // if you want to use a different Application Delegate class from "AppDelegate" - // you can specify it here. - UIApplication.Main(args, null, typeof(AppDelegate)); - } + private static void Main(string[] args) => + // if you want to use a different Application Delegate class from "AppDelegate" + // you can specify it here. + UIApplication.Main(args, null, typeof(AppDelegate)); } diff --git a/samples/APES.UI.MAUI.Sample/Platforms/Windows/App.xaml.cs b/samples/APES.UI.MAUI.Sample/Platforms/Windows/App.xaml.cs index c04f978..a38b8c2 100644 --- a/samples/APES.UI.MAUI.Sample/Platforms/Windows/App.xaml.cs +++ b/samples/APES.UI.MAUI.Sample/Platforms/Windows/App.xaml.cs @@ -1,4 +1,4 @@ -using Microsoft.UI.Xaml; + // To learn more about WinUI, the WinUI project structure, // and more about our project templates, see: http://aka.ms/winui-project-info. @@ -16,13 +16,13 @@ public partial class App : MauiWinUIApplication /// public App() { - this.UnhandledException += App_UnhandledException; - this.InitializeComponent(); + UnhandledException += App_UnhandledException; + InitializeComponent(); } private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e) { - APES.UI.XF.Logger.Error(e.Exception); + XF.Logger.Error(e.Exception); e.Handled = true; } diff --git a/samples/APES.UI.MAUI.Sample/Platforms/iOS/Program.cs b/samples/APES.UI.MAUI.Sample/Platforms/iOS/Program.cs index 33351e8..6befbc6 100644 --- a/samples/APES.UI.MAUI.Sample/Platforms/iOS/Program.cs +++ b/samples/APES.UI.MAUI.Sample/Platforms/iOS/Program.cs @@ -1,15 +1,12 @@ -using ObjCRuntime; -using UIKit; +using UIKit; namespace APES.UI.MAUI.Sample; public class Program { // This is the main entry point of the application. - static void Main(string[] args) - { - // if you want to use a different Application Delegate class from "AppDelegate" - // you can specify it here. - UIApplication.Main(args, null, typeof(AppDelegate)); - } + private static void Main(string[] args) => + // if you want to use a different Application Delegate class from "AppDelegate" + // you can specify it here. + UIApplication.Main(args, null, typeof(AppDelegate)); } diff --git a/samples/APES.UI.Samples.Shared/AsyncCommand.cs b/samples/APES.UI.Samples.Shared/AsyncCommand.cs index 711f829..58167fd 100644 --- a/samples/APES.UI.Samples.Shared/AsyncCommand.cs +++ b/samples/APES.UI.Samples.Shared/AsyncCommand.cs @@ -1,6 +1,4 @@ -using System; -using System.Threading.Tasks; -using System.Windows.Input; +using System.Windows.Input; namespace APES.UI.XF.Sample { public class AsyncCommand : IAsyncCommand @@ -22,10 +20,7 @@ public AsyncCommand( _errorHandler = errorHandler; } - public bool CanExecute() - { - return !_isExecuting && (_canExecute?.Invoke() ?? true); - } + public bool CanExecute() => !_isExecuting && (_canExecute?.Invoke() ?? true); public async Task ExecuteAsync() { @@ -45,21 +40,13 @@ public async Task ExecuteAsync() RaiseCanExecuteChanged(); } - public void RaiseCanExecuteChanged() - { - CanExecuteChanged?.Invoke(this, EventArgs.Empty); - } + public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty); #region Explicit implementations - bool ICommand.CanExecute(object parameter) - { - return CanExecute(); - } + bool ICommand.CanExecute(object parameter) => CanExecute(); + + void ICommand.Execute(object parameter) => ExecuteAsync().FireAndForgetSafeAsync(_errorHandler); - void ICommand.Execute(object parameter) - { - ExecuteAsync().FireAndForgetSafeAsync(_errorHandler); - } #endregion } } diff --git a/samples/APES.UI.Samples.Shared/IAsyncCommand.cs b/samples/APES.UI.Samples.Shared/IAsyncCommand.cs index a21893f..14d1ce1 100644 --- a/samples/APES.UI.Samples.Shared/IAsyncCommand.cs +++ b/samples/APES.UI.Samples.Shared/IAsyncCommand.cs @@ -1,5 +1,4 @@ -using System.Threading.Tasks; -using System.Windows.Input; +using System.Windows.Input; namespace APES.UI.XF.Sample { public interface IAsyncCommand : ICommand diff --git a/samples/APES.UI.Samples.Shared/IErrorHandler.cs b/samples/APES.UI.Samples.Shared/IErrorHandler.cs index 90b4cf6..f3ecfb0 100644 --- a/samples/APES.UI.Samples.Shared/IErrorHandler.cs +++ b/samples/APES.UI.Samples.Shared/IErrorHandler.cs @@ -1,5 +1,4 @@ -using System; -namespace APES.UI.XF.Sample +namespace APES.UI.XF.Sample { public interface IErrorHandler { diff --git a/samples/APES.UI.Samples.Shared/TaskExtensions.cs b/samples/APES.UI.Samples.Shared/TaskExtensions.cs index e4b9c5e..7525dfa 100644 --- a/samples/APES.UI.Samples.Shared/TaskExtensions.cs +++ b/samples/APES.UI.Samples.Shared/TaskExtensions.cs @@ -1,7 +1,4 @@ -using System; -using System.Threading.Tasks; - -namespace APES.UI.XF.Sample +namespace APES.UI.XF.Sample { public static class TaskExtension { diff --git a/samples/APES.UI.Samples.Shared/ViewModels/MainViewModel.cs b/samples/APES.UI.Samples.Shared/ViewModels/MainViewModel.cs index efadc93..6cc3cc4 100644 --- a/samples/APES.UI.Samples.Shared/ViewModels/MainViewModel.cs +++ b/samples/APES.UI.Samples.Shared/ViewModels/MainViewModel.cs @@ -1,7 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; using System.ComponentModel; using System.Windows.Input; #if MAUI @@ -15,10 +11,8 @@ namespace APES.UI.XF.Sample.ViewModels public class MainViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler? PropertyChanged; - protected void NotifyPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] String propertyName = "") - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } + protected void NotifyPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] String propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + protected bool SetField(ref T field, T value, [System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) { if (EqualityComparer.Default.Equals(field, value)) return false; @@ -26,7 +20,8 @@ protected bool SetField(ref T field, T value, [System.Runtime.CompilerService NotifyPropertyChanged(propertyName); return true; } - string text = "Default text"; + + private string text = "Default text"; public string Text { get => text; @@ -39,7 +34,7 @@ public string Text public ICommand ConstructiveCommand { get; } public ICommand NeverEndingCommand { get; } - ContextMenuItems imageContextItems = new ContextMenuItems(); + private ContextMenuItems imageContextItems = new ContextMenuItems(); public ContextMenuItems ImageContextItems { get => imageContextItems; @@ -101,16 +96,13 @@ public MainViewModel() FillAllImageActions(); } - void OnFirstCommandExecuted(string s) - { - Text = s; - } - void OnSecondCommandExecuted() - { - Text = $"Action was pressed {++SecondCounter} times!"; - } + + private void OnFirstCommandExecuted(string s) => Text = s; + + private void OnSecondCommandExecuted() => Text = $"Action was pressed {++SecondCounter} times!"; internal int SecondCounter { get; set; } = 0; - void DestructiveHandler() + + private void DestructiveHandler() { ImageContextItems.Clear(); ImageContextItems.Add(new ContextMenuItem() @@ -122,13 +114,14 @@ void DestructiveHandler() NotifyPropertyChanged(nameof(ImageContextItems)); } - void ConstructiveHandler() + + private void ConstructiveHandler() { ImageContextItems.Clear(); FillAllImageActions(); } - void FillAllImageActions() + private void FillAllImageActions() { for (var i = 1; i < 5; i++) { @@ -149,7 +142,8 @@ void FillAllImageActions() }); NotifyPropertyChanged(nameof(ImageContextItems)); } - async Task NeverendingTask() + + private async Task NeverendingTask() { while(true) { @@ -158,7 +152,8 @@ async Task NeverendingTask() await Task.Delay(5000); } } - long neverEndingCounter; + + private long neverEndingCounter; public long NeverEndingCounter { get => neverEndingCounter; @@ -166,10 +161,10 @@ public long NeverEndingCounter } - readonly FileImageSource logoIconSource; + private readonly FileImageSource logoIconSource; public FileImageSource LogoIconSource => logoIconSource; - readonly FileImageSource deleteIconSource; - readonly FileImageSource settingsIconSource; + private readonly FileImageSource deleteIconSource; + private readonly FileImageSource settingsIconSource; public FileImageSource SettingsIconSource => settingsIconSource; } diff --git a/samples/APES.UI.XF.Sample/App.xaml.cs b/samples/APES.UI.XF.Sample/App.xaml.cs index 2125e54..d76b08a 100644 --- a/samples/APES.UI.XF.Sample/App.xaml.cs +++ b/samples/APES.UI.XF.Sample/App.xaml.cs @@ -1,6 +1,4 @@ -using System; -using Xamarin.Forms; -using Xamarin.Forms.Xaml; +using Xamarin.Forms; namespace APES.UI.XF.Sample { @@ -9,7 +7,7 @@ public partial class App : Application public App() { InitializeComponent(); - APES.UI.XF.ContextMenuContainer.Init(); + ContextMenuContainer.Init(); MainPage = new NavPage(new MainPage()); } diff --git a/samples/APES.UI.XF.Sample/MainPage.xaml.cs b/samples/APES.UI.XF.Sample/MainPage.xaml.cs index cdb9376..eb391f9 100644 --- a/samples/APES.UI.XF.Sample/MainPage.xaml.cs +++ b/samples/APES.UI.XF.Sample/MainPage.xaml.cs @@ -1,18 +1,9 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Xamarin.Forms; +using Xamarin.Forms; namespace APES.UI.XF.Sample { public partial class MainPage : ContentPage { - public MainPage() - { - InitializeComponent(); - } + public MainPage() => InitializeComponent(); } } diff --git a/samples/APES.UI.XF.Sample/NavPage.xaml.cs b/samples/APES.UI.XF.Sample/NavPage.xaml.cs index de1972d..86d7d30 100644 --- a/samples/APES.UI.XF.Sample/NavPage.xaml.cs +++ b/samples/APES.UI.XF.Sample/NavPage.xaml.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using Xamarin.Forms; +using Xamarin.Forms; using Xamarin.Forms.Xaml; namespace APES.UI.XF.Sample @@ -12,9 +6,6 @@ namespace APES.UI.XF.Sample [XamlCompilation(XamlCompilationOptions.Compile)] public partial class NavPage : NavigationPage { - public NavPage(Page root) : base(root) - { - InitializeComponent(); - } + public NavPage(Page root) : base(root) => InitializeComponent(); } } \ No newline at end of file diff --git a/src/APES.UI.XF.csproj b/src/APES.UI.XF.csproj index fc28953..8c00612 100644 --- a/src/APES.UI.XF.csproj +++ b/src/APES.UI.XF.csproj @@ -8,7 +8,8 @@ $(TargetFrameworks);net6.0-windows10.0.17763.0;uap10.0.17763 14.2 14.0 - 21.0 + 21.0 + 29.0 10.0.17763.0 10.0.17763.0 @@ -37,11 +38,11 @@ https://github.com/anpin/ContextMenuContainer https://github.com/anpin/ContextMenuContainer Added MAUI targets - xamarin, windows, ios, android, mac, xamarin.forms, plugin, contextmenu, rightmenu, MAUI + xamarin, windows, ios, android, mac, xamarin.forms, plugin, contextmenu, rightmenu, actions, MAUI ContextMenuContainer Plugin for Xamarin.Forms and MAUI - Xamarin.Forms and MAUI multi-platform context menu plugin - Xamarin.Forms and MAUI plugin to add native context menu to any view. Supports UWP, Android, iOS and macOS and MAUI. + Xamarin.Forms and MAUI multi-platform context menu plugin + Xamarin.Forms and MAUI plugin to add native context menu to any view. Renders platform-specific context action on UWP, Android, iOS and macOS. Pavel Anpin @@ -169,4 +170,14 @@ --> + + ..\.stylecop.ruleset + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + diff --git a/src/APES.UI.XF.csproj.DotSettings b/src/APES.UI.XF.csproj.DotSettings new file mode 100644 index 0000000..3b3ba33 --- /dev/null +++ b/src/APES.UI.XF.csproj.DotSettings @@ -0,0 +1,4 @@ + + True + True + True \ No newline at end of file diff --git a/src/Droid/ContextMenuContainerRenderer.cs b/src/Droid/ContextMenuContainerRenderer.cs index 7b0143d..b97eee9 100644 --- a/src/Droid/ContextMenuContainerRenderer.cs +++ b/src/Droid/ContextMenuContainerRenderer.cs @@ -1,179 +1,189 @@ +// MIT License +// Copyright (c) 2021 Pavel Anpin + +#pragma warning disable SA1137 using System; using System.Linq; using System.Threading; -using Android.App; using Android.Content; -using Android.Graphics; -using Android.OS; -using Android.Views; +using Android.Graphics.Drawables; using Android.Text; using Android.Text.Style; -using Android.Graphics.Drawables; -using Android.Runtime; -using Android.Util; +using Android.Views; using AndroidX.AppCompat.Widget; -using DrawableWrapperX = AndroidX.AppCompat.Graphics.Drawable.DrawableWrapper; using Java.Lang.Reflect; -using APES.UI.XF; -using Path = System.IO.Path; using AColor = Android.Graphics.Color; +using DrawableWrapperX = AndroidX.AppCompat.Graphics.Drawable.DrawableWrapper; +using Path = System.IO.Path; #if MAUI +using Android.Runtime; +using Android.Util; using Microsoft.Maui; -using Microsoft.Maui.Platform; using Microsoft.Maui.Controls; +using Microsoft.Maui.Dispatching; using Microsoft.Maui.Handlers; -using JetBrains.Annotations; +using Microsoft.Maui.Platform; -namespace APES.UI.XF; -sealed partial class ContextMenuContainerRenderer : ContentViewHandler +namespace APES.UI.XF { - //TODO: not sure if this is really needed, will test when debug is available for MAUI - bool wasSetOnce = false; - public override void SetVirtualView(IView view) + internal sealed class ContextMenuContainerRenderer : ContentViewHandler { - if (wasSetOnce) - { - var old = VirtualView as ContextMenuContainer; - old.BindingContextChanged -= Element_BindingContextChanged; - old.MenuItems.CollectionChanged -= MenuItems_CollectionChanged; - } + private bool _wasSetOnce; - base.SetVirtualView(view); - - if (VirtualView is ContextMenuContainer newElement) + public override void SetVirtualView(IView view) { - newElement.BindingContextChanged += Element_BindingContextChanged; - newElement.MenuItems.CollectionChanged += MenuItems_CollectionChanged; - - //PlatformView.AddSubview(newElement.Content.ToContainerView(MauiContext)); - //SetContent(); - //PlatformView.View = newElement; - RefillMenuItems(); - wasSetOnce = true; - } - } + if (_wasSetOnce) + { + if (VirtualView is ContextMenuContainer old) + { + old.BindingContextChanged -= Element_BindingContextChanged; + if (old.MenuItems != null) + { + old.MenuItems.CollectionChanged -= MenuItems_CollectionChanged; + } + } + } + base.SetVirtualView(view); - void RefillMenuItems() - { - if (VirtualView is ContextMenuContainer container) - { - constructInteraction(container); - } - } + if (VirtualView is ContextMenuContainer newElement) + { + newElement.BindingContextChanged += Element_BindingContextChanged; + if (newElement.MenuItems != null) + { + newElement.MenuItems.CollectionChanged += MenuItems_CollectionChanged; + } - void MenuItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) - { - RefillMenuItems(); - } + RefillMenuItems(); + _wasSetOnce = true; + } + } - void Element_BindingContextChanged(object sender, EventArgs e) - { - RefillMenuItems(); - } + protected override ContentViewGroup CreatePlatformView() + { + if (VirtualView == null) + { + throw new InvalidOperationException($"{nameof(VirtualView)} must be set to create a ContentViewGroup"); + } - void constructInteraction(ContextMenuContainer menuItems) - { - ((ContainerViewGroup)PlatformView).SetupMenu(menuItems); - //deconstructIntercation(); - //if (menuItems?.Count > 0) - //{ - // foreach (var item in menuItems) - // { - // AddMenuItem(item); - // } - //} - } + if (VirtualView is not ContextMenuContainer) + { + throw new InvalidOperationException( + $"{nameof(VirtualView)} must be of type ContextMenuContainer, but was {VirtualView.GetType()} "); + } - protected override ContentViewGroup CreatePlatformView() - { - if (VirtualView == null) - { - throw new InvalidOperationException($"{nameof(VirtualView)} must be set to create a ContentViewGroup"); + var viewGroup = new ContainerViewGroup(Context); + return viewGroup; } - if (VirtualView is not ContextMenuContainer) + + private void RefillMenuItems() { - throw new InvalidOperationException($"{nameof(VirtualView)} must be of type ContextMenuContainer, but was {VirtualView.GetType()} "); + if (VirtualView is ContextMenuContainer container) + { + ConstructInteraction(container); + } } - var viewGroup = new ContainerViewGroup(Context); - //{ - // CrossPlatformMeasure = VirtualView.CrossPlatformMeasure, - // CrossPlatformArrange = VirtualView.CrossPlatformArrange - //}; - return viewGroup; - } + private void MenuItems_CollectionChanged( + object? sender, + System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + => RefillMenuItems(); - class ContainerViewGroup : ContentViewGroup - { + private void Element_BindingContextChanged(object? sender, EventArgs e) + => RefillMenuItems(); + private void ConstructInteraction(ContextMenuContainer menuItems) => + ((ContainerViewGroup)PlatformView).SetupMenu(menuItems); - private ContextMenuContainer? Element; - public ContainerViewGroup([NotNull] Context context) : base(context) + private class ContainerViewGroup : ContentViewGroup { +#pragma warning disable SA1306 +#pragma warning disable SX1309 + // ReSharper disable once InconsistentNaming + private ContextMenuContainer? Element; +#pragma warning restore SX1309 +#pragma warning restore SA1306 + + public ContainerViewGroup(Context context) + : base(context) + { + } - } - - public ContainerViewGroup(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer) - { - } + // ReSharper disable once UnusedMember.Local + public ContainerViewGroup(IntPtr javaReference, JniHandleOwnership transfer) + : base(javaReference, transfer) + { + } - public ContainerViewGroup([NotNull] Context context, [NotNull] IAttributeSet attrs) : base(context, attrs) - { - } + // ReSharper disable once UnusedMember.Local + public ContainerViewGroup(Context context, IAttributeSet attrs) + : base(context, attrs) + { + } - public ContainerViewGroup([NotNull] Context context, [NotNull] IAttributeSet attrs, int defStyleAttr) : - base(context, attrs, defStyleAttr) - { - } + // ReSharper disable once UnusedMember.Local + public ContainerViewGroup(Context context, IAttributeSet attrs, int defStyleAttr) + : base(context, attrs, defStyleAttr) + { + } - public ContainerViewGroup([NotNull] Context context, [NotNull] IAttributeSet attrs, int defStyleAttr, - int defStyleRes) : base(context, attrs, defStyleAttr, defStyleRes) - { - } + // ReSharper disable once UnusedMember.Local + public ContainerViewGroup(Context context, IAttributeSet attrs, int defStyleAttr, int defStyleRes) + : base(context, attrs, defStyleAttr, defStyleRes) + { + } - public void SetupMenu(ContextMenuContainer? container) - { - deconstructIntercation(); - Element = container; - } + public void SetupMenu(ContextMenuContainer? container) + { + DeconstructInteraction(); + Element = container; + } #else +using Android.OS; +using APES.UI.XF; +using APES.UI.XF.Droid; using Xamarin.Forms; -using Xamarin.Forms.Internals; using Xamarin.Forms.Platform.Android; -using APES.UI.XF.Droid; -using XView = Xamarin.Forms.View; using PreserveAttribute = Xamarin.Forms.Internals.PreserveAttribute; +using XView = Xamarin.Forms.View; + [assembly: ExportRenderer(typeof(ContextMenuContainer), typeof(ContextMenuContainerRenderer))] + namespace APES.UI.XF.Droid { [Preserve(AllMembers = true)] - class ContextMenuContainerRenderer : ViewRenderer - + internal class ContextMenuContainerRenderer : ViewRenderer { - - public ContextMenuContainerRenderer(Context context) : base(context) + public ContextMenuContainerRenderer(Context context) + : base(context) { - } - + protected override void OnElementChanged(ElementChangedEventArgs e) { base.OnElementChanged(e); if (e.OldElement is ContextMenuContainer old) { old.BindingContextChanged -= Element_BindingContextChanged; - old.MenuItems.CollectionChanged -= MenuItems_CollectionChanged; + if (old.MenuItems != null) + { + old.MenuItems.CollectionChanged -= MenuItems_CollectionChanged; + } } + if (e.NewElement is ContextMenuContainer newElement) - { + { newElement.BindingContextChanged += Element_BindingContextChanged; - newElement.MenuItems.CollectionChanged += MenuItems_CollectionChanged; + if (newElement.MenuItems != null) + { + newElement.MenuItems.CollectionChanged += MenuItems_CollectionChanged; + } } } - void constructInteraction(ContextMenuContainer container) + + private void ConstructInteraction(ContextMenuContainer container) { - deconstructIntercation(); + DeconstructInteraction(); if (container.MenuItems?.Count > 0) { foreach (var item in container.MenuItems) @@ -183,67 +193,68 @@ void constructInteraction(ContextMenuContainer container) } } - void RefillMenuItems() + private void RefillMenuItems() { if (Element is ContextMenuContainer container) { - constructInteraction(container); + ConstructInteraction(container); } } - void MenuItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) - { - RefillMenuItems(); - } - void Element_BindingContextChanged(object sender, EventArgs e) - { - RefillMenuItems(); - } + private void MenuItems_CollectionChanged( + object? sender, + System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + => RefillMenuItems(); + + private void Element_BindingContextChanged(object sender, EventArgs e) + => RefillMenuItems(); #endif - PopupMenu? contextMenu; - MyTimer? timer; - bool timerFired = false; +#pragma warning disable SA1201 + private PopupMenu? _contextMenu; +#pragma warning restore SA1201 + private MyTimer? _timer; + private bool _timerFired; - private bool enabled => Element is ContextMenuContainer element && element.MenuItems.Count > 0; + // ReSharper disable once RedundantTypeCheckInPattern + private bool ContextMenuIsNotEmpty => Element is ContextMenuContainer {MenuItems.Count: > 0}; - void deconstructIntercation() + public override bool DispatchTouchEvent(MotionEvent? e) { - if (Element != null && contextMenu != null) + if (e == null) { - contextMenu.Dismiss(); - contextMenu.Menu.Clear(); + return base.DispatchTouchEvent(e); } - } - public override bool DispatchTouchEvent(MotionEvent e) - { + bool result; Logger.Debug("ContextMenuContainer DispatchTouchEvent fired {0}", e.Action); - if (enabled && e.Action == MotionEventActions.Down) + if (ContextMenuIsNotEmpty && e.Action == MotionEventActions.Down) { - //You can change the timespan of the long press - timerFired = false; - timer = new MyTimer(TimeSpan.FromMilliseconds(1500), () => + // You can change the timespan of the long press + _timerFired = false; + _timer = new MyTimer(TimeSpan.FromMilliseconds(1500), () => { - timerFired = true; + _timerFired = true; OpenContextMenu(); }); - timer.Start(); + _timer.Start(); } - if (timerFired) + if (_timerFired) { result = true; } - else if (e.Action == MotionEventActions.Up || e.Action == MotionEventActions.Cancel) + else if (e.Action is MotionEventActions.Up or MotionEventActions.Cancel) { - timer?.Stop(); + _timer?.Stop(); result = base.DispatchTouchEvent(e); } else { result = base.DispatchTouchEvent(e); - if (!result && enabled) + + // ReSharper disable once ConvertIfToOrExpression + if (!result && ContextMenuIsNotEmpty) { result = true; } @@ -252,53 +263,70 @@ public override bool DispatchTouchEvent(MotionEvent e) return result; } + private void DeconstructInteraction() + { + if (Element != null && _contextMenu != null) + { + _contextMenu.Dismiss(); + _contextMenu.Menu.Clear(); + } + } - - void OpenContextMenu() + private void OpenContextMenu() { if (GetContextMenu() == null) { ConstructNativeMenu(); FillMenuItems(); - } - contextMenu?.Show(); + _contextMenu?.Show(); } - void ConstructNativeMenu() + private void ConstructNativeMenu() { var child = GetChildAt(0); if (child == null) + { return; - contextMenu = new PopupMenu(Context, child); - contextMenu.MenuItemClick += ContextMenu_MenuItemClick; - Field field = contextMenu.Class.GetDeclaredField("mPopup"); + } + + _contextMenu = new PopupMenu(Context, child); + _contextMenu.MenuItemClick += ContextMenu_MenuItemClick; + Field field = _contextMenu.Class.GetDeclaredField("mPopup"); field.Accessible = true; - Java.Lang.Object? menuPopupHelper = field.Get(contextMenu); + Java.Lang.Object? menuPopupHelper = field.Get(_contextMenu); Method? setForceIcons = - menuPopupHelper?.Class.GetDeclaredMethod("setForceShowIcon", Java.Lang.Boolean.Type); + menuPopupHelper?.Class.GetDeclaredMethod("setForceShowIcon", Java.Lang.Boolean.Type!); setForceIcons?.Invoke(menuPopupHelper, true); } - void DeconstructNativeMenu() + private void DeconstructNativeMenu() { - if (contextMenu == null) + if (_contextMenu == null) + { return; - contextMenu.MenuItemClick -= ContextMenu_MenuItemClick; - contextMenu.Dispose(); - contextMenu = null; - } + } + _contextMenu.MenuItemClick -= ContextMenu_MenuItemClick; + _contextMenu.Dispose(); + _contextMenu = null; + } - void AddMenuItem(ContextMenuItem item) + private void AddMenuItem(ContextMenuItem item) { - if (contextMenu == null) + if (_contextMenu == null) + { return; + } + var title = new SpannableString(item.Text); if (item.IsDestructive) + { title.SetSpan(new ForegroundColorSpan(AColor.Red), 0, title.Length(), 0); - var contextAction = contextMenu.Menu.Add(title); + } + + var contextAction = _contextMenu.Menu.Add(title); if (contextAction == null) { Logger.Error("We couldn't create IMenuItem with title {0}", item.Text); @@ -308,56 +336,61 @@ void AddMenuItem(ContextMenuItem item) contextAction.SetEnabled(item.IsEnabled); if (item.Icon != null) { - var name = Path.GetFileNameWithoutExtension(item.Icon.File); - var id = Context.GetDrawableId(name); - if (id != 0) + string name = Path.GetFileNameWithoutExtension(item.Icon.File); + int id = Context?.GetDrawableId(name) ?? 0; + if (id == 0) { + return; + } #if MAUI - Drawable? drawable = Context?.GetDrawable(id); + Drawable? drawable = Context?.GetDrawable(id); #else - Drawable? drawable = (int)Build.VERSION.SdkInt >= 21 - ? Context?.GetDrawable(id) - : Context?.GetDrawable(name); + Drawable? drawable = (int)Build.VERSION.SdkInt >= 21 + ? Context?.GetDrawable(id) + : Context?.GetDrawable(name); #endif - if (drawable != null) + if (drawable != null) + { + var wrapper = new DrawableWrapperX(drawable); + if (item.IsDestructive) { - var wrapper = new DrawableWrapperX(drawable); - if (item.IsDestructive) - wrapper.SetTint(AColor.Red); - contextAction.SetIcon(wrapper); + wrapper.SetTint(AColor.Red); } + + contextAction.SetIcon(wrapper); } } } - void FillMenuItems() + private void FillMenuItems() { - if (Element is ContextMenuContainer element) + // ReSharper disable once RedundantTypeCheckInPattern + if (Element is ContextMenuContainer {MenuItems.Count: > 0} element) { - if (element.MenuItems.Count > 0) + foreach (var item in element.MenuItems) { - foreach (var item in element.MenuItems) - { - AddMenuItem(item); - } + AddMenuItem(item); } } } - PopupMenu? GetContextMenu() +#pragma warning disable SA1137 + private PopupMenu? GetContextMenu() +#pragma warning restore SA1137 { - if (contextMenu != null && Element is ContextMenuContainer element) + // ReSharper disable once ConvertTypeCheckPatternToNullCheck + if (_contextMenu != null && Element is ContextMenuContainer element) { - if (element.MenuItems.Count != contextMenu.Menu.Size()) + if (element.MenuItems?.Count != _contextMenu.Menu.Size()) { DeconstructNativeMenu(); } else { - for (int i = 0; i < contextMenu.Menu.Size(); i++) + for (int i = 0; i < _contextMenu.Menu.Size(); i++) { if (!element.MenuItems[i].Text - .Equals(contextMenu.Menu.GetItem(i)?.TitleFormatted?.ToString())) + .Equals(_contextMenu.Menu.GetItem(i)?.TitleFormatted?.ToString())) { DeconstructNativeMenu(); break; @@ -366,50 +399,61 @@ void FillMenuItems() } } - return contextMenu; + return _contextMenu; } - private void ContextMenu_MenuItemClick(object sender, PopupMenu.MenuItemClickEventArgs e) + private void ContextMenu_MenuItemClick(object? sender, PopupMenu.MenuItemClickEventArgs e) { - - var item = ((ContextMenuContainer)Element).MenuItems.FirstOrDefault(x => + // ReSharper disable once RedundantCast + var item = ((ContextMenuContainer?)Element)?.MenuItems?.FirstOrDefault(x => x.Text == e.Item.TitleFormatted?.ToString()); item?.OnItemTapped(); } + #if MAUI - } + } + #endif - class MyTimer + private class MyTimer { - private readonly TimeSpan timespan; - private readonly Action callback; + private readonly TimeSpan _timespan; + private readonly Action _callback; - private CancellationTokenSource cancellation; + private CancellationTokenSource _cancellation; public MyTimer(TimeSpan timespan, Action callback) { - this.timespan = timespan; - this.callback = callback; - this.cancellation = new CancellationTokenSource(); + _timespan = timespan; + _callback = callback; + _cancellation = new CancellationTokenSource(); } + public void Start() { - CancellationTokenSource cts = this.cancellation; // safe copy - Device.StartTimer(this.timespan, - () => + CancellationTokenSource cts = _cancellation; // safe copy +#if MAUI + DispatcherProvider.Current.GetForCurrentThread() !.StartTimer( +#else + Device.StartTimer( +#endif +#pragma warning disable SA1114 + interval: _timespan, +#pragma warning restore SA1114 + callback: () => { - if (cts.IsCancellationRequested) return false; - this.callback.Invoke(); + if (cts.IsCancellationRequested) + { + return false; + } + + _callback.Invoke(); return false; // or true for periodic behavior }); } - public void Stop() - { - Interlocked.Exchange(ref this.cancellation, new CancellationTokenSource()).Cancel(); - } + public void Stop() => Interlocked.Exchange(ref _cancellation, new CancellationTokenSource()).Cancel(); } } -#if !MAUI } -#endif + +#pragma warning restore SA1137 diff --git a/src/Mac/ContextContainerNativeView.cs b/src/Mac/ContextContainerNativeView.cs index f625e1a..518e572 100644 --- a/src/Mac/ContextContainerNativeView.cs +++ b/src/Mac/ContextContainerNativeView.cs @@ -1,133 +1,159 @@ -using Foundation; +// MIT License +// Copyright (c) 2021 Pavel Anpin + +using System; using AppKit; -using CoreText; using CoreGraphics; +using Foundation; using Xamarin.Forms.Platform.MacOS; -using Xamarin.Forms.PlatformConfiguration.macOSSpecific; -using System; + namespace APES.UI.XF.Mac { - public class ContextContainerNativeView : NSView + public sealed class ContextContainerNativeView : NSView { - readonly ContextMenuItems? MenuItems; - NSMenu contextMenu; - IVisualElementRenderer ChildRenderer; + private readonly ContextMenuItems? _menuItems; + private NSMenu? _contextMenu; + public ContextContainerNativeView(IVisualElementRenderer childRenderer, ContextMenuItems? contextMenuItems) { - ChildRenderer = childRenderer ?? throw new ArgumentNullException(nameof(childRenderer)); - MenuItems = contextMenuItems; - if(MenuItems != null) - MenuItems.CollectionChanged += MenuItems_CollectionChanged; - AddSubview(ChildRenderer.NativeView); + if (childRenderer == null) + { + throw new ArgumentNullException(nameof(childRenderer)); + } + + _menuItems = contextMenuItems; + if (_menuItems != null) + { + _menuItems.CollectionChanged += MenuItems_CollectionChanged; + } + + AddSubview(childRenderer.NativeView); } - private void MenuItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + public override void RightMouseDown(NSEvent theEvent) { - RefillMenuItems(); + HandleContextActions(theEvent); + + base.RightMouseDown(theEvent); } - public override void RightMouseDown(NSEvent theEvent) - { - HandleContextActions(theEvent); - - base.RightMouseDown(theEvent); - } - - NSMenu? GetContextMenu() - { - if (contextMenu != null && MenuItems != null) - { - if (MenuItems.Count != contextMenu.Count) - { - RefillMenuItems(); - } - else - { - for (int i = 0; i < contextMenu.Count; i++) - { - var nativeItem = contextMenu.ItemWithTag(i); - if (!MenuItems[i].Text.Equals(nativeItem?.AttributedTitle.Value)) - { - RefillMenuItems(); - break; - } - } - } - } - return contextMenu; - } - void HandleContextActions(NSEvent theEvent) - { - if (MenuItems == null) - return; - var count = MenuItems.Count; - if (count == 0) - return; - if (GetContextMenu() == null) - { - ConstructNativeMenu(); - FillMenuItems(); - - } - NSMenu.PopUpContextMenu(contextMenu, theEvent, this); - - } - - void ConstructNativeMenu() + private void MenuItems_CollectionChanged( + object? sender, + System.Collections.Specialized.NotifyCollectionChangedEventArgs e) => RefillMenuItems(); + + private NSMenu? GetContextMenu() + { + if (_contextMenu != null && _menuItems != null) + { + if (_menuItems.Count != _contextMenu.Count) + { + RefillMenuItems(); + } + else + { + for (int i = 0; i < _contextMenu.Count; i++) + { + var nativeItem = _contextMenu.ItemWithTag(i); + if (!_menuItems[i].Text.Equals(nativeItem?.AttributedTitle.Value)) + { + RefillMenuItems(); + break; + } + } + } + } + + return _contextMenu; + } + + private void HandleContextActions(NSEvent theEvent) + { + if (_menuItems == null) + { + return; + } + + int count = _menuItems.Count; + if (count == 0) + { + return; + } + + if (GetContextMenu() == null) + { + ConstructNativeMenu(); + FillMenuItems(); + } + + NSMenu.PopUpContextMenu(_contextMenu!, theEvent, this); + } + + private void ConstructNativeMenu() => _contextMenu = new NSMenu(); + + private void FillMenuItems() { - contextMenu = new NSMenu(); + if (_menuItems?.Count > 0) + { + for (int i = 0; i < _menuItems.Count; i++) + { + _contextMenu!.AddItem(ToNSMenuItem(i, _menuItems[i])); + } + + _contextMenu!.Update(); + } + } + + private void RefillMenuItems() + { + if (_contextMenu == null) + { + return; + } + + _contextMenu.CancelTracking(); + _contextMenu.RemoveAllItems(); + FillMenuItems(); + } + +#pragma warning disable SA1202 + + // ReSharper disable once InconsistentNaming + public NSMenuItem ToNSMenuItem(int i, ContextMenuItem menuItem) +#pragma warning restore SA1202 + { + NSMenuItem nsMenuItem = new NSMenuItem(); + nsMenuItem.AttributedTitle = new NSAttributedString( + menuItem.Text, + foregroundColor: menuItem.IsDestructive ? NSColor.Red : null); + nsMenuItem.Tag = i; + nsMenuItem.Enabled = menuItem.IsEnabled; + nsMenuItem.Activated += NsMenuItem_Activated; + nsMenuItem.ValidateMenuItem = (t) => t.Enabled; + var nativeIcon = menuItem.Icon?.ToNative(); + if (nativeIcon != null) + { + var elementColor = menuItem.IsDestructive ? NSColor.Red : + NSAppearance.CurrentAppearance.Name == NSAppearance.NameDarkAqua ? NSColor.White : NSColor.Black; + nsMenuItem.Image = ImageHandler.ImageTintedWithColor(nativeIcon, elementColor, new CGSize(25, 25)); + } + + return nsMenuItem; } - - - void FillMenuItems() - { - if (MenuItems?.Count > 0) - { - for (var i = 0; i < MenuItems.Count; i++) - { - contextMenu.AddItem(ToNSMenuItem(i, MenuItems[i])); - } - contextMenu.Update(); - } - } - void RefillMenuItems() - { - if (contextMenu == null) - return; - contextMenu.CancelTracking(); - contextMenu.RemoveAllItems(); - FillMenuItems(); - } - public NSMenuItem ToNSMenuItem(int i, ContextMenuItem menuItem) - { - NSMenuItem nsMenuItem = new NSMenuItem(); - nsMenuItem.AttributedTitle = new NSAttributedString(menuItem.Text ?? "", foregroundColor: menuItem.IsDestructive ? NSColor.Red : null ); - nsMenuItem.Tag = i; - nsMenuItem.Enabled = menuItem.IsEnabled; - nsMenuItem.Activated += NsMenuItem_Activated; - nsMenuItem.ValidateMenuItem = new Func((t) => t.Enabled); - var nativeIcon = menuItem.Icon.ToNative(); - if (nativeIcon != null) - { - var elementColor = menuItem.IsDestructive ? NSColor.Red : NSAppearance.CurrentAppearance.Name == NSAppearance.NameDarkAqua ? NSColor.White : NSColor.Black; - nsMenuItem.Image = ImageHandler.ImageTintedWithColor(nativeIcon, elementColor, new CGSize(25, 25)); - } - return nsMenuItem; - } private void NsMenuItem_Activated(object sender, EventArgs e) { - var nsMenuItem = sender as NSMenuItem; - if(nsMenuItem == null) + var nsMenuItem = sender as NSMenuItem; + if (nsMenuItem == null) + { + Logger.Error("Couldn't cast sender to NSMenuItem"); + return; + } + + // that seems to have no effect + if (nsMenuItem.Enabled) { - Logger.Error("Couldn't cast sender to NSMenuItem"); - return; + _menuItems?[(int)nsMenuItem.Tag].OnItemTapped(); } - //that seems to have no effect - if (nsMenuItem.Enabled) - { - MenuItems[(int)nsMenuItem.Tag].OnItemTapped(); - } - } - } + } + } } diff --git a/src/Mac/ContextMenuContainerRenderer.cs b/src/Mac/ContextMenuContainerRenderer.cs index 58ae4da..2e63957 100644 --- a/src/Mac/ContextMenuContainerRenderer.cs +++ b/src/Mac/ContextMenuContainerRenderer.cs @@ -1,32 +1,30 @@ -using System; -using System.Collections.Generic; -using System.Text; -using AppKit; +// MIT License +// Copyright (c) 2021 Pavel Anpin + +using APES.UI.XF; +using APES.UI.XF.Mac; using Xamarin.Forms; using Xamarin.Forms.Internals; using Xamarin.Forms.Platform.MacOS; -using APES.UI.XF; -using APES.UI.XF.Mac; + [assembly: ExportRenderer(typeof(ContextMenuContainer), typeof(ContextMenuContainerRenderer))] + namespace APES.UI.XF.Mac { [Preserve(AllMembers = true)] - class ContextMenuContainerRenderer : ViewRenderer + internal class ContextMenuContainerRenderer : ViewRenderer { protected override void OnElementChanged(ElementChangedEventArgs e) { base.OnElementChanged(e); - if (e.OldElement != null) - { - - } if (e.NewElement == null || e.NewElement.Content == null) { return; } + var childRenderer = Platform.CreateRenderer(Element.Content); - var nativeConainer = new ContextContainerNativeView(childRenderer, Element.MenuItems); - SetNativeControl(nativeConainer); + var nativeContainer = new ContextContainerNativeView(childRenderer, Element.MenuItems); + SetNativeControl(nativeContainer); } } } diff --git a/src/Mac/ImageHandler.cs b/src/Mac/ImageHandler.cs index 40190bf..50d82d8 100644 --- a/src/Mac/ImageHandler.cs +++ b/src/Mac/ImageHandler.cs @@ -1,25 +1,32 @@ -using System.IO; +// MIT License +// Copyright (c) 2021 Pavel Anpin + +using System.IO; using AppKit; using CoreGraphics; using Xamarin.Forms; + namespace APES.UI.XF.Mac { public static class ImageHandler { - public static NSImage? ToNative(this FileImageSource source) + public static NSImage? ToNative(this FileImageSource? source) { NSImage? image = null; - var file = source?.File; + string? file = source?.File; if (!string.IsNullOrWhiteSpace(file)) - image = File.Exists(file) ? new NSImage(file) : NSImage.ImageNamed(file); + { + image = File.Exists(file) ? new NSImage(file!) : NSImage.ImageNamed(file!); + } + return image; } - public static NSImage ImageTintedWithColor(NSImage sourceImage, NSColor tintColor, CGSize? size = null) - { - return NSImage.ImageWithSize(size ?? sourceImage.Size, false, rect => + + public static NSImage ImageTintedWithColor(NSImage sourceImage, NSColor tintColor, CGSize? size = null) => + NSImage.ImageWithSize(size ?? sourceImage.Size, false, rect => { // Draw the original source image - sourceImage.DrawInRect(rect, CGRect.Empty, NSCompositingOperation.SourceOver, 1f); + sourceImage.Draw(rect, CGRect.Empty, NSCompositingOperation.SourceOver, 1f); // Apply tint tintColor.Set(); @@ -27,6 +34,5 @@ public static NSImage ImageTintedWithColor(NSImage sourceImage, NSColor tintColo return true; }); - } } } diff --git a/src/Shared/AssemblyConfiguration.cs b/src/Shared/AssemblyConfiguration.cs index de4a350..265d9ea 100644 --- a/src/Shared/AssemblyConfiguration.cs +++ b/src/Shared/AssemblyConfiguration.cs @@ -1,11 +1,15 @@ -using System.Runtime.CompilerServices; +// MIT License +// Copyright (c) 2021 Pavel Anpin + +using System.Runtime.CompilerServices; #if MAUI -using Microsoft.Maui; using Microsoft.Maui.Controls; -#else + +#else using Xamarin.Forms; + #endif [assembly: XmlnsDefinition("http://apes.ge", "APES.UI.XF")] [assembly: XmlnsPrefix("http://apes.ge", "apes")] [assembly: InternalsVisibleTo("APES.UI.XF.Tests")] -[assembly: InternalsVisibleTo("APES.UI.MAUI.Sample")] \ No newline at end of file +[assembly: InternalsVisibleTo("APES.UI.MAUI.Sample")] diff --git a/src/Shared/ContextMenuContainer.cs b/src/Shared/ContextMenuContainer.cs index 007a1df..95dcc5f 100644 --- a/src/Shared/ContextMenuContainer.cs +++ b/src/Shared/ContextMenuContainer.cs @@ -1,110 +1,102 @@ -using System; +// MIT License +// Copyright (c) 2021 Pavel Anpin + using System.Collections.Generic; #if MAUI -using Microsoft.Maui; using Microsoft.Maui.Controls; + #else using Xamarin.Forms; + #endif namespace APES.UI.XF { public class ContextMenuContainer : ContentView { + public static readonly BindableProperty MenuItemsProperty = + BindableProperty.Create( + nameof(MenuItems), + typeof(ContextMenuItems), + typeof(VisualElement), + defaultValueCreator: DefaultMenuItemsCreator, + propertyChanged: OnMenuItemsChanged); + + public ContextMenuItems? MenuItems + { + get => (ContextMenuItems?)GetValue(MenuItemsProperty); + set => SetValue(MenuItemsProperty, value); + } + /// - /// Call this in order to preserve our code during linking and allow namespace resolution in XAML + /// Call this in order to preserve our code during linking and allow namespace resolution in XAML. /// public static void Init() { - //maybe do something here later + // maybe do something here later } - public static readonly BindableProperty MenuItemsProperty = - BindableProperty.Create(nameof(MenuItems), - typeof(ContextMenuItems), - typeof(VisualElement), - defaultValueCreator: DefaulfMenuItemsCreator, - propertyChanged: OnMenuItemsChanged); - static object DefaulfMenuItemsCreator(BindableObject bindableObject) + protected override void OnBindingContextChanged() + { + base.OnBindingContextChanged(); + if (MenuItems != null) + { + SetBindingContextForItems(MenuItems); + } + } + + private static object DefaultMenuItemsCreator(BindableObject bindableObject) { var menuItems = new ContextMenuItems(); - menuItems.CollectionChanged += (s, e) => + menuItems.CollectionChanged += (_, e) => { if (e.OldItems != null) { foreach (ContextMenuItem item in e.OldItems) { - item.RemoveBinding(ContextMenuItem.BindingContextProperty); + item.RemoveBinding(BindingContextProperty); } } + if (e.NewItems != null) { foreach (ContextMenuItem item in e.NewItems) { - BindableObject.SetInheritedBindingContext(item, bindableObject.BindingContext); + SetInheritedBindingContext(item, bindableObject.BindingContext); } } }; return menuItems; } - static void OnMenuItemsChanged(BindableObject bindableObject, object newValue, object oldValue) + + private static void OnMenuItemsChanged(BindableObject bindableObject, object newValue, object oldValue) { if (oldValue is ContextMenuItems oldItems) { foreach (ContextMenuItem item in oldItems) { - item.RemoveBinding(ContextMenuContainer.BindingContextProperty); + item.RemoveBinding(BindingContextProperty); } - //oldItems.CollectionChanged -= MenuItems_CollectionChanged; + + // oldItems.CollectionChanged -= MenuItems_CollectionChanged; } + if (newValue is ContextMenuItems newItems) { foreach (ContextMenuItem item in newItems) { - BindableObject.SetInheritedBindingContext(item, bindableObject.BindingContext); + SetInheritedBindingContext(item, bindableObject.BindingContext); } } } - //private static void MenuItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) - //{ - // if (e.OldItems != null) - // { - // foreach (ContextMenuItem item in e.OldItems) - // { - // item.RemoveBinding(ContextMenuItem.BindingContextProperty); - // } - // } - // if (e.NewItems != null) - // { - // ((ContextMenuContainer)sender).SetBindingContextForItems((IList)e.NewItems); - // } - //} - - public ContextMenuItems MenuItems + private void SetBindingContextForItems(IList items) { - get => (ContextMenuItems)GetValue(MenuItemsProperty); - set => SetValue(MenuItemsProperty, value); - } - protected override void OnBindingContextChanged() - { - base.OnBindingContextChanged(); - SetBindingContextForItems(MenuItems); - } - void SetBindingContextForItems(IList items) - { - - for (var i = 0; i < items.Count; i++) + for (int i = 0; i < items.Count; i++) { SetBindingContextForItem(items[i]); } } - void SetBindingContextForItem(ContextMenuItem item) - { - BindableObject.SetInheritedBindingContext(item, BindingContext); - } - public ContextMenuContainer() - { - //GestureRecognizers.Add(); - } + + private void SetBindingContextForItem(ContextMenuItem item) => SetInheritedBindingContext(item, BindingContext); } } diff --git a/src/Shared/ContextMenuContainerExtensions.cs b/src/Shared/ContextMenuContainerExtensions.cs index 06b27a9..6ab62be 100644 --- a/src/Shared/ContextMenuContainerExtensions.cs +++ b/src/Shared/ContextMenuContainerExtensions.cs @@ -1,6 +1,7 @@ -using System; -using System.Collections.Generic; -#if MAUI +// MIT License +// Copyright (c) 2021 Pavel Anpin + +#if MAUI && (__ANDROID__ || __IOS__ || __MACCATALYST__ || __WINDOWS__) using Microsoft.Maui.Hosting; #endif @@ -8,17 +9,11 @@ namespace APES.UI.XF { public static class ContextMenuContainerExtensions { - public static bool HasMenuOptions(this ContextMenuContainer container) => container.MenuItems.Count > 0; -#if MAUI - public static MauiAppBuilder ConfigureContextMenuContainer(this MauiAppBuilder mauiAppBuilder) - { - - return mauiAppBuilder.ConfigureMauiHandlers(handlers => - { - handlers.AddHandler(); + public static bool HasMenuOptions(this ContextMenuContainer container) => container.MenuItems?.Count > 0; - }); - } +#if MAUI && (__ANDROID__ || __IOS__ || __MACCATALYST__ || __WINDOWS__) + public static MauiAppBuilder ConfigureContextMenuContainer(this MauiAppBuilder mauiAppBuilder) => + mauiAppBuilder.ConfigureMauiHandlers(handlers => handlers.AddHandler()); #endif } } diff --git a/src/Shared/ContextMenuContainerRenderer.cs b/src/Shared/ContextMenuContainerRenderer.cs deleted file mode 100644 index ffffb10..0000000 --- a/src/Shared/ContextMenuContainerRenderer.cs +++ /dev/null @@ -1,12 +0,0 @@ -#if MAUI -using System; -using Microsoft.Maui; -using Microsoft.Maui.Controls.Internals; -using Microsoft.Maui.Handlers; -namespace APES.UI.XF; - -sealed partial class ContextMenuContainerRenderer : ContentViewHandler -{ - -} -#endif diff --git a/src/Shared/ContextMenuItem.cs b/src/Shared/ContextMenuItem.cs index a59ba8c..2ae51b3 100644 --- a/src/Shared/ContextMenuItem.cs +++ b/src/Shared/ContextMenuItem.cs @@ -1,68 +1,78 @@ -using System; -using System.Collections.Generic; -using System.Text; +// MIT License +// Copyright (c) 2021 Pavel Anpin + using System.Windows.Input; + #if MAUI -using Microsoft.Maui; using Microsoft.Maui.Controls; + #else using Xamarin.Forms; + #endif namespace APES.UI.XF { - public partial class ContextMenuItem : Element + public delegate void ItemTapped(ContextMenuItem item); + + public class ContextMenuItem : Element { public static readonly BindableProperty TextProperty = BindableProperty.Create(nameof(Text), typeof(string), typeof(ContextMenuItem)); + + public static readonly BindableProperty CommandProperty = BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(ContextMenuItem)); + + public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create(nameof(CommandParameter), typeof(object), typeof(ContextMenuItem)); + + public static readonly BindableProperty IsEnabledProperty = BindableProperty.Create(nameof(IsEnabled), typeof(bool), typeof(ContextMenuItem), true); + + public static readonly BindableProperty IsDestructiveProperty = BindableProperty.Create(nameof(IsDestructive), typeof(bool), typeof(ContextMenuItem)); + + public static readonly BindableProperty IconProperty = BindableProperty.Create(nameof(Icon), typeof(FileImageSource), typeof(ContextMenuItem)); + + public event ItemTapped? ItemTapped; + public string Text { get => (string)GetValue(TextProperty); set => SetValue(TextProperty, value); } - public static readonly BindableProperty CommandProperty = BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(ContextMenuItem)); - public ICommand Command + public ICommand? Command { - get => (ICommand)GetValue(CommandProperty); + get => (ICommand?)GetValue(CommandProperty); set => SetValue(CommandProperty, value); } - public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create(nameof(CommandParameter), typeof(object), typeof(ContextMenuItem)); public object CommandParameter { - get => (object)GetValue(CommandParameterProperty); + get => GetValue(CommandParameterProperty); set => SetValue(CommandParameterProperty, value); } - public static readonly BindableProperty IsEnabledProperty = BindableProperty.Create(nameof(IsEnabled), typeof(bool), typeof(ContextMenuItem), true); public bool IsEnabled { get => (bool)GetValue(IsEnabledProperty); set => SetValue(IsEnabledProperty, value); } - public static readonly BindableProperty IsDestructiveProperty = BindableProperty.Create(nameof(IsDestructive), typeof(bool), typeof(ContextMenuItem)); public bool IsDestructive { get => (bool)GetValue(IsDestructiveProperty); set => SetValue(IsDestructiveProperty, value); } - public static readonly BindableProperty IconProperty = BindableProperty.Create(nameof(Icon), typeof(FileImageSource), typeof(ContextMenuItem)); - public FileImageSource Icon + public FileImageSource? Icon { - get => (FileImageSource)GetValue(IconProperty); + get => (FileImageSource?)GetValue(IconProperty); set => SetValue(IconProperty, value); } internal void OnItemTapped() { - - ItemTapped?.Invoke(this, new EventArgs()); - if (Command?.CanExecute(CommandParameter) ?? false && IsEnabled) + ItemTapped?.Invoke(this); + if (Command?.CanExecute(CommandParameter) ?? IsEnabled) { - Command.Execute(CommandParameter); + Command?.Execute(CommandParameter); } } - public event EventHandler? ItemTapped; } } diff --git a/src/Shared/ContextMenuItems.cs b/src/Shared/ContextMenuItems.cs index 01daa40..2e9804a 100644 --- a/src/Shared/ContextMenuItems.cs +++ b/src/Shared/ContextMenuItems.cs @@ -1,5 +1,9 @@ -using System; +// MIT License +// Copyright (c) 2021 Pavel Anpin + +using System; using System.Collections.ObjectModel; + namespace APES.UI.XF { public class ContextMenuItems : ObservableCollection @@ -8,7 +12,7 @@ public class ContextMenuItems : ObservableCollection private ContextMenuItem FindTextIndex(string text) { - for (int j = 0; j < Items.Count; j++) + for (int j = 0; j < Items.Count; j++) { if (Items[j].Text == text) { diff --git a/src/Shared/Logger.cs b/src/Shared/Logger.cs index 6f80ef9..a415cde 100644 --- a/src/Shared/Logger.cs +++ b/src/Shared/Logger.cs @@ -1,9 +1,14 @@ -using System; +// MIT License +// Copyright (c) 2021 Pavel Anpin + +using System; using System.Runtime.CompilerServices; + [assembly: InternalsVisibleTo("APES.UI.XF.Droid")] [assembly: InternalsVisibleTo("APES.UI.XF.iOS")] [assembly: InternalsVisibleTo("APES.UI.XF.UWP")] [assembly: InternalsVisibleTo("APES.UI.XF.Mac")] + namespace APES.UI.XF { internal static class Logger @@ -11,27 +16,25 @@ internal static class Logger public static bool DebugEnabled { get; set; } = #if DEBUG true; + #else false; #endif public static void Debug(string format, params object[] parameters) { - if(DebugEnabled) + if (DebugEnabled) + { DiagnosticLog("DEBUG " + format, parameters); + } } - public static void Error(string format, params object[] parameters) - { - DiagnosticLog("ERROR " + format, parameters); - } + public static void Error(string format, params object[] parameters) => DiagnosticLog("ERROR " + format, parameters); + + public static void Error(Exception exception) => Error($"{exception.Message}{Environment.NewLine}{exception.Source}{Environment.NewLine}{exception.StackTrace}"); - public static void Error(Exception exception) - { - Error($"{exception.Message}{Environment.NewLine}{exception.Source}{Environment.NewLine}{exception.StackTrace}"); - } private static void DiagnosticLog(string format, params object[] parameters) { - var formatWithHeader = " APES.UI.XF " + DateTime.Now.ToString("MM-dd H:mm:ss.fff ") + format; + string formatWithHeader = " APES.UI.XF " + DateTime.Now.ToString("MM-dd H:mm:ss.fff ") + format; #if DEBUG System.Diagnostics.Debug.WriteLine(formatWithHeader, parameters); #else diff --git a/src/UWP/ContextMenuContainerRenderer.cs b/src/UWP/ContextMenuContainerRenderer.cs index 667a9b4..14415e6 100644 --- a/src/UWP/ContextMenuContainerRenderer.cs +++ b/src/UWP/ContextMenuContainerRenderer.cs @@ -1,97 +1,98 @@ +// MIT License +// Copyright (c) 2021 Pavel Anpin + using System; -using System.Linq; -using System.ComponentModel; -using System.Numerics; -using System.Threading.Tasks; -using System.Threading; -using System.Collections.Generic; using System.Collections.Specialized; #if MAUI -using Microsoft.Maui; -using Microsoft.Maui.Platform; +using APES.UI.XF.UWP; using Microsoft.Maui.Controls.Internals; using Microsoft.Maui.Handlers; +using Microsoft.Maui.Platform; using Microsoft.UI.Input; using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Automation.Peers; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls.Primitives; using Microsoft.UI.Xaml.Input; -using Microsoft.UI.Xaml.Media; -using Microsoft.UI.Xaml.Media.Animation; -using WColors = Microsoft.UI.Colors; -using WBinding = Microsoft.UI.Xaml.Data.Binding; using MenuFlyoutItem = Microsoft.UI.Xaml.Controls.MenuFlyoutItem; using Setter = Microsoft.UI.Xaml.Setter; using SolidColorBrush = Microsoft.UI.Xaml.Media.SolidColorBrush; using Style = Microsoft.UI.Xaml.Style; -using APES.UI.XF.UWP; +using WBinding = Microsoft.UI.Xaml.Data.Binding; +using WColors = Microsoft.UI.Colors; +using WControl = Microsoft.UI.Xaml.Controls.Control; -namespace APES.UI.XF; -[Preserve(AllMembers = true)] -sealed partial class ContextMenuContainerRenderer : ContentViewHandler +namespace APES.UI.XF { - private ContextMenuContainer Element => (ContextMenuContainer)VirtualView; - - protected override ContentPanel CreatePlatformView() + [Preserve(AllMembers = true)] + internal sealed class ContextMenuContainerRenderer : ContentViewHandler { - var result =base.CreatePlatformView(); - result.PointerReleased += PlatformViewPointerReleased; - return result; - } + private ContextMenuContainer Element => (ContextMenuContainer)VirtualView; + + protected override ContentPanel CreatePlatformView() + { + var result = base.CreatePlatformView(); + result.PointerReleased += PlatformViewPointerReleased; + return result; + } + + // ReSharper disable once InconsistentNaming +#pragma warning disable SA1300 +#pragma warning disable SA1201 + private FrameworkElement _platformView => PlatformView; +#pragma warning restore SA1201 +#pragma warning restore SA1300 - public override void SetVirtualView(IView view) - { - base.SetVirtualView(view); - - } #else +using APES.UI.XF; +using APES.UI.XF.UWP; using Windows.UI.Input; using Windows.UI.Xaml; -using Windows.UI.Xaml.Automation.Peers; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls.Primitives; using Windows.UI.Xaml.Input; using Windows.UI.Xaml.Media; -using Windows.UI.Xaml.Media.Animation; using Xamarin.Forms.Internals; using Xamarin.Forms.Platform.UWP; -using WColors = Windows.UI.Colors; using WBinding = Windows.UI.Xaml.Data.Binding; -using APES.UI.XF; -using APES.UI.XF.UWP; +using WColors = Windows.UI.Colors; +using WControl = Windows.UI.Xaml.Controls.Control; [assembly: ExportRenderer(typeof(ContextMenuContainer), typeof(ContextMenuContainerRenderer))] -namespace APES.UI.XF.UWP +// ReSharper disable once CheckNamespace +namespace APES.UI.XF.UWP { [Preserve(AllMembers = true)] public class ContextMenuContainerRenderer : ViewRenderer { +#pragma warning disable SA1201 + private FrameworkElement? _platformView; +#pragma warning restore SA1201 + + public ContextMenuContainerRenderer() => AutoPackage = false; - FrameworkElement? PlatformView; - public ContextMenuContainerRenderer() - { - AutoPackage = false; - } protected override void OnElementChanged(ElementChangedEventArgs e) { base.OnElementChanged(e); if (e.OldElement != null) { - //unsubscribe from events here + // unsubscribe from events here } + if (e.NewElement == null) { return; } + if (Control == null) { SetNativeControl(new ContentControl()); } + Pack(); } - void Pack() + + private void Pack() { if (Element.Content == null) { @@ -99,152 +100,180 @@ void Pack() } IVisualElementRenderer renderer = Element.Content.GetOrCreateRenderer(); - PlatformView = renderer.ContainerElement; - PlatformView.PointerReleased += PlatformViewPointerReleased; - //PlatformView.Holding += FrameworkElement_Holding; - Control.Content = PlatformView; + _platformView = renderer.ContainerElement; + _platformView.PointerReleased += PlatformViewPointerReleased; + + // PlatformView.Holding += FrameworkElement_Holding; + Control.Content = _platformView; } + #endif - private void PlatformViewPointerReleased(object sender, PointerRoutedEventArgs e) { - PointerPoint point = e.GetCurrentPoint(PlatformView); + PointerPoint point = e.GetCurrentPoint(_platformView); if (point.Properties.PointerUpdateKind != PointerUpdateKind.RightButtonReleased) + { return; + } + try { if (Element.HasMenuOptions()) + { OpenContextMenu(); + } } catch (Exception ex) { Logger.Error(ex); } } - MenuFlyout? GetContextMenu() + + private MenuFlyout? GetContextMenu() { - if (FlyoutBase.GetAttachedFlyout(PlatformView) is MenuFlyout flyout) + if (FlyoutBase.GetAttachedFlyout(_platformView) is MenuFlyout flyout) { var actions = Element.MenuItems; - if (flyout.Items.Count != actions.Count) + if (flyout.Items?.Count != actions?.Count) + { return null; + } - for (int i = 0; i < flyout.Items.Count; i++) + for (int i = 0; i < flyout.Items?.Count; i++) { - if (flyout.Items[i].DataContext != actions[i]) + if (flyout.Items[i].DataContext != actions?[i]) + { return null; + } } + return flyout; } + return null; } - void OpenContextMenu() + + private void OpenContextMenu() { if (GetContextMenu() == null) { var flyout = new MenuFlyout(); SetupMenuItems(flyout); - Element.MenuItems.CollectionChanged += MenuItems_CollectionChanged; + if (Element.MenuItems != null) + { + Element.MenuItems.CollectionChanged += MenuItems_CollectionChanged; + } - FlyoutBase.SetAttachedFlyout(PlatformView, flyout); + FlyoutBase.SetAttachedFlyout(_platformView, flyout); } - FlyoutBase.ShowAttachedFlyout(PlatformView); + FlyoutBase.ShowAttachedFlyout(_platformView); } - private void MenuItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + private void MenuItems_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { var menu = GetContextMenu(); if (menu != null) { - menu.Items.Clear(); + if (menu.Items != null) + { + menu.Items.Clear(); + } + SetupMenuItems(menu); } } - void SetupMenuItems(MenuFlyout menu) + private void SetupMenuItems(MenuFlyout menu) { - - foreach (var item in Element.MenuItems) + if (Element.MenuItems != null) { - AddMenuItem(menu, item); + foreach (var item in Element.MenuItems) + { + AddMenuItem(menu, item); + } } } - void AddMenuItem(MenuFlyout contextMenu, ContextMenuItem item) + + private void AddMenuItem(MenuFlyout contextMenu, ContextMenuItem item) { var nativeItem = new MenuFlyoutItem(); - nativeItem.SetBinding(MenuFlyoutItem.TextProperty, new WBinding() - { - Path = new PropertyPath(nameof(ContextMenuItem.Text)), - }); - - //nativeItem.SetBinding(MenuFlyoutItem.CommandProperty, new WBinding() - //{ - // Path = new PropertyPath(nameof(ContextMenuItem.Command)), - //}); - - //nativeItem.SetBinding(MenuFlyoutItem.CommandParameterProperty, new WBinding() - //{ - // Path = new PropertyPath(nameof(ContextMenuItem.CommandParameter)), - //}); + nativeItem.SetBinding( + MenuFlyoutItem.TextProperty, + new WBinding() { Path = new PropertyPath(nameof(ContextMenuItem.Text)) }); - nativeItem.SetBinding(MenuFlyoutItem.IconProperty, new WBinding() - { - Path = new PropertyPath(nameof(ContextMenuItem.Icon)), - Converter = ImageConverter, - }); - nativeItem.SetBinding(MenuFlyoutItem.StyleProperty, new WBinding() - { - Path = new PropertyPath(nameof(ContextMenuItem.IsDestructive)), - Converter = BoolToStytleConverter, - }); - nativeItem.SetBinding(MenuFlyoutItem.IsEnabledProperty, new WBinding() + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (ImageConverter != null) { - Path = new PropertyPath(nameof(ContextMenuItem.IsEnabled)), - }); + nativeItem.SetBinding( + MenuFlyoutItem.IconProperty, + new WBinding() { Path = new PropertyPath(nameof(ContextMenuItem.Icon)), Converter = ImageConverter }); + } + + nativeItem.SetBinding( + FrameworkElement.StyleProperty, + new WBinding() + { + Path = new PropertyPath(nameof(ContextMenuItem.IsDestructive)), + Converter = BoolToStyleConverter, + }); + nativeItem.SetBinding( + WControl.IsEnabledProperty, + new WBinding() { Path = new PropertyPath(nameof(ContextMenuItem.IsEnabled)) }); nativeItem.Click += NativeItem_Click; nativeItem.DataContext = item; - contextMenu.Items.Add(nativeItem); + if (contextMenu.Items != null) + { + contextMenu.Items.Add(nativeItem); + } } private void NativeItem_Click(object sender, RoutedEventArgs e) { var item = sender as MenuFlyoutItem; - if(item == null) + if (item == null) { Logger.Error("Couldn't cast to MenuFlyoutItem"); return; } - var context = item.DataContext as ContextMenuItem; - if (context == null) + + if (item.DataContext is not ContextMenuItem context) { Logger.Error("Couldn't cast MenuFlyoutItem.DataContext to ContextMenuItem"); return; } + context.OnItemTapped(); } - static Style DestructiveStyle { get; } = new Style() +#pragma warning disable SA1201 + private static Style DestructiveStyle { get; } = new Style() +#pragma warning restore SA1201 { TargetType = typeof(MenuFlyoutItem), Setters = { - new Setter(MenuFlyoutItem.ForegroundProperty, new SolidColorBrush(WColors.Red)), - } + new Setter( + WControl.ForegroundProperty, + new SolidColorBrush(WColors.Red)), + }, }; - static Style NondDestructiveStyle { get; } = new Style() + + private static Style NonDestructiveStyle { get; } = new Style() { TargetType = typeof(MenuFlyoutItem), Setters = { - //new Setter(MenuFlyoutItem.ForegroundProperty, new SolidColorBrush(WColors.Red)), - } + // new Setter(MenuFlyoutItem.ForegroundProperty, new SolidColorBrush(WColors.Red)), + }, }; - static FileImageSourceToBitmapIconSourceConverter ImageConverter { get; } = new FileImageSourceToBitmapIconSourceConverter(); - static GenericBoolConverter