From 239965ff472354780a9e5acefb82c4971230392c Mon Sep 17 00:00:00 2001 From: Yauheni Pakala Date: Fri, 27 Jan 2023 15:30:04 +0200 Subject: [PATCH 01/26] Update documentation & CI (#501) --- README.md | 3 +- .../INotificationsSettingsProvider.cs | 21 ++++---- azure-pipelines/Documentation.yml | 4 ++ azure-pipelines/templates/vars.yml | 4 +- documentation/Properties/AssemblyInfo.cs | 14 ------ .../Softeq.XToolkit.Documentation.csproj | 48 ------------------- .../Softeq.XToolkit.Documentation.sln | 17 ------- .../create-storyboard-viewcontroller.md | 6 ++- documentation/articles/xtoolkit/overview.md | 2 +- .../articles/xtoolkit/push-notifications.md | 6 +-- .../articles/xtoolkit/version-support.md | 41 +++++----------- documentation/articles/xtoolkit/whitelabel.md | 2 +- .../articles/xtoolkit/whitelabel/forms.md | 3 ++ documentation/build.sh | 9 ++-- 14 files changed, 50 insertions(+), 130 deletions(-) delete mode 100644 documentation/Properties/AssemblyInfo.cs delete mode 100644 documentation/Softeq.XToolkit.Documentation.csproj delete mode 100644 documentation/Softeq.XToolkit.Documentation.sln diff --git a/README.md b/README.md index 40dd0175a..733d6f34b 100644 --- a/README.md +++ b/README.md @@ -17,10 +17,9 @@ Common | [![Softeq.XToolkit.Common](https://buildstats.info/nuget/Softeq.XToo Bindings | [![Softeq.XToolkit.Bindings](https://buildstats.info/nuget/Softeq.XToolkit.Bindings?includePreReleases=true)](https://www.nuget.org/packages/Softeq.XToolkit.Bindings) Permissions | [![Softeq.XToolkit.Permissions](https://buildstats.info/nuget/Softeq.XToolkit.Permissions?includePreReleases=true)](https://www.nuget.org/packages/Softeq.XToolkit.Permissions) PushNotifications | [![Softeq.XToolkit.PushNotifications](https://buildstats.info/nuget/Softeq.XToolkit.PushNotifications?includePreReleases=true)](https://www.nuget.org/packages/Softeq.XToolkit.PushNotifications) +Remote | [![Softeq.XToolkit.Remote](https://buildstats.info/nuget/Softeq.XToolkit.Remote?includePreReleases=true)](https://www.nuget.org/packages/Softeq.XToolkit.Remote) WhiteLabel | [![Softeq.XToolkit.WhiteLabel](https://buildstats.info/nuget/Softeq.XToolkit.WhiteLabel?includePreReleases=true)](https://www.nuget.org/packages/Softeq.XToolkit.WhiteLabel) WhiteLabel.Essentials | [![Softeq.XToolkit.WhiteLabel.Essentials](https://buildstats.info/nuget/Softeq.XToolkit.WhiteLabel.Essentials?includePreReleases=true)](https://www.nuget.org/packages/Softeq.XToolkit.WhiteLabel.Essentials) -WhiteLabel.Forms | [![Softeq.XToolkit.WhiteLabel.Forms](https://buildstats.info/nuget/Softeq.XToolkit.WhiteLabel.Forms?includePreReleases=true)](https://www.nuget.org/packages/Softeq.XToolkit.WhiteLabel.Forms) -Remote | [![Softeq.XToolkit.Remote](https://buildstats.info/nuget/Softeq.XToolkit.Remote?includePreReleases=true)](https://www.nuget.org/packages/Softeq.XToolkit.Remote) ## Documentation diff --git a/Softeq.XToolkit.PushNotifications.Droid/INotificationsSettingsProvider.cs b/Softeq.XToolkit.PushNotifications.Droid/INotificationsSettingsProvider.cs index 29245fd90..a7f844775 100644 --- a/Softeq.XToolkit.PushNotifications.Droid/INotificationsSettingsProvider.cs +++ b/Softeq.XToolkit.PushNotifications.Droid/INotificationsSettingsProvider.cs @@ -22,10 +22,12 @@ public interface INotificationsSettingsProvider string LaunchedFromPushNotificationKey { get; } /// - /// Obtains a dictionary of notification channles where the key is channel id and the value is channel name. - /// - /// Note: if you are using "notification" notifications, Firebase will use a separate channel for them when received in Background. + /// Obtains a dictionary of notification channels where the key is channel id and the value is channel name. /// + /// + /// if you are using "notification" notifications, + /// Firebase will use a separate channel for them when received in Background. + /// /// /// Context for obtaining channel names from resources. /// @@ -45,9 +47,8 @@ public interface INotificationsSettingsProvider /// /// You can add some custom configuration for a created Notification Channel (like description, sound, /// turn off badges, create and set group - to create a group, etc.). - /// - /// This method will only be called on API 26+. /// + /// This method will only be called on API 26+. /// Channel Id string. /// /// A NotificationChannel that will be registered in the system. Contains already set channelId, @@ -68,18 +69,22 @@ public interface INotificationsSettingsProvider /// want them to replace each other. /// /// Push notification data. - /// + /// Styles. PushNotificationStyles GetStylesForNotification(PushNotificationModel pushNotification); /// - /// You can customize how push notification will be shown (apart from ContentTitle, ContentText, channelid, + /// You can customize how push notification will be shown (apart from ContentTitle, ContentText, channelId, /// styles set in GetStylesForNotification and content intent). For instance, you can use SetNumber to change badge /// value increment on Android 26+; add Action buttons; add groups and create additional notifications - like group /// summary notification; add progress bar and later update for this notificationId; or simply save notificationId; etc. /// /// Already created notification builder that can be further customized. /// Push notification data. - void CustomizeNotificationBuilder(NotificationCompat.Builder notificationBuilder, PushNotificationModel pushNotification, int notificationId); + /// + void CustomizeNotificationBuilder( + NotificationCompat.Builder notificationBuilder, + PushNotificationModel pushNotification, + int notificationId); /// /// Provides info about the Activity which will be opened by tap on the given notification. diff --git a/azure-pipelines/Documentation.yml b/azure-pipelines/Documentation.yml index cb302a5ba..bb3700750 100644 --- a/azure-pipelines/Documentation.yml +++ b/azure-pipelines/Documentation.yml @@ -29,6 +29,10 @@ jobs: # restore, build - script: 'msbuild XToolkit.sln -r /p:Configuration="Release" /p:Platform="Any CPU"' displayName: Build Solution + - task: UseDotNet@2 + displayName: Use .NET 6 SDK + inputs: + version: 6.0.405 - bash: 'cd documentation && sh build.sh' displayName: Build Documentation - task: CopyFiles@2 diff --git a/azure-pipelines/templates/vars.yml b/azure-pipelines/templates/vars.yml index afc2207cc..0ee98239a 100644 --- a/azure-pipelines/templates/vars.yml +++ b/azure-pipelines/templates/vars.yml @@ -3,8 +3,8 @@ # https://github.com/actions/runner-images variables: - MONO_VERSION: 6_12_19 - XCODE_VERSION: 13.4.1 + MONO_VERSION: 6_12_21 + XCODE_VERSION: 14.2 DOTNET_SDK_VERSION: 5.0.408 BuildConfiguration: 'Release' MACOS_VM_IMAGE: 'macos-12' diff --git a/documentation/Properties/AssemblyInfo.cs b/documentation/Properties/AssemblyInfo.cs deleted file mode 100644 index f9fe5a23c..000000000 --- a/documentation/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Reflection; - -[assembly: AssemblyTitle("Softeq.XToolkit.Documentation")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("")] -[assembly: AssemblyCopyright("${AuthorCopyright}")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: AssemblyVersion("1.0.*")] diff --git a/documentation/Softeq.XToolkit.Documentation.csproj b/documentation/Softeq.XToolkit.Documentation.csproj deleted file mode 100644 index d240afe2c..000000000 --- a/documentation/Softeq.XToolkit.Documentation.csproj +++ /dev/null @@ -1,48 +0,0 @@ - - - - Debug - AnyCPU - {8BCD873E-2BD1-4261-AFDF-D79421DBFEDF} - Library - Softeq.XToolkit.Documentation - Softeq.XToolkit.Documentation - v4.7 - - - true - portable - false - bin\Debug - DEBUG; - prompt - 4 - false - - - true - bin\Release - prompt - 4 - false - portable - - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - - \ No newline at end of file diff --git a/documentation/Softeq.XToolkit.Documentation.sln b/documentation/Softeq.XToolkit.Documentation.sln deleted file mode 100644 index a298eeaf8..000000000 --- a/documentation/Softeq.XToolkit.Documentation.sln +++ /dev/null @@ -1,17 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Softeq.XToolkit.Documentation", "Softeq.XToolkit.Documentation.csproj", "{8BCD873E-2BD1-4261-AFDF-D79421DBFEDF}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {8BCD873E-2BD1-4261-AFDF-D79421DBFEDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8BCD873E-2BD1-4261-AFDF-D79421DBFEDF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8BCD873E-2BD1-4261-AFDF-D79421DBFEDF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8BCD873E-2BD1-4261-AFDF-D79421DBFEDF}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal diff --git a/documentation/articles/create-storyboard-viewcontroller.md b/documentation/articles/create-storyboard-viewcontroller.md index 396cb322a..d8e6133c2 100644 --- a/documentation/articles/create-storyboard-viewcontroller.md +++ b/documentation/articles/create-storyboard-viewcontroller.md @@ -7,17 +7,21 @@ First of all, see [Navigation Requirements](xtoolkit/whitelabel/navigation-requi ### 1. Create Storyboard - VS4Mac: `Solution folder -> Add -> New File -> iOS -> Storyboard` +- Rider: `Solution folder -> Add -> Storyboard` - Create empty `MainPageStoryboard` ### 2. Interface Builder - Open storyboard via Interface Builder (IB) - VS4Mac: `MainPageStoryboard -> Open With -> Xcode Interface Builder` - - Rider: `todo` + - Rider: `MainPageStoryboard -> Open in Xcode` - IB: Select **Identity Inspector** and update next properties: - Custom Class > `Class` to `MainPageViewController` - Identity > `Storyboard ID` to `MainPageViewController` +> More detailed info: [Creating a Storyboard with Xcode +](https://learn.microsoft.com/en-us/xamarin/ios/user-interface/storyboards/indepth-storyboard?tabs=macos#creating-a-storyboard-with-xcode) + ### 3. Setup ViewController ```cs diff --git a/documentation/articles/xtoolkit/overview.md b/documentation/articles/xtoolkit/overview.md index 5385a9ffc..903108436 100644 --- a/documentation/articles/xtoolkit/overview.md +++ b/documentation/articles/xtoolkit/overview.md @@ -10,7 +10,7 @@ Library | Description | Supported platforms [Softeq.XToolkit.Remote](remote.md) | Library provides a common way to make HTTP requests. | Core [Softeq.XToolkit.WhiteLabel](whitelabel.md) | MVVM framework (DI, Navigation, MVVM) that based on XToolkit components. | Core, Android, iOS [Softeq.XToolkit.WhiteLabel.Essentials](whitelabel/essentials.md) | Library over the XToolkit.WhiteLabel that contains optional components for any application (like ImagePicker). | Core, Android, iOS -[Softeq.XToolkit.WhiteLabel.Forms](whitelabel/forms.md) | Integration library for using XToolkit.WhiteLabel in [Xamarin.Forms](https://github.com/xamarin/Xamarin.Forms) projects. | Core +~~[Softeq.XToolkit.WhiteLabel.Forms](whitelabel/forms.md)~~ (Deprecated) | Integration library for using XToolkit.WhiteLabel in [Xamarin.Forms](https://github.com/xamarin/Xamarin.Forms) projects. | Core ## Additional diff --git a/documentation/articles/xtoolkit/push-notifications.md b/documentation/articles/xtoolkit/push-notifications.md index 168f22377..2cfa2cf8b 100644 --- a/documentation/articles/xtoolkit/push-notifications.md +++ b/documentation/articles/xtoolkit/push-notifications.md @@ -96,8 +96,8 @@ Install-Package Softeq.XToolkit.PushNotifications 2. Perform standard Firebase setup * Create application project in Firebase Console and pass Server Key to the server-side * Add `Xamarin.Firebase.Messaging` to packages - * Add `google-services.json` to root Droid project with GoogleServicesJson Build Action - * Add the following to your **AndroidManifet.xml** inside application tag + * Add `google-services.json` to root Droid project with Build Action > `GoogleServicesJson` + * Add the following to your **AndroidManifet.xml** inside ``: ```xml @@ -109,8 +109,8 @@ Install-Package Softeq.XToolkit.PushNotifications ``` + * Optionally add also the following to specify icon and color: - * Optionally add also the following to specify icon and color ```xml diff --git a/documentation/articles/xtoolkit/version-support.md b/documentation/articles/xtoolkit/version-support.md index 3f804135e..8588748b9 100644 --- a/documentation/articles/xtoolkit/version-support.md +++ b/documentation/articles/xtoolkit/version-support.md @@ -1,29 +1,24 @@ # Version Support -Actual for the latest release: [v2.0.0-beta6](https://github.com/Softeq/XToolkit.WhiteLabel/releases/tag/v2.0.0-beta6) +Actual for the latest release: [v2.0.0-beta10](https://github.com/Softeq/XToolkit.WhiteLabel/releases/tag/v2.0.0-beta10) ## Main Versions -- Android v5.0 (API level 21), target v10.0 (API level 29) +- Android v5.0 (API level 21), target v12.0 (API level 31) - iOS 12+ -## Minimal Versions - -- Android v4.0 (API level 14) provided by support libraries -- iOS 11 - ## Components Versions ### Common - netstandard 2.1 -- Android - target v10 (API level 29) +- Android - target v12 (API level 31) - iOS 11+ ### Bindings - netstandard 2.1 -- Android - target v10 (API level 29) +- Android - target v12 (API level 31) - iOS 11+ ### Connectivity @@ -34,12 +29,12 @@ Actual for the latest release: [v2.0.0-beta6](https://github.com/Softeq/XToolkit ### Permissions - netstandard 2.1 -- Android - target v10 (API level 29) +- Android - target v12 (API level 31) ### Push Notifications - netstandard 2.1 -- Android - target v10 (API level 29) +- Android - target v12 (API level 31) - iOS 11+ ### Remote @@ -49,32 +44,22 @@ Actual for the latest release: [v2.0.0-beta6](https://github.com/Softeq/XToolkit ### WhiteLabel - netstandard 2.1 -- Android - target v10 (API level 29) +- Android - target v12 (API level 31) - iOS 11+ ### WhiteLabel.Essentials - netstandard 2.1 -- Android - target v10 (API level 29) -- iOS 11+ - -### WhiteLabel.Forms - -- netstandard 2.1 -- Android - target v10 (API level 29) +- Android - target v12 (API level 31) - iOS 11+ ## Development environment -Latest update [#262](https://github.com/Softeq/XToolkit.WhiteLabel/pull/262): - -- macOS 10.15+ -- Xcode 11.3.1+ -- Mono 6.6.0+ -- .NET SDK 3.1.102+ - -## Related Resources +Latest update in PR [#501](https://github.com/Softeq/XToolkit.WhiteLabel/pull/501): -- [Android Support Libraries - Version Support](https://developer.android.com/topic/libraries/support-library) +- macOS 12+ +- Xcode 14.2+ +- Mono 6.12.21 +- .NET SDK 5.0.408+ --- diff --git a/documentation/articles/xtoolkit/whitelabel.md b/documentation/articles/xtoolkit/whitelabel.md index 65752b227..eb760779d 100644 --- a/documentation/articles/xtoolkit/whitelabel.md +++ b/documentation/articles/xtoolkit/whitelabel.md @@ -31,6 +31,6 @@ Install-Package Softeq.XToolkit.WhiteLabel - [WhiteLabel.iOS](whitelabel/ios.md) - [WhiteLabel.Droid](whitelabel/droid.md) - [WhiteLabel.Essentials](whitelabel/essentials.md) -- [WhiteLabel.Forms](whitelabel/forms.md) +- ~~[WhiteLabel.Forms](whitelabel/forms.md)~~ (Deprecated) --- diff --git a/documentation/articles/xtoolkit/whitelabel/forms.md b/documentation/articles/xtoolkit/whitelabel/forms.md index 010112ea0..6e94c9025 100644 --- a/documentation/articles/xtoolkit/whitelabel/forms.md +++ b/documentation/articles/xtoolkit/whitelabel/forms.md @@ -2,6 +2,9 @@ Integration library for using WhiteLabel in Xamarin.Forms projects. +> [!IMPORTANT] +> This package is not supported anymore. The main reason is [Xamarin.Forms End of Support](https://dotnet.microsoft.com/en-us/platform/support/policy/xamarin). + ## Install You can install via NuGet: [![Softeq.XToolkit.WhiteLabel.Forms](https://buildstats.info/nuget/Softeq.XToolkit.WhiteLabel.Forms?includePreReleases=true)](https://www.nuget.org/packages/Softeq.XToolkit.WhiteLabel.Forms) diff --git a/documentation/build.sh b/documentation/build.sh index d512dc828..aac35cca0 100644 --- a/documentation/build.sh +++ b/documentation/build.sh @@ -3,17 +3,16 @@ # Build via csproj # msbuild -r Softeq.XToolkit.Documentation.csproj -DOCFX_VERSION="2.59.4" -DOCFX_PATH="$HOME/.nuget/packages/docfx.console/$DOCFX_VERSION/tools/docfx.exe" +DOCFX_VERSION="2.60.2" DOCFX_CONFIG_PATH="docfx.json" -nuget restore +dotnet tool install -g docfx --version $DOCFX_VERSION # Prepate toc.yml -mono $DOCFX_PATH metadata $DOCFX_CONFIG_PATH +docfx metadata $DOCFX_CONFIG_PATH # Apply patch to toc.yml sudo pwsh nested-namespaces.ps1 ./obj/docfxapi/toc.yml # Build documentation -mono $DOCFX_PATH build $DOCFX_CONFIG_PATH +docfx build $DOCFX_CONFIG_PATH From 846a0c50404a232f275b755f67e26e880ff02eab Mon Sep 17 00:00:00 2001 From: Yauheni Pakala Date: Tue, 14 Feb 2023 11:01:43 +0200 Subject: [PATCH 02/26] Simplify projects to SDK style (#498) --- Directory.Build.props | 5 + .../Properties/AssemblyInfo.cs | 31 -- .../Softeq.XToolkit.Bindings.Droid.csproj | 85 +--- .../Properties/AssemblyInfo.cs | 34 -- .../Softeq.XToolkit.Bindings.iOS.csproj | 83 +--- .../Properties/AssemblyInfo.cs | 31 -- .../Softeq.XToolkit.Common.Droid.Tests.csproj | 105 +---- .../Properties/AssemblyInfo.cs | 31 -- .../Softeq.XToolkit.Common.Droid.csproj | 63 +-- Softeq.XToolkit.Common.iOS.Tests/Info.plist | 2 +- .../Softeq.XToolkit.Common.iOS.Tests.csproj | 139 ++---- .../Properties/AssemblyInfo.cs | 34 -- .../Softeq.XToolkit.Common.iOS.csproj | 62 +-- .../Properties/AssemblyInfo.cs | 34 -- .../Softeq.XToolkit.Connectivity.iOS.csproj | 54 +-- .../Properties/AssemblyInfo.cs | 30 -- .../Softeq.XToolkit.Permissions.Droid.csproj | 59 +-- .../Properties/AssemblyInfo.cs | 30 -- .../Softeq.XToolkit.Permissions.iOS.csproj | 79 +--- .../Properties/AssemblyInfo.cs | 31 -- ...eq.XToolkit.PushNotifications.Droid.csproj | 71 +-- .../Properties/AssemblyInfo.cs | 34 -- ...fteq.XToolkit.PushNotifications.iOS.csproj | 63 +-- .../Properties/AssemblyInfo.cs | 31 -- ...on.axml => activity_bottom_navigation.xml} | 0 .../Softeq.XToolkit.WhiteLabel.Droid.csproj | 149 +------ .../Properties/AssemblyInfo.cs | 31 -- ...Toolkit.WhiteLabel.Essentials.Droid.csproj | 128 ++---- .../Properties/AssemblyInfo.cs | 34 -- ....XToolkit.WhiteLabel.Essentials.iOS.csproj | 122 ++---- .../Properties/AssemblyInfo.cs | 34 -- .../Softeq.XToolkit.WhiteLabel.iOS.csproj | 115 +---- global.json | 5 + .../Playground.Droid/Assets/AboutAssets.txt | 19 - .../Playground.Droid/Playground.Droid.csproj | 210 ++------- .../Properties/AssemblyInfo.cs | 31 -- .../Resources/AboutResources.txt | 44 -- .../Playground.iOS/Playground.iOS.csproj | 411 +++--------------- .../Playground.iOS/Properties/AssemblyInfo.cs | 35 -- .../Dialogs/CustomTransitioningDelegate.cs | 5 +- 40 files changed, 367 insertions(+), 2227 deletions(-) delete mode 100644 Softeq.XToolkit.Bindings.Droid/Properties/AssemblyInfo.cs delete mode 100644 Softeq.XToolkit.Bindings.iOS/Properties/AssemblyInfo.cs delete mode 100644 Softeq.XToolkit.Common.Droid.Tests/Properties/AssemblyInfo.cs delete mode 100644 Softeq.XToolkit.Common.Droid/Properties/AssemblyInfo.cs delete mode 100644 Softeq.XToolkit.Common.iOS/Properties/AssemblyInfo.cs delete mode 100644 Softeq.XToolkit.Connectivity.iOS/Properties/AssemblyInfo.cs delete mode 100644 Softeq.XToolkit.Permissions.Droid/Properties/AssemblyInfo.cs delete mode 100644 Softeq.XToolkit.Permissions.iOS/Properties/AssemblyInfo.cs delete mode 100644 Softeq.XToolkit.PushNotifications.Droid/Properties/AssemblyInfo.cs delete mode 100644 Softeq.XToolkit.PushNotifications.iOS/Properties/AssemblyInfo.cs delete mode 100644 Softeq.XToolkit.WhiteLabel.Droid/Properties/AssemblyInfo.cs rename Softeq.XToolkit.WhiteLabel.Droid/Resources/layout/{activity_bottom_navigation.axml => activity_bottom_navigation.xml} (100%) delete mode 100644 Softeq.XToolkit.WhiteLabel.Essentials.Droid/Properties/AssemblyInfo.cs delete mode 100644 Softeq.XToolkit.WhiteLabel.Essentials.iOS/Properties/AssemblyInfo.cs delete mode 100644 Softeq.XToolkit.WhiteLabel.iOS/Properties/AssemblyInfo.cs create mode 100644 global.json delete mode 100644 samples/Playground/Playground.Droid/Assets/AboutAssets.txt delete mode 100644 samples/Playground/Playground.Droid/Properties/AssemblyInfo.cs delete mode 100644 samples/Playground/Playground.Droid/Resources/AboutResources.txt delete mode 100644 samples/Playground/Playground.iOS/Properties/AssemblyInfo.cs diff --git a/Directory.Build.props b/Directory.Build.props index dbffb33ed..4a2fb705a 100755 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,6 +4,11 @@ 8.0 enable + + + XToolkit + Softeq Development Corporation + Copyright © 2023 Softeq Development Corporation \ No newline at end of file diff --git a/Softeq.XToolkit.Bindings.Droid/Properties/AssemblyInfo.cs b/Softeq.XToolkit.Bindings.Droid/Properties/AssemblyInfo.cs deleted file mode 100644 index 39b8e0f83..000000000 --- a/Softeq.XToolkit.Bindings.Droid/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Softeq.XToolkit.Bindings.Droid")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Softeq Development Corporation")] -[assembly: AssemblyProduct("XToolkit.Bindings")] -[assembly: AssemblyCopyright("Copyright © Softeq Development Corporation 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Softeq.XToolkit.Bindings.Droid/Softeq.XToolkit.Bindings.Droid.csproj b/Softeq.XToolkit.Bindings.Droid/Softeq.XToolkit.Bindings.Droid.csproj index d5a91973c..b45a5aef6 100644 --- a/Softeq.XToolkit.Bindings.Droid/Softeq.XToolkit.Bindings.Droid.csproj +++ b/Softeq.XToolkit.Bindings.Droid/Softeq.XToolkit.Bindings.Droid.csproj @@ -1,84 +1,31 @@ - - + + - Debug - AnyCPU - 8.0.30703 - 2.0 - {2B4A678B-63B4-49DB-99B1-BFE7793110C4} - {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Library - Properties - Softeq.XToolkit.Bindings.Droid - Softeq.XToolkit.Bindings.Droid - 512 - Resources\Resource.Designer.cs - Off - v12.0 + monoandroid12.0 + portable + true - portable - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 None + - portable - true - bin\Release\ - TRACE - prompt - 4 SdkOnly + - + + - - - - - - - - - + + + + - - - - - - - - - - - - - - - - - - - {0F1F09A8-9CDB-4933-AA1B-898AB43D394C} - Softeq.XToolkit.Bindings - - - {18d3fdc1-b0a1-401e-87f2-1c43034e610c} - Softeq.XToolkit.Common.Droid - - - {24588814-B93D-4528-8917-9C2A3C4E85CA} - Softeq.XToolkit.Common - + - - \ No newline at end of file + + diff --git a/Softeq.XToolkit.Bindings.iOS/Properties/AssemblyInfo.cs b/Softeq.XToolkit.Bindings.iOS/Properties/AssemblyInfo.cs deleted file mode 100644 index 56877ab0c..000000000 --- a/Softeq.XToolkit.Bindings.iOS/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Softeq.XToolkit.Binding.iOS")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Softeq Development Corporation")] -[assembly: AssemblyProduct("XToolkit.Bindings")] -[assembly: AssemblyCopyright("Copyright © Softeq Development Corporation 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("2d399ba9-1878-43e2-af05-6873eae9151b")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Softeq.XToolkit.Bindings.iOS/Softeq.XToolkit.Bindings.iOS.csproj b/Softeq.XToolkit.Bindings.iOS/Softeq.XToolkit.Bindings.iOS.csproj index 67f3b4e21..fd450bd8e 100644 --- a/Softeq.XToolkit.Bindings.iOS/Softeq.XToolkit.Bindings.iOS.csproj +++ b/Softeq.XToolkit.Bindings.iOS/Softeq.XToolkit.Bindings.iOS.csproj @@ -1,84 +1,23 @@ - - + + - Debug - AnyCPU - 8.0.30703 - 2.0 - {2D399BA9-1878-43E2-AF05-6873EAE9151B} - {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Library - Softeq.XToolkit.Bindings.iOS - Softeq.XToolkit.Bindings.iOS - Resources + xamarin.ios10 + portable + true - portable - false - bin\Debug - DEBUG; - prompt - 4 - false None + - portable - true - bin\Release - prompt - 4 - false SdkOnly + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {0F1F09A8-9CDB-4933-AA1B-898AB43D394C} - Softeq.XToolkit.Bindings - - - {24588814-B93D-4528-8917-9C2A3C4E85CA} - Softeq.XToolkit.Common - - - {6BCB2009-2E46-458C-BCAA-AFC27A631924} - Softeq.XToolkit.Common.iOS - + + + - + \ No newline at end of file diff --git a/Softeq.XToolkit.Common.Droid.Tests/Properties/AssemblyInfo.cs b/Softeq.XToolkit.Common.Droid.Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index a759c959e..000000000 --- a/Softeq.XToolkit.Common.Droid.Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Softeq.XToolkit.Common.Droid.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Softeq Development Corporation")] -[assembly: AssemblyProduct("XToolkit.Common")] -[assembly: AssemblyCopyright("Copyright © Softeq Development Corporation 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Softeq.XToolkit.Common.Droid.Tests/Softeq.XToolkit.Common.Droid.Tests.csproj b/Softeq.XToolkit.Common.Droid.Tests/Softeq.XToolkit.Common.Droid.Tests.csproj index e0e308857..53e17a26d 100644 --- a/Softeq.XToolkit.Common.Droid.Tests/Softeq.XToolkit.Common.Droid.Tests.csproj +++ b/Softeq.XToolkit.Common.Droid.Tests/Softeq.XToolkit.Common.Droid.Tests.csproj @@ -1,104 +1,37 @@ - - + + - Debug - AnyCPU - 8.0.30703 - 2.0 - {69A56CFD-E5A6-4A7D-9F41-25EB88975BF8} - {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - {122416d6-6b49-4ee2-a1e8-b825f31c79fe} - Library - Properties - Softeq.XToolkit.Common.Droid.Tests - Softeq.XToolkit.Common.Droid.Tests - 512 - True + monoandroid12.0 + portable True - Resources\Resource.designer.cs - Resource - Off - v12.0 Properties\AndroidManifest.xml - Resources - Assets - true - true + Xamarin.Android.Net.AndroidClientHandler + - True - portable - False - bin\Debug\ - DEBUG;TRACE - prompt - 4 - True + true None - False - armeabi-v7a;x86;arm64-v8a;x86_64 + - True - portable - True - bin\Release\ - TRACE - prompt - 4 - true - False SdkOnly - True + True + - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - {18d3fdc1-b0a1-401e-87f2-1c43034e610c} - Softeq.XToolkit.Common.Droid - - - {24588814-B93D-4528-8917-9C2A3C4E85CA} - Softeq.XToolkit.Common - - + - - + + \ No newline at end of file diff --git a/Softeq.XToolkit.Common.Droid/Properties/AssemblyInfo.cs b/Softeq.XToolkit.Common.Droid/Properties/AssemblyInfo.cs deleted file mode 100644 index 811f5b392..000000000 --- a/Softeq.XToolkit.Common.Droid/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Softeq.XToolkit.Common.Droid")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Softeq Development Corporation")] -[assembly: AssemblyProduct("XToolkit.Common")] -[assembly: AssemblyCopyright("Copyright © Softeq Development Corporation 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Softeq.XToolkit.Common.Droid/Softeq.XToolkit.Common.Droid.csproj b/Softeq.XToolkit.Common.Droid/Softeq.XToolkit.Common.Droid.csproj index 0c87beb16..30054cb53 100644 --- a/Softeq.XToolkit.Common.Droid/Softeq.XToolkit.Common.Droid.csproj +++ b/Softeq.XToolkit.Common.Droid/Softeq.XToolkit.Common.Droid.csproj @@ -1,64 +1,25 @@ - - + + - Debug - AnyCPU - 8.0.30703 - 2.0 - {18D3FDC1-B0A1-401E-87F2-1C43034E610C} - {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Library - Properties - Softeq.XToolkit.Common.Droid - Softeq.XToolkit.Common.Droid - 512 - Resources\Resource.Designer.cs - Off - v12.0 + monoandroid12.0 + portable + true - portable - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 None + - portable - true - bin\Release\ - TRACE - prompt - 4 SdkOnly + - - - - - - - - - - - - - - - - - + + - - {24588814-B93D-4528-8917-9C2A3C4E85CA} - Softeq.XToolkit.Common - + - - + + \ No newline at end of file diff --git a/Softeq.XToolkit.Common.iOS.Tests/Info.plist b/Softeq.XToolkit.Common.iOS.Tests/Info.plist index 176aea3ab..042428b91 100644 --- a/Softeq.XToolkit.Common.iOS.Tests/Info.plist +++ b/Softeq.XToolkit.Common.iOS.Tests/Info.plist @@ -13,7 +13,7 @@ LSRequiresIPhoneOS MinimumOSVersion - 12.4 + 12.0 UIDeviceFamily 1 diff --git a/Softeq.XToolkit.Common.iOS.Tests/Softeq.XToolkit.Common.iOS.Tests.csproj b/Softeq.XToolkit.Common.iOS.Tests/Softeq.XToolkit.Common.iOS.Tests.csproj index 93f442e21..4595a540d 100644 --- a/Softeq.XToolkit.Common.iOS.Tests/Softeq.XToolkit.Common.iOS.Tests.csproj +++ b/Softeq.XToolkit.Common.iOS.Tests/Softeq.XToolkit.Common.iOS.Tests.csproj @@ -1,132 +1,61 @@ - - + + - Debug - iPhoneSimulator - {8F494CF3-61BE-4140-AA49-D9660C0271C6} - {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + xamarin.ios10 Exe - Softeq.XToolkit.Common.iOS.Tests - Softeq.XToolkit.Common.iOS.Tests - Resources + portable + 12.0 + iPhone Developer + NSUrlSessionHandler + true - portable - false - bin\iPhoneSimulator\Debug - DEBUG; - prompt - 4 - iPhone Developer + None + x86_64 true true true - 40309 - None - x86_64 - NSUrlSessionHandler false - - Default - - - - portable - true - bin\iPhone\Release - - prompt - 4 - iPhone Developer - true - true - Entitlements.plist - SdkOnly - ARM64 - NSUrlSessionHandler - - Default - - - - portable - true - bin\iPhoneSimulator\Release - - prompt - 4 - iPhone Developer - true - None - x86_64 - NSUrlSessionHandler - - Default - + true - portable - false - bin\iPhone\Debug - DEBUG; - prompt - 4 - iPhone Developer - true + SdkOnly + ARM64 true true true + true + true true - Entitlements.plist - 52119 + + + + SdkOnly + x86_64 + true + + + SdkOnly ARM64 - NSUrlSessionHandler - - Default - + true + true - - - - - - - - - + + + + - - - - - - - - - - - - - - - - - - {24588814-B93D-4528-8917-9C2A3C4E85CA} - Softeq.XToolkit.Common - - - {6BCB2009-2E46-458C-BCAA-AFC27A631924} - Softeq.XToolkit.Common.iOS - + + + - + \ No newline at end of file diff --git a/Softeq.XToolkit.Common.iOS/Properties/AssemblyInfo.cs b/Softeq.XToolkit.Common.iOS/Properties/AssemblyInfo.cs deleted file mode 100644 index 63261470a..000000000 --- a/Softeq.XToolkit.Common.iOS/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Softeq.XToolkit.Common.iOS")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Softeq Development Corporation")] -[assembly: AssemblyProduct("XToolkit.Common")] -[assembly: AssemblyCopyright("Copyright © Softeq Development Corporation 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("6bcb2009-2e46-458c-bcaa-afc27a631924")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Softeq.XToolkit.Common.iOS/Softeq.XToolkit.Common.iOS.csproj b/Softeq.XToolkit.Common.iOS/Softeq.XToolkit.Common.iOS.csproj index 52a74a129..b6266643d 100644 --- a/Softeq.XToolkit.Common.iOS/Softeq.XToolkit.Common.iOS.csproj +++ b/Softeq.XToolkit.Common.iOS/Softeq.XToolkit.Common.iOS.csproj @@ -1,65 +1,21 @@ - - + + - Debug - AnyCPU - 8.0.30703 - 2.0 - {6BCB2009-2E46-458C-BCAA-AFC27A631924} - {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Library - Softeq.XToolkit.Common.iOS - Resources - Softeq.XToolkit.Common.iOS + xamarin.ios10 + portable + true - portable - false - bin\Debug - DEBUG; - prompt - 4 - false None + - portable - true - bin\Release - prompt - 4 - false SdkOnly + - - - - - - - - - - - - - - - - - - - - - - - - - {24588814-B93D-4528-8917-9C2A3C4E85CA} - Softeq.XToolkit.Common - + - + \ No newline at end of file diff --git a/Softeq.XToolkit.Connectivity.iOS/Properties/AssemblyInfo.cs b/Softeq.XToolkit.Connectivity.iOS/Properties/AssemblyInfo.cs deleted file mode 100644 index c8204f46a..000000000 --- a/Softeq.XToolkit.Connectivity.iOS/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Softeq.XToolkit.Connectivity.iOS")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Softeq Development Corporation")] -[assembly: AssemblyProduct("XToolkit.Connectivity")] -[assembly: AssemblyCopyright("Copyright © Softeq Development Corporation 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("50c7b8c9-e664-45af-b88e-0c9b8b9c1be1")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Softeq.XToolkit.Connectivity.iOS/Softeq.XToolkit.Connectivity.iOS.csproj b/Softeq.XToolkit.Connectivity.iOS/Softeq.XToolkit.Connectivity.iOS.csproj index f18d43df5..18c9082d6 100644 --- a/Softeq.XToolkit.Connectivity.iOS/Softeq.XToolkit.Connectivity.iOS.csproj +++ b/Softeq.XToolkit.Connectivity.iOS/Softeq.XToolkit.Connectivity.iOS.csproj @@ -1,57 +1,25 @@ - - + + - Debug - AnyCPU - 8.0.30703 - 2.0 - {E3CCEEA3-C0B0-47B0-B414-2D6C18AC100A} - {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - {a52b8a63-bc84-4b47-910d-692533484892} - Library - Softeq.XToolkit.Connectivity.iOS - Resources - Softeq.XToolkit.Connectivity.iOS - PackageReference + xamarin.ios10 + portable + true - portable - false - bin\Debug - DEBUG; - prompt - 4 None + - portable - true - bin\Release - prompt - 4 SdkOnly + - - - - - - - - - - - - {044152C1-3B2A-45A6-B233-80A51C511129} - Softeq.XToolkit.Connectivity - + + - - 3.2.0 - + - + \ No newline at end of file diff --git a/Softeq.XToolkit.Permissions.Droid/Properties/AssemblyInfo.cs b/Softeq.XToolkit.Permissions.Droid/Properties/AssemblyInfo.cs deleted file mode 100644 index 33242b045..000000000 --- a/Softeq.XToolkit.Permissions.Droid/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Reflection; -using System.Runtime.InteropServices; - -// Information about this assembly is defined by the following attributes. -// Change them to the values specific to your project. -[assembly: AssemblyTitle("Softeq.XToolkit.Permissions.Droid")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Softeq Development Corporation")] -[assembly: AssemblyProduct("WhiteLabel.Permissions")] -[assembly: AssemblyCopyright("Copyright © Softeq Development Corporation 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Softeq.XToolkit.Permissions.Droid/Softeq.XToolkit.Permissions.Droid.csproj b/Softeq.XToolkit.Permissions.Droid/Softeq.XToolkit.Permissions.Droid.csproj index 824cc4afb..9c0b4f1ee 100644 --- a/Softeq.XToolkit.Permissions.Droid/Softeq.XToolkit.Permissions.Droid.csproj +++ b/Softeq.XToolkit.Permissions.Droid/Softeq.XToolkit.Permissions.Droid.csproj @@ -1,63 +1,30 @@ - - + + - Debug - AnyCPU - {955A4101-4989-4764-9134-E621FC4D9C86} - {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Library - Softeq.XToolkit.Permissions.Droid - Softeq.XToolkit.Permissions.Droid - v12.0 - Resources\Resource.designer.cs - Resource - Resources - Assets + monoandroid12.0 + portable + true - portable - false - bin\Debug - DEBUG; - prompt - 4 None - - + - portable - true - bin\Release - prompt - 4 SdkOnly + - - - - + + - - - - + + + - - - {18d3fdc1-b0a1-401e-87f2-1c43034e610c} - Softeq.XToolkit.Common.Droid - - - {C8DA5EA8-703B-481F-9ED8-AB3AD29E5409} - Softeq.XToolkit.Permissions - - - + \ No newline at end of file diff --git a/Softeq.XToolkit.Permissions.iOS/Properties/AssemblyInfo.cs b/Softeq.XToolkit.Permissions.iOS/Properties/AssemblyInfo.cs deleted file mode 100644 index 5c1f72717..000000000 --- a/Softeq.XToolkit.Permissions.iOS/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Reflection; -using System.Runtime.InteropServices; - -// Information about this assembly is defined by the following attributes. -// Change them to the values specific to your project. -[assembly: AssemblyTitle("Softeq.XToolkit.Permissions.iOS")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Softeq Development Corporation")] -[assembly: AssemblyProduct("WhiteLabel.Permissions")] -[assembly: AssemblyCopyright("Copyright © Softeq Development Corporation 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Softeq.XToolkit.Permissions.iOS/Softeq.XToolkit.Permissions.iOS.csproj b/Softeq.XToolkit.Permissions.iOS/Softeq.XToolkit.Permissions.iOS.csproj index 1e1944c80..2ffe80ff0 100644 --- a/Softeq.XToolkit.Permissions.iOS/Softeq.XToolkit.Permissions.iOS.csproj +++ b/Softeq.XToolkit.Permissions.iOS/Softeq.XToolkit.Permissions.iOS.csproj @@ -1,56 +1,25 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {7EB2ADA9-C599-4644-AC6B-109F1AEED19F} - {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Library - Softeq.XToolkit.Permissions.iOS - Softeq.XToolkit.Permissions.iOS - Resources - - - true - portable - false - bin\Debug - DEBUG; - prompt - 4 - None - false - - - portable - true - bin\Release - prompt - 4 - false - SdkOnly - - - - - - - - - - - - - - - - - - {C8DA5EA8-703B-481F-9ED8-AB3AD29E5409} - Softeq.XToolkit.Permissions - - - + + + + xamarin.ios10 + portable + + + + true + None + + + + SdkOnly + + + + + + + + + + \ No newline at end of file diff --git a/Softeq.XToolkit.PushNotifications.Droid/Properties/AssemblyInfo.cs b/Softeq.XToolkit.PushNotifications.Droid/Properties/AssemblyInfo.cs deleted file mode 100644 index 8f7e2067d..000000000 --- a/Softeq.XToolkit.PushNotifications.Droid/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Softeq.XToolkit.PushNotifications.Droid")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Softeq Development Corporation")] -[assembly: AssemblyProduct("XToolkit.PushNotifications")] -[assembly: AssemblyCopyright("Copyright © Softeq Development Corporation 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Softeq.XToolkit.PushNotifications.Droid/Softeq.XToolkit.PushNotifications.Droid.csproj b/Softeq.XToolkit.PushNotifications.Droid/Softeq.XToolkit.PushNotifications.Droid.csproj index 5de3362ef..b8a7608b6 100644 --- a/Softeq.XToolkit.PushNotifications.Droid/Softeq.XToolkit.PushNotifications.Droid.csproj +++ b/Softeq.XToolkit.PushNotifications.Droid/Softeq.XToolkit.PushNotifications.Droid.csproj @@ -1,78 +1,33 @@ - - + + - Debug - AnyCPU - 8.0.30703 - 2.0 - {FACBCCE2-C5F5-4AA8-9E19-8D61B4386EEA} - {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - {9ef11e43-1701-4396-8835-8392d57abb70} - Library - Properties - Softeq.XToolkit.PushNotifications.Droid - Softeq.XToolkit.PushNotifications.Droid - 512 - Resources\Resource.designer.cs - Off - v12.0 + monoandroid12.0 + portable + true - portable - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 None + - portable - true - bin\Release\ - TRACE - prompt - 4 SdkOnly + - - - - - - - - + + - - - - - - - - - - + + + - - - - {E0C9CDE2-4C35-410A-AE95-A7871BEE0180} - Softeq.XToolkit.PushNotifications - - - {24588814-B93D-4528-8917-9C2A3C4E85CA} - Softeq.XToolkit.Common - - + \ No newline at end of file diff --git a/Softeq.XToolkit.PushNotifications.iOS/Properties/AssemblyInfo.cs b/Softeq.XToolkit.PushNotifications.iOS/Properties/AssemblyInfo.cs deleted file mode 100644 index c57929311..000000000 --- a/Softeq.XToolkit.PushNotifications.iOS/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Softeq.XToolkit.PushNotifications.iOS")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Softeq Development Corporation")] -[assembly: AssemblyProduct("XToolkit.PushNotifications")] -[assembly: AssemblyCopyright("Copyright © Softeq Development Corporation 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("50c7b8c9-e664-45af-b88e-0c9b8b9c1be1")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Softeq.XToolkit.PushNotifications.iOS/Softeq.XToolkit.PushNotifications.iOS.csproj b/Softeq.XToolkit.PushNotifications.iOS/Softeq.XToolkit.PushNotifications.iOS.csproj index eb9b94b8f..e0dd913f2 100644 --- a/Softeq.XToolkit.PushNotifications.iOS/Softeq.XToolkit.PushNotifications.iOS.csproj +++ b/Softeq.XToolkit.PushNotifications.iOS/Softeq.XToolkit.PushNotifications.iOS.csproj @@ -1,65 +1,22 @@ - - + + - Debug - AnyCPU - 8.0.30703 - 2.0 - {0EAE7634-9497-4FB8-B62F-DDACF85985D2} - {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - {a52b8a63-bc84-4b47-910d-692533484892} - Library - Softeq.XToolkit.PushNotifications.iOS - Resources - Softeq.XToolkit.PushNotifications.iOS + xamarin.ios10 + portable + true - portable - false - bin\Debug - DEBUG; - prompt - 4 - false None + - portable - true - bin\Release - prompt - 4 - false SdkOnly + - - - - - - - - - - - - - - - - - - - - {E0C9CDE2-4C35-410A-AE95-A7871BEE0180} - Softeq.XToolkit.PushNotifications - - - {24588814-B93D-4528-8917-9C2A3C4E85CA} - Softeq.XToolkit.Common - + + - + \ No newline at end of file diff --git a/Softeq.XToolkit.WhiteLabel.Droid/Properties/AssemblyInfo.cs b/Softeq.XToolkit.WhiteLabel.Droid/Properties/AssemblyInfo.cs deleted file mode 100644 index d40ccb653..000000000 --- a/Softeq.XToolkit.WhiteLabel.Droid/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Softeq.XToolkit.WhiteLabel.Droid")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Softeq Development Corporation")] -[assembly: AssemblyProduct("XToolkit.WhiteLabel")] -[assembly: AssemblyCopyright("Copyright © Softeq Development Corporation 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Softeq.XToolkit.WhiteLabel.Droid/Resources/layout/activity_bottom_navigation.axml b/Softeq.XToolkit.WhiteLabel.Droid/Resources/layout/activity_bottom_navigation.xml similarity index 100% rename from Softeq.XToolkit.WhiteLabel.Droid/Resources/layout/activity_bottom_navigation.axml rename to Softeq.XToolkit.WhiteLabel.Droid/Resources/layout/activity_bottom_navigation.xml diff --git a/Softeq.XToolkit.WhiteLabel.Droid/Softeq.XToolkit.WhiteLabel.Droid.csproj b/Softeq.XToolkit.WhiteLabel.Droid/Softeq.XToolkit.WhiteLabel.Droid.csproj index bdc30d5b0..2c37facb0 100644 --- a/Softeq.XToolkit.WhiteLabel.Droid/Softeq.XToolkit.WhiteLabel.Droid.csproj +++ b/Softeq.XToolkit.WhiteLabel.Droid/Softeq.XToolkit.WhiteLabel.Droid.csproj @@ -1,145 +1,36 @@ - - + + - Debug - AnyCPU - 8.0.30703 - 2.0 - {7F44F0F6-8396-42D0-8A1B-DC40CE8C478C} - {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Library - Properties - Softeq.XToolkit.WhiteLabel.Droid - Softeq.XToolkit.WhiteLabel.Droid - 512 - Resources\Resource.Designer.cs - Off - v12.0 + monoandroid12.0 + portable + true - portable - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 None + - portable - true - bin\Release\ - TRACE - prompt - 4 SdkOnly + - - - - + + - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {98C3B9F4-4E4F-4C0B-BAA6-C61685E0AC49} - Softeq.XToolkit.WhiteLabel - - - {18D3FDC1-B0A1-401E-87F2-1C43034E610C} - Softeq.XToolkit.Common.Droid - - - {0F1F09A8-9CDB-4933-AA1B-898AB43D394C} - Softeq.XToolkit.Bindings - - - {24588814-B93D-4528-8917-9C2A3C4E85CA} - Softeq.XToolkit.Common - - - {2B4A678B-63B4-49DB-99B1-BFE7793110C4} - Softeq.XToolkit.Bindings.Droid - - - - - - - - - - - - + + + + - + \ No newline at end of file diff --git a/Softeq.XToolkit.WhiteLabel.Essentials.Droid/Properties/AssemblyInfo.cs b/Softeq.XToolkit.WhiteLabel.Essentials.Droid/Properties/AssemblyInfo.cs deleted file mode 100644 index 6d49e697a..000000000 --- a/Softeq.XToolkit.WhiteLabel.Essentials.Droid/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Softeq.XToolkit.WhiteLabel.Essentials.Droid")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Softeq Development Corporation")] -[assembly: AssemblyProduct("XToolkit.WhiteLabel.Essentials")] -[assembly: AssemblyCopyright("Copyright © Softeq Development Corporation 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Softeq.XToolkit.WhiteLabel.Essentials.Droid/Softeq.XToolkit.WhiteLabel.Essentials.Droid.csproj b/Softeq.XToolkit.WhiteLabel.Essentials.Droid/Softeq.XToolkit.WhiteLabel.Essentials.Droid.csproj index 94e120e40..0a3e446ae 100644 --- a/Softeq.XToolkit.WhiteLabel.Essentials.Droid/Softeq.XToolkit.WhiteLabel.Essentials.Droid.csproj +++ b/Softeq.XToolkit.WhiteLabel.Essentials.Droid/Softeq.XToolkit.WhiteLabel.Essentials.Droid.csproj @@ -1,95 +1,35 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {CAEA919A-5B4A-4D97-AB4C-33229484BD7A} - {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Library - Properties - Softeq.XToolkit.WhiteLabel.Essentials.Droid - Softeq.XToolkit.WhiteLabel.Essentials.Droid - 512 - Resources\Resource.Designer.cs - Off - v12.0 - - - true - portable - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - None - - - portable - true - bin\Release\ - TRACE - prompt - 4 - SdkOnly - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {0f1f09a8-9cdb-4933-aa1b-898ab43d394c} - Softeq.XToolkit.Bindings - - - {24588814-b93d-4528-8917-9c2a3c4e85ca} - Softeq.XToolkit.Common - - - {c8da5ea8-703b-481f-9ed8-ab3ad29e5409} - Softeq.XToolkit.Permissions - - - {7f44f0f6-8396-42d0-8a1b-dc40ce8c478c} - Softeq.XToolkit.WhiteLabel.Droid - - - {887c6ce5-075b-420d-a88f-60479c4dc25a} - Softeq.XToolkit.WhiteLabel.Essentials - - - {98c3b9f4-4e4f-4c0b-baa6-c61685e0ac49} - Softeq.XToolkit.WhiteLabel - - - - - - - + + + + monoandroid12.0 + portable + + + + true + None + + + + SdkOnly + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Softeq.XToolkit.WhiteLabel.Essentials.iOS/Properties/AssemblyInfo.cs b/Softeq.XToolkit.WhiteLabel.Essentials.iOS/Properties/AssemblyInfo.cs deleted file mode 100644 index 14b2465a9..000000000 --- a/Softeq.XToolkit.WhiteLabel.Essentials.iOS/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Softeq.XToolkit.WhiteLabel.Essentials.iOS")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Softeq Development Corporation")] -[assembly: AssemblyProduct("WhiteLabel.Essentials")] -[assembly: AssemblyCopyright("Copyright © Softeq Development Corporation 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("23AA0E71-BF8F-47A8-A915-C8AC4841E064")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Softeq.XToolkit.WhiteLabel.Essentials.iOS/Softeq.XToolkit.WhiteLabel.Essentials.iOS.csproj b/Softeq.XToolkit.WhiteLabel.Essentials.iOS/Softeq.XToolkit.WhiteLabel.Essentials.iOS.csproj index e2cead83e..3c5d1e36c 100644 --- a/Softeq.XToolkit.WhiteLabel.Essentials.iOS/Softeq.XToolkit.WhiteLabel.Essentials.iOS.csproj +++ b/Softeq.XToolkit.WhiteLabel.Essentials.iOS/Softeq.XToolkit.WhiteLabel.Essentials.iOS.csproj @@ -1,91 +1,33 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {23AA0E71-BF8F-47A8-A915-C8AC4841E064} - {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Library - Softeq.XToolkit.WhiteLabel.Essentials.iOS - Resources - Softeq.XToolkit.WhiteLabel.Essentials.iOS - - - true - portable - false - bin\Debug - DEBUG; - prompt - 4 - false - None - - - portable - true - bin\Release - prompt - 4 - false - SdkOnly - - - - - - - - - - - FullScreenImageViewController.cs - - - - - - - - {2d399ba9-1878-43e2-af05-6873eae9151b} - Softeq.XToolkit.Bindings.iOS - - - {0f1f09a8-9cdb-4933-aa1b-898ab43d394c} - Softeq.XToolkit.Bindings - - - {6bcb2009-2e46-458c-bcaa-afc27a631924} - Softeq.XToolkit.Common.iOS - - - {24588814-b93d-4528-8917-9c2a3c4e85ca} - Softeq.XToolkit.Common - - - {c8da5ea8-703b-481f-9ed8-ab3ad29e5409} - Softeq.XToolkit.Permissions - - - {887c6ce5-075b-420d-a88f-60479c4dc25a} - Softeq.XToolkit.WhiteLabel.Essentials - - - {cc89dbc6-e68a-4c85-91c4-e276d3bc3c2e} - Softeq.XToolkit.WhiteLabel.iOS - - - {98c3b9f4-4e4f-4c0b-baa6-c61685e0ac49} - Softeq.XToolkit.WhiteLabel - - - - - - - - - + + + + xamarin.ios10 + portable + + + + true + None + + + + SdkOnly + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Softeq.XToolkit.WhiteLabel.iOS/Properties/AssemblyInfo.cs b/Softeq.XToolkit.WhiteLabel.iOS/Properties/AssemblyInfo.cs deleted file mode 100644 index 0ca4c5134..000000000 --- a/Softeq.XToolkit.WhiteLabel.iOS/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Softeq.XToolkit.WhiteLabel.iOS")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Softeq Development Corporation")] -[assembly: AssemblyProduct("XToolkit.WhiteLabel")] -[assembly: AssemblyCopyright("Copyright © Softeq Development Corporation 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("cc89dbc6-e68a-4c85-91c4-e276d3bc3c2e")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Softeq.XToolkit.WhiteLabel.iOS/Softeq.XToolkit.WhiteLabel.iOS.csproj b/Softeq.XToolkit.WhiteLabel.iOS/Softeq.XToolkit.WhiteLabel.iOS.csproj index 91115f01b..6413aa90d 100644 --- a/Softeq.XToolkit.WhiteLabel.iOS/Softeq.XToolkit.WhiteLabel.iOS.csproj +++ b/Softeq.XToolkit.WhiteLabel.iOS/Softeq.XToolkit.WhiteLabel.iOS.csproj @@ -1,113 +1,30 @@ - - + + - Debug - AnyCPU - 8.0.30703 - 2.0 - {CC89DBC6-E68A-4C85-91C4-E276D3BC3C2E} - {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Library - Softeq.XToolkit.WhiteLabel.iOS - Resources - Softeq.XToolkit.WhiteLabel.iOS + xamarin.ios10 + portable + true - portable - false - bin\Debug - DEBUG; - prompt - 4 - false None + - portable - true - bin\Release - prompt - 4 - false SdkOnly + - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {98C3B9F4-4E4F-4C0B-BAA6-C61685E0AC49} - Softeq.XToolkit.WhiteLabel - - - {0F1F09A8-9CDB-4933-AA1B-898AB43D394C} - Softeq.XToolkit.Bindings - - - {2D399BA9-1878-43E2-AF05-6873EAE9151B} - Softeq.XToolkit.Bindings.iOS - - - {24588814-B93D-4528-8917-9C2A3C4E85CA} - Softeq.XToolkit.Common - - - {6BCB2009-2E46-458C-BCAA-AFC27A631924} - Softeq.XToolkit.Common.iOS - + + - + \ No newline at end of file diff --git a/global.json b/global.json new file mode 100644 index 000000000..d268ac7fb --- /dev/null +++ b/global.json @@ -0,0 +1,5 @@ +{ + "msbuild-sdks": { + "MSBuild.Sdk.Extras": "3.0.44", + } +} \ No newline at end of file diff --git a/samples/Playground/Playground.Droid/Assets/AboutAssets.txt b/samples/Playground/Playground.Droid/Assets/AboutAssets.txt deleted file mode 100644 index b0633374b..000000000 --- a/samples/Playground/Playground.Droid/Assets/AboutAssets.txt +++ /dev/null @@ -1,19 +0,0 @@ -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 "AndroidAsset". - -These files will be deployed with you package and will be accessible using Android's -AssetManager, like this: - -public class ReadAsset : Activity -{ - protected override void OnCreate (Bundle bundle) - { - base.OnCreate (bundle); - - InputStream input = Assets.Open ("my_asset.txt"); - } -} - -Additionally, some Android functions will automatically load asset files: - -Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf"); \ No newline at end of file diff --git a/samples/Playground/Playground.Droid/Playground.Droid.csproj b/samples/Playground/Playground.Droid/Playground.Droid.csproj index 148fbd216..a6c033f05 100644 --- a/samples/Playground/Playground.Droid/Playground.Droid.csproj +++ b/samples/Playground/Playground.Droid/Playground.Droid.csproj @@ -1,201 +1,41 @@ - - + + - Debug - AnyCPU - 8.0.30703 - 2.0 - {EDFA0566-1772-4442-86F8-828E5EB98B16} - {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - {122416d6-6b49-4ee2-a1e8-b825f31c79fe} - Library - Properties - Playground.Droid - Playground.Droid - 512 + monoandroid12.0 + portable True - Resources\Resource.designer.cs - Resource - Off - v12.0 Properties\AndroidManifest.xml - Resources - Assets - true Xamarin.Android.Net.AndroidClientHandler + - True - portable - False - bin\Debug\ - DEBUG;TRACE - prompt - 4 + true None - False + - false - portable - true - bin\Release\ - TRACE - prompt - 4 SdkOnly - false + True + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - {7F44F0F6-8396-42D0-8A1B-DC40CE8C478C} - Softeq.XToolkit.WhiteLabel.Droid - - - {caea919a-5b4a-4d97-ab4c-33229484bd7a} - Softeq.XToolkit.WhiteLabel.Essentials.Droid - - - {887c6ce5-075b-420d-a88f-60479c4dc25a} - Softeq.XToolkit.WhiteLabel.Essentials - - - {00250664-56DC-4B3D-93B9-7C1E8B381F5F} - Playground - - - {98C3B9F4-4E4F-4C0B-BAA6-C61685E0AC49} - Softeq.XToolkit.WhiteLabel - - - {24588814-B93D-4528-8917-9C2A3C4E85CA} - Softeq.XToolkit.Common - - - {18D3FDC1-B0A1-401E-87F2-1C43034E610C} - Softeq.XToolkit.Common.Droid - - - {0F1F09A8-9CDB-4933-AA1B-898AB43D394C} - Softeq.XToolkit.Bindings - - - {2B4A678B-63B4-49DB-99B1-BFE7793110C4} - Softeq.XToolkit.Bindings.Droid - - - {C8DA5EA8-703B-481F-9ED8-AB3AD29E5409} - Softeq.XToolkit.Permissions - - - {955A4101-4989-4764-9134-E621FC4D9C86} - Softeq.XToolkit.Permissions.Droid - - - {044152C1-3B2A-45A6-B233-80A51C511129} - Softeq.XToolkit.Connectivity - + + + + + + + + + + + + - + \ No newline at end of file diff --git a/samples/Playground/Playground.Droid/Properties/AssemblyInfo.cs b/samples/Playground/Playground.Droid/Properties/AssemblyInfo.cs deleted file mode 100644 index 68ede84af..000000000 --- a/samples/Playground/Playground.Droid/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Playground.Droid")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Softeq Development Corporation")] -[assembly: AssemblyProduct("XToolkit")] -[assembly: AssemblyCopyright("Copyright © Softeq Development Corporation 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/samples/Playground/Playground.Droid/Resources/AboutResources.txt b/samples/Playground/Playground.Droid/Resources/AboutResources.txt deleted file mode 100644 index c2bca974c..000000000 --- a/samples/Playground/Playground.Droid/Resources/AboutResources.txt +++ /dev/null @@ -1,44 +0,0 @@ -Images, layout descriptions, binary blobs and string dictionaries can be included -in your application as resource files. Various Android APIs are designed to -operate on the resource IDs instead of dealing with images, strings or binary blobs -directly. - -For example, a sample Android app that contains a user interface layout (main.axml), -an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png) -would keep its resources in the "Resources" directory of the application: - -Resources/ - drawable/ - icon.png - - layout/ - main.axml - - values/ - strings.xml - -In order to get the build system to recognize Android resources, set the build action to -"AndroidResource". The native Android APIs do not operate directly with filenames, but -instead operate on resource IDs. When you compile an Android application that uses resources, -the build system will package the resources for distribution and generate a class called "R" -(this is an Android convention) that contains the tokens for each one of the resources -included. For example, for the above Resources layout, this is what the R class would expose: - -public class R { - public class drawable { - public const int icon = 0x123; - } - - public class layout { - public const int main = 0x456; - } - - public class strings { - public const int first_string = 0xabc; - public const int second_string = 0xbcd; - } -} - -You would then use R.drawable.icon to reference the drawable/icon.png file, or R.layout.main -to reference the layout/main.axml file, or R.strings.first_string to reference the first -string in the dictionary file values/strings.xml. \ No newline at end of file diff --git a/samples/Playground/Playground.iOS/Playground.iOS.csproj b/samples/Playground/Playground.iOS/Playground.iOS.csproj index e69d67f92..fe423b6de 100644 --- a/samples/Playground/Playground.iOS/Playground.iOS.csproj +++ b/samples/Playground/Playground.iOS/Playground.iOS.csproj @@ -1,382 +1,81 @@ - - + + - Debug - iPhoneSimulator - {6B7105F6-6F18-4999-BA4E-8069D92F3150} - {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - {edc1b0fa-90cd-4038-8fad-98fe74adb368} + xamarin.ios10 Exe - Playground.iOS - Playground.iOS - Resources - true + portable + 12.0 + iPhone Developer NSUrlSessionHandler - true + true - portable - false - bin\iPhoneSimulator\Debug - DEBUG - prompt - 4 - false - x86_64 None - true - iPhone Developer - - - portable - true - bin\iPhoneSimulator\Release - prompt - 4 - SdkOnly x86_64 - false - iPhone Developer + true + true + true + false + true - portable - false - bin\iPhone\Debug - DEBUG - prompt - 4 - false + SdkOnly ARM64 - Entitlements.plist - iPhone Developer true - None + true + true + true + true + true + + + + SdkOnly + x86_64 + true + - portable - true - bin\iPhone\Release - prompt - 4 - Entitlements.plist - ARM64 - false - iPhone Developer SdkOnly + ARM64 + true + true - - - - - - - - - - - 13.0.1 - - - 1.0.8 - - - 2.4.11.982 - - - 3.2.0 - - - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + - - - - - - - FramesBlueViewController.cs - - - - FramesRedViewController.cs - - - - - - - - - FramesViewController.cs - - - - FramesYellowViewController.cs - - - - SplitFrameViewController.cs - - - - MovieCollectionViewCell.cs - - - - - - MainPageGroupHeaderViewCell.cs - - - - MainPageItemViewCell.cs - - - - DetailsPageViewController.cs - - - - MainPageViewController.cs - - - - - PermissionsPageViewController.cs - - - - CollectionPageViewController.cs - - - - - BlueViewController.cs - - - - RedViewController.cs - - - - YellowViewController.cs - - - - GreenViewController.cs - - - - EmptyPageViewController.cs - - - - DialogsPageViewController.cs - - - - SimpleDialogPageViewController.cs - - - - GroupedCollectionPageViewController.cs - - - - ProductViewCell.cs - - - - - GroupedHeaderView.cs - - - - - GroupedListPageViewController.cs - - - - ProductTableViewCell.cs - - - - - GroupedTableHeaderView.cs - - - - TablePageViewController.cs - - - - MovieTableViewCell.cs - - - - PhotoBrowserViewController.cs - - - - ConnectivityPageViewController.cs - - - - - GesturesPageViewController.cs - - - - CompositionalLayoutPageViewController.cs - - - - DummyCell.cs - + + + + + + + + + + + + + + - - {2d399ba9-1878-43e2-af05-6873eae9151b} - Softeq.XToolkit.Bindings.iOS - - - {23aa0e71-bf8f-47a8-a915-c8ac4841e064} - Softeq.XToolkit.WhiteLabel.Essentials.iOS - - - {887c6ce5-075b-420d-a88f-60479c4dc25a} - Softeq.XToolkit.WhiteLabel.Essentials - - - {CC89DBC6-E68A-4C85-91C4-E276D3BC3C2E} - Softeq.XToolkit.WhiteLabel.iOS - - - {00250664-56DC-4B3D-93B9-7C1E8B381F5F} - Playground - - - {98C3B9F4-4E4F-4C0B-BAA6-C61685E0AC49} - Softeq.XToolkit.WhiteLabel - - - {24588814-B93D-4528-8917-9C2A3C4E85CA} - Softeq.XToolkit.Common - - - {6BCB2009-2E46-458C-BCAA-AFC27A631924} - Softeq.XToolkit.Common.iOS - - - {0F1F09A8-9CDB-4933-AA1B-898AB43D394C} - Softeq.XToolkit.Bindings - - - {7EB2ADA9-C599-4644-AC6B-109F1AEED19F} - Softeq.XToolkit.Permissions.iOS - - - {C8DA5EA8-703B-481F-9ED8-AB3AD29E5409} - Softeq.XToolkit.Permissions - - - {044152C1-3B2A-45A6-B233-80A51C511129} - Softeq.XToolkit.Connectivity - - - {E3CCEEA3-C0B0-47B0-B414-2D6C18AC100A} - Softeq.XToolkit.Connectivity.iOS - + + + + - + \ No newline at end of file diff --git a/samples/Playground/Playground.iOS/Properties/AssemblyInfo.cs b/samples/Playground/Playground.iOS/Properties/AssemblyInfo.cs deleted file mode 100644 index d01aae539..000000000 --- a/samples/Playground/Playground.iOS/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Playground.iOS")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Softeq Development Corporation")] -[assembly: AssemblyProduct("XToolkit")] -[assembly: AssemblyCopyright("Copyright © Softeq Development Corporation 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("50c7b8c9-e664-45af-b88e-0c9b8b9c1be1")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/samples/Playground/Playground.iOS/ViewControllers/Dialogs/CustomTransitioningDelegate.cs b/samples/Playground/Playground.iOS/ViewControllers/Dialogs/CustomTransitioningDelegate.cs index eaf698b0f..1cb6e43f2 100644 --- a/samples/Playground/Playground.iOS/ViewControllers/Dialogs/CustomTransitioningDelegate.cs +++ b/samples/Playground/Playground.iOS/ViewControllers/Dialogs/CustomTransitioningDelegate.cs @@ -63,9 +63,8 @@ public override void ContainerViewWillLayoutSubviews() private CGPath GeneratePath() { var path = new CGPath(); - path.AddEllipseInRect(new CGRect(120, 0, 160, 300)); - path.AddEllipseInRect(new CGRect(50, 200, 150, 150)); - path.AddEllipseInRect(new CGRect(200, 200, 150, 150)); + path.AddEllipseInRect(new CGRect(100, 0, 200, 100)); + path.AddRoundedRect(new CGRect(50, 100, 300, 200), 20, 20); return path; } } From 13e3ad0d8053cbdb03a4671bff84f5ead330cc50 Mon Sep 17 00:00:00 2001 From: Yauheni Pakala Date: Tue, 21 Feb 2023 19:12:12 +0200 Subject: [PATCH 03/26] Migrate to .NET6+ (#513) --- Directory.Build.props | 7 +- .../Softeq.XToolkit.Bindings.Droid.csproj | 5 +- .../Softeq.XToolkit.Bindings.Tests.csproj | 2 +- .../Softeq.XToolkit.Bindings.iOS.csproj | 10 +- .../Softeq.XToolkit.Bindings.csproj | 2 +- .../Properties/AndroidManifest.xml | 2 +- .../Softeq.XToolkit.Common.Droid.Tests.csproj | 13 ++- .../Softeq.XToolkit.Common.Droid.csproj | 5 +- .../MockFileSystemWrapper.cs | 5 + .../Softeq.XToolkit.Common.Tests.csproj | 4 +- Softeq.XToolkit.Common.iOS.Tests/Info.plist | 2 +- .../Softeq.XToolkit.Common.iOS.Tests.csproj | 47 +++------ .../Softeq.XToolkit.Common.iOS.csproj | 10 +- .../Softeq.XToolkit.Common.csproj | 5 +- .../Softeq.XToolkit.Connectivity.iOS.csproj | 10 +- .../Softeq.XToolkit.Connectivity.csproj | 2 +- .../Softeq.XToolkit.Permissions.Droid.csproj | 8 +- .../Softeq.XToolkit.Permissions.iOS.csproj | 12 +-- .../Softeq.XToolkit.Permissions.csproj | 4 +- ...eq.XToolkit.PushNotifications.Droid.csproj | 5 +- ...fteq.XToolkit.PushNotifications.iOS.csproj | 10 +- .../Softeq.XToolkit.PushNotifications.csproj | 2 +- .../Softeq.XToolkit.Remote.Tests.csproj | 2 +- .../Softeq.XToolkit.Remote.csproj | 2 +- .../Extensions/ImageViewExtensions.cs | 38 ------- .../Interfaces/IDroidImageService.cs | 19 ++++ .../Internal/ViewModelCache.cs | 2 +- .../Services/DefaultDroidImageService.cs | 93 ++++++++++++++++++ .../Softeq.XToolkit.WhiteLabel.Droid.csproj | 8 +- .../ViewComponents/ViewModelComponent.cs | 4 +- .../FullScreenImageDialogFragment.cs | 16 +-- ...Toolkit.WhiteLabel.Essentials.Droid.csproj | 6 +- .../FullScreenImageViewController.cs | 16 +-- .../ImagePicker/IosImagePickerService.cs | 6 +- ....XToolkit.WhiteLabel.Essentials.iOS.csproj | 13 ++- .../ImagePicker/IImagePickerService.cs | 8 +- ...fteq.XToolkit.WhiteLabel.Essentials.csproj | 2 +- .../Softeq.XToolkit.WhiteLabel.Tests.csproj | 4 +- .../MvxCachedImageViewExtensions.cs | 63 ------------ .../Interfaces/IIosImageService.cs | 19 ++++ .../Services/DefaultIosImageService.cs | 34 +++++++ .../Softeq.XToolkit.WhiteLabel.iOS.csproj | 13 ++- .../Softeq.XToolkit.WhiteLabel.csproj | 6 +- XToolkit.sln | 58 ----------- _Tests.targets | 10 +- azure-pipelines/XToolkit.yml | 32 +++++- azure-pipelines/templates/app-jobs.yml | 31 ------ azure-pipelines/templates/setup-dotnet.yml | 24 +++++ azure-pipelines/templates/setup-xcode.yml | 13 +++ azure-pipelines/templates/vars.yml | 4 +- generate_test_coverage_report.sh | 26 ++--- global.json | 6 +- .../Playground.Forms.Droid.csproj | 2 - .../Playground.Forms.iOS.csproj | 12 +-- .../CustomDroidBootstrapper.cs | 3 + .../Playground.Droid/MainApplication.cs | 4 +- .../Playground.Droid/Playground.Droid.csproj | 23 +++-- .../Properties/AndroidManifest.xml | 4 +- .../Resources/drawable/background_splash.xml | 2 +- .../Resources/mipmap-anydpi-v26/appicon.xml | 1 + .../mipmap-anydpi-v26/appicon_round.xml | 1 + .../mipmap-anydpi-v26/ic_launcher.xml | 5 - .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 - .../Resources/mipmap-hdpi/appicon.png | Bin 0 -> 1149 bytes .../mipmap-hdpi/appicon_background.png | Bin 0 -> 532 bytes .../mipmap-hdpi/appicon_foreground.png | Bin 0 -> 1511 bytes .../Resources/mipmap-hdpi/ic_launcher.png | Bin 1634 -> 0 bytes .../mipmap-hdpi/ic_launcher_foreground.png | Bin 1441 -> 0 bytes .../mipmap-hdpi/ic_launcher_round.png | Bin 3552 -> 0 bytes .../Resources/mipmap-mdpi/appicon.png | Bin 0 -> 868 bytes .../mipmap-mdpi/appicon_background.png | Bin 0 -> 352 bytes .../mipmap-mdpi/appicon_foreground.png | Bin 0 -> 1281 bytes .../Resources/mipmap-mdpi/ic_launcher.png | Bin 1362 -> 0 bytes .../mipmap-mdpi/ic_launcher_foreground.png | Bin 958 -> 0 bytes .../mipmap-mdpi/ic_launcher_round.png | Bin 2413 -> 0 bytes .../Resources/mipmap-xhdpi/appicon.png | Bin 0 -> 1473 bytes .../mipmap-xhdpi/appicon_background.png | Bin 0 -> 702 bytes .../mipmap-xhdpi/appicon_foreground.png | Bin 0 -> 2164 bytes .../Resources/mipmap-xhdpi/ic_launcher.png | Bin 2307 -> 0 bytes .../mipmap-xhdpi/ic_launcher_foreground.png | Bin 2056 -> 0 bytes .../mipmap-xhdpi/ic_launcher_round.png | Bin 4858 -> 0 bytes .../Resources/mipmap-xxhdpi/appicon.png | Bin 0 -> 1914 bytes .../mipmap-xxhdpi/appicon_background.png | Bin 0 -> 1040 bytes .../mipmap-xxhdpi/appicon_foreground.png | Bin 0 -> 2619 bytes .../Resources/mipmap-xxhdpi/ic_launcher.png | Bin 3871 -> 0 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 3403 -> 0 bytes .../mipmap-xxhdpi/ic_launcher_round.png | Bin 8001 -> 0 bytes .../Resources/mipmap-xxxhdpi/appicon.png | Bin 0 -> 2557 bytes .../mipmap-xxxhdpi/appicon_background.png | Bin 0 -> 1728 bytes .../mipmap-xxxhdpi/appicon_foreground.png | Bin 0 -> 4350 bytes .../Resources/mipmap-xxxhdpi/ic_launcher.png | Bin 5016 -> 0 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 4889 -> 0 bytes .../mipmap-xxxhdpi/ic_launcher_round.png | Bin 10893 -> 0 bytes .../Services/GlideImageService.cs | 24 +++++ .../Collections/MovieCollectionViewHolder.cs | 7 +- .../Views/Collections/ProductViewHolder.cs | 7 +- .../AppIcon.appiconset/Contents.json | 5 +- .../AppIcon.appiconset/Icon1024.png | Bin 70429 -> 10753 bytes .../AppIcon.appiconset/Icon120.png | Bin 3773 -> 1155 bytes .../AppIcon.appiconset/Icon152.png | Bin 4750 -> 1381 bytes .../AppIcon.appiconset/Icon167.png | Bin 4692 -> 1503 bytes .../AppIcon.appiconset/Icon180.png | Bin 5192 -> 1558 bytes .../AppIcon.appiconset/Icon20.png | Bin 1313 -> 352 bytes .../AppIcon.appiconset/Icon29.png | Bin 845 -> 422 bytes .../AppIcon.appiconset/Icon40.png | Bin 1101 -> 537 bytes .../AppIcon.appiconset/Icon58.png | Bin 1761 -> 700 bytes .../AppIcon.appiconset/Icon60.png | Bin 2537 -> 700 bytes .../AppIcon.appiconset/Icon76.png | Bin 2332 -> 870 bytes .../AppIcon.appiconset/Icon80.png | Bin 2454 -> 888 bytes .../AppIcon.appiconset/Icon87.png | Bin 2758 -> 942 bytes .../Playground.iOS/CustomIosBootstrapper.cs | 3 + samples/Playground/Playground.iOS/Info.plist | 2 +- .../Playground.iOS/Playground.iOS.csproj | 62 +++++------- .../Playground.iOS/PlaygroundStyles.cs | 2 +- .../Services/NukeImageService.cs | 30 ++++++ .../AdaptiveSectionsViewController.cs | 2 +- .../NestedGroupsViewController.cs | 2 +- .../Views/Collections/ProductViewCell.cs | 5 +- .../Views/MovieCollectionViewCell.cs | 5 +- .../Views/MovieTableViewCell.cs | 5 +- .../Views/Table/ProductTableViewCell.cs | 5 +- .../Playground/Playground/Playground.csproj | 2 +- 122 files changed, 520 insertions(+), 460 deletions(-) delete mode 100644 Softeq.XToolkit.WhiteLabel.Droid/Extensions/ImageViewExtensions.cs create mode 100644 Softeq.XToolkit.WhiteLabel.Droid/Interfaces/IDroidImageService.cs create mode 100644 Softeq.XToolkit.WhiteLabel.Droid/Services/DefaultDroidImageService.cs delete mode 100644 Softeq.XToolkit.WhiteLabel.iOS/Extensions/MvxCachedImageViewExtensions.cs create mode 100644 Softeq.XToolkit.WhiteLabel.iOS/Interfaces/IIosImageService.cs create mode 100644 Softeq.XToolkit.WhiteLabel.iOS/Services/DefaultIosImageService.cs delete mode 100644 azure-pipelines/templates/app-jobs.yml create mode 100644 azure-pipelines/templates/setup-dotnet.yml create mode 100644 azure-pipelines/templates/setup-xcode.yml create mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-anydpi-v26/appicon.xml create mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-anydpi-v26/appicon_round.xml delete mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-anydpi-v26/ic_launcher.xml delete mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-hdpi/appicon.png create mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-hdpi/appicon_background.png create mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-hdpi/appicon_foreground.png delete mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-hdpi/ic_launcher.png delete mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-hdpi/ic_launcher_foreground.png delete mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-hdpi/ic_launcher_round.png create mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-mdpi/appicon.png create mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-mdpi/appicon_background.png create mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-mdpi/appicon_foreground.png delete mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-mdpi/ic_launcher.png delete mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-mdpi/ic_launcher_foreground.png delete mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-mdpi/ic_launcher_round.png create mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-xhdpi/appicon.png create mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-xhdpi/appicon_background.png create mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-xhdpi/appicon_foreground.png delete mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-xhdpi/ic_launcher.png delete mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-xhdpi/ic_launcher_foreground.png delete mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-xhdpi/ic_launcher_round.png create mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-xxhdpi/appicon.png create mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-xxhdpi/appicon_background.png create mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-xxhdpi/appicon_foreground.png delete mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-xxhdpi/ic_launcher.png delete mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-xxhdpi/ic_launcher_foreground.png delete mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-xxxhdpi/appicon.png create mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-xxxhdpi/appicon_background.png create mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-xxxhdpi/appicon_foreground.png delete mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-xxxhdpi/ic_launcher_foreground.png delete mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 samples/Playground/Playground.Droid/Services/GlideImageService.cs create mode 100644 samples/Playground/Playground.iOS/Services/NukeImageService.cs diff --git a/Directory.Build.props b/Directory.Build.props index 4a2fb705a..6514dac69 100755 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,10 +2,13 @@ - 8.0 + 10.0 enable + portable + - + + XToolkit Softeq Development Corporation Copyright © 2023 Softeq Development Corporation diff --git a/Softeq.XToolkit.Bindings.Droid/Softeq.XToolkit.Bindings.Droid.csproj b/Softeq.XToolkit.Bindings.Droid/Softeq.XToolkit.Bindings.Droid.csproj index b45a5aef6..5b875deae 100644 --- a/Softeq.XToolkit.Bindings.Droid/Softeq.XToolkit.Bindings.Droid.csproj +++ b/Softeq.XToolkit.Bindings.Droid/Softeq.XToolkit.Bindings.Droid.csproj @@ -1,8 +1,7 @@ - + - monoandroid12.0 - portable + net6.0-android32.0 diff --git a/Softeq.XToolkit.Bindings.Tests/Softeq.XToolkit.Bindings.Tests.csproj b/Softeq.XToolkit.Bindings.Tests/Softeq.XToolkit.Bindings.Tests.csproj index 666e5710b..a7eca7ccf 100644 --- a/Softeq.XToolkit.Bindings.Tests/Softeq.XToolkit.Bindings.Tests.csproj +++ b/Softeq.XToolkit.Bindings.Tests/Softeq.XToolkit.Bindings.Tests.csproj @@ -1,8 +1,8 @@ + net6.0 Exe - net5.0 disable diff --git a/Softeq.XToolkit.Bindings.iOS/Softeq.XToolkit.Bindings.iOS.csproj b/Softeq.XToolkit.Bindings.iOS/Softeq.XToolkit.Bindings.iOS.csproj index fd450bd8e..4393f4b45 100644 --- a/Softeq.XToolkit.Bindings.iOS/Softeq.XToolkit.Bindings.iOS.csproj +++ b/Softeq.XToolkit.Bindings.iOS/Softeq.XToolkit.Bindings.iOS.csproj @@ -1,17 +1,17 @@ - + - xamarin.ios10 - portable + net6.0-ios12.0 + false true - None + None - SdkOnly + SdkOnly diff --git a/Softeq.XToolkit.Bindings/Softeq.XToolkit.Bindings.csproj b/Softeq.XToolkit.Bindings/Softeq.XToolkit.Bindings.csproj index b59267f4d..a39c9f858 100644 --- a/Softeq.XToolkit.Bindings/Softeq.XToolkit.Bindings.csproj +++ b/Softeq.XToolkit.Bindings/Softeq.XToolkit.Bindings.csproj @@ -1,7 +1,7 @@  - netstandard2.1 + net6.0 diff --git a/Softeq.XToolkit.Common.Droid.Tests/Properties/AndroidManifest.xml b/Softeq.XToolkit.Common.Droid.Tests/Properties/AndroidManifest.xml index b0e0044bd..07b561a06 100644 --- a/Softeq.XToolkit.Common.Droid.Tests/Properties/AndroidManifest.xml +++ b/Softeq.XToolkit.Common.Droid.Tests/Properties/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/Softeq.XToolkit.Common.Droid.Tests/Softeq.XToolkit.Common.Droid.Tests.csproj b/Softeq.XToolkit.Common.Droid.Tests/Softeq.XToolkit.Common.Droid.Tests.csproj index 53e17a26d..b4fb843a8 100644 --- a/Softeq.XToolkit.Common.Droid.Tests/Softeq.XToolkit.Common.Droid.Tests.csproj +++ b/Softeq.XToolkit.Common.Droid.Tests/Softeq.XToolkit.Common.Droid.Tests.csproj @@ -1,11 +1,10 @@ - + - monoandroid12.0 - portable - True - Properties\AndroidManifest.xml - Xamarin.Android.Net.AndroidClientHandler + net6.0-android32.0 + Exe + false + false @@ -29,7 +28,7 @@ - + diff --git a/Softeq.XToolkit.Common.Droid/Softeq.XToolkit.Common.Droid.csproj b/Softeq.XToolkit.Common.Droid/Softeq.XToolkit.Common.Droid.csproj index 30054cb53..0a1d306e8 100644 --- a/Softeq.XToolkit.Common.Droid/Softeq.XToolkit.Common.Droid.csproj +++ b/Softeq.XToolkit.Common.Droid/Softeq.XToolkit.Common.Droid.csproj @@ -1,8 +1,7 @@ - + - monoandroid12.0 - portable + net6.0-android32.0 diff --git a/Softeq.XToolkit.Common.Tests/Files/BaseFileProviderTests/MockFileSystemWrapper.cs b/Softeq.XToolkit.Common.Tests/Files/BaseFileProviderTests/MockFileSystemWrapper.cs index dfe944839..0288c0255 100644 --- a/Softeq.XToolkit.Common.Tests/Files/BaseFileProviderTests/MockFileSystemWrapper.cs +++ b/Softeq.XToolkit.Common.Tests/Files/BaseFileProviderTests/MockFileSystemWrapper.cs @@ -2,6 +2,7 @@ // http://www.softeq.com using System.Collections.Generic; +using System.IO; using System.IO.Abstractions.TestingHelpers; using Softeq.XToolkit.Common.Files.Abstractions; using IDirectory = Softeq.XToolkit.Common.Files.Abstractions.IDirectory; @@ -28,6 +29,10 @@ public MockFileWrapper(IMockFileDataAccessor mockFileDataAccessor) : base(mockFileDataAccessor) { } + + Stream IFile.OpenWrite(string path) => OpenWrite(path); + + Stream IFile.OpenRead(string path) => OpenRead(path); } internal class MockDirectoryWrapper : MockDirectory, IDirectory diff --git a/Softeq.XToolkit.Common.Tests/Softeq.XToolkit.Common.Tests.csproj b/Softeq.XToolkit.Common.Tests/Softeq.XToolkit.Common.Tests.csproj index 991d5b12d..d04cb66bd 100644 --- a/Softeq.XToolkit.Common.Tests/Softeq.XToolkit.Common.Tests.csproj +++ b/Softeq.XToolkit.Common.Tests/Softeq.XToolkit.Common.Tests.csproj @@ -2,7 +2,7 @@ Exe - net5.0 + net6.0 disable @@ -11,7 +11,7 @@ - + diff --git a/Softeq.XToolkit.Common.iOS.Tests/Info.plist b/Softeq.XToolkit.Common.iOS.Tests/Info.plist index 042428b91..56b0926cd 100644 --- a/Softeq.XToolkit.Common.iOS.Tests/Info.plist +++ b/Softeq.XToolkit.Common.iOS.Tests/Info.plist @@ -13,7 +13,7 @@ LSRequiresIPhoneOS MinimumOSVersion - 12.0 + 10.0 UIDeviceFamily 1 diff --git a/Softeq.XToolkit.Common.iOS.Tests/Softeq.XToolkit.Common.iOS.Tests.csproj b/Softeq.XToolkit.Common.iOS.Tests/Softeq.XToolkit.Common.iOS.Tests.csproj index 4595a540d..e0de69a46 100644 --- a/Softeq.XToolkit.Common.iOS.Tests/Softeq.XToolkit.Common.iOS.Tests.csproj +++ b/Softeq.XToolkit.Common.iOS.Tests/Softeq.XToolkit.Common.iOS.Tests.csproj @@ -1,56 +1,37 @@ - + - xamarin.ios10 + net6.0-ios16.0 Exe - portable - 12.0 + iPhoneSimulator;iPhone;AnyCPU + 10.0 + false + true iPhone Developer - NSUrlSessionHandler true - None - x86_64 - true - true - true - false + None + iossimulator-x64 true - SdkOnly - ARM64 - true - true - true - true - true - true + SdkOnly + ios-arm64 - SdkOnly - x86_64 - true + SdkOnly + iossimulator-x64 - SdkOnly - ARM64 - true - true + SdkOnly + ios-arm64 - - - - - - - diff --git a/Softeq.XToolkit.Common.iOS/Softeq.XToolkit.Common.iOS.csproj b/Softeq.XToolkit.Common.iOS/Softeq.XToolkit.Common.iOS.csproj index b6266643d..55767c699 100644 --- a/Softeq.XToolkit.Common.iOS/Softeq.XToolkit.Common.iOS.csproj +++ b/Softeq.XToolkit.Common.iOS/Softeq.XToolkit.Common.iOS.csproj @@ -1,17 +1,17 @@ - + - xamarin.ios10 - portable + net6.0-ios12.0 + false true - None + None - SdkOnly + SdkOnly diff --git a/Softeq.XToolkit.Common/Softeq.XToolkit.Common.csproj b/Softeq.XToolkit.Common/Softeq.XToolkit.Common.csproj index b17e301c6..b62727fd7 100644 --- a/Softeq.XToolkit.Common/Softeq.XToolkit.Common.csproj +++ b/Softeq.XToolkit.Common/Softeq.XToolkit.Common.csproj @@ -1,8 +1,7 @@ - - + - netstandard2.1 + net6.0 diff --git a/Softeq.XToolkit.Connectivity.iOS/Softeq.XToolkit.Connectivity.iOS.csproj b/Softeq.XToolkit.Connectivity.iOS/Softeq.XToolkit.Connectivity.iOS.csproj index 18c9082d6..01458f3cb 100644 --- a/Softeq.XToolkit.Connectivity.iOS/Softeq.XToolkit.Connectivity.iOS.csproj +++ b/Softeq.XToolkit.Connectivity.iOS/Softeq.XToolkit.Connectivity.iOS.csproj @@ -1,17 +1,17 @@ - + - xamarin.ios10 - portable + net6.0-ios10.0 + false true - None + None - SdkOnly + SdkOnly diff --git a/Softeq.XToolkit.Connectivity/Softeq.XToolkit.Connectivity.csproj b/Softeq.XToolkit.Connectivity/Softeq.XToolkit.Connectivity.csproj index 89f60f546..dbd57e59e 100644 --- a/Softeq.XToolkit.Connectivity/Softeq.XToolkit.Connectivity.csproj +++ b/Softeq.XToolkit.Connectivity/Softeq.XToolkit.Connectivity.csproj @@ -1,7 +1,7 @@ - netstandard2.1 + net6.0 diff --git a/Softeq.XToolkit.Permissions.Droid/Softeq.XToolkit.Permissions.Droid.csproj b/Softeq.XToolkit.Permissions.Droid/Softeq.XToolkit.Permissions.Droid.csproj index 9c0b4f1ee..e994c1275 100644 --- a/Softeq.XToolkit.Permissions.Droid/Softeq.XToolkit.Permissions.Droid.csproj +++ b/Softeq.XToolkit.Permissions.Droid/Softeq.XToolkit.Permissions.Droid.csproj @@ -1,8 +1,7 @@ - + - monoandroid12.0 - portable + net6.0-android32.0 @@ -24,7 +23,8 @@ - + + \ No newline at end of file diff --git a/Softeq.XToolkit.Permissions.iOS/Softeq.XToolkit.Permissions.iOS.csproj b/Softeq.XToolkit.Permissions.iOS/Softeq.XToolkit.Permissions.iOS.csproj index 2ffe80ff0..212050bb7 100644 --- a/Softeq.XToolkit.Permissions.iOS/Softeq.XToolkit.Permissions.iOS.csproj +++ b/Softeq.XToolkit.Permissions.iOS/Softeq.XToolkit.Permissions.iOS.csproj @@ -1,17 +1,17 @@ - + - xamarin.ios10 - portable + net6.0-ios10.0 + false true - None + None - SdkOnly + SdkOnly @@ -19,7 +19,7 @@ - + \ No newline at end of file diff --git a/Softeq.XToolkit.Permissions/Softeq.XToolkit.Permissions.csproj b/Softeq.XToolkit.Permissions/Softeq.XToolkit.Permissions.csproj index 22c0ddf15..0a0c3a2cb 100644 --- a/Softeq.XToolkit.Permissions/Softeq.XToolkit.Permissions.csproj +++ b/Softeq.XToolkit.Permissions/Softeq.XToolkit.Permissions.csproj @@ -1,7 +1,7 @@ - netstandard2.1 + net6.0 @@ -9,7 +9,7 @@ - + diff --git a/Softeq.XToolkit.PushNotifications.Droid/Softeq.XToolkit.PushNotifications.Droid.csproj b/Softeq.XToolkit.PushNotifications.Droid/Softeq.XToolkit.PushNotifications.Droid.csproj index b8a7608b6..50dcfe3db 100644 --- a/Softeq.XToolkit.PushNotifications.Droid/Softeq.XToolkit.PushNotifications.Droid.csproj +++ b/Softeq.XToolkit.PushNotifications.Droid/Softeq.XToolkit.PushNotifications.Droid.csproj @@ -1,8 +1,7 @@ - + - monoandroid12.0 - portable + net6.0-android32.0 diff --git a/Softeq.XToolkit.PushNotifications.iOS/Softeq.XToolkit.PushNotifications.iOS.csproj b/Softeq.XToolkit.PushNotifications.iOS/Softeq.XToolkit.PushNotifications.iOS.csproj index e0dd913f2..145be86a8 100644 --- a/Softeq.XToolkit.PushNotifications.iOS/Softeq.XToolkit.PushNotifications.iOS.csproj +++ b/Softeq.XToolkit.PushNotifications.iOS/Softeq.XToolkit.PushNotifications.iOS.csproj @@ -1,17 +1,17 @@ - + - xamarin.ios10 - portable + net6.0-ios10.0 + false true - None + None - SdkOnly + SdkOnly diff --git a/Softeq.XToolkit.PushNotifications/Softeq.XToolkit.PushNotifications.csproj b/Softeq.XToolkit.PushNotifications/Softeq.XToolkit.PushNotifications.csproj index 9e57e48f1..7bdc3593f 100644 --- a/Softeq.XToolkit.PushNotifications/Softeq.XToolkit.PushNotifications.csproj +++ b/Softeq.XToolkit.PushNotifications/Softeq.XToolkit.PushNotifications.csproj @@ -1,7 +1,7 @@ - netstandard2.1 + net6.0 diff --git a/Softeq.XToolkit.Remote.Tests/Softeq.XToolkit.Remote.Tests.csproj b/Softeq.XToolkit.Remote.Tests/Softeq.XToolkit.Remote.Tests.csproj index 6116a53ac..8c81a741c 100644 --- a/Softeq.XToolkit.Remote.Tests/Softeq.XToolkit.Remote.Tests.csproj +++ b/Softeq.XToolkit.Remote.Tests/Softeq.XToolkit.Remote.Tests.csproj @@ -2,7 +2,7 @@ Exe - net5.0 + net6.0 diff --git a/Softeq.XToolkit.Remote/Softeq.XToolkit.Remote.csproj b/Softeq.XToolkit.Remote/Softeq.XToolkit.Remote.csproj index 8be766aee..4eb6fe2cb 100644 --- a/Softeq.XToolkit.Remote/Softeq.XToolkit.Remote.csproj +++ b/Softeq.XToolkit.Remote/Softeq.XToolkit.Remote.csproj @@ -1,7 +1,7 @@ - netstandard2.1 + net6.0 diff --git a/Softeq.XToolkit.WhiteLabel.Droid/Extensions/ImageViewExtensions.cs b/Softeq.XToolkit.WhiteLabel.Droid/Extensions/ImageViewExtensions.cs deleted file mode 100644 index 51f9ca69e..000000000 --- a/Softeq.XToolkit.WhiteLabel.Droid/Extensions/ImageViewExtensions.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System; -using Android.Widget; -using FFImageLoading; -using FFImageLoading.Work; -using Softeq.XToolkit.WhiteLabel.Droid.Controls; -using Softeq.XToolkit.WhiteLabel.Droid.Providers; - -namespace Softeq.XToolkit.WhiteLabel.Droid.Extensions -{ - public static class ImageExtensions - { - public static void LoadImageWithTextPlaceholder(this ImageView imageView, - string url, - string name, - AvatarPlaceholderDrawable.AvatarStyles styles, - Action? transform = null) - { - var context = Dependencies.Container.Resolve().CurrentActivity; - imageView.SetImageDrawable(new AvatarPlaceholderDrawable(context, name, styles)); - - if (string.IsNullOrEmpty(url)) - { - return; - } - - var loader = ImageService.Instance - .LoadUrl(url) - .DownSampleInDip(styles.Size.Width, styles.Size.Height); - - transform?.Invoke(loader); - - loader.IntoAsync(imageView); - } - } -} diff --git a/Softeq.XToolkit.WhiteLabel.Droid/Interfaces/IDroidImageService.cs b/Softeq.XToolkit.WhiteLabel.Droid/Interfaces/IDroidImageService.cs new file mode 100644 index 000000000..aa34540e9 --- /dev/null +++ b/Softeq.XToolkit.WhiteLabel.Droid/Interfaces/IDroidImageService.cs @@ -0,0 +1,19 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using Android.Widget; + +namespace Softeq.XToolkit.WhiteLabel.Droid.Interfaces; + +/// +/// Provides methods to work with images on Android. +/// +public interface IDroidImageService +{ + /// + /// Load remote image target image view. + /// + /// Remote image url. + /// Target image view. + void LoadImage(string url, ImageView into); +} diff --git a/Softeq.XToolkit.WhiteLabel.Droid/Internal/ViewModelCache.cs b/Softeq.XToolkit.WhiteLabel.Droid/Internal/ViewModelCache.cs index 4714b5643..0d15ab1dd 100644 --- a/Softeq.XToolkit.WhiteLabel.Droid/Internal/ViewModelCache.cs +++ b/Softeq.XToolkit.WhiteLabel.Droid/Internal/ViewModelCache.cs @@ -15,7 +15,7 @@ internal static class ViewModelCache { if (_cache.TryGetValue(containerId, out var container)) { - return (TViewModel) container.GetValueOrDefault(key); + return container.GetValueOrDefault(key) as TViewModel; } return null; diff --git a/Softeq.XToolkit.WhiteLabel.Droid/Services/DefaultDroidImageService.cs b/Softeq.XToolkit.WhiteLabel.Droid/Services/DefaultDroidImageService.cs new file mode 100644 index 000000000..186ba4d20 --- /dev/null +++ b/Softeq.XToolkit.WhiteLabel.Droid/Services/DefaultDroidImageService.cs @@ -0,0 +1,93 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System.Threading.Tasks; +using Android.Graphics.Drawables; +using Android.Util; +using Android.Widget; +using Java.Net; +using Softeq.XToolkit.Common.Threading; +using Softeq.XToolkit.WhiteLabel.Droid.Interfaces; + +namespace Softeq.XToolkit.WhiteLabel.Droid.Services; + +/// +/// Default Android implementation of based on Android SDK. +/// +public class DefaultDroidImageService : IDroidImageService +{ + /// + /// Gets internal in-memory cache. + /// + protected virtual SimpleLruCache ImageCache { get; } = new(size: 50); + + /// + public void LoadImage(string url, ImageView into) + { + Task.Run(async () => + { + var cachedDrawable = ImageCache.Get(url); + if (cachedDrawable != null) + { + SetDrawable(into, cachedDrawable); + return; + } + + var downloadedDrawable = await DownloadDrawableAsync(url); + if (downloadedDrawable == null) + { + return; + } + + ImageCache.Put(url, downloadedDrawable); + + SetDrawable(into, downloadedDrawable); + }); + } + + protected virtual async Task DownloadDrawableAsync(string url) + { + var nativeUrl = new URL(url); + var drawable = await Drawable.CreateFromStreamAsync(nativeUrl.OpenStream(), null); + return drawable; + } + + protected virtual void SetDrawable(ImageView image, Drawable drawable) + { + Execute.CurrentExecutor.OnUIThread(() => + { + image.SetImageDrawable(drawable); + }); + } + + /// + /// Simple In-Memory cache based on . + /// + /// Type of cache items. + protected class SimpleLruCache where T : Java.Lang.Object + { + private readonly LruCache _memoryCache; + + protected SimpleLruCache(LruCache cache) + { + _memoryCache = cache; + } + + public SimpleLruCache(int size) : this(new LruCache(size)) + { + } + + public void Put(string key, T drawable) + { + if (_memoryCache.Get(key) == null) + { + _memoryCache.Put(key, drawable); + } + } + + public T? Get(string key) + { + return _memoryCache.Get(key) as T; + } + } +} diff --git a/Softeq.XToolkit.WhiteLabel.Droid/Softeq.XToolkit.WhiteLabel.Droid.csproj b/Softeq.XToolkit.WhiteLabel.Droid/Softeq.XToolkit.WhiteLabel.Droid.csproj index 2c37facb0..0f4790f1b 100644 --- a/Softeq.XToolkit.WhiteLabel.Droid/Softeq.XToolkit.WhiteLabel.Droid.csproj +++ b/Softeq.XToolkit.WhiteLabel.Droid/Softeq.XToolkit.WhiteLabel.Droid.csproj @@ -1,8 +1,7 @@ - + - monoandroid12.0 - portable + net6.0-android32.0 @@ -27,8 +26,7 @@ - - + diff --git a/Softeq.XToolkit.WhiteLabel.Droid/ViewComponents/ViewModelComponent.cs b/Softeq.XToolkit.WhiteLabel.Droid/ViewComponents/ViewModelComponent.cs index 5870a2308..4607f3075 100644 --- a/Softeq.XToolkit.WhiteLabel.Droid/ViewComponents/ViewModelComponent.cs +++ b/Softeq.XToolkit.WhiteLabel.Droid/ViewComponents/ViewModelComponent.cs @@ -7,10 +7,10 @@ namespace Softeq.XToolkit.WhiteLabel.Droid.ViewComponents { /// - /// Component, which encapsulates logic of storing/restoring ViewModel instance within FragmentManager + /// Component, which encapsulates logic of storing/restoring ViewModel instance within FragmentManager. /// /// - /// Type of the ViewModel instance + /// Type of the ViewModel instance. /// public sealed class ViewModelComponent where TViewModel : class { diff --git a/Softeq.XToolkit.WhiteLabel.Essentials.Droid/FullScreenImage/FullScreenImageDialogFragment.cs b/Softeq.XToolkit.WhiteLabel.Essentials.Droid/FullScreenImage/FullScreenImageDialogFragment.cs index aa18cb524..9701ed8ab 100644 --- a/Softeq.XToolkit.WhiteLabel.Essentials.Droid/FullScreenImage/FullScreenImageDialogFragment.cs +++ b/Softeq.XToolkit.WhiteLabel.Essentials.Droid/FullScreenImage/FullScreenImageDialogFragment.cs @@ -5,9 +5,9 @@ using Android.OS; using Android.Views; using Android.Widget; -using FFImageLoading; using Softeq.XToolkit.Bindings; using Softeq.XToolkit.WhiteLabel.Droid.Dialogs; +using Softeq.XToolkit.WhiteLabel.Droid.Interfaces; using Softeq.XToolkit.WhiteLabel.Essentials.FullScreenImage; namespace Softeq.XToolkit.WhiteLabel.Essentials.Droid.FullScreenImage @@ -20,7 +20,7 @@ public class FullScreenImageDialogFragment : DialogFragmentBase Resource.Style.FullScreenImageDialogAnimation; - public override View? OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) + public override View? OnCreateView(LayoutInflater inflater, ViewGroup? container, Bundle? savedInstanceState) { return LayoutInflater.Inflate(Resource.Layout.dialog_full_screen_image, container, true); } @@ -37,7 +37,7 @@ public override void OnViewCreated(View view, Bundle savedInstanceState) _gestureDetector = new GestureDetector(Context, listener); // Load Image - var imageView = View.FindViewById(Resource.Id.dialog_full_screen_image_image); + var imageView = View!.FindViewById(Resource.Id.dialog_full_screen_image_image); LoadImageInto(imageView!); } @@ -49,13 +49,13 @@ public bool OnTouch(View? v, MotionEvent? e) private void LoadImageInto(ImageView imageView) { - var imageService = ImageService.Instance; + var imageService = Dependencies.Container.Resolve(); - var task = string.IsNullOrEmpty(ViewModel.ImagePath) == false - ? imageService.LoadFile(ViewModel.ImagePath) - : imageService.LoadUrl(ViewModel.ImageUrl); + var url = string.IsNullOrEmpty(ViewModel.ImagePath) + ? ViewModel.ImageUrl + : ViewModel.ImagePath; - task.IntoAsync(imageView); + imageService.LoadImage(url!, imageView); } } } diff --git a/Softeq.XToolkit.WhiteLabel.Essentials.Droid/Softeq.XToolkit.WhiteLabel.Essentials.Droid.csproj b/Softeq.XToolkit.WhiteLabel.Essentials.Droid/Softeq.XToolkit.WhiteLabel.Essentials.Droid.csproj index 0a3e446ae..68878b767 100644 --- a/Softeq.XToolkit.WhiteLabel.Essentials.Droid/Softeq.XToolkit.WhiteLabel.Essentials.Droid.csproj +++ b/Softeq.XToolkit.WhiteLabel.Essentials.Droid/Softeq.XToolkit.WhiteLabel.Essentials.Droid.csproj @@ -1,8 +1,7 @@ - + - monoandroid12.0 - portable + net6.0-android32.0 @@ -29,7 +28,6 @@ - \ No newline at end of file diff --git a/Softeq.XToolkit.WhiteLabel.Essentials.iOS/FullScreenImage/FullScreenImageViewController.cs b/Softeq.XToolkit.WhiteLabel.Essentials.iOS/FullScreenImage/FullScreenImageViewController.cs index 495fb95c3..12100ccb9 100644 --- a/Softeq.XToolkit.WhiteLabel.Essentials.iOS/FullScreenImage/FullScreenImageViewController.cs +++ b/Softeq.XToolkit.WhiteLabel.Essentials.iOS/FullScreenImage/FullScreenImageViewController.cs @@ -1,18 +1,18 @@ // Developed by Softeq Development Corporation // http://www.softeq.com -using System; -using FFImageLoading; +using ObjCRuntime; using Softeq.XToolkit.Bindings.iOS.Gestures; using Softeq.XToolkit.WhiteLabel.Essentials.FullScreenImage; using Softeq.XToolkit.WhiteLabel.iOS; +using Softeq.XToolkit.WhiteLabel.iOS.Interfaces; using UIKit; namespace Softeq.XToolkit.WhiteLabel.Essentials.iOS.FullScreenImage { public partial class FullScreenImageViewController : ViewControllerBase { - public FullScreenImageViewController(IntPtr handle) + public FullScreenImageViewController(NativeHandle handle) : base(handle) { } @@ -61,13 +61,13 @@ private void InitView() private void LoadImage() { - var imageService = ImageService.Instance; + var imageService = Dependencies.Container.Resolve(); - var task = string.IsNullOrEmpty(ViewModel.ImagePath) == false - ? imageService.LoadFile(ViewModel.ImagePath) - : imageService.LoadUrl(ViewModel.ImageUrl); + var url = string.IsNullOrEmpty(ViewModel.ImagePath) + ? ViewModel.ImageUrl + : ViewModel.ImagePath; - task.IntoAsync(ImageView); + imageService.LoadImage(url!, ImageView); } } } diff --git a/Softeq.XToolkit.WhiteLabel.Essentials.iOS/ImagePicker/IosImagePickerService.cs b/Softeq.XToolkit.WhiteLabel.Essentials.iOS/ImagePicker/IosImagePickerService.cs index c35820108..6fdfe5501 100644 --- a/Softeq.XToolkit.WhiteLabel.Essentials.iOS/ImagePicker/IosImagePickerService.cs +++ b/Softeq.XToolkit.WhiteLabel.Essentials.iOS/ImagePicker/IosImagePickerService.cs @@ -14,11 +14,13 @@ public class IosImagePickerService : IImagePickerService private TaskCompletionSource? _taskCompletionSource; private UIImagePickerController? _pickerController; + /// public Task PickPhotoAsync(float quality) { return GetImageAsync(UIImagePickerControllerSourceType.PhotoLibrary, quality); } + /// public Task TakePhotoAsync(float quality) { return GetImageAsync(UIImagePickerControllerSourceType.Camera, quality); @@ -48,14 +50,14 @@ public class IosImagePickerService : IImagePickerService }; } - private void OnFinishedPickingMedia(object sender, UIImagePickerMediaPickedEventArgs e) + private void OnFinishedPickingMedia(object? sender, UIImagePickerMediaPickedEventArgs e) { _pickerController!.FinishedPickingMedia -= OnFinishedPickingMedia; _taskCompletionSource!.SetResult(e.OriginalImage); ReleaseImagePicker(); } - private void OnCanceled(object sender, EventArgs e) + private void OnCanceled(object? sender, EventArgs e) { _pickerController!.Canceled -= OnCanceled; _taskCompletionSource!.SetResult(null); diff --git a/Softeq.XToolkit.WhiteLabel.Essentials.iOS/Softeq.XToolkit.WhiteLabel.Essentials.iOS.csproj b/Softeq.XToolkit.WhiteLabel.Essentials.iOS/Softeq.XToolkit.WhiteLabel.Essentials.iOS.csproj index 3c5d1e36c..a76803a2c 100644 --- a/Softeq.XToolkit.WhiteLabel.Essentials.iOS/Softeq.XToolkit.WhiteLabel.Essentials.iOS.csproj +++ b/Softeq.XToolkit.WhiteLabel.Essentials.iOS/Softeq.XToolkit.WhiteLabel.Essentials.iOS.csproj @@ -1,17 +1,17 @@ - + - xamarin.ios10 - portable + net6.0-ios12.0 + false true - None + None - SdkOnly + SdkOnly @@ -26,8 +26,7 @@ - - + \ No newline at end of file diff --git a/Softeq.XToolkit.WhiteLabel.Essentials/ImagePicker/IImagePickerService.cs b/Softeq.XToolkit.WhiteLabel.Essentials/ImagePicker/IImagePickerService.cs index 46ac8ecc3..94b50eba9 100644 --- a/Softeq.XToolkit.WhiteLabel.Essentials/ImagePicker/IImagePickerService.cs +++ b/Softeq.XToolkit.WhiteLabel.Essentials/ImagePicker/IImagePickerService.cs @@ -16,9 +16,9 @@ public interface IImagePickerService /// /// The compression quality to use, 0 is the maximum compression (worse quality), /// and 1 minimum compression (best quality) - /// Default is 1 = 100% + /// Default is 1 = 100%. /// - /// + /// Operation result. Task PickPhotoAsync(float quality = 1); /// @@ -27,9 +27,9 @@ public interface IImagePickerService /// /// The compression quality to use, 0 is the maximum compression (worse quality), /// and 1 minimum compression (best quality) - /// Default is 1 = 100% + /// Default is 1 = 100%. /// - /// + /// Operation result. Task TakePhotoAsync(float quality = 1); } } diff --git a/Softeq.XToolkit.WhiteLabel.Essentials/Softeq.XToolkit.WhiteLabel.Essentials.csproj b/Softeq.XToolkit.WhiteLabel.Essentials/Softeq.XToolkit.WhiteLabel.Essentials.csproj index ae058c129..f21d6a239 100644 --- a/Softeq.XToolkit.WhiteLabel.Essentials/Softeq.XToolkit.WhiteLabel.Essentials.csproj +++ b/Softeq.XToolkit.WhiteLabel.Essentials/Softeq.XToolkit.WhiteLabel.Essentials.csproj @@ -1,7 +1,7 @@ - netstandard2.1 + net6.0 diff --git a/Softeq.XToolkit.WhiteLabel.Tests/Softeq.XToolkit.WhiteLabel.Tests.csproj b/Softeq.XToolkit.WhiteLabel.Tests/Softeq.XToolkit.WhiteLabel.Tests.csproj index 1ad71b980..11171f4e7 100644 --- a/Softeq.XToolkit.WhiteLabel.Tests/Softeq.XToolkit.WhiteLabel.Tests.csproj +++ b/Softeq.XToolkit.WhiteLabel.Tests/Softeq.XToolkit.WhiteLabel.Tests.csproj @@ -1,8 +1,8 @@  + net6.0 Exe - net5.0 disable @@ -11,7 +11,7 @@ - + diff --git a/Softeq.XToolkit.WhiteLabel.iOS/Extensions/MvxCachedImageViewExtensions.cs b/Softeq.XToolkit.WhiteLabel.iOS/Extensions/MvxCachedImageViewExtensions.cs deleted file mode 100644 index 581982d21..000000000 --- a/Softeq.XToolkit.WhiteLabel.iOS/Extensions/MvxCachedImageViewExtensions.cs +++ /dev/null @@ -1,63 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System; -using FFImageLoading; -using FFImageLoading.Cross; -using FFImageLoading.Work; -using UIKit; -using static Softeq.XToolkit.WhiteLabel.iOS.Helpers.AvatarImageHelpers; - -namespace Softeq.XToolkit.WhiteLabel.iOS.Extensions -{ - public static class MvxCachedImageViewExtensions - { - public static void LoadImageAsync(this MvxCachedImageView imageView, string boundleResource, string url) - { - if (string.IsNullOrEmpty(url)) - { - imageView.Image?.Dispose(); - imageView.Image = null; - if (!string.IsNullOrEmpty(boundleResource)) - { - imageView.Image = UIImage.FromBundle(boundleResource); - } - - return; - } - - var expr = ImageService.Instance.LoadUrl(url); - - if (!string.IsNullOrEmpty(boundleResource)) - { - expr = expr.LoadingPlaceholder(boundleResource, ImageSource.CompiledResource) - .ErrorPlaceholder(boundleResource, ImageSource.CompiledResource); - } - - expr.IntoAsync(imageView); - } - - public static void LoadImageWithTextPlaceholder( - this UIImageView imageView, - string url, - string name, - AvatarStyles styles, - Action? transform = null) - { - imageView.Image = CreateAvatarWithTextPlaceholder(name, styles); - - if (string.IsNullOrEmpty(url)) - { - return; - } - - var loader = ImageService.Instance - .LoadUrl(url) - .DownSampleInDip(styles.Size.Width, styles.Size.Height); - - transform?.Invoke(loader); - - loader.IntoAsync(imageView); - } - } -} diff --git a/Softeq.XToolkit.WhiteLabel.iOS/Interfaces/IIosImageService.cs b/Softeq.XToolkit.WhiteLabel.iOS/Interfaces/IIosImageService.cs new file mode 100644 index 000000000..0af1fe4a2 --- /dev/null +++ b/Softeq.XToolkit.WhiteLabel.iOS/Interfaces/IIosImageService.cs @@ -0,0 +1,19 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using UIKit; + +namespace Softeq.XToolkit.WhiteLabel.iOS.Interfaces; + +/// +/// Provides methods to work with images on iOS. +/// +public interface IIosImageService +{ + /// + /// Load remote image target image view. + /// + /// Remote image url. + /// Target image view. + void LoadImage(string url, UIImageView into); +} diff --git a/Softeq.XToolkit.WhiteLabel.iOS/Services/DefaultIosImageService.cs b/Softeq.XToolkit.WhiteLabel.iOS/Services/DefaultIosImageService.cs new file mode 100644 index 000000000..c51de7d47 --- /dev/null +++ b/Softeq.XToolkit.WhiteLabel.iOS/Services/DefaultIosImageService.cs @@ -0,0 +1,34 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using CoreFoundation; +using Foundation; +using Softeq.XToolkit.WhiteLabel.iOS.Interfaces; +using UIKit; + +namespace Softeq.XToolkit.WhiteLabel.iOS.Services; + +/// +/// Default iOS implementation of based on iOS SDK. +/// +public class DefaultIosImageService : IIosImageService +{ + /// + public void LoadImage(string url, UIImageView into) + { + DispatchQueue.DefaultGlobalQueue.DispatchAsync(() => + { + using var nativeUrl = new NSUrl(url); + using var data = NSData.FromUrl(nativeUrl); + + var image = UIImage.LoadFromData(data); + if (image != null) + { + DispatchQueue.MainQueue.DispatchAsync(() => + { + into.Image = image; + }); + } + }); + } +} diff --git a/Softeq.XToolkit.WhiteLabel.iOS/Softeq.XToolkit.WhiteLabel.iOS.csproj b/Softeq.XToolkit.WhiteLabel.iOS/Softeq.XToolkit.WhiteLabel.iOS.csproj index 6413aa90d..003a0c3f9 100644 --- a/Softeq.XToolkit.WhiteLabel.iOS/Softeq.XToolkit.WhiteLabel.iOS.csproj +++ b/Softeq.XToolkit.WhiteLabel.iOS/Softeq.XToolkit.WhiteLabel.iOS.csproj @@ -1,17 +1,17 @@ - + - xamarin.ios10 - portable + net6.0-ios12.0 + false true - None + None - SdkOnly + SdkOnly @@ -23,8 +23,7 @@ - - + \ No newline at end of file diff --git a/Softeq.XToolkit.WhiteLabel/Softeq.XToolkit.WhiteLabel.csproj b/Softeq.XToolkit.WhiteLabel/Softeq.XToolkit.WhiteLabel.csproj index 01dce0e21..9ae7cdf80 100644 --- a/Softeq.XToolkit.WhiteLabel/Softeq.XToolkit.WhiteLabel.csproj +++ b/Softeq.XToolkit.WhiteLabel/Softeq.XToolkit.WhiteLabel.csproj @@ -1,7 +1,7 @@  - netstandard2.1 + net6.0 @@ -9,9 +9,9 @@ - + - + diff --git a/XToolkit.sln b/XToolkit.sln index 504abd872..2443d5108 100644 --- a/XToolkit.sln +++ b/XToolkit.sln @@ -73,26 +73,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Softeq.XToolkit.WhiteLabel. EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WhiteLabel.Essentials", "WhiteLabel.Essentials", "{E99D8F55-E428-4D94-947A-CC25E5B61BE6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Playground.Forms", "samples\Playground.Forms\Playground.Forms\Playground.Forms.csproj", "{E8FAE67B-887D-4EEB-96BC-7C48A0ACACC8}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Playground.Forms.iOS", "samples\Playground.Forms\Playground.Forms.iOS\Playground.Forms.iOS.csproj", "{7923F495-A6D6-4D60-ACE0-0D5D61C1BC92}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Softeq.XToolkit.WhiteLabel.Forms", "Softeq.XToolkit.WhiteLabel.Forms\Softeq.XToolkit.WhiteLabel.Forms.csproj", "{EE5E06CB-17B2-4ECA-BC82-DD06D60EAB6D}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Playground.Forms", "Playground.Forms", "{56850655-3264-43CE-AAE6-4A59F9CCB0EF}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WhiteLabel.Forms", "WhiteLabel.Forms", "{0991B9DD-73BF-4A3E-9CF7-E417045FC889}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_Playground", "_Playground", "{730602C4-6848-412A-A457-724474A504B7}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Playground", "Playground", "{6A926916-1067-40F9-A266-A2F71C3B2DD4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Softeq.XToolkit.Common.iOS.Tests", "Softeq.XToolkit.Common.iOS.Tests\Softeq.XToolkit.Common.iOS.Tests.csproj", "{8F494CF3-61BE-4140-AA49-D9660C0271C6}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Softeq.XToolkit.Common.Droid.Tests", "Softeq.XToolkit.Common.Droid.Tests\Softeq.XToolkit.Common.Droid.Tests.csproj", "{69A56CFD-E5A6-4A7D-9F41-25EB88975BF8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Playground.RemoteData", "samples\Playground.RemoteData\Playground.RemoteData.csproj", "{43D3F9A3-CBDB-4F98-A13B-A424FA2E150B}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Release|Any CPU = Release|Any CPU @@ -288,21 +274,6 @@ Global {CAEA919A-5B4A-4D97-AB4C-33229484BD7A}.Debug.Droid|Any CPU.Build.0 = Debug|Any CPU {CAEA919A-5B4A-4D97-AB4C-33229484BD7A}.Debug.iOS|iPhoneSimulator.ActiveCfg = Debug|Any CPU {CAEA919A-5B4A-4D97-AB4C-33229484BD7A}.Debug.iOS|iPhone.ActiveCfg = Debug|Any CPU - {E8FAE67B-887D-4EEB-96BC-7C48A0ACACC8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E8FAE67B-887D-4EEB-96BC-7C48A0ACACC8}.Release|Any CPU.Build.0 = Release|Any CPU - {E8FAE67B-887D-4EEB-96BC-7C48A0ACACC8}.Debug.Droid|Any CPU.ActiveCfg = Debug|Any CPU - {E8FAE67B-887D-4EEB-96BC-7C48A0ACACC8}.Debug.Droid|Any CPU.Build.0 = Debug|Any CPU - {E8FAE67B-887D-4EEB-96BC-7C48A0ACACC8}.Debug.iOS|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {E8FAE67B-887D-4EEB-96BC-7C48A0ACACC8}.Debug.iOS|iPhoneSimulator.Build.0 = Debug|Any CPU - {E8FAE67B-887D-4EEB-96BC-7C48A0ACACC8}.Debug.iOS|iPhone.ActiveCfg = Debug|Any CPU - {E8FAE67B-887D-4EEB-96BC-7C48A0ACACC8}.Debug.iOS|iPhone.Build.0 = Debug|Any CPU - {7923F495-A6D6-4D60-ACE0-0D5D61C1BC92}.Release|Any CPU.ActiveCfg = Release|iPhoneSimulator - {7923F495-A6D6-4D60-ACE0-0D5D61C1BC92}.Release|Any CPU.Build.0 = Release|iPhoneSimulator - {7923F495-A6D6-4D60-ACE0-0D5D61C1BC92}.Debug.Droid|Any CPU.ActiveCfg = Debug|iPhoneSimulator - {7923F495-A6D6-4D60-ACE0-0D5D61C1BC92}.Debug.iOS|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator - {7923F495-A6D6-4D60-ACE0-0D5D61C1BC92}.Debug.iOS|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator - {7923F495-A6D6-4D60-ACE0-0D5D61C1BC92}.Debug.iOS|iPhone.ActiveCfg = Debug|iPhone - {7923F495-A6D6-4D60-ACE0-0D5D61C1BC92}.Debug.iOS|iPhone.Build.0 = Debug|iPhone {6B7105F6-6F18-4999-BA4E-8069D92F3150}.Release|Any CPU.ActiveCfg = Release|iPhoneSimulator {6B7105F6-6F18-4999-BA4E-8069D92F3150}.Release|Any CPU.Build.0 = Release|iPhoneSimulator {6B7105F6-6F18-4999-BA4E-8069D92F3150}.Debug.Droid|Any CPU.ActiveCfg = Debug|iPhoneSimulator @@ -310,14 +281,6 @@ Global {6B7105F6-6F18-4999-BA4E-8069D92F3150}.Debug.iOS|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator {6B7105F6-6F18-4999-BA4E-8069D92F3150}.Debug.iOS|iPhone.ActiveCfg = Debug|iPhone {6B7105F6-6F18-4999-BA4E-8069D92F3150}.Debug.iOS|iPhone.Build.0 = Debug|iPhone - {EE5E06CB-17B2-4ECA-BC82-DD06D60EAB6D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EE5E06CB-17B2-4ECA-BC82-DD06D60EAB6D}.Release|Any CPU.Build.0 = Release|Any CPU - {EE5E06CB-17B2-4ECA-BC82-DD06D60EAB6D}.Debug.Droid|Any CPU.ActiveCfg = Debug|Any CPU - {EE5E06CB-17B2-4ECA-BC82-DD06D60EAB6D}.Debug.Droid|Any CPU.Build.0 = Debug|Any CPU - {EE5E06CB-17B2-4ECA-BC82-DD06D60EAB6D}.Debug.iOS|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {EE5E06CB-17B2-4ECA-BC82-DD06D60EAB6D}.Debug.iOS|iPhoneSimulator.Build.0 = Debug|Any CPU - {EE5E06CB-17B2-4ECA-BC82-DD06D60EAB6D}.Debug.iOS|iPhone.ActiveCfg = Debug|Any CPU - {EE5E06CB-17B2-4ECA-BC82-DD06D60EAB6D}.Debug.iOS|iPhone.Build.0 = Debug|Any CPU {1CD42D28-1118-4FEF-8DA9-8C14A47098B4}.Release|Any CPU.ActiveCfg = Release|Any CPU {1CD42D28-1118-4FEF-8DA9-8C14A47098B4}.Release|Any CPU.Build.0 = Release|Any CPU {1CD42D28-1118-4FEF-8DA9-8C14A47098B4}.Debug.Droid|Any CPU.ActiveCfg = Debug|Any CPU @@ -326,27 +289,12 @@ Global {1CD42D28-1118-4FEF-8DA9-8C14A47098B4}.Debug.iOS|iPhoneSimulator.Build.0 = Debug|Any CPU {1CD42D28-1118-4FEF-8DA9-8C14A47098B4}.Debug.iOS|iPhone.ActiveCfg = Debug|Any CPU {1CD42D28-1118-4FEF-8DA9-8C14A47098B4}.Debug.iOS|iPhone.Build.0 = Debug|Any CPU - {8F494CF3-61BE-4140-AA49-D9660C0271C6}.Release|Any CPU.ActiveCfg = Release|iPhoneSimulator - {8F494CF3-61BE-4140-AA49-D9660C0271C6}.Release|Any CPU.Build.0 = Release|iPhoneSimulator - {8F494CF3-61BE-4140-AA49-D9660C0271C6}.Debug.Droid|Any CPU.ActiveCfg = Debug|iPhoneSimulator - {8F494CF3-61BE-4140-AA49-D9660C0271C6}.Debug.iOS|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator - {8F494CF3-61BE-4140-AA49-D9660C0271C6}.Debug.iOS|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator - {8F494CF3-61BE-4140-AA49-D9660C0271C6}.Debug.iOS|iPhone.ActiveCfg = Debug|iPhone - {8F494CF3-61BE-4140-AA49-D9660C0271C6}.Debug.iOS|iPhone.Build.0 = Debug|iPhone {69A56CFD-E5A6-4A7D-9F41-25EB88975BF8}.Release|Any CPU.ActiveCfg = Release|Any CPU {69A56CFD-E5A6-4A7D-9F41-25EB88975BF8}.Release|Any CPU.Build.0 = Release|Any CPU {69A56CFD-E5A6-4A7D-9F41-25EB88975BF8}.Debug.Droid|Any CPU.ActiveCfg = Debug|Any CPU {69A56CFD-E5A6-4A7D-9F41-25EB88975BF8}.Debug.Droid|Any CPU.Build.0 = Debug|Any CPU {69A56CFD-E5A6-4A7D-9F41-25EB88975BF8}.Debug.iOS|iPhoneSimulator.ActiveCfg = Debug|Any CPU {69A56CFD-E5A6-4A7D-9F41-25EB88975BF8}.Debug.iOS|iPhone.ActiveCfg = Debug|Any CPU - {43D3F9A3-CBDB-4F98-A13B-A424FA2E150B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {43D3F9A3-CBDB-4F98-A13B-A424FA2E150B}.Release|Any CPU.Build.0 = Release|Any CPU - {43D3F9A3-CBDB-4F98-A13B-A424FA2E150B}.Debug.Droid|Any CPU.ActiveCfg = Debug|Any CPU - {43D3F9A3-CBDB-4F98-A13B-A424FA2E150B}.Debug.Droid|Any CPU.Build.0 = Debug|Any CPU - {43D3F9A3-CBDB-4F98-A13B-A424FA2E150B}.Debug.iOS|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {43D3F9A3-CBDB-4F98-A13B-A424FA2E150B}.Debug.iOS|iPhoneSimulator.Build.0 = Debug|Any CPU - {43D3F9A3-CBDB-4F98-A13B-A424FA2E150B}.Debug.iOS|iPhone.ActiveCfg = Debug|Any CPU - {43D3F9A3-CBDB-4F98-A13B-A424FA2E150B}.Debug.iOS|iPhone.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {24588814-B93D-4528-8917-9C2A3C4E85CA} = {2CC5305B-22A3-48B0-858A-98478AC14EF9} @@ -373,17 +321,11 @@ Global {887C6CE5-075B-420D-A88F-60479C4DC25A} = {E99D8F55-E428-4D94-947A-CC25E5B61BE6} {CAEA919A-5B4A-4D97-AB4C-33229484BD7A} = {E99D8F55-E428-4D94-947A-CC25E5B61BE6} {23AA0E71-BF8F-47A8-A915-C8AC4841E064} = {E99D8F55-E428-4D94-947A-CC25E5B61BE6} - {E8FAE67B-887D-4EEB-96BC-7C48A0ACACC8} = {56850655-3264-43CE-AAE6-4A59F9CCB0EF} - {7923F495-A6D6-4D60-ACE0-0D5D61C1BC92} = {56850655-3264-43CE-AAE6-4A59F9CCB0EF} - {EE5E06CB-17B2-4ECA-BC82-DD06D60EAB6D} = {0991B9DD-73BF-4A3E-9CF7-E417045FC889} {1CD42D28-1118-4FEF-8DA9-8C14A47098B4} = {4C271D8F-4A71-4D1F-9715-BFBFB4805493} {6A926916-1067-40F9-A266-A2F71C3B2DD4} = {730602C4-6848-412A-A457-724474A504B7} {00250664-56DC-4B3D-93B9-7C1E8B381F5F} = {6A926916-1067-40F9-A266-A2F71C3B2DD4} {EDFA0566-1772-4442-86F8-828E5EB98B16} = {6A926916-1067-40F9-A266-A2F71C3B2DD4} {6B7105F6-6F18-4999-BA4E-8069D92F3150} = {6A926916-1067-40F9-A266-A2F71C3B2DD4} - {56850655-3264-43CE-AAE6-4A59F9CCB0EF} = {730602C4-6848-412A-A457-724474A504B7} - {8F494CF3-61BE-4140-AA49-D9660C0271C6} = {2CC5305B-22A3-48B0-858A-98478AC14EF9} {69A56CFD-E5A6-4A7D-9F41-25EB88975BF8} = {2CC5305B-22A3-48B0-858A-98478AC14EF9} - {43D3F9A3-CBDB-4F98-A13B-A424FA2E150B} = {56850655-3264-43CE-AAE6-4A59F9CCB0EF} EndGlobalSection EndGlobal diff --git a/_Tests.targets b/_Tests.targets index 157e4e6a5..55a7dab5d 100644 --- a/_Tests.targets +++ b/_Tests.targets @@ -2,17 +2,17 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - runtime; build; native; contentfiles; analyzers; buildtransitive + all + runtime; build; native; contentfiles; analyzers; buildtransitive - + \ No newline at end of file diff --git a/azure-pipelines/XToolkit.yml b/azure-pipelines/XToolkit.yml index 94aeb9e8f..b6f044925 100644 --- a/azure-pipelines/XToolkit.yml +++ b/azure-pipelines/XToolkit.yml @@ -18,6 +18,32 @@ variables: - template: templates/vars.yml jobs: -- template: templates/app-jobs.yml - parameters: - SolutionPath: 'XToolkit.sln' \ No newline at end of file +- job: macOS + pool: + vmImage: $(MACOS_VM_IMAGE) + steps: + - template: templates/setup-dotnet.yml + parameters: + version: $(DOTNET_SDK_VERSION) + + - template: templates/setup-xcode.yml + parameters: + version: $(XCODE_VERSION) + + - task: DotNetCoreCLI@2 + displayName: Build Solution + inputs: + command: build + configuration: Release + arguments: --verbosity Detailed + + - task: DotNetCoreCLI@2 + displayName: Run Tests + inputs: + command: test + projects: | + **/*.Tests.csproj + !**/*.iOS.Tests.csproj + !**/*.Droid.Tests.csproj + configuration: Release + arguments: --verbosity Detailed \ No newline at end of file diff --git a/azure-pipelines/templates/app-jobs.yml b/azure-pipelines/templates/app-jobs.yml deleted file mode 100644 index 05baed15b..000000000 --- a/azure-pipelines/templates/app-jobs.yml +++ /dev/null @@ -1,31 +0,0 @@ -parameters: # defaults for any parameters that aren't specified - SolutionPath: '' - -jobs: -- job: macOS - pool: - vmImage: $(MACOS_VM_IMAGE) - steps: - # setup env - - task: UseDotNet@2 - displayName: Use .NET SDK - inputs: - version: $(DOTNET_SDK_VERSION) - - bash: sudo $AGENT_HOMEDIRECTORY/scripts/select-xamarin-sdk.sh $(MONO_VERSION) - displayName: Select Xamarin SDK - - bash: | - echo '##vso[task.setvariable variable=MD_APPLE_SDK_ROOT;]'/Applications/Xcode_$(XCODE_VERSION).app - sudo xcode-select -s /Applications/Xcode_$(XCODE_VERSION).app/Contents/Developer - displayName: Select Xcode - # restore, build - - script: 'msbuild ${{ parameters.SolutionPath }} -r /p:Configuration="$(BuildConfiguration)" /p:Platform="Any CPU"' - displayName: Build Solution - - task: DotNetCoreCLI@2 - displayName: Run Tests - inputs: - command: test - projects: | - **/*.Tests.csproj - !**/*.iOS.Tests.csproj - !**/*.Droid.Tests.csproj - arguments: '-c Release' diff --git a/azure-pipelines/templates/setup-dotnet.yml b/azure-pipelines/templates/setup-dotnet.yml new file mode 100644 index 000000000..55511b850 --- /dev/null +++ b/azure-pipelines/templates/setup-dotnet.yml @@ -0,0 +1,24 @@ +parameters: +- name: version + type: string + default: 6.0.402 + +steps: +- task: UseDotNet@2 + displayName: Use .NET ${{ parameters.version }} + inputs: + packageType: 'sdk' + version: ${{ parameters.version }} + +- task: UseDotNet@2 + displayName: Use .NET from GlobalJson + inputs: + packageType: 'sdk' + useGlobalJson: true + +- task: Bash@3 + displayName: Install .NET Workloads + inputs: + targetType: 'inline' + script: | + dotnet workload install ios android \ No newline at end of file diff --git a/azure-pipelines/templates/setup-xcode.yml b/azure-pipelines/templates/setup-xcode.yml new file mode 100644 index 000000000..b650d2a4c --- /dev/null +++ b/azure-pipelines/templates/setup-xcode.yml @@ -0,0 +1,13 @@ +parameters: +- name: version + type: string + default: 14.2 + +steps: +- task: Bash@3 + displayName: Select Xcode + inputs: + targetType: 'inline' + script: | + echo '##vso[task.setvariable variable=MD_APPLE_SDK_ROOT;]'/Applications/Xcode_${{ parameters.version }}.app + sudo xcode-select -s /Applications/Xcode_${{ parameters.version }}.app/Contents/Developer \ No newline at end of file diff --git a/azure-pipelines/templates/vars.yml b/azure-pipelines/templates/vars.yml index 0ee98239a..97c2374ff 100644 --- a/azure-pipelines/templates/vars.yml +++ b/azure-pipelines/templates/vars.yml @@ -3,8 +3,6 @@ # https://github.com/actions/runner-images variables: - MONO_VERSION: 6_12_21 XCODE_VERSION: 14.2 - DOTNET_SDK_VERSION: 5.0.408 - BuildConfiguration: 'Release' + DOTNET_SDK_VERSION: 6.0.402 MACOS_VM_IMAGE: 'macos-12' diff --git a/generate_test_coverage_report.sh b/generate_test_coverage_report.sh index eb8fe7709..9421f6e2f 100644 --- a/generate_test_coverage_report.sh +++ b/generate_test_coverage_report.sh @@ -1,30 +1,32 @@ #!/bin/bash -GIT_HASH=$(git rev-parse HEAD) -COVERAGE_REPORT_PATH=temp_coverage_report_$GIT_HASH - - # install tools if ! dotnet tool list -g | grep "dotnet-reportgenerator-globaltool" > /dev/null; -then +then dotnet tool install -g dotnet-reportgenerator-globaltool fi # run tests -dotnet test \ - --filter "(FullyQualifiedName!~Droid.Tests)&(FullyQualifiedName!~iOS.Tests)" \ - /p:CollectCoverage=true \ - /p:CoverletOutputFormat=cobertura \ - /p:CoverletOutput=$COVERAGE_REPORT_PATH/ +test_projects=( + Softeq.XToolkit.Common.Tests + Softeq.XToolkit.Bindings.Tests + Softeq.XToolkit.WhiteLabel.Tests + Softeq.XToolkit.Remote.Tests +) + +for test_project in "${test_projects[@]}" +do + dotnet test $test_project --collect:"XPlat Code Coverage;Format=cobertura" +done # create reports reportgenerator \ - -reports:*.Tests/$COVERAGE_REPORT_PATH/coverage.cobertura.xml \ + -reports:*.Tests/TestResults/*/coverage.cobertura.xml \ -targetdir:coverage_report \ -reporttypes:Html # cleanup -rm -rf $(find . -path "./*.Tests/${COVERAGE_REPORT_PATH}") +rm -rf $(find . -path "./*.Tests/TestResults") # open report in default browser open coverage_report/index.html diff --git a/global.json b/global.json index d268ac7fb..73a5d6411 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,7 @@ { - "msbuild-sdks": { - "MSBuild.Sdk.Extras": "3.0.44", + "sdk": { + "version": "7.0.103", + "rollForward": "latestMajor", + "allowPrerelease": false } } \ No newline at end of file diff --git a/samples/Playground.Forms/Playground.Forms.Droid/Playground.Forms.Droid.csproj b/samples/Playground.Forms/Playground.Forms.Droid/Playground.Forms.Droid.csproj index 0a25d629a..139d4c9bf 100644 --- a/samples/Playground.Forms/Playground.Forms.Droid/Playground.Forms.Droid.csproj +++ b/samples/Playground.Forms/Playground.Forms.Droid/Playground.Forms.Droid.csproj @@ -22,7 +22,6 @@ true - portable false bin\Debug DEBUG; @@ -33,7 +32,6 @@ true - portable true bin\Release prompt diff --git a/samples/Playground.Forms/Playground.Forms.iOS/Playground.Forms.iOS.csproj b/samples/Playground.Forms/Playground.Forms.iOS/Playground.Forms.iOS.csproj index 9bd88516f..5e4bc8525 100644 --- a/samples/Playground.Forms/Playground.Forms.iOS/Playground.Forms.iOS.csproj +++ b/samples/Playground.Forms/Playground.Forms.iOS/Playground.Forms.iOS.csproj @@ -18,28 +18,25 @@ true - portable false bin\iPhoneSimulator\Debug DEBUG prompt 4 x86_64 - None + None true - portable true bin\iPhoneSimulator\Release prompt 4 - SdkOnly + SdkOnly x86_64 true - portable false bin\iPhone\Debug DEBUG @@ -49,11 +46,10 @@ iPhone Developer true Entitlements.plist - None + None -all - portable true bin\iPhone\Release prompt @@ -61,7 +57,7 @@ ARM64 iPhone Developer Entitlements.plist - SdkOnly + SdkOnly diff --git a/samples/Playground/Playground.Droid/CustomDroidBootstrapper.cs b/samples/Playground/Playground.Droid/CustomDroidBootstrapper.cs index 5f56c3e04..d9335e49a 100644 --- a/samples/Playground/Playground.Droid/CustomDroidBootstrapper.cs +++ b/samples/Playground/Playground.Droid/CustomDroidBootstrapper.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Reflection; using Playground.Droid.Extended; +using Playground.Droid.Services; using Playground.Extended; using Softeq.XToolkit.Common.Droid.Permissions; using Softeq.XToolkit.Common.Extensions; @@ -13,6 +14,7 @@ using Softeq.XToolkit.WhiteLabel.Bootstrapper.Abstract; using Softeq.XToolkit.WhiteLabel.Droid; using Softeq.XToolkit.WhiteLabel.Droid.Dialogs; +using Softeq.XToolkit.WhiteLabel.Droid.Interfaces; using Softeq.XToolkit.WhiteLabel.Droid.Services; using Softeq.XToolkit.WhiteLabel.Essentials.Droid.FullScreenImage; using Softeq.XToolkit.WhiteLabel.Essentials.Droid.ImagePicker; @@ -47,6 +49,7 @@ protected override void ConfigureIoc(IContainerBuilder builder) builder.Singleton(); // image picker + builder.Singleton(); builder.Singleton(); builder.Singleton(); diff --git a/samples/Playground/Playground.Droid/MainApplication.cs b/samples/Playground/Playground.Droid/MainApplication.cs index 35f2d6b43..220c93361 100644 --- a/samples/Playground/Playground.Droid/MainApplication.cs +++ b/samples/Playground/Playground.Droid/MainApplication.cs @@ -16,8 +16,8 @@ namespace Playground.Droid #endif public class MainApplication : MainApplicationBase { - protected MainApplication(IntPtr handle, JniHandleOwnership transer) - : base(handle, transer) + protected MainApplication(IntPtr handle, JniHandleOwnership transfer) + : base(handle, transfer) { } diff --git a/samples/Playground/Playground.Droid/Playground.Droid.csproj b/samples/Playground/Playground.Droid/Playground.Droid.csproj index a6c033f05..d97d9068b 100644 --- a/samples/Playground/Playground.Droid/Playground.Droid.csproj +++ b/samples/Playground/Playground.Droid/Playground.Droid.csproj @@ -1,21 +1,22 @@ - + + + - monoandroid12.0 - portable - True - Properties\AndroidManifest.xml - Xamarin.Android.Net.AndroidClientHandler + net7.0-android33.0 + Exe + android-arm;android-arm64;android-x86;android-x64 true - None - SdkOnly - True + true @@ -38,4 +39,8 @@ + + + + \ No newline at end of file diff --git a/samples/Playground/Playground.Droid/Properties/AndroidManifest.xml b/samples/Playground/Playground.Droid/Properties/AndroidManifest.xml index c12207c06..7a54ddb85 100644 --- a/samples/Playground/Playground.Droid/Properties/AndroidManifest.xml +++ b/samples/Playground/Playground.Droid/Properties/AndroidManifest.xml @@ -1,12 +1,12 @@  - + - + diff --git a/samples/Playground/Playground.Droid/Resources/drawable/background_splash.xml b/samples/Playground/Playground.Droid/Resources/drawable/background_splash.xml index 96f1a1860..1ac6c7d0e 100644 --- a/samples/Playground/Playground.Droid/Resources/drawable/background_splash.xml +++ b/samples/Playground/Playground.Droid/Resources/drawable/background_splash.xml @@ -4,7 +4,7 @@ diff --git a/samples/Playground/Playground.Droid/Resources/mipmap-anydpi-v26/appicon.xml b/samples/Playground/Playground.Droid/Resources/mipmap-anydpi-v26/appicon.xml new file mode 100644 index 000000000..aea3c73a8 --- /dev/null +++ b/samples/Playground/Playground.Droid/Resources/mipmap-anydpi-v26/appicon.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/samples/Playground/Playground.Droid/Resources/mipmap-anydpi-v26/appicon_round.xml b/samples/Playground/Playground.Droid/Resources/mipmap-anydpi-v26/appicon_round.xml new file mode 100644 index 000000000..aea3c73a8 --- /dev/null +++ b/samples/Playground/Playground.Droid/Resources/mipmap-anydpi-v26/appicon_round.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/samples/Playground/Playground.Droid/Resources/mipmap-anydpi-v26/ic_launcher.xml b/samples/Playground/Playground.Droid/Resources/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index c9ad5f98f..000000000 --- a/samples/Playground/Playground.Droid/Resources/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/samples/Playground/Playground.Droid/Resources/mipmap-anydpi-v26/ic_launcher_round.xml b/samples/Playground/Playground.Droid/Resources/mipmap-anydpi-v26/ic_launcher_round.xml deleted file mode 100644 index c9ad5f98f..000000000 --- a/samples/Playground/Playground.Droid/Resources/mipmap-anydpi-v26/ic_launcher_round.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/samples/Playground/Playground.Droid/Resources/mipmap-hdpi/appicon.png b/samples/Playground/Playground.Droid/Resources/mipmap-hdpi/appicon.png new file mode 100644 index 0000000000000000000000000000000000000000..77d8b10f4c5c5a2884b0be4144e2eeecb19198ed GIT binary patch literal 1149 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@ZgvM!EX7WqAsj$Z!;#VfiL!9jb`wk+||g#qTJm)B;*x;T~u z2be4pTf;4yIl)bQQdZQ3Sy9=#D|@%Ce0I^ss?z^iVa%DcXJ?#w;=gRN)YtzH4$XXM zzyAHd?{|vZt<&5eM|MvVK5uW^u(gj%D8P|P%fo?{bAWv3}Y*opX=X>6B_g2U`UFqg8aXlZ;iU%?{xysEd3l-Gzns3c5p0O51Ot`t*-oXQ|kNY7YTFDecQNZ<9FMLW7F7Ay~vR- zW@YW>JzITfN_b`Qx5B0BZT1QOuALA1^2ca%8H?_dkF~EYYm=H*e!SB4+RtTj%&U2f zkDs(`P2jmWktJ^Z!{_hS?%qh4*zQ{{#%ORwKK+5$o!W!wUuB1Mue&zw`T0|gp>MnE&j(){YufgAH0fU| z7J6iOx?laW@Qt57Gmf2AJ)M7KqK$g>(kIdS(yZc7_|6?z{+{PzZJJcW@p;krsvO;> z=~_P)GYdQreke@0nIY-2W>oaA8NHEG=F=~HahY0E#=A~s{dobMs2UFuqvo*EiQB~L zY^{BdU(C*_UovU=xBLfpR(k4{a(6E?K6v}R*0Tq{C;nVh^hEZ?U1z1bB#7c<^<`4z?d$eh*B&%0~-TEmxXeAM#pR4-p$Haq^rN&VwL{=F`IcQfqN zy65Hv&yJ*gJJ7SXpv2wC`ck0n}-T7Pj7(aZtocJoAJ2n4E+WP%5*L>JQ+iG*N*C|{QC=$PS=Af?4CCC5dFJ^9}3 zn^E^q&_ng%+UdV5R(=ihIyG%x#^Jk1x72JAe7$7K!Gw(^a?+3YhX4L7`>X4Ob#sqr zyb*(Dw&&7k`{yf%uZrJia5#K^&$GNcE5$jaR%O?=Wd8-Cyq#N=zEA!a^!Hiw&Uyc{ zelWLHzt>;-{0`6Gy_+Mr<@kRvsp%S+>E|iR>Hqliaz_eN`Qvn+AI-;ft@)oEsIfG@ zH@BnF;Y5P&5}^qi7s4LwV)-k7VSWrN=@|?ye;$bc%j{X3>v8oGusmV#boFyt=akR{ E0As5jqW}N^ literal 0 HcmV?d00001 diff --git a/samples/Playground/Playground.Droid/Resources/mipmap-hdpi/appicon_background.png b/samples/Playground/Playground.Droid/Resources/mipmap-hdpi/appicon_background.png new file mode 100644 index 0000000000000000000000000000000000000000..292cfd22cb98f86d3376e79757498241d532f319 GIT binary patch literal 532 zcmeAS@N?(olHy`uVBq!ia0vp^i$Iuz4M-mPBqj}{Sc;uILpXq-h9jkefr0U~r;B4q z#hka73>gn72pl+&|8@b}`>(+%6CV0Gw%JtI{y&iV{w>1{LnT2sm4h8B5))aJCndND n^>{Q1dKxHkjtUN*0I|6Lhk5e1*{6Wf%D~|1>gTe~DWM4fpue45 literal 0 HcmV?d00001 diff --git a/samples/Playground/Playground.Droid/Resources/mipmap-hdpi/appicon_foreground.png b/samples/Playground/Playground.Droid/Resources/mipmap-hdpi/appicon_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..8180c727115fce0ca70c2d2c3e19dfcbdd546337 GIT binary patch literal 1511 zcmbVM`!~}I0H0YIH?))YBi(Uw<&NvIyoPg`SPTo@2x&5TZ_WETb!9ZUN*>#AN}jQM zMToh+M;_5o@+Ow+xxV_@S?+I|yQ}E>dS@T#q)Ni!t zqQw&z!}45Zk;XEow1Z3DwJ8&^54G4Fi?Fiqbuk3%;QH?kLlT$=IzP&%If$hXlxAbW zGav%V(8{4E_0iJwiIFuzEf{IXQsgU@9LP)*?SPWtd?LXgI@`T13jG6(vR$+ctsi>E zmlo;}F@w#T#e3msaS%;-t(JP&^;e2byDNhp4>N!9O?ZO01`I&>tl*jag}=k5qayvA zB}B%8Ce;b6ew@CpO4vLi3=R&LV$tKYP3b|d-5%)-WAYkX{mFb=GR-`@ks@UxXor!a zkME?o3)6@+156VmpFMivg5A#;-cUmsaxNi>y;iARCH622a|Lj+*5HaeCjZkdil zO1>$jx<|j2;LWR!dLdsWgEX?K+!kpSSQMyJeYcGx#t&95YvWfA1mlY}FV_POc18qP zAlF1dlkSd60vp{sl{@60eJ4^EUm^({ItN)%v|P``R#_$GAp+s2A^rxBBEY*hmhM|L z1z)yId0F))>x!9Whol%1-{SeN%xr0KSyAY-+CjX|m+hM$7SVN$V6jqbBYq|USk&%a zjqZybymS_i)*;FhtD7S9dtUV94!2?(i_VwOl}9@%DT<51VEG$MdLGr}(*C!ru;h?a z4QA(TASmyK1l@ACcR95sb{yI3HHJ4G(S)UzbX!oF>SU9u5J#|?Y7ao^d1>>Ncq|TT zWTBlXGqA66N8)5Eu(w1>NcTXQM;01d`cFAn)-4%sL;@qkn7b;5V&Z)Af ztLJ7QpVtwnF=hl6{TWN107n&`uLmWW2(~d^$j9ap(TSmRC$pd@VVRT;MTP;`fPo*A zX3J}=eV4UiC8pA1F=MMW39Gp<=hKx}as)5ZpJRc+;yq6^+maxzh4@HD@;=(%*)g@o zt+In$x_2(zeFYd&xcLLako7m55?I6e`LM(_jE3$r+Lq+!vtTo#o^?MuIQnZ0hu ztK~3z6_z`eer{t@W2DV4W}aXbt+0*hUFI=iMQSY~VUt3)scnFv@BpBUo1$;|yxyZPzSGkUO z$nw}Z>Zq?;$_1mv4E{1|z+?NRC$}KX>Jtaw^l3Zu=}}WvNR=&E+~C=q!C$tg-%`Ee z{1aKTA?`NOas$hI50HTcAaCvgE6*NTV+Nla;B)}Tny!Wu?OHKSA=*AgXo!sz3YuKJ zpt-L>L~bk}C#mJ0fNe$-4m2j;6AmT3q+4Wsi;HK}dlM0GIhuD|@UNHPbJzycu(!&; zjLW7P!Uy_DG1IZHZFqQ|xIi@FT_HSnxAKdn)8^PDE+#S5pBmWB5v%l+Spq1T|dfSdk7 za#*CfIbxnerN)^EtN;t9@SZm)N^UB}c0+Bf+{1SKpEQww$mDjg`N5%)3Ypcpg{;lk vR|r~?=|Qp;vsdHm=PT5I^y73e<@+aTist=BO7*%&&ke#jxT0Bh0k{7H-e=s- literal 0 HcmV?d00001 diff --git a/samples/Playground/Playground.Droid/Resources/mipmap-hdpi/ic_launcher.png b/samples/Playground/Playground.Droid/Resources/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 2531cb31efc3a0a3de6113ab9c7845dc1d9654e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1634 zcmV-o2A%ndP)B+Z3$1(8#|f~9B42Y^N-3=o2YCq0YUY$Zu=pM;#hG{lHi%n~Vh z1d1vN#EDO19X?u$`cV z!a}AKG@Bb*#1cdYg8af_;jP69b`k%G1n?0=F^8bI^o>wg-vEliK^U}y^!D|^p|ax; zC|pK=f+FHp!RUAhtlpGGUxJb|wm^5! z<1r%$<$TR02wajxKZ4MiR#aAxDLE(##UNyD|ABr4WoGRF*?@e^2|~Hq(gurSSJH*;Q~5lw{J5A_(PCXBWhzZE${qgzv0{dk-F( z1<}>r181tLiEla&f1j&?p2xjbfp2cTt-c1Ox~?9EhK9`cJ9Vatf)loIoQ@#h&}cIGD>Z#QLE}&(bMo@7Ff|7f#Nm^$PJpVcbj+v~K7wfVwF}=) zRQsc+`=A-+C)vrRvaIC-5u>|;3h z*G4-u#RI<_vuSN~vZ6{|I~q5FFk3%de#+*>UFG>&bq6~ zUEMZ~FIOmFO=kA^5rkp-Msw?^63xvdXVZ-rv@{6{iVO}M!}^Je%2BPbi+(L<5<%~h z2v^D+f<|j%7~cJjOzg*!GPQ{%uE{i%YgcZhuZh{yNlQ}RhaU1jd=K+AopVKP+D}&} zZ3y$llqZiln=Z_A$!qzkGbX0D{?l(v5@1|`QyCvCnQ`eKI>|zj_zo%y#fKf85VhQ} zP)y&j4P*nR3q{-o35iV6nx7QDqq<;WDVIt}|N%`!dgv*y3va8eLNj zU9x(?ieweHfQ*yXk8|=ssZ~qJEz^QoKJ|iGa>ge_Vm_8l}S+UvJ{8g4jr+o#aTSFsz1W;PDP zW765JXGU#3JL>SlIl3NRV2{7B2dLO1cIP)a4ZRYL|MBD36O1#oSgAf}APz5@;x=_U-<=y)Py7*}O5(uu7BL_eLe6Ek7pH|G zMq)FrF1EFq&yruS5b=F=w)fVVoPd(oeRyTFym_Uwyn~L=OL(O?cf^2L5R(SmjORx6 z%nmZf^W=3pkvT*>@osUNi>DULH1hL;y`JGQX$onRCr_U0=H~Viodq!<7Q{3rPk~{G gu#IhOV;e2n|1(WJB~7~kivR!s07*qoM6N<$g7lUVaR2}S diff --git a/samples/Playground/Playground.Droid/Resources/mipmap-hdpi/ic_launcher_foreground.png b/samples/Playground/Playground.Droid/Resources/mipmap-hdpi/ic_launcher_foreground.png deleted file mode 100644 index 7a859c25556af7a2e46e22a2220eaded55628e9f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1441 zcma)+`#%#30L6FjMQg%tuA0%p#0??L`*E=rD#U2F4L5n@F+O9Sp;(QwEQy7+?sX?r zCWN(!Hg`+j5k8*H@|yQEtnAi*(D{7M`Tlf1=eKjq)BUsp2nqrK01B=yNUv`!`EH=x zx8$xJQUd^Fuec%|(TT&0V}4orr_==mmCnEuzD+ff8Pg>pJRqsWsD{#?eGPaCu0(sEH_2RG@<6-Nt<8 ztPMUmmAz9Ga$23Y9~p9dqJSgJJ#Jk_r@o13^%d-Xf46i+Lrmz3 zy9(DUDVXj;Zny7nO+yn&W2flEX=C!8&D0zI`G# z8;XmlonoghgRFUY*$+7pPLa}Uy)onw>TT9t(FTV6#BV8&lXWDPRvQW_n~xZ|yLcZjX>m$Eaf1)dwXS`&E^ zkNjO;%;fWywchc=+w4utQ0Vbn%B>b~yy4I#D{?1!P`$P>Wdo+ljCo(tYia04JTc=$$u+IbzDVPFYpm8+AQj+ zGKH zfS{{hN%W)kF+(26oZpkURD5Q_G_z97F6{Jval+TOj-;5y)*Rdo3a$^^k~q5gpTzmp1q@+2X9O z;_VUF>;s~C1~gpFrFoh?{aQ|LlBIYz!z^P~lndX5-ES)p#+9GW*|-WBTzQ*&gKOE` zM##bUaWl`6rZBXw0!~_oUhf+H$tNc@lLZCj0bZT^KSo@C|P?7YR8dP0se1jj z9aA0|7MONf(ZYaLZs$s}r*05fx25-iN6mZe_*Rq%uyz(+^-k;t`!R`?uf~rn#1ZC7 zuv3}UrmMzcBbo4jym@fS5%I+G`GJIC1s$)MQs3Vhld?a2U;w}$@V%dC@%qpO7+3#$ N&GnQ!lI8SQ#{X#Iv!eh2 diff --git a/samples/Playground/Playground.Droid/Resources/mipmap-hdpi/ic_launcher_round.png b/samples/Playground/Playground.Droid/Resources/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index b8d35b3a1cfe5308bb099a5a1429555c41417e36..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3552 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 diff --git a/samples/Playground/Playground.Droid/Resources/mipmap-mdpi/ic_launcher_round.png b/samples/Playground/Playground.Droid/Resources/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index 8f56909cddfa86f1387074bf43003f36d6e67be1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2413 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 QzWd1l-r2+Hu_H0<9}d)``~Uy| literal 0 HcmV?d00001 diff --git a/samples/Playground/Playground.Droid/Resources/mipmap-xhdpi/appicon_background.png b/samples/Playground/Playground.Droid/Resources/mipmap-xhdpi/appicon_background.png new file mode 100644 index 0000000000000000000000000000000000000000..2f75a9b3bb6f7e2a956ce5a66cceab42f0c3159e GIT binary patch literal 702 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 b;}yxl--Ss53i@>Q4YQuNcebmsMJN0NT!aL! diff --git a/samples/Playground/Playground.Droid/Resources/mipmap-xhdpi/ic_launcher_round.png b/samples/Playground/Playground.Droid/Resources/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index 9737d79c0492b2c8a811621660eba33e79fab959..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4858 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 diff --git a/samples/Playground/Playground.Droid/Resources/mipmap-xxhdpi/appicon.png b/samples/Playground/Playground.Droid/Resources/mipmap-xxhdpi/appicon.png new file mode 100644 index 0000000000000000000000000000000000000000..3eef1dd0b4ced300a92c9f23ac65842473473822 GIT binary patch literal 1914 zcmd6o`BM@I6vt8USOdj0H9%XBEp^LqG4EV0L@6;z+if+E+)5L}yrLAe)|G%{wPQ6i zJoBhb@Wd=pGY1W?@<;{oU{^HCgUt*tmi;gG&Aj*h;my4H&U|Lxmmc8n1JQx$00008 z))#$Jr8)n#wx()IF|*Ao0VN*625GB`tbL9K0BG4`(OyAUAAS;2VhxfJ{FQ;wHT2rx z;W&3N(h`ljT}=1gONHX8H(XeH*1OT@?GA19HWFiRMU&`doUr=vT5AwQH}}$3%d+lR zHq4ox)r|iYd4XHISn{zXHCna@em;~PB3xpM^9N*ma?4u9i{jR)Po!rjPX;V#;C~;E zjsxVwffo9jXbUh6Z33kuzukG()9TfA-`~}4YybZiR(mJlP)6o=kFCT0+|79@k2U6r zVzQ%II~aj@IYa5H_bGmQp6A%dXDcIg7M~wHJDi--nxV8kHmzj2rR_+);-Cf)ZzA=s zDOSonJVdKN&()&IuVg{n^3@|DMP`2b0SPf+xm*IwmHy3_uu-ATTenb~$rJn4WDVy* zQ>HF~vkqMIpvLqU$?}ky$DBThOM>)!5T_aZ1Ad+=VrASekpG%A*|V zx&A{J)G;?ItnXDhDzwU7c!_ZwP~jF3VZaBCLYJD<_K zMn3jL)=7pc;1o-o<4)|qO|P6yltIgE~bHv$Hp^}*g>H)JxM zldFN!6G_z8nkn6e>~*C;mVDu}ls4BjODRsEc9 z9b9Oa%4*M}!z?ceqW$!9Ms4N?iCSk4Z%bu$%;)!AE#+;*tR&Mz2_e+%U`*s6*Bu(f zi1tkVPM=MX#&=KakLY;G>@lvMmfSAcp`WI?`HtU?s0Y1_yIJ4&iA&zz3D>z5tN(0> zrqw6=M(JaZ#p3l^kI><@0r~> z>|%6WpdLrL|IJOLDYHqkzVUL5$McA={yr?9{!5}khQ|PHX>KOV0Wp*!?JNY^yn)^BS`}W~|WMaDu5wZ}ECAy(|@VU;OPSY}XH?lJ5OZ)hoZVQEb^I-*?GF$Kv zgW#!v8}|1TNZ{!q5{#p`wZH*yU3sEYeJ$1EiCvO{i21NW6u+o`GL z8%>Y#WT;!6K4n!=MWaJ_{6|sLt?eE5s8;#T{c!naNSv5;k=-U{Lsp&(g z^~h)74i1CJ4oCGGPaSrq^wKrDZbL$YNfM{nfurd~#>*DV@DiASXUCPiHm5{&mL0HF zQxT0lKb@ymzqcz=Txy6=;5RlFcA7Z_)b{-`o{9ApfR*n@Ti|gxouyc#Jg8!Ppla<^ zX`;u^(9bAWAgn0=ErFj*N(rbPsS!vs79A~h!#(e*%bT^@a#0DDNzS6LM=V{L4zvL-5w?EE|Kx*k9GK*}9vlD-cuk&#%{p`)(zDc`# z@n^f_vYD80G`rxoL57wYR(<+MFH+;guFn?R9SSU`oS(G{nZu{o@Wvn;jkVnI2aje; z_;>W-m0t^mQ%haXCH|kkm^`hn(nQZi^|xK@LH_^0zbccu(|h~?VGWh2%c?L1V7>j( JHAfS|Iv5z3k9)c} zhE&XXd&Q9PfC9q-gErYKXMX=V5X}F^L*&b=&+GE}8IJ9dW%wZ8#v$O$(xJ$7M1V1= vgTc_HK|;xaM^Ir@cr;8#6U0F0h7*i;<5{ln^LKg*%;F55u6{1-oD!M<;3w?g literal 0 HcmV?d00001 diff --git a/samples/Playground/Playground.Droid/Resources/mipmap-xxhdpi/appicon_foreground.png b/samples/Playground/Playground.Droid/Resources/mipmap-xxhdpi/appicon_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..13ecfb9e0348ebf6e324125ff66f744ee4ec2edf GIT binary patch literal 2619 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 diff --git a/samples/Playground/Playground.Droid/Resources/mipmap-xxhdpi/ic_launcher_round.png b/samples/Playground/Playground.Droid/Resources/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index c3ae5f5ccdecc01a9b17a2a0c2b1bb20602f0151..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8001 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 literal 0 HcmV?d00001 diff --git a/samples/Playground/Playground.Droid/Resources/mipmap-xxxhdpi/appicon_background.png b/samples/Playground/Playground.Droid/Resources/mipmap-xxxhdpi/appicon_background.png new file mode 100644 index 0000000000000000000000000000000000000000..a54c0b8723a4dbca68752050bd4338f04ef49c85 GIT binary patch literal 1728 zcmeAS@N?(olHy`uVBq!ia0y~yVB7%09Be?56MhC-K#HZ<$uool2x>S|Iv5z($~|2i zLn`LHz3Rw&K!M@F0YmE*2d(R7wAXq}wr2fXx@PBjW(LFeZy6YV96rF{VPwECiHD~_ wg_&95PA%Ya!(3==kN&poplYj^+h5$;35WoW{m_t!iuu#Mqigdgf z5HJElf}tM}bwWD^jZ$Mt3>u0?AX1b&ocI0p{sr$lYt5Rq_Ut`t@9+LR&$H*|@na5~ zHf-M@B_*{9bUf-NCAFq@^jG{_No`?(M-QL8SU5G7 z(5rdcTkh-3(AabYPee>>_v-CB0nPp1Ys>~aJfU$Ura5leKO&HNkr_8L%8Ct|{uvKB zH^R;|_Vt`(Hg6_|07%1$n%*12hiOBxp4MP(q zpbGdPBVt>d6zrB$L}^aLYaJpCxTF zzM18%&t>V3KB5W{Uhp35pMKJ7=Xx)Ourc)v_)lZ7CxoTYU8bIl+8ux2x#3R7L}w&; z<}AgkW#*7i$E?(OC+V_{o-pnJLRs9*-;#1NNnRJDthqkzks8cbVsIVi)z{T(0DGHe zO97$$6a7d>Nk_`>8`B_J``j3S>X(05-E(R4+O^N@2+y4~8Ht#L8NoHc2Ig~slCxji zf*5*|qIvD1qR*cuMt<;YTMrP-n`eBR^ZD}t&+72H1g-;2k{dMkaS zcliFQOwW@z1!wU?+py@pXPu)W^9MMma0M)L(lR7JIl-7^{EZ3MY z5QD5ZnNL4A@M-#aY}!65UCV!`^%BSGMbdK6cOgu%8C?itT5i9WGJq0HqIY zn|7@&-J__SI{yR+zOPcW?r)X#Y2*ZJ#L$pAlE(pxxDJl;x z-Uf05zY9JptkyF-=ySF;W9rDOxhG9tchm05K5JlVQ_n)Go)Ih8fQmPy+fzpOT?@2z z@ejr#4IG@L{YehmEF99V#j5X~cL1p5{59$cR+r3NwtdKyH`JI&M!%5ZgcWt}02X*T zfjAvxK+C+Yx|9?W-EGxSJ$tWPrZuzUF1O!Ql$d}+c=z-4Lfk!}Wg-;3d19!REFZq* zd8VI=<@2bgK<(V%{Fk4#fG1uz4qgs|M4ma(7oj&=R7Zt@ls@bYe@Edh3^7)%XR9g) zL~6qa_yNYWcuhnEjlO>T5av7_Z>cW$yF5nH`_9%!Pxmgox;nvS9zj>IY+8S@(G_aJ zA(h-sGQ$u$BW`@KNbs6$Q|eEFc+OFyxWj!RI`o>Sd;h#!usSfnw%Y^05Czo_Mi3Jq z+-`&tN*@uXg^x`fnauDv_e0Sl$8~BOzTXdLB>Z!_y4IND%i5YiM zIS&`tdOlv55%dGXfHv2g=c*V+{)e7dopi5;ZulCZ+cVrCHm1D-N(fOA!&a&}fD0(T zMXKg~Jnyc#wae?IvXiK^J=={v1&(L)%c8SZhJo8f{!4WxX^F~cA>fV)_9igV2DxC^ zXk&zkLg~-XZ{x%s_o%zWokY131@FrK5s;E`TYDiDi1YltP7}ex%K^EO;iZqSDD6&q zNh{#=!WYRsQ@5SORU}ba^TzeWQ>&^W>fg_LY}417E7V-Tr|xC~=H^H>h3;@(W#pRC zQQ|H|Ng6`>9_0bJxRoulF8j_W6)PSv~+X+UY_9$+c6GfA>tOT?h+@g(00_Q!1 z3IwqXc*cYBGusV6QdyM}RxHQfk`PZrWAOX0d$H3=^pe1sJg0s`ptQd)UUvzK--+0PF^xJOYJmzG=m#mJzKh-Or>MN~6n?1aQ(~V3 z2@@Fx3ALzha_{9ohsHV9b?Bga_RMH1a*Sh zn9dwG>pSR-DTb+LhDbCgQMr+QE8R;zeU-FN=Dy8lL%chmh8`X8zUgp3kNyi`a1RXZ zt$OH_?C8_$H;r-96;QxmX2>>Dj^qIl44C_l6E8=i40T-}G-&3;ShotbsrK}_7t=>b z7Grr`@8XnAPJngiX<0uR+(v*J1yyL~T?lw@0-h=6-y(_Hoi*+jKSN=TgX8Bex97%- z>eYX&6tw23jK|f!89u+VIXgmtGRhs;srSu|of}F&cTD`%w0>aV%PZ4pgEKJy898>u z&&Kw|VEiZEXOk4;*(c|ITvAj{SUd!^OErr_EJzG8({I3tbHblLx`+0=^Jdvk}z$UJXf*9?R^QIHfDd2hKSzE$P{0@hjlT zyFsyrlE1!0xnj{BwEg-Em5Gzv_Hjyi53fd5O0kq-$jI=j1%10(I8+qTYtSdgr7i{CN_x0Xadp z9;kz7?()g1#hXTQ=gZWt2k3womCSPXXrTgeEv$tvbQw8Ck%znEOMv#mpr*>^vHc6! z7?RKbrURzOddMc19}c#0{6@k-jUv7N{sas974wN(|QeYsYIQ;nfQ?fs24M{x8avU_6UnS9LKa6?;_*OC6A{>`Nyr4*H~ zp2oBGBi*2NR*a-J!(~-Vptuh+LO8Sckb6Lo!7qIzZq?Pxf<|K~&~t5;`IP;xCj9j^ z5Q?jg{zo_VFOtYco27%SC$A0Z{V+~$QXJX*L29l2w%ui}c$RrzYpqN9m<%FuhL=fG zZGss2g6a1t?8*hzP>X)wqOX6DUZw9J|AZ)}wPX?|m;bUx+~THSfIz@%jB$oqwRUdccTq5qFU>5iORH;XREI!CBl&F7{pwpwGR5y-0YE zr#Yx!CIKQ9Y2U zZEk@K!pq^%w%4SDvvNACwm*p~uJK&i3_H1voMia&zF_VlAf$m_gF575>SCGco*8Rq z<~#eUtf)ZxoXem=QPf4=x8Bll#bJn_MG|RxufuP3|f^}(HtOL%9 z%{1X3m;VKIrl{Wu17zb%j2-&yuQZE0o`7M?KVkz7d z*gy6Bg9seY;j`Oh)?jRQHm;*&PaPBg0=7xiUud~kYT`Z(>{~6Vag7|DXR47K z#LC*HMSXX8{Ld5lOJ?Ul@n<)-y87f&90w=g`mNcRC9Hm#04v+?_dY?Nt2L#h6kSv} zXZb3aSHK^3W6!B~H|+0={hFEcO@7KM4vJ@Hae6ikF}O>b)QOZ!#q-f)g?uc$RGyY5 z%Y{KrroOY-LKE4;F}MerDs?K6gR-9(A?dW=PXKGw4oQoK0@7>LVH-K45SBJl1pB`K iFMo#rr<)}cD^isndrj@jb(yP$0PT()eRRa<%6|X`5Q)G5 literal 0 HcmV?d00001 diff --git a/samples/Playground/Playground.Droid/Resources/mipmap-xxxhdpi/ic_launcher.png b/samples/Playground/Playground.Droid/Resources/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index d4fd714eeda1899a263f4f8420c4726bbdb10718..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5016 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 z 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 F@PB1o0BisN diff --git a/samples/Playground/Playground.Droid/Resources/mipmap-xxxhdpi/ic_launcher_round.png b/samples/Playground/Playground.Droid/Resources/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index ef89bd5215ffcc38c68b119a7495a77a7084543b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10893 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 diff --git a/samples/Playground/Playground.Droid/Services/GlideImageService.cs b/samples/Playground/Playground.Droid/Services/GlideImageService.cs new file mode 100644 index 000000000..12a072aa1 --- /dev/null +++ b/samples/Playground/Playground.Droid/Services/GlideImageService.cs @@ -0,0 +1,24 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using Android.Widget; +using Bumptech.Glide; +using Softeq.XToolkit.WhiteLabel.Droid.Interfaces; + +namespace Playground.Droid.Services; + +/// +/// Custom implementation of base on Glide library. +/// +/// +/// - Native library: https://github.com/bumptech/glide +/// - Xamarin binding: https://github.com/xamarin/GooglePlayServicesComponents/tree/main/source/com.github.bumptech.glide. +/// +public class GlideImageService : IDroidImageService +{ + /// + public void LoadImage(string url, ImageView into) + { + Glide.With(into.Context).Load(url).Into(into); + } +} diff --git a/samples/Playground/Playground.Droid/Views/Collections/MovieCollectionViewHolder.cs b/samples/Playground/Playground.Droid/Views/Collections/MovieCollectionViewHolder.cs index 113d2d481..d2f6cb035 100644 --- a/samples/Playground/Playground.Droid/Views/Collections/MovieCollectionViewHolder.cs +++ b/samples/Playground/Playground.Droid/Views/Collections/MovieCollectionViewHolder.cs @@ -3,10 +3,11 @@ using Android.Views; using Android.Widget; -using FFImageLoading; using Playground.Models; using Softeq.XToolkit.Bindings.Droid.Bindable; using Softeq.XToolkit.Bindings.Extensions; +using Softeq.XToolkit.WhiteLabel; +using Softeq.XToolkit.WhiteLabel.Droid.Interfaces; namespace Playground.Droid.Views.Collections { @@ -26,9 +27,7 @@ public override void DoAttachBindings() { base.DoAttachBindings(); - ImageService.Instance - .LoadUrl(ViewModel.IconUrl) - .Into(_image); + Dependencies.Container.Resolve().LoadImage(ViewModel.IconUrl, _image); this.Bind(() => ViewModel.Title, () => _name.Text); } diff --git a/samples/Playground/Playground.Droid/Views/Collections/ProductViewHolder.cs b/samples/Playground/Playground.Droid/Views/Collections/ProductViewHolder.cs index 33ae904d7..c4f517b11 100644 --- a/samples/Playground/Playground.Droid/Views/Collections/ProductViewHolder.cs +++ b/samples/Playground/Playground.Droid/Views/Collections/ProductViewHolder.cs @@ -3,11 +3,12 @@ using Android.Views; using Android.Widget; -using FFImageLoading; using Playground.ViewModels.Collections.Products; using Softeq.XToolkit.Bindings; using Softeq.XToolkit.Bindings.Droid.Bindable; using Softeq.XToolkit.Bindings.Extensions; +using Softeq.XToolkit.WhiteLabel; +using Softeq.XToolkit.WhiteLabel.Droid.Interfaces; namespace Playground.Droid.Views.Collections { @@ -31,9 +32,7 @@ public override void DoAttachBindings() { base.DoAttachBindings(); - ImageService.Instance - .LoadUrl(ViewModel.PhotoUrl) - .Into(_image); + Dependencies.Container.Resolve().LoadImage(ViewModel.PhotoUrl, _image); this.Bind(() => ViewModel.Title, () => _title.Text); this.Bind(() => ViewModel.Count, () => _count.Text, BindingMode.TwoWay); diff --git a/samples/Playground/Playground.iOS/Assets.xcassets/AppIcon.appiconset/Contents.json b/samples/Playground/Playground.iOS/Assets.xcassets/AppIcon.appiconset/Contents.json index 98f4d035c..79d29a5dd 100644 --- a/samples/Playground/Playground.iOS/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/samples/Playground/Playground.iOS/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,4 +1,4 @@ -{ +{ "images": [ { "scale": "2x", @@ -114,4 +114,5 @@ "version": 1, "author": "xcode" } -} \ No newline at end of file +} + diff --git a/samples/Playground/Playground.iOS/Assets.xcassets/AppIcon.appiconset/Icon1024.png b/samples/Playground/Playground.iOS/Assets.xcassets/AppIcon.appiconset/Icon1024.png index 9174c989a9c8b8a5ca133228f4ed7c173fffd2ee..b573205418896a726aca2711a04b355f2ac39377 100644 GIT binary patch literal 10753 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 z zCn)b8&=oMu6ygwwJfdasJX|@L6?m1Dv0X9t>JAWO^UIj0#&(3UrHx;vP^3g= zL{(XT!?`D*pP8)WoGHYEZZc$!odTzb8n)q0|88*>6P z`?6&CSv_W7r2yF0beQ2*?V^_%pKktVAo`)T^26X@NpK_*-ni{D7{Sp{C0A<|16l(; zOL*xGW|*sKsiwHvE!h3QXe@^a#6W3}8!DQu-h?A_4gkeRYkt4NC~GR5P8eyp;9kVQ8$QG$5ad7Fo23Z~ak1jY~RXG{v?3G$RarFe`XePu3X{R+=mBOw&X zks)|Sc$RcG-jhn!`~-x|vg!&DA&@}QH^RNdyy9nq56yrU$^qAaS+F_NOaeFb)CVaH z?!UvPajgrK&zqdAs>&Def#wkcG_UhmYOVw^M`VZz@+4IWAVzK%`+za9rm2SD9={u@ zlx5D6UDL;lc7#9`+%vnlP3PescU=N`DHQPt_N55GNBMkVCRMR4?fvp zAFsvcHN4c9rb>J@{*IH>RTr9de%9i4Gd(cbFa9SP4anhoP;TA0!oZyB8?lNMDHPHK zCaOaFU9?x2A!o>p>mCF9r+hKs9Czu_P1l$LWU%}q#)=T3p`ZnYyeHmsewqw`}L^4LuHqfo+CG6<2n7#l^3;H^^!1 zsaieYFnN)Kc7Mv}^xE)4kXUw8<9I+jMB@QV9T9I8haLDt1Ne#exWUfGYG$4uMoEu& zo81#2up18Y40h%tIsOZglp(ltVsE*j1~$lVd|;rN)&${~o~-%KZnJp&3|OFR{^8E9 zJ;fCu53Ysw%}@VYWE*z7r)&4P=^B-SF%a@>*9g84<4aFUZT7x)qdsS+#2tu5NbpU@ zg;EwV)l-#sK>#r9>(0Figx{9lKm>KvRj;y<8 zc8SxMW4<11(s@QMV_}n9MRzA*62->vzxmHh1)GVASEJY7LVtRw`Rv{v`(Fuc00(&o z%m>gS2aJekmdNQ4p<{pD3HqZ-%4hdU1__xYhLi9mTJXD|E zE`t6SX)}l_DY5vO0Xrs#O6_DKtPKn0f+e~SprDYmJL_`<053iA5P`zn z4<5etc%aF58sHFr#M;U-9|=;l)J#Q2vS!Q9(d(EX6fubL%uA_lqa2%!cpNIv78QZ}Ayo(>C(ZpsRtKhzD--fpuoCch87cX-Bna9_{z%$b*dHM0?+T&Hk!+^UM`r|vq z2Id$??bX^|tfYaE+h#Nik(ZcN+wt)28q^gWe!y8jDCXrD<2qV#49x@5$8&Zrd5NTs zNYcix;9fe#PQQ;T?!6hG>9K{K+RCPqiGc9z%t{=`QaX>7O{l(+#7mJ1>Rae^J?82e z6cLqLypskTCyu>uc~$0-XZ^1Qvhwr+pKQ#CKImhGu*MGM*ZrROuAHWuT*yM$ieEy8*KLFMMdLZL|D+yDmy@3_PELTEVMI6nwfcYA3ZQ9wwKdtkT z;`;z7fU{U6>CS7kr3=A-()_G*G(Mjf2wXKe

Fpy)y!S(AQHSG#udd_8#b4sQu!R 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 PgFh2POM|ilu9yD@)d*=D diff --git a/samples/Playground/Playground.iOS/Assets.xcassets/AppIcon.appiconset/Icon120.png b/samples/Playground/Playground.iOS/Assets.xcassets/AppIcon.appiconset/Icon120.png index 9c60a1761dbf62cc2a45ff98b9fdb63ade16e4d9..0b74155a21440a7412ff958122ff3f563200c8b7 100644 GIT binary patch literal 1155 zcmeAS@N?(olHy`uVBq!ia0vp^6(G#P1|%(0%q{^b&H|6fVg?3oVGw3ym^DWND9BhG z;Y5k!AK68d6vWSzX>e`c^g^NY$Uz6CW4NNV%L;E`iqEnY*AE9ke(;k+-CZ7CBfR5Y-Gy7bR>>YewEF*(cT?XB&G|q3^<5X{@axm^ z{@&drzh}vTZ`XzYub;!izSuCRc0KFuJ^tH&RCVgDzoEbVcUCLc^WVh<&dHS*rybsV z!rZ$=?e5>wqQyT~yS!1ot;=s!m$LWlqh~7*&Ei|Ww{~XjzxR?h?<~G;DUWmdoGtnO zvaTD;wWYsqt<{pO{1SX8>1muFPTgzM5!<`pr{v7W zm^+0<-GYs!Dw6VCp6UK)uWdY6ZTjis;jD{kkKgUv)f0bxM_7AhWA9Is%tuFqE(NXK zY?zvM<#?+y^SNHWbH{E*Y<}b#ldl ztV8$yTsU8pym->NW4dqM7aM=}oAHB9OZ-^u#f9sar0?bKOP%8bkC#$>1uJ{UgJ@b4Ja{fnBzD^I{j`F?JPTE8+I^NbKWDzW8 z`~B6=q8y7EB^RSAQty6j%Y64){$#sdVu`eI;Z~2*x@*a;aXJ68OKo503ca_UUjKO7 zq-zaZ73DN<|Cu`Jgx&${a2<)_gr(QUiGf2s!EcOG_cHI@O1TaS?83{ F1OQcq5<>t0 literal 3773 zcmd5Qra_NbPsUDT>o4MFW16^bHa?;thPQctK&rS>W+B}UBFt`R&+h&_v< zqNm6`y<|S-VgWQbM8I&)BSXlEX)moOgESS007X~NYC=GBL5fH=>M|1yXw?m zq4m-+(*Xb)(ah&AbN~Qrh_Rl|6C@Sc(Fbll$ODEoHa05eeN}CVZs5B8sGzzmDNEW~ zrrdYNBJPc}N$y=)5o4)|GN~qIZ6hOX;n6;};zGQ055)_y5z zYO2#i(6%l4gOWE96?MFESgQOf=#EDju3pHe+6j#F_bp`rFPTLAZ~*w`YEMUU!o3U) z=imMCu5d^oP5XWPYz50%e1OrwpG18q?7qLMM{6rRkTSMZ-yPUqx2 z3(FU?z|p2}-bKxpzo+k}#D4a{wtF%ko$qnYOe}il&d!I3Q$>aO@u;}<4lm+F+R_sh z(OdQ)A97v6kh{mFE$f>6I27~G+jjWfnymB;py=FMf6R{j;E(O67uJPuFU4i(5FjYp zV+k$O-tghokizW5x?jWn@c^3rlqqYi8#{zFnm_*5v1&>GM*(MB|ft51-fc_x27vEDaT&WVM4yT7* z?SpjnO|fjao$Yj4>t}qZ z)MmqDMipBDH%w@hgh^t&>QJn*S|;yfd9L9e#!hO@Zy$&B`k&~gEIFs=_~VizNh4R? z)Sch(QV*6FHoaYD8Ocu@b>Wxv-`ywA8AVxcn`RaoRi`hW$z+ik$Y_ZcR(V$t=aTOv zdbdY(e=8Jt3<1vZf-?dEPTm3KxhEwpu@Zjfc0*U7Rd1QLvqAK`ox=}hO`};Lzd*WS zL{@yFsz^Z@w%zf??Hl&QS5!GZl(8G@RO@^c`hz1-+O$VnXS8}|xlyks`n}!?B^hfv zb3#0x)JyCzDjS#!o>2;1H(LKN`GoE2JlmaKM0&kj@YABf&WX<1OU%Np=lG#wX5cX^ z>xfyVWNnv3;6&OhpzQJ9|UDTOJIb+?oBAV_O!TQGd7)VLm;YtQp zTE}Au9Bs<`TV($VN~R$r&9=E3?EP!b%l68bO0UnJuBIE{km#=rhXQMCX(jKkiU+Hh z$009o^Dgt#(snl5!Y_xJPp4n;49r2{vRIKN+5;=5;O((VSF(pw3*nnGr(Kr{vUdkt zkkWLdv8;n8SfL6_{bd@r5$n83Bo{{3SMC?3_Um+oiJOmQ%U!-)t4+E$`**EBWe^Oe z>B^O+E1a5v0gyoOwaQxpPd42b1jn5qnGXCWR3&kch{jM&#nIIQ$JxFbfvFCJZxXVX zj$CAyWfGqCaD=Xjvo25ZwKKaob3nZ>WPF~lV0(Y?-<^2abE`iCN+|Vi$}in*Xsgd2 zZldO}a-Y0$EwNP{UgD^p>dF26_}*-M`)BF1d8f}x9Jc16UY5?9| ztV>Gx+R>|%J!Pj!gQN=!z0p|dQES4(AEWzHcER~Yv{?^Owg_VEQ{;FyW5DaZug0)7 zDJz;BD{iyyS{mn+ygi#SsgP(xY$;#;XC3oWB#0uT?aO|vq-2)SloJxgh#HfLY?AWPjXh=1OKT^9G zKn&m*WOu+y#|bL!kWO<4pXu|C->IPb&mz?O(7!D#XoLL^0rD@%92Xuu5gpOEP%~h= z1oCM&{H9q)L#$9(lEcD8F%62!ds+*9=X~ZBddkXbg|}{My`4htHBYXzvKC>hCA=aw zFfF@NcV+il?ng9Qh8IE^kfO1hSc3+XsqALhZi|BY>bOK2#wk_MVBSzrMU+x{z0Ad}XTj5-!%`gC&WRQKr>+cL`Q(Rt_Q5(P)$c zz?HVNCtLA4?ICKBP8_v{H8VG_jq=pC2o*seimT@JV#4u;gc$sMa?_tZ*xony;ZTxw37#vrSfi7fW1wPy85{bk0VUz(Rl z5AdtLAQ+MDZB$M*Zve#-}D3oZ@ z2djxmI^0PqUrMvTDQiG~w{pSj5{ejgKYSNiV5K@V<%$Ekj2QH?RE8->x9hWChn;r z1>^3}!X}>U7gK4lfQ;GDx)wJL6f#vXnY&WCYCrJQdsRN=|GIpfoJkx_v1Sp$H=$IN zbW&Pja15Fbf)*&E+;?rtv&9L1gmRYH2(E>4@CJ3hJ4$vfUw0irn@X2X3DB17?pQtq zthET!z{f)P<^;tO|X-I?gR$^CuEXBj-`*)xqM+BJ8iW(%9>wH%StEpws~;g! z&Xc6@%j#+WbUa7=Gx7vPR$wOHj$E+?=Y8f)u8%)wtWb%RDr~l;4JhNS*FPw}Lpu)% z!M+pat-qf7(ImySZs}TbnFb*k)y|-iakie^kR(6$=)I)BdEDj8ADCzSOQ{vfGAiDR z32WU>Jh%a<93;eZx#Q=X=N^0k!h^nN+T8$R-H@hnn+Udj1G%+oDpeY@yTI%hNjXJl z)JJbmu7|vMzAE)?z`ttSlnRmayKhP(+3gXC&)h<}-1u)<(`b<=8jt1noEBJK=Hd|Q z74+51D)%1a;nBWP_|xsqM}owg;`d4kC&AtK-O05m=98nOm3I9}$7A4HFG7Da)QQ^- zTf-qV>M|4F3FSH)&4yGtI;ls7nVqO`nSkQdBRFd*{I~0M?ZD5HCDO*As5N9*p?l@v z)WRpky&MEItf(jtHzG47_1X>OyR6p(4PW&ZvE zRYAjG6V1>sJ3u*hENp{Ms(J`pd8h4sT_CN{e*Xi^|21qEKT8Z(EB}sCrW`o#d!!_DOXyrGPCcdB5zT0 z-q4cs3-Y(EES^Y9LAo}NklD|KlHaL@MZf$x-0{+xFmG(M^=whkagr7-f15pK^dNr?i|kroE1@q#5K`X{fsJ|UtGs#x%GPs_oCI-}P7 zG_UFl_9vaHvg83DjvhztV=M~!{c9wa1;0#CPqZt3GVyqEHN;9GZRazd)XEgOwAr1x zaccQQTM9+-@^xRWPsd!IwBOK;ppxq`Tk}EpA>Jy~a^s1ATI1Qu_JQ)dze9^c2F^O? zlw;aYs5;HwQ3vu^yw0M@qdPt(1`ShrB`r(v#1b@EdkMVzwm73l)Xc+6_OBJR4dI!AY7$>yT+2t8XKcu#+#&rH`%J_AIBCwF$2NQnP< zH>_n&Ijv!waYBUTS3ZV;ZErdA#!G9-gV>$Z1`JX!pWDeNR0hb@(PkCD+6bx>dSt9k zb5|U@<~apm-~&mGso*VLnF1t$2t;G%I`sczbj4QjrDu@J?qcxo9|aieo9op*bdLES Dh-f%Y diff --git a/samples/Playground/Playground.iOS/Assets.xcassets/AppIcon.appiconset/Icon152.png b/samples/Playground/Playground.iOS/Assets.xcassets/AppIcon.appiconset/Icon152.png index 448d6efb577d07e227a5c62545173ddf6bd86b55..1c1931312b67f4d54580f0307a9b19af555085d6 100644 GIT binary patch literal 1381 zcmeAS@N?(olHy`uVBq!ia0vp^GeDSw4M<8HQcwg^oCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB?k`Ul$B+ufw|9I!Lb7F!e_X!nd(p}47@?)% z9=BMwoxOH*QLm$8NO#N=tv9Kc4=u>WDg4FZjhT?Z6g6DBAb zcnC@?;ou2kVbf%478zv1U#Dj)%#Hq6+RlEx;=Z}noKk`S9bG^ z`1|AE-R~b?_Tu--W2cwTztiizKKt8Gu}v4wFS-)YxOL0Ag6QpAf800Uac%CM?{{=V zSDugVI(jek;eXFhpRaZ&@J;;2{pxM>+kp6{sAgG-7X`Af{}-;1=Pm8fInKB6___7P z>HPakGH1;HEoQqf{L9D6ZFX7uo87W9W9R*r31*8?1}DLm?0QxwOR6>A-BntS8sYgR?SU%2f>>VL2H64RQ5Z>tL5T$`P7!YcmDzJD2JZ6&Om z*-rZ(d7NXmt-irIbygGS8`qLssUVF=l3egI8r|%18GL_Maw7t z^8dT@?5#Kkn_b`cWdIdql?AtG{phxPV={?1@$rma)#aM&w4^@vFz1*Z6U*Maj6=s> zT03R-_Z#NRu9{qTy|w6Gxs>Mq{56qccRjOb9q^j#cIx~wt()HcN3&OH?|vI#`7>eL zi??TvmCSl~jnR3z@jcyZa~@q>(S0CCHzl0urt{wN(~myoZftXV)f~oss!ZX?e8)Wg zbk)h8|>w>cj+Q@2>)x`|q3+DxdOFAPv z?w7fj_cNqIigWAKUrP74KiKB%7Bu}|zsbRMmrR!KW82KS-M0Jdx2;dbP6|JM?=83M z&CRqIK0m81LJp-Ri~H|6Rr}USUQA-&y}r1Xk~Q_uvp#+m&sO@iSbNXKlYeq*=Ql=d q+n)WZo!MoFyY`^X*6?ztj*0VtTXf!f`6a+ok-^i|&t;ucLK6V1xMsHi literal 4750 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 diff --git a/samples/Playground/Playground.iOS/Assets.xcassets/AppIcon.appiconset/Icon167.png b/samples/Playground/Playground.iOS/Assets.xcassets/AppIcon.appiconset/Icon167.png index 8524768f8d764da7e9c452a444208708f2d18ff1..4e3e8bd8efb7f2bfc0cd3ec6233298f4285e47d6 100644 GIT binary patch literal 1503 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 diff --git a/samples/Playground/Playground.iOS/Assets.xcassets/AppIcon.appiconset/Icon180.png b/samples/Playground/Playground.iOS/Assets.xcassets/AppIcon.appiconset/Icon180.png index 60a64703c0f11d08705cd607f7751338707f5919..40a7371251557c3b54b96a498b8106611756cad0 100644 GIT binary patch literal 1558 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 delta 822 zcmV-61IhfR1I-4IB!2{FK}|sb0I`n?{9y$E00RU`L_t(Y4eeFGPg7A8{$5+90aMVb zL<@wZ!HE2bgO)fnad30i#l%V71fz-l7r;bCN8KD?FmZEpV=&a17!V~UBwC6FV+9&Y zO6a4W@6gwu?>+~Vi7$CQ@19@ZJNKS@Y83_8WBB%%PwDna|9|VQ2mY4*)6ls9Be zjF;?uXB^|1#|;gn)61)Pvz)CtOP*6Qlf%@H746FF(SPGor434UDfP6|uMBx-9_Fh@ z1L^2&O14$HRf4J?>YjeusrFWH1d%2`D2XT38)7tH9cv9~zF^~hW~0ufvYh^v!`JMF zRyP)ls^mH;dJ~wBd8<)F8VvYxF5FyguX!Ij+Jf#-GdMI7$pw{M$3$-e^D%EVYDj5F z40P#v>3?;QZgeEiaQIAz#yzcF13d$?N47J zkA+(hKXF*Ad$I6S&yZ3_rRZrdUzt)2^DtjM8c6AIf3Oq&Vr2+Wcc*Ws%IEXnk=>Ri zlaWWsb+w)Etd#N0;|AlvzxVbFUZ&SHIg{u>pnv4RD0Q~#9UHHU*x2#B!&f?5@$kYa zH%_=#O3%B4T?mSmV^6ORhkhVX9{luCJ&CBNorL1gIL4b`B#pELajiRCb52T9sLtPI?Sa4rz>40?Xs=>KV_U3JV`m?CNK74AaoEuUWvk%@u8i5 z^e^}N1LwyW8_DZScWd*kbhpV(@%yBvo%tvFKW=fGb^`-mBme*a07*qoM6N<$f;20T AzyJUM diff --git a/samples/Playground/Playground.iOS/Assets.xcassets/AppIcon.appiconset/Icon40.png b/samples/Playground/Playground.iOS/Assets.xcassets/AppIcon.appiconset/Icon40.png index cc7edcf5cb47d55bcd90dd20694555e7cc40e08f..75d27899e32c19478fd914072864166c937fcc0c 100644 GIT binary patch delta 511 zcmVPx#1ZP1_ zK>z@;j|==^1poj6u1Q2eRCodHm%U3uQ53+BmJpgt;-eHIX(+JO`U8R{mmqM-A*ZmM z8Vsa{aA;|ZV|lJs%!!}m~o&wr113f^#jaM643@xCAD zcrHBCy!ZMW42&5J4hdo*K`bPQg#@vXAQlqDVtB#+UV?4*J~qrC=4-#!Ipxx)64MBe z7hD$AGh5F>3HpOW5C{v9I(de}?I&eDv_1?@Ca}4bh3Q#4MB?tI-|_XWqcPPpm*?)= zO7-7jk}>;*Nv_v5+7Z62wA+SV#~H31Tt4;19}uiP6?-dYu3O002ovPDHLkV1mjw B=#~Hg delta 1080 zcmV-81jqZC1kDJLB!2{FK}|sb0I`n?{9y$E00aa{L_t(o3GG)qOq4+oo_nIG0Zxd3 zBFe)@qXKH9kyv3QMjLHRG_kd?HX<6(XklV3ZA_#vJ{m1FQW=Sr3IP+v2cSGfgB*w; z9RI`l_HNIgyZ`>%gM?ghlkEMQnb~i@o!On;bD1oZ#^O$6ZhwMWhTu{&l!;&_f=i9S zQpczL9`vTV!qJ&Iy6~o#UXA^sznVehaydirJ+RX2rv3S=>FSYqiHq$ykr=H266>=Xs2z=lhJ06(8P9}~7L%KoN zuzI&gY;x;~#i_jcewb^aPC0%rU8yc5(s_e%Yvw+5u)znu*0JKiFeb1uhm$xGyz_CG z`ew8RsehGVNY!Y_pxYmYgsQG2pN?(F6AgU`Z1Aye4PoFP=zxtmoFrkPp68WSKWV2) zgKJ$R0#+b#C^(&KTn4q(SJB!PJ_>UAz(&Z;^3m9Ah-#m9(v{0T3Y%T@93bON34aMrZ=Ar&IRh5dA;zT|k3W|n4 z(hQrmMgmg`hyxpQIEf=cKS%NWj*&>Bu?Z3ek$QLA_c3@_i?e^T_#jb?O*9fN`02L`ZIMVTM~Pi-j@&vv91(GaQRG$#Eb-Q4ix2SzHQoMH)@)_sc}lriGv yBqry|5R{Qv>^Bp^#YRq!%S2E{VzJ+}kKjKzI6RdL?()R|0000_ diff --git a/samples/Playground/Playground.iOS/Assets.xcassets/AppIcon.appiconset/Icon58.png b/samples/Playground/Playground.iOS/Assets.xcassets/AppIcon.appiconset/Icon58.png index 1ad04f004b638bf781012290d78e4138f97bbe5e..06afa60917f35358a1f61d08d10c440432bc0d01 100644 GIT binary patch delta 676 zcmaFJyN7jxNU}oXkrghb7(7*O7r?V?Xzw zL{x+r@b1Lz za+Asse!=$!uf2S5wTWH5l_B+s`)8lbkOLc23x{r zacAex#TRCoU3%rreCgJPv>#`zD<@dUf49CGm>m_K?_w4)ZSlUH8@Tp-bCx{kp!r04 zn%LrftLJ3CsbXDfvHNGsQp;Dz_so*I*?Ru#(+_|BS$FK(?t8ob_NlIg8#f1)=X>1t ze6nzAgqBwiZ_9_TTb=Lr7k%)NIUprAAtb!0ohM`R`u;N_U?4r@c`UYYfUlWA+&9it>FmdK II;Vst0J^Y2GXMYp delta 1745 zcmV;?1}^!$1>p^lB!2{FK}|sb0I`n?{9y$E00x*zL_t(&1?^c|Y*bYg-80k2P-tm+ zw%7*+3_+}IBiI-c>W5Jy(eTlj`1(+O{Lv2;3Gx9%2tN7X%b187CH^$g#NY?Pv=o@>gEH-OX58z{oHMtVd!2LdorH{?l}zVx_J7%Xt+V&p_uN}wfA$oV-Y9!V&U% zc=j==#_}M2ylE}1m>{= zFF9_0x3Zn}(1RBcF%ZiOTni0PMd{$V%c5lE>MPQ``hSU;X!3>6De-2a^7n*|z4VK1 zfmn#?7OsW9&*c=FNj8mH!CBN>>nf?YZ6ozR(?ZYIloKnRWismvk3CmgE^J}HgXfy< z(U=emG2OzI&_8cZ(AWJpRmo!IjToDrp+KrRZLVBM6p!7JRhdnUH7{PErjPPmBb0BB zftZNx27j)E?m68{F&|$qS&SlLh<>DZM3vc=x~0@sUBP0L%>)7!`15^bL29dCCVoO` zVy+pYggF*sBDNbi7Wz%^H9B=;Qf1Q|6ynEYM4w)`qK5WvSVh&r0AViz#%%6Z7bkEe^w8M>x;hoNUvWu_GZJPVpI;bM zTsBpf(@U$Ch(-gk0T#WpsaB1{7ISQ~Y48mW;Nk?@LjM_?q{BV-D=veoOmJoncVDC1 zGk@uo-_ck{YZnx<*f_P-RM5JLWD|l7Z0)jX>6^KLlPtX9r1rp;B!&WdSWH^*t~ z4_!2Tds=vDNQQ}U(|>vW zR5y*ree~g(rW$+ZuJ=zOZ|U;|t88CT9TzKcaCPOy3iK7vaE!*t!C0GkqBr7V>Wj+ zu2d!``R4hvp6jCNpljkdcc7zRjeiSUi!-q@;14P$#|9U0(uKBAIDB<|4OJEU)2AD| zD7@lZsut3QrCjceaXzST|FwtWXlSx?b2IsIWSou#g7($P*LCjE>7S` zXh~s!b~bPTo0XSkRm2T(M`QZbv-`Imx*Ulqk*aI*aqa*=>61?nFb8w3wtt7QQosRR zoWPM#INr*48ZC>tWNbNJlARt}Qqw zVuOQII2MWkdz$OTS=DOgF}`eG`f_n%c+~PHz?jvJ8+@Hk!8lj`CMyN!W5jj?A3L$` zDjB@(`sHu>vM)rwK(d5DTz{1pTyl8}aCstgN|?c~j=iLp;1kq~brrP#p*02%`X5~j z#Z7YS_Z<|9r5{=&tR!ZpFsoz@gPH8bVsHbh2qftElTB1!%r^xt;Hq>H2Laz$j|=_m zhhH*t=65@8W;^*E_Clnnj<|^BRwR767K+KwuUXEILU=#AtA4o141e~G7>MNso`m87 z*lwIoY%Vumh^4a`6EWn&Hn%=K2}RI{`N_f4EBKKnmoQ+r=ft4qALWC6!Z%`gg=e84 z@OE<@mH7AxJ$FWV$&ndx^^SedzTgwSvjNv#*Bt!Vz;!xw{tBJte*`CUYqrq;j@7@P z{}KG!x_juc8oj%dOiJFfCNxu^yFH$j(!JXvbmHfQ>a0>O+2k{tOWD%ln$M`tD&>+* nKBKvmEgi1;jOwgXF4_DG_&r&PcxYTT00000NkvXXu0mjf*&|;` diff --git a/samples/Playground/Playground.iOS/Assets.xcassets/AppIcon.appiconset/Icon60.png b/samples/Playground/Playground.iOS/Assets.xcassets/AppIcon.appiconset/Icon60.png index 2dd52620a8f15577e56ec7fe8e671988dd17ab0f..2e0db2a6123440f30a37f1b6a69753c01d74bc6e 100644 GIT binary patch delta 684 zcmaDUyoYsyOg)3upPNMt3=EtF9+AZi4BWyX%*Zfnjs#GUu{g-xiDBJ2nU_EgOS+@4 zBLl<6e(pbstPBiHfu1goAr*{oXIXkIb`)ut|9V&Zq`gju@=~U^ud-7IBOZ=kl|kzu}#hsnwL6FXa`e|J1F z|Kyqf|L@zsKResu)2i6U=o5E3u?xo4hvb;$G1b7sTc?FU!yDBIm3(EfSzjGx|$5|vr~8fWwtg-Yo6EqtB% zhw=2~QUEdnH#Nd?L>-l_9;%k?#RQ?%vJxx^cH{&M3`n87?1>203zIXZj zykMBnoa0i>r|&QC82L0@L$Etyu4jkZExE)~U!-`y!g|b@k-d>l$ux-}T;EI=gt=2d#(y-|26N-;eaP zr33h01}VP&`ghv#8P#qNU%bB8{BY;~O(Ckub`Pp9h6Qipoaqp0A+l%6ZR;m7IiCcl zZ0iVLlsj+D*<{5JCNtw}mQUI(_{UN4mwnvltxrDAkLrL#7$P23(IZmUulXp4X>zF* QFvT%=y85}Sb4q9e00l}&r~m)} delta 2535 zcmVTfox2hG|f7uRZ{7dCNS$k!NW<#` zm*kmICFk!tEERe?wf;US8WE@{jE&0m>|Jvej|>M>;}l{M410%2UXA^??LK1KUtXD` zAK%hILYdpqOMmcketcZ)f3kN#5T!9wRYjRhhB{W?N${Y8Q3y5z0UQeXJPow3@Yn+L z&e|;HM@Ph7Gyn{xyg9enMh7?WYD~3Ta-D}Lll8j@T`=&=`)#xdowZgv6NWJ|3bc~t5>jE^Fpn?n?6GRRVyNK#ZTFa!LMhxq0|hqy^K+S9TW_92m}wD7 z*Z`WE!Z@91Z?>zY9O`PcTpb;w#y|IwE`JiXn{#jUP-eyq@d|`(W1+1-m`(e?yhM*4 z_)O{{v>}_P!;g4Dpes32up%OiQ{h^d^n}o+Syi;BZCNC0sH<&k3?>W^FiQg9zylB6 za9ylW@JX~dNm#kdyEu9>%#X+QCY!BgLFp^aVTSsgqos|mfwA7=m7zlu7 z%liXya-)O2=Z~KC5jM}cPEsj|pMNlj08prz2NO~(l4dC`oe+$W2LWRggj$SDoF<|` z2u3{@hYXMA*)v5b6zD9DU<7+^-sh#`|1mUfAyn||Cocs07EHk$fIzRnE`|aMJr{?4 zG-@>>)-VVN#^zgh_%?xO^{}a0$wD<18D=dIL9_GBWkX{Z0)o508noN}Wq)RbP!{l; zrdv!~PcZ>tD(nK&gh4>?n`@gw<12}|#>WVuEMQajaS3fv?r2#QNs}%3Ltfwm0)mlk zm`V8nBM62705ZPBpW0(A9MHm{EwNIfoYZ(X48HDlORbuTVn8sO4*qnx`DE~})`|dr zhd}fn=5JAg>s+fr^POygZhx(>1&aWL3Z7G2O*ioJVOy}h^)4aqg0;m31HvEcJ|Xp@ zRPj4T1+4A)xtBo~1m_kRbB&DWX~FKp63p8QD7{z=0hTbdp&oU&at0sioG1vj7|6)+ z++FW&N5Ra!lwnvstc?>2+DO4@#1;fhNVzcsN$(Z&h}QQM7mGJpBIE)8P{9L#7t zif0^QI9eCk$BKaGAO7_Pz1V-2MiaJA3+iXm+igp!il^5kNF_LlNjdGws+Gev1&K!YrK zvNPLms;57;Hdew?Xe%}tKL#Ac-qbR-Vyzqo57ILRim4DbFnw(s6p|go@E(~?bHK%`e|w{Cgh z1U*i!wv}u!PN7=<)<^&4-&@?CRfV>%SwO45|2>sK*wAjmCYBRVy0t^2;A3**&Ca%# zg>qu0L^!$HxR29%&gfAY*gSXh?!B(Mw0XCrW$D7uC^f%%B-B_{lr5r-T_2nD0P1n{ z*1-m8jzSH^6MuDR1C9bx3*Z4>E{Ma}32-=cflj4B>K^`LaEKmw>nIHucw_E(-4>|E zc!n(Uk}*XD7$Qj;PG1O!vC%m8Xs~6VtUWz5Q=s=AS`wLGap_VSeC*;Nt$U}R^41Sw zE+hLr(P;D@LkH(@G#%Y@+sg-<8x6JemaRbtKL;hpja7n+69e zy?kk+U4PqvE7^VyGE&Ro86LVp$25@2U@+RcY7zbV_BqDrD20-Yl@kLjPkfq_ zZC*T2*`$7!WiYtofO5;BlO@lpsWo#DjWc=e-n!y8YQN(q2P55U*#?9CJA9P9nNM{~ zT}slaHi2au``W4n^dSF{UoKQ+FoeqgiZ}}|as5oIux;$FA2rg7x!0#94aKY2U~t7R z5@3&W5@7%NtDFSjpQ`b_rVJcHwc<+oUNJ#&6t%jcj$Y->zxy9~6m4Anq!=70;96@- xy$)Y%ikGX;w87``W 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 rrig%|x@MRLOc4P^bpOs^ diff --git a/samples/Playground/Playground.iOS/Assets.xcassets/AppIcon.appiconset/Icon80.png b/samples/Playground/Playground.iOS/Assets.xcassets/AppIcon.appiconset/Icon80.png index 02e47a261154ed39af857a1b8475475b5507fb18..6559bb4da5ee3d06b35a1218ae3ae7a9698aa48e 100644 GIT binary patch literal 888 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 D(44G6 diff --git a/samples/Playground/Playground.iOS/Assets.xcassets/AppIcon.appiconset/Icon87.png b/samples/Playground/Playground.iOS/Assets.xcassets/AppIcon.appiconset/Icon87.png index 4954a4bd33f613d45f74dc0b12beb516e3b38661..ca28c8d7a95e221f0280fb4fb41acbc653d20b5f 100644 GIT binary patch literal 942 zcmeAS@N?(olHy`uVBq!ia0vp^;ULVx1|$nl+{^(|oCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&di49w1+E{-7;jBn={`UD4xwDB)G7T_S_$gG#6 zwnM9MiJD&00Z%sT2Pq~Sj67ndc!Z0(Sv_FzlbHFsXUz-&@s4FHR5cb(S;6w!_PWT2 zrL|ui|Hpm$RL^;`xjee!{@(X@{>x4ZyPG%vJZEEp4+qaFR;Qf~8sZ#JnHI(UeNxecVf1CH;JhSbLPtAAP`**5>takg~eq55}eQ$51cE!2=Kd-H- zrIze`+QPDM%KnWaT59F*S@rb(OyMt{?Rxz64Qu{cZ$JKt`BuNP`rOZn^|4!aJKkFF zqQdd=%KJ?xN`u6Hwd;HN#%^ty+qEV}f7h>FN$F3&-sqBHi%snRJ+0=rjr0$dt@Aqf zWY&FOd&9EYW_HZu-9 zYOC*mx6lyP=MG-Hd-d0Q4_G;kMWU#}2# zo0F3vb@ZO!npwU}FJ8O9@8D-4`5!utomawVpUl*%(6ijDroA94O3Uww)+`aLMWtm| z%UoXjimm;4c3T;o(KU+Fh#8-FS2R z_Xf{n%qf2R=j={YJ8$auRIBrc`O6(~`S~B$?e5(c#-{l8$F=W=E5m(GJ4bAOKQY6y zYV-Th%@zWH1Eys0;~vT}C$bxmv8=DplYYQ69;m5X6t{`{4epL?ox zO6!|n2lX`sk8k-O6R>TnwdJg7F?#A-pG_>eba}a~{Ldu$P;bZ0?>k-|e0IEA-AjIM zJkL_Es`b47s}0Yu(Tx1bu{=`JRn&eiC;R{3R7rHGX||qm$-h4Zra#Y40cI}-Pgg&e IbxsLQ06r+Fc>n+a delta 2750 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 C+DNwm diff --git a/samples/Playground/Playground.iOS/CustomIosBootstrapper.cs b/samples/Playground/Playground.iOS/CustomIosBootstrapper.cs index 58fdbda2d..f2123da3f 100644 --- a/samples/Playground/Playground.iOS/CustomIosBootstrapper.cs +++ b/samples/Playground/Playground.iOS/CustomIosBootstrapper.cs @@ -5,6 +5,7 @@ using System.Reflection; using Playground.Extended; using Playground.iOS.Extended; +using Playground.iOS.Services; using Softeq.XToolkit.Common.Extensions; using Softeq.XToolkit.Connectivity; using Softeq.XToolkit.Connectivity.iOS; @@ -16,6 +17,7 @@ using Softeq.XToolkit.WhiteLabel.Essentials.iOS.ImagePicker; using Softeq.XToolkit.WhiteLabel.Interfaces; using Softeq.XToolkit.WhiteLabel.iOS; +using Softeq.XToolkit.WhiteLabel.iOS.Interfaces; using Softeq.XToolkit.WhiteLabel.iOS.Services; using Softeq.XToolkit.WhiteLabel.Navigation; @@ -45,6 +47,7 @@ protected override void ConfigureIoc(IContainerBuilder builder) builder.Singleton(); // image picker + builder.Singleton(); builder.Singleton(); // connectivity diff --git a/samples/Playground/Playground.iOS/Info.plist b/samples/Playground/Playground.iOS/Info.plist index 84a12531a..5f2a8620f 100644 --- a/samples/Playground/Playground.iOS/Info.plist +++ b/samples/Playground/Playground.iOS/Info.plist @@ -13,7 +13,7 @@ LSRequiresIPhoneOS MinimumOSVersion - 12.0 + 10.0 UIDeviceFamily 1 diff --git a/samples/Playground/Playground.iOS/Playground.iOS.csproj b/samples/Playground/Playground.iOS/Playground.iOS.csproj index fe423b6de..1c71c022d 100644 --- a/samples/Playground/Playground.iOS/Playground.iOS.csproj +++ b/samples/Playground/Playground.iOS/Playground.iOS.csproj @@ -1,59 +1,46 @@ - + + + - xamarin.ios10 + net7.0-ios16.2 Exe - portable - 12.0 + iPhoneSimulator;iPhone;AnyCPU + 10.0 + false + true iPhone Developer - NSUrlSessionHandler true - None - x86_64 - true - true - true - false + None + iossimulator-x64 true - SdkOnly - ARM64 - true - true - true - true - true - true + SdkOnly + ios-arm64 - SdkOnly - x86_64 - true + SdkOnly + iossimulator-x64 - SdkOnly - ARM64 - true - true + SdkOnly + ios-arm64 - - - - - - - - - - + @@ -72,9 +59,8 @@ + - - diff --git a/samples/Playground/Playground.iOS/PlaygroundStyles.cs b/samples/Playground/Playground.iOS/PlaygroundStyles.cs index da8d560fa..9827f3af3 100644 --- a/samples/Playground/Playground.iOS/PlaygroundStyles.cs +++ b/samples/Playground/Playground.iOS/PlaygroundStyles.cs @@ -9,7 +9,7 @@ public static class PlaygroundStyles { public static UIColor DefaultBackgroundColor => UIDevice.CurrentDevice.CheckSystemVersion(13, 0) - ? UIColor.SystemBackgroundColor + ? UIColor.SystemBackground : UIColor.White; } } diff --git a/samples/Playground/Playground.iOS/Services/NukeImageService.cs b/samples/Playground/Playground.iOS/Services/NukeImageService.cs new file mode 100644 index 000000000..2ffe35826 --- /dev/null +++ b/samples/Playground/Playground.iOS/Services/NukeImageService.cs @@ -0,0 +1,30 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using Foundation; +using ImageCaching.Nuke; +using Softeq.XToolkit.WhiteLabel.iOS.Interfaces; +using UIKit; + +namespace Playground.iOS.Services; + +///

+/// Custom implementation of base on Nuke library. +/// +/// +/// - Native library: https://github.com/kean/Nuke +/// - Xamarin binding: https://github.com/roubachof/NukeProxy. +/// +public class NukeImageService : IIosImageService +{ + /// + public void LoadImage(string url, UIImageView into) + { + using var nativeUrl = new NSUrl(url); + + ImagePipeline.Shared.LoadImageWithUrl(nativeUrl, (image, _) => + { + into.Image = image; + }); + } +} diff --git a/samples/Playground/Playground.iOS/ViewControllers/Collections/CompositionalLayout/AdaptiveSectionsViewController.cs b/samples/Playground/Playground.iOS/ViewControllers/Collections/CompositionalLayout/AdaptiveSectionsViewController.cs index 2877b7cc0..583e0e83e 100644 --- a/samples/Playground/Playground.iOS/ViewControllers/Collections/CompositionalLayout/AdaptiveSectionsViewController.cs +++ b/samples/Playground/Playground.iOS/ViewControllers/Collections/CompositionalLayout/AdaptiveSectionsViewController.cs @@ -29,7 +29,7 @@ private void ConfigureHierarchy() { _collectionView = new UICollectionView(View!.Bounds, CreateLayout()); _collectionView.AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight; - _collectionView.BackgroundColor = UIColor.SystemBackgroundColor; + _collectionView.BackgroundColor = UIColor.SystemBackground; _collectionView.RegisterNibForCell(DummyCell.Nib, DummyCell.Key); View.AddSubview(_collectionView); } diff --git a/samples/Playground/Playground.iOS/ViewControllers/Collections/CompositionalLayout/NestedGroupsViewController.cs b/samples/Playground/Playground.iOS/ViewControllers/Collections/CompositionalLayout/NestedGroupsViewController.cs index 9cd6d20d0..b876c728f 100644 --- a/samples/Playground/Playground.iOS/ViewControllers/Collections/CompositionalLayout/NestedGroupsViewController.cs +++ b/samples/Playground/Playground.iOS/ViewControllers/Collections/CompositionalLayout/NestedGroupsViewController.cs @@ -29,7 +29,7 @@ private void ConfigureHierarchy() { _collectionView = new UICollectionView(View!.Bounds, CreateLayout()); _collectionView.AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight; - _collectionView.BackgroundColor = UIColor.SystemBackgroundColor; + _collectionView.BackgroundColor = UIColor.SystemBackground; _collectionView.RegisterNibForCell(DummyCell.Nib, DummyCell.Key); View.AddSubview(_collectionView); } diff --git a/samples/Playground/Playground.iOS/Views/Collections/ProductViewCell.cs b/samples/Playground/Playground.iOS/Views/Collections/ProductViewCell.cs index f3b42d941..b8f1bdafe 100644 --- a/samples/Playground/Playground.iOS/Views/Collections/ProductViewCell.cs +++ b/samples/Playground/Playground.iOS/Views/Collections/ProductViewCell.cs @@ -2,11 +2,12 @@ // http://www.softeq.com using System; -using FFImageLoading; using Foundation; using Playground.ViewModels.Collections.Products; using Softeq.XToolkit.Bindings.Extensions; using Softeq.XToolkit.Bindings.iOS.Bindable; +using Softeq.XToolkit.WhiteLabel; +using Softeq.XToolkit.WhiteLabel.iOS.Interfaces; using UIKit; namespace Playground.iOS.Views.Collections @@ -34,7 +35,7 @@ public override void DoAttachBindings() { base.DoAttachBindings(); - ImageService.Instance.LoadUrl(ViewModel.PhotoUrl).Into(PhotoImage); + Dependencies.Container.Resolve().LoadImage(ViewModel.PhotoUrl, PhotoImage); this.Bind(() => ViewModel.Title, () => NameLabel.Text); this.Bind(() => ViewModel.Count, () => CountField.Text); diff --git a/samples/Playground/Playground.iOS/Views/MovieCollectionViewCell.cs b/samples/Playground/Playground.iOS/Views/MovieCollectionViewCell.cs index 700f4f20a..76addcfb7 100644 --- a/samples/Playground/Playground.iOS/Views/MovieCollectionViewCell.cs +++ b/samples/Playground/Playground.iOS/Views/MovieCollectionViewCell.cs @@ -2,11 +2,12 @@ // http://www.softeq.com using System; -using FFImageLoading; using Foundation; using Playground.Models; using Softeq.XToolkit.Bindings.Extensions; using Softeq.XToolkit.Bindings.iOS.Bindable; +using Softeq.XToolkit.WhiteLabel; +using Softeq.XToolkit.WhiteLabel.iOS.Interfaces; using UIKit; namespace Playground.iOS.Views @@ -26,7 +27,7 @@ public override void DoAttachBindings() { base.DoAttachBindings(); - ImageService.Instance.LoadUrl(ViewModel.IconUrl).Into(Poster); + Dependencies.Container.Resolve().LoadImage(ViewModel.IconUrl, Poster); this.Bind(() => ViewModel.Title, () => Title.Text); } diff --git a/samples/Playground/Playground.iOS/Views/MovieTableViewCell.cs b/samples/Playground/Playground.iOS/Views/MovieTableViewCell.cs index 6666bc129..63a120a31 100644 --- a/samples/Playground/Playground.iOS/Views/MovieTableViewCell.cs +++ b/samples/Playground/Playground.iOS/Views/MovieTableViewCell.cs @@ -2,11 +2,12 @@ // http://www.softeq.com using System; -using FFImageLoading; using Foundation; using Playground.Models; using Softeq.XToolkit.Bindings.Extensions; using Softeq.XToolkit.Bindings.iOS.Bindable; +using Softeq.XToolkit.WhiteLabel; +using Softeq.XToolkit.WhiteLabel.iOS.Interfaces; using UIKit; namespace Playground.iOS.Views @@ -30,7 +31,7 @@ public override void DoAttachBindings() { base.DoAttachBindings(); - ImageService.Instance.LoadUrl(ViewModel.IconUrl).Into(Poster); + Dependencies.Container.Resolve().LoadImage(ViewModel.IconUrl, Poster); this.Bind(() => ViewModel.Title, () => Title.Text); } diff --git a/samples/Playground/Playground.iOS/Views/Table/ProductTableViewCell.cs b/samples/Playground/Playground.iOS/Views/Table/ProductTableViewCell.cs index 4dc0e4eb3..a4d410ed9 100644 --- a/samples/Playground/Playground.iOS/Views/Table/ProductTableViewCell.cs +++ b/samples/Playground/Playground.iOS/Views/Table/ProductTableViewCell.cs @@ -2,11 +2,12 @@ // http://www.softeq.com using System; -using FFImageLoading; using Foundation; using Playground.ViewModels.Collections.Products; using Softeq.XToolkit.Bindings.Extensions; using Softeq.XToolkit.Bindings.iOS.Bindable; +using Softeq.XToolkit.WhiteLabel; +using Softeq.XToolkit.WhiteLabel.iOS.Interfaces; using UIKit; namespace Playground.iOS.Views.Table @@ -32,7 +33,7 @@ public override void DoAttachBindings() { base.DoAttachBindings(); - ImageService.Instance.LoadUrl(ViewModel.PhotoUrl).Into(PhotoImage); + Dependencies.Container.Resolve().LoadImage(ViewModel.PhotoUrl, PhotoImage); this.Bind(() => ViewModel.Title, () => NameLabel.Text); this.Bind(() => ViewModel.Count, () => CountField.Text); diff --git a/samples/Playground/Playground/Playground.csproj b/samples/Playground/Playground/Playground.csproj index 65d7a5640..41bcd1ff4 100644 --- a/samples/Playground/Playground/Playground.csproj +++ b/samples/Playground/Playground/Playground.csproj @@ -1,7 +1,7 @@  - netstandard2.1 + net7.0 From 545b06a6e9da274875c9eb4f6fbff547f863baaa Mon Sep 17 00:00:00 2001 From: Yauheni Pakala Date: Fri, 24 Feb 2023 17:16:38 +0200 Subject: [PATCH 04/26] Migrate from Xamarin.Essentials to Microsoft.Maui.Essentials (#517) --- .../PermissionsManager.cs | 44 ++++++------ .../PermissionsService.cs | 6 +- .../RequestResultHandler.cs | 2 +- .../Softeq.XToolkit.Permissions.Droid.csproj | 6 +- .../PermissionsManager.cs | 4 +- .../PermissionsService.cs | 6 +- .../Softeq.XToolkit.Permissions.iOS.csproj | 5 +- .../DefaultPermissionsDialogService.cs | 2 +- .../IPermissionsDialogService.cs | 2 +- .../IPermissionsManager.cs | 2 +- .../IPermissionsService.cs | 2 +- .../NotificationsPermission.cs | 2 +- .../PluginPermissionStatusExtensions.cs | 2 +- .../Softeq.XToolkit.Permissions.csproj | 5 +- .../MainApplicationBase.cs | 2 +- .../Providers/ContextProvider.cs | 21 +++--- .../Services/DroidAppInfoService.cs | 17 ----- .../Softeq.XToolkit.WhiteLabel.Droid.csproj | 2 +- .../ImagePicker/DroidImagePickerService.cs | 4 +- ...Toolkit.WhiteLabel.Essentials.Droid.csproj | 1 + ....XToolkit.WhiteLabel.Essentials.iOS.csproj | 5 +- .../Services/IosAppInfoService.cs | 17 ----- .../Softeq.XToolkit.WhiteLabel.iOS.csproj | 5 +- .../Interfaces/IAppInfoService.cs | 8 +-- Softeq.XToolkit.WhiteLabel/Model/Platform.cs | 13 +++- .../Services/EssentialsAppInfoService.cs | 67 ++++++++++++++++--- .../Services/EssentialsLauncherService.cs | 3 +- .../Services/InternalSettings.cs | 2 +- .../Softeq.XToolkit.WhiteLabel.csproj | 2 +- azure-pipelines/templates/setup-dotnet.yml | 2 +- .../CustomDroidBootstrapper.cs | 4 -- .../Playground.Droid/Playground.Droid.csproj | 1 + .../Playground.iOS/CustomIosBootstrapper.cs | 3 - .../Playground.iOS/Playground.iOS.csproj | 1 + .../Playground/CustomBootstrapper.cs | 4 ++ .../Playground/Playground/Playground.csproj | 1 + .../Components/PermissionViewModel.cs | 2 +- .../Components/PermissionsPageViewModel.cs | 10 +-- 38 files changed, 149 insertions(+), 138 deletions(-) delete mode 100644 Softeq.XToolkit.WhiteLabel.Droid/Services/DroidAppInfoService.cs delete mode 100644 Softeq.XToolkit.WhiteLabel.iOS/Services/IosAppInfoService.cs diff --git a/Softeq.XToolkit.Permissions.Droid/PermissionsManager.cs b/Softeq.XToolkit.Permissions.Droid/PermissionsManager.cs index b9592c667..57388e889 100644 --- a/Softeq.XToolkit.Permissions.Droid/PermissionsManager.cs +++ b/Softeq.XToolkit.Permissions.Droid/PermissionsManager.cs @@ -3,8 +3,8 @@ using System; using System.Threading.Tasks; -using Xamarin.Essentials; -using BasePermission = Xamarin.Essentials.Permissions.BasePermission; +using Microsoft.Maui.Storage; +using BasePermission = Microsoft.Maui.ApplicationModel.Permissions.BasePermission; namespace Softeq.XToolkit.Permissions.Droid { @@ -20,8 +20,8 @@ public PermissionsManager( { _permissionsService = permissionsService; _permissionsDialogService = new DefaultPermissionsDialogService(); - } - + } + /// public virtual Task CheckAsync() where T : BasePermission, new() @@ -67,29 +67,29 @@ private void OpenSettings() } private void ApplyKeysMigration(PermissionStatus permissionStatus) - where T : BasePermission, new() + where T : BasePermission, new() { var requestedKeyName = GetPermissionRequestedKey(); if (!Preferences.ContainsKey(requestedKeyName)) - { - return; + { + return; + } + + if (Preferences.Get(requestedKeyName, false)) + { + if (permissionStatus == PermissionStatus.Denied) + { + SetPermissionDenied(true); + } + + Preferences.Remove(requestedKeyName); } - if (Preferences.Get(requestedKeyName, false)) - { - if (permissionStatus == PermissionStatus.Denied) - { - SetPermissionDenied(true); - } - - Preferences.Remove(requestedKeyName); - } - - string GetPermissionRequestedKey() - where TPermission : BasePermission - { - return $"{nameof(PermissionsManager)}_IsPermissionRequested_{typeof(T).Name}"; - } + string GetPermissionRequestedKey() + where TPermission : BasePermission + { + return $"{nameof(PermissionsManager)}_IsPermissionRequested_{typeof(T).Name}"; + } } private async Task CommonCheckWithRequestAsync() diff --git a/Softeq.XToolkit.Permissions.Droid/PermissionsService.cs b/Softeq.XToolkit.Permissions.Droid/PermissionsService.cs index 5ad63aef0..666720e77 100644 --- a/Softeq.XToolkit.Permissions.Droid/PermissionsService.cs +++ b/Softeq.XToolkit.Permissions.Droid/PermissionsService.cs @@ -2,9 +2,9 @@ // http://www.softeq.com using System.Threading.Tasks; -using Xamarin.Essentials; -using BasePermission = Xamarin.Essentials.Permissions.BasePermission; -using EssentialsPermissions = Xamarin.Essentials.Permissions; +using Microsoft.Maui.ApplicationModel; +using BasePermission = Microsoft.Maui.ApplicationModel.Permissions.BasePermission; +using EssentialsPermissions = Microsoft.Maui.ApplicationModel.Permissions; namespace Softeq.XToolkit.Permissions.Droid { diff --git a/Softeq.XToolkit.Permissions.Droid/RequestResultHandler.cs b/Softeq.XToolkit.Permissions.Droid/RequestResultHandler.cs index 48f99e16a..33d71f9b0 100644 --- a/Softeq.XToolkit.Permissions.Droid/RequestResultHandler.cs +++ b/Softeq.XToolkit.Permissions.Droid/RequestResultHandler.cs @@ -16,7 +16,7 @@ public void Handle(int requestCode, string[] permissions, object grantResults) private void HandleImpl(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults) { - Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults); + Microsoft.Maui.ApplicationModel.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults); } } } diff --git a/Softeq.XToolkit.Permissions.Droid/Softeq.XToolkit.Permissions.Droid.csproj b/Softeq.XToolkit.Permissions.Droid/Softeq.XToolkit.Permissions.Droid.csproj index e994c1275..3d4bddbde 100644 --- a/Softeq.XToolkit.Permissions.Droid/Softeq.XToolkit.Permissions.Droid.csproj +++ b/Softeq.XToolkit.Permissions.Droid/Softeq.XToolkit.Permissions.Droid.csproj @@ -2,6 +2,7 @@ net6.0-android32.0 + true @@ -22,9 +23,4 @@ - - - - - \ No newline at end of file diff --git a/Softeq.XToolkit.Permissions.iOS/PermissionsManager.cs b/Softeq.XToolkit.Permissions.iOS/PermissionsManager.cs index 950430a70..85286ab82 100644 --- a/Softeq.XToolkit.Permissions.iOS/PermissionsManager.cs +++ b/Softeq.XToolkit.Permissions.iOS/PermissionsManager.cs @@ -3,8 +3,8 @@ using System; using System.Threading.Tasks; -using Xamarin.Essentials; -using BasePermission = Xamarin.Essentials.Permissions.BasePermission; +using Microsoft.Maui.Storage; +using BasePermission = Microsoft.Maui.ApplicationModel.Permissions.BasePermission; namespace Softeq.XToolkit.Permissions.iOS { diff --git a/Softeq.XToolkit.Permissions.iOS/PermissionsService.cs b/Softeq.XToolkit.Permissions.iOS/PermissionsService.cs index 1950884bc..062db5170 100644 --- a/Softeq.XToolkit.Permissions.iOS/PermissionsService.cs +++ b/Softeq.XToolkit.Permissions.iOS/PermissionsService.cs @@ -3,10 +3,10 @@ using System; using System.Threading.Tasks; +using Microsoft.Maui.ApplicationModel; using UserNotifications; -using Xamarin.Essentials; -using BasePermission = Xamarin.Essentials.Permissions.BasePermission; -using EssentialsPermissions = Xamarin.Essentials.Permissions; +using BasePermission = Microsoft.Maui.ApplicationModel.Permissions.BasePermission; +using EssentialsPermissions = Microsoft.Maui.ApplicationModel.Permissions; namespace Softeq.XToolkit.Permissions.iOS { diff --git a/Softeq.XToolkit.Permissions.iOS/Softeq.XToolkit.Permissions.iOS.csproj b/Softeq.XToolkit.Permissions.iOS/Softeq.XToolkit.Permissions.iOS.csproj index 212050bb7..b78e88eb7 100644 --- a/Softeq.XToolkit.Permissions.iOS/Softeq.XToolkit.Permissions.iOS.csproj +++ b/Softeq.XToolkit.Permissions.iOS/Softeq.XToolkit.Permissions.iOS.csproj @@ -2,6 +2,7 @@ net6.0-ios10.0 + true false @@ -18,8 +19,4 @@
- - - -
\ No newline at end of file diff --git a/Softeq.XToolkit.Permissions/DefaultPermissionsDialogService.cs b/Softeq.XToolkit.Permissions/DefaultPermissionsDialogService.cs index 1703bd63a..7154736ac 100644 --- a/Softeq.XToolkit.Permissions/DefaultPermissionsDialogService.cs +++ b/Softeq.XToolkit.Permissions/DefaultPermissionsDialogService.cs @@ -2,7 +2,7 @@ // http://www.softeq.com using System.Threading.Tasks; -using BasePermission = Xamarin.Essentials.Permissions.BasePermission; +using BasePermission = Microsoft.Maui.ApplicationModel.Permissions.BasePermission; namespace Softeq.XToolkit.Permissions { diff --git a/Softeq.XToolkit.Permissions/IPermissionsDialogService.cs b/Softeq.XToolkit.Permissions/IPermissionsDialogService.cs index d2ef9d7d0..edb860e66 100644 --- a/Softeq.XToolkit.Permissions/IPermissionsDialogService.cs +++ b/Softeq.XToolkit.Permissions/IPermissionsDialogService.cs @@ -2,7 +2,7 @@ // http://www.softeq.com using System.Threading.Tasks; -using BasePermission = Xamarin.Essentials.Permissions.BasePermission; +using BasePermission = Microsoft.Maui.ApplicationModel.Permissions.BasePermission; namespace Softeq.XToolkit.Permissions { diff --git a/Softeq.XToolkit.Permissions/IPermissionsManager.cs b/Softeq.XToolkit.Permissions/IPermissionsManager.cs index fcdd04a60..01e91dc5f 100644 --- a/Softeq.XToolkit.Permissions/IPermissionsManager.cs +++ b/Softeq.XToolkit.Permissions/IPermissionsManager.cs @@ -2,7 +2,7 @@ // http://www.softeq.com using System.Threading.Tasks; -using BasePermission = Xamarin.Essentials.Permissions.BasePermission; +using BasePermission = Microsoft.Maui.ApplicationModel.Permissions.BasePermission; namespace Softeq.XToolkit.Permissions { diff --git a/Softeq.XToolkit.Permissions/IPermissionsService.cs b/Softeq.XToolkit.Permissions/IPermissionsService.cs index 9a9d9d91b..f7870a8f1 100644 --- a/Softeq.XToolkit.Permissions/IPermissionsService.cs +++ b/Softeq.XToolkit.Permissions/IPermissionsService.cs @@ -2,7 +2,7 @@ // http://www.softeq.com using System.Threading.Tasks; -using BasePermission = Xamarin.Essentials.Permissions.BasePermission; +using BasePermission = Microsoft.Maui.ApplicationModel.Permissions.BasePermission; namespace Softeq.XToolkit.Permissions { diff --git a/Softeq.XToolkit.Permissions/NotificationsPermission.cs b/Softeq.XToolkit.Permissions/NotificationsPermission.cs index 7fbf16bcf..22a27db92 100644 --- a/Softeq.XToolkit.Permissions/NotificationsPermission.cs +++ b/Softeq.XToolkit.Permissions/NotificationsPermission.cs @@ -3,7 +3,7 @@ namespace Softeq.XToolkit.Permissions { - public class NotificationsPermission : Xamarin.Essentials.Permissions.BasePlatformPermission + public class NotificationsPermission : Microsoft.Maui.ApplicationModel.Permissions.BasePlatformPermission { } } diff --git a/Softeq.XToolkit.Permissions/PluginPermissionStatusExtensions.cs b/Softeq.XToolkit.Permissions/PluginPermissionStatusExtensions.cs index 33d0499db..e944f5a30 100644 --- a/Softeq.XToolkit.Permissions/PluginPermissionStatusExtensions.cs +++ b/Softeq.XToolkit.Permissions/PluginPermissionStatusExtensions.cs @@ -2,7 +2,7 @@ // http://www.softeq.com using System.ComponentModel; -using PluginPermissionStatus = Xamarin.Essentials.PermissionStatus; +using PluginPermissionStatus = Microsoft.Maui.ApplicationModel.PermissionStatus; namespace Softeq.XToolkit.Permissions { diff --git a/Softeq.XToolkit.Permissions/Softeq.XToolkit.Permissions.csproj b/Softeq.XToolkit.Permissions/Softeq.XToolkit.Permissions.csproj index 0a0c3a2cb..44e9fe8b1 100644 --- a/Softeq.XToolkit.Permissions/Softeq.XToolkit.Permissions.csproj +++ b/Softeq.XToolkit.Permissions/Softeq.XToolkit.Permissions.csproj @@ -2,14 +2,11 @@ net6.0 + true True - - - -
diff --git a/Softeq.XToolkit.WhiteLabel.Droid/MainApplicationBase.cs b/Softeq.XToolkit.WhiteLabel.Droid/MainApplicationBase.cs index caebedce6..3c28ef96c 100644 --- a/Softeq.XToolkit.WhiteLabel.Droid/MainApplicationBase.cs +++ b/Softeq.XToolkit.WhiteLabel.Droid/MainApplicationBase.cs @@ -61,7 +61,7 @@ protected void InitStrictMode() protected virtual void InitializeExternalDependencies() { - Xamarin.Essentials.Platform.Init(this); + Microsoft.Maui.ApplicationModel.Platform.Init(this); } protected abstract IBootstrapper CreateBootstrapper(); diff --git a/Softeq.XToolkit.WhiteLabel.Droid/Providers/ContextProvider.cs b/Softeq.XToolkit.WhiteLabel.Droid/Providers/ContextProvider.cs index 505edefa0..8e6cc55ac 100644 --- a/Softeq.XToolkit.WhiteLabel.Droid/Providers/ContextProvider.cs +++ b/Softeq.XToolkit.WhiteLabel.Droid/Providers/ContextProvider.cs @@ -1,10 +1,11 @@ // Developed by Softeq Development Corporation // http://www.softeq.com -using System; +using System; using Android.App; using Android.Content; -using Xamarin.Essentials; +using Microsoft.Maui.ApplicationModel; +using EssentialsActivityState = Microsoft.Maui.ApplicationModel.ActivityState; #nullable disable @@ -57,17 +58,17 @@ public EssentialsContextProvider() public Context AppContext => Platform.AppContext; - private static ActivityState ToLifecycleState(Xamarin.Essentials.ActivityState activityState) + private static ActivityState ToLifecycleState(EssentialsActivityState activityState) { return activityState switch { - Xamarin.Essentials.ActivityState.Created => ActivityState.Created, - Xamarin.Essentials.ActivityState.Destroyed => ActivityState.Destroyed, - Xamarin.Essentials.ActivityState.Paused => ActivityState.Paused, - Xamarin.Essentials.ActivityState.Resumed => ActivityState.Resumed, - Xamarin.Essentials.ActivityState.SaveInstanceState => ActivityState.SaveInstanceState, - Xamarin.Essentials.ActivityState.Started => ActivityState.Started, - Xamarin.Essentials.ActivityState.Stopped => ActivityState.Stopped, + EssentialsActivityState.Created => ActivityState.Created, + EssentialsActivityState.Destroyed => ActivityState.Destroyed, + EssentialsActivityState.Paused => ActivityState.Paused, + EssentialsActivityState.Resumed => ActivityState.Resumed, + EssentialsActivityState.SaveInstanceState => ActivityState.SaveInstanceState, + EssentialsActivityState.Started => ActivityState.Started, + EssentialsActivityState.Stopped => ActivityState.Stopped, _ => throw new NotImplementedException() }; } diff --git a/Softeq.XToolkit.WhiteLabel.Droid/Services/DroidAppInfoService.cs b/Softeq.XToolkit.WhiteLabel.Droid/Services/DroidAppInfoService.cs deleted file mode 100644 index e9838de24..000000000 --- a/Softeq.XToolkit.WhiteLabel.Droid/Services/DroidAppInfoService.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using Softeq.XToolkit.WhiteLabel.Model; -using Softeq.XToolkit.WhiteLabel.Services; - -namespace Softeq.XToolkit.WhiteLabel.Droid.Services -{ - /// - /// Android platform-specific extended implementation of class. - /// - public class DroidAppInfoService : EssentialsAppInfoService - { - /// - public override Platform Platform => Platform.Android; - } -} diff --git a/Softeq.XToolkit.WhiteLabel.Droid/Softeq.XToolkit.WhiteLabel.Droid.csproj b/Softeq.XToolkit.WhiteLabel.Droid/Softeq.XToolkit.WhiteLabel.Droid.csproj index 0f4790f1b..e2ad7ec51 100644 --- a/Softeq.XToolkit.WhiteLabel.Droid/Softeq.XToolkit.WhiteLabel.Droid.csproj +++ b/Softeq.XToolkit.WhiteLabel.Droid/Softeq.XToolkit.WhiteLabel.Droid.csproj @@ -2,6 +2,7 @@ net6.0-android32.0 + true @@ -26,7 +27,6 @@
- diff --git a/Softeq.XToolkit.WhiteLabel.Essentials.Droid/ImagePicker/DroidImagePickerService.cs b/Softeq.XToolkit.WhiteLabel.Essentials.Droid/ImagePicker/DroidImagePickerService.cs index de3fa2d4e..42ab27d32 100644 --- a/Softeq.XToolkit.WhiteLabel.Essentials.Droid/ImagePicker/DroidImagePickerService.cs +++ b/Softeq.XToolkit.WhiteLabel.Essentials.Droid/ImagePicker/DroidImagePickerService.cs @@ -7,9 +7,9 @@ using Softeq.XToolkit.Permissions; using Softeq.XToolkit.WhiteLabel.Droid.Providers; using Softeq.XToolkit.WhiteLabel.Essentials.ImagePicker; -using CameraPermission = Xamarin.Essentials.Permissions.Camera; +using CameraPermission = Microsoft.Maui.ApplicationModel.Permissions.Camera; using PermissionStatus = Softeq.XToolkit.Permissions.PermissionStatus; -using PhotosPermission = Xamarin.Essentials.Permissions.Photos; +using PhotosPermission = Microsoft.Maui.ApplicationModel.Permissions.Photos; namespace Softeq.XToolkit.WhiteLabel.Essentials.Droid.ImagePicker { diff --git a/Softeq.XToolkit.WhiteLabel.Essentials.Droid/Softeq.XToolkit.WhiteLabel.Essentials.Droid.csproj b/Softeq.XToolkit.WhiteLabel.Essentials.Droid/Softeq.XToolkit.WhiteLabel.Essentials.Droid.csproj index 68878b767..4d18917b6 100644 --- a/Softeq.XToolkit.WhiteLabel.Essentials.Droid/Softeq.XToolkit.WhiteLabel.Essentials.Droid.csproj +++ b/Softeq.XToolkit.WhiteLabel.Essentials.Droid/Softeq.XToolkit.WhiteLabel.Essentials.Droid.csproj @@ -2,6 +2,7 @@ net6.0-android32.0 + true diff --git a/Softeq.XToolkit.WhiteLabel.Essentials.iOS/Softeq.XToolkit.WhiteLabel.Essentials.iOS.csproj b/Softeq.XToolkit.WhiteLabel.Essentials.iOS/Softeq.XToolkit.WhiteLabel.Essentials.iOS.csproj index a76803a2c..ea273283e 100644 --- a/Softeq.XToolkit.WhiteLabel.Essentials.iOS/Softeq.XToolkit.WhiteLabel.Essentials.iOS.csproj +++ b/Softeq.XToolkit.WhiteLabel.Essentials.iOS/Softeq.XToolkit.WhiteLabel.Essentials.iOS.csproj @@ -2,6 +2,7 @@ net6.0-ios12.0 + true false @@ -25,8 +26,4 @@ - - - -
\ No newline at end of file diff --git a/Softeq.XToolkit.WhiteLabel.iOS/Services/IosAppInfoService.cs b/Softeq.XToolkit.WhiteLabel.iOS/Services/IosAppInfoService.cs deleted file mode 100644 index ed3860b6a..000000000 --- a/Softeq.XToolkit.WhiteLabel.iOS/Services/IosAppInfoService.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using Softeq.XToolkit.WhiteLabel.Model; -using Softeq.XToolkit.WhiteLabel.Services; - -namespace Softeq.XToolkit.WhiteLabel.iOS.Services -{ - /// - /// iOS platform-specific extended implementation of class. - /// - public class IosAppInfoService : EssentialsAppInfoService - { - /// - public override Platform Platform => Platform.iOS; - } -} diff --git a/Softeq.XToolkit.WhiteLabel.iOS/Softeq.XToolkit.WhiteLabel.iOS.csproj b/Softeq.XToolkit.WhiteLabel.iOS/Softeq.XToolkit.WhiteLabel.iOS.csproj index 003a0c3f9..aff63a8b2 100644 --- a/Softeq.XToolkit.WhiteLabel.iOS/Softeq.XToolkit.WhiteLabel.iOS.csproj +++ b/Softeq.XToolkit.WhiteLabel.iOS/Softeq.XToolkit.WhiteLabel.iOS.csproj @@ -2,6 +2,7 @@ net6.0-ios12.0 + true false @@ -22,8 +23,4 @@ - - - - \ No newline at end of file diff --git a/Softeq.XToolkit.WhiteLabel/Interfaces/IAppInfoService.cs b/Softeq.XToolkit.WhiteLabel/Interfaces/IAppInfoService.cs index d67bcb041..934094be6 100644 --- a/Softeq.XToolkit.WhiteLabel/Interfaces/IAppInfoService.cs +++ b/Softeq.XToolkit.WhiteLabel/Interfaces/IAppInfoService.cs @@ -18,21 +18,21 @@ public interface IAppInfoService /// /// Gets application Name. /// - string? Name { get; } + string Name { get; } /// /// Gets package Name/Application Identifier (com.company.test_app). /// - string? PackageName { get; } + string PackageName { get; } /// /// Gets application Version (1.0.0). /// - string? Version { get; } + string Version { get; } /// /// Gets application Build Number (1). /// - string? Build { get; } + string Build { get; } } } diff --git a/Softeq.XToolkit.WhiteLabel/Model/Platform.cs b/Softeq.XToolkit.WhiteLabel/Model/Platform.cs index 41fccdaf8..6a326c5b9 100644 --- a/Softeq.XToolkit.WhiteLabel/Model/Platform.cs +++ b/Softeq.XToolkit.WhiteLabel/Model/Platform.cs @@ -1,6 +1,7 @@ // Developed by Softeq Development Corporation // http://www.softeq.com +using System; using System.Diagnostics.CodeAnalysis; namespace Softeq.XToolkit.WhiteLabel.Model @@ -14,15 +15,23 @@ public enum Platform Unknown, Android, iOS, + + [Obsolete] WindowsPhone, Windows, + + [Obsolete] WindowsTablet, + + [Obsolete] SurfaceHub, Xbox, IoT, tvOS, watchOS, macOS, - Tizen + Tizen, + MacCatalyst, + WinUI } -} \ No newline at end of file +} diff --git a/Softeq.XToolkit.WhiteLabel/Services/EssentialsAppInfoService.cs b/Softeq.XToolkit.WhiteLabel/Services/EssentialsAppInfoService.cs index bd88eb09d..25653ea59 100644 --- a/Softeq.XToolkit.WhiteLabel/Services/EssentialsAppInfoService.cs +++ b/Softeq.XToolkit.WhiteLabel/Services/EssentialsAppInfoService.cs @@ -1,30 +1,79 @@ // Developed by Softeq Development Corporation // http://www.softeq.com +using Microsoft.Maui.ApplicationModel; +using Microsoft.Maui.Devices; using Softeq.XToolkit.WhiteLabel.Interfaces; -using Softeq.XToolkit.WhiteLabel.Model; -using Xamarin.Essentials; +using Platform = Softeq.XToolkit.WhiteLabel.Model.Platform; namespace Softeq.XToolkit.WhiteLabel.Services { /// - /// Xamarin.Essentials thread-safe implementation for interface. + /// Maui.Essentials thread-safe implementation for interface. /// - public abstract class EssentialsAppInfoService : IAppInfoService + public class EssentialsAppInfoService : IAppInfoService { /// - public abstract Platform Platform { get; } + public virtual Platform Platform + { + get + { + var devicePlatform = DeviceInfo.Platform; + + if (devicePlatform == DevicePlatform.iOS) + { + return Platform.iOS; + } + + if (devicePlatform == DevicePlatform.Android) + { + return Platform.Android; + } + + if (devicePlatform == DevicePlatform.macOS) + { + return Platform.macOS; + } + + if (devicePlatform == DevicePlatform.Tizen) + { + return Platform.Tizen; + } + + if (devicePlatform == DevicePlatform.tvOS) + { + return Platform.tvOS; + } + + if (devicePlatform == DevicePlatform.watchOS) + { + return Platform.watchOS; + } + + if (devicePlatform == DevicePlatform.WinUI) + { + return Platform.WinUI; + } + + if (devicePlatform == DevicePlatform.MacCatalyst) + { + return Platform.MacCatalyst; + } + + return Platform.Unknown; + } + } /// - public string? Name => AppInfo.Name; + public string Name => AppInfo.Name; /// - public string? PackageName => AppInfo.PackageName; + public string PackageName => AppInfo.PackageName; /// - public string? Version => AppInfo.VersionString; + public string Version => AppInfo.VersionString; /// - public string? Build => AppInfo.BuildString; + public string Build => AppInfo.BuildString; } } diff --git a/Softeq.XToolkit.WhiteLabel/Services/EssentialsLauncherService.cs b/Softeq.XToolkit.WhiteLabel/Services/EssentialsLauncherService.cs index eebb0535c..b33ff84f9 100644 --- a/Softeq.XToolkit.WhiteLabel/Services/EssentialsLauncherService.cs +++ b/Softeq.XToolkit.WhiteLabel/Services/EssentialsLauncherService.cs @@ -1,11 +1,12 @@ // Developed by Softeq Development Corporation // http://www.softeq.com +using Microsoft.Maui.ApplicationModel; +using Microsoft.Maui.ApplicationModel.Communication; using Softeq.XToolkit.Common.Extensions; using Softeq.XToolkit.Common.Logger; using Softeq.XToolkit.Common.Threading; using Softeq.XToolkit.WhiteLabel.Interfaces; -using Xamarin.Essentials; namespace Softeq.XToolkit.WhiteLabel.Services { diff --git a/Softeq.XToolkit.WhiteLabel/Services/InternalSettings.cs b/Softeq.XToolkit.WhiteLabel/Services/InternalSettings.cs index 13c765fd0..dcb1204da 100644 --- a/Softeq.XToolkit.WhiteLabel/Services/InternalSettings.cs +++ b/Softeq.XToolkit.WhiteLabel/Services/InternalSettings.cs @@ -2,8 +2,8 @@ // http://www.softeq.com using System; +using Microsoft.Maui.Storage; using Softeq.XToolkit.Common.Interfaces; -using Xamarin.Essentials; namespace Softeq.XToolkit.WhiteLabel.Services { diff --git a/Softeq.XToolkit.WhiteLabel/Softeq.XToolkit.WhiteLabel.csproj b/Softeq.XToolkit.WhiteLabel/Softeq.XToolkit.WhiteLabel.csproj index 9ae7cdf80..4891d1148 100644 --- a/Softeq.XToolkit.WhiteLabel/Softeq.XToolkit.WhiteLabel.csproj +++ b/Softeq.XToolkit.WhiteLabel/Softeq.XToolkit.WhiteLabel.csproj @@ -2,6 +2,7 @@ net6.0 + true @@ -11,7 +12,6 @@ - diff --git a/azure-pipelines/templates/setup-dotnet.yml b/azure-pipelines/templates/setup-dotnet.yml index 55511b850..9a08735eb 100644 --- a/azure-pipelines/templates/setup-dotnet.yml +++ b/azure-pipelines/templates/setup-dotnet.yml @@ -21,4 +21,4 @@ steps: inputs: targetType: 'inline' script: | - dotnet workload install ios android \ No newline at end of file + dotnet workload install ios android maui-ios maui-android \ No newline at end of file diff --git a/samples/Playground/Playground.Droid/CustomDroidBootstrapper.cs b/samples/Playground/Playground.Droid/CustomDroidBootstrapper.cs index d9335e49a..970ce6805 100644 --- a/samples/Playground/Playground.Droid/CustomDroidBootstrapper.cs +++ b/samples/Playground/Playground.Droid/CustomDroidBootstrapper.cs @@ -15,10 +15,8 @@ using Softeq.XToolkit.WhiteLabel.Droid; using Softeq.XToolkit.WhiteLabel.Droid.Dialogs; using Softeq.XToolkit.WhiteLabel.Droid.Interfaces; -using Softeq.XToolkit.WhiteLabel.Droid.Services; using Softeq.XToolkit.WhiteLabel.Essentials.Droid.FullScreenImage; using Softeq.XToolkit.WhiteLabel.Essentials.Droid.ImagePicker; -using Softeq.XToolkit.WhiteLabel.Interfaces; using Softeq.XToolkit.WhiteLabel.Navigation; using IImagePickerService = Softeq.XToolkit.WhiteLabel.Essentials.ImagePicker.IImagePickerService; @@ -38,8 +36,6 @@ protected override void ConfigureIoc(IContainerBuilder builder) // core CustomBootstrapper.Configure(builder); - builder.Singleton(); - builder.Singleton(); builder.Singleton(); diff --git a/samples/Playground/Playground.Droid/Playground.Droid.csproj b/samples/Playground/Playground.Droid/Playground.Droid.csproj index d97d9068b..335925531 100644 --- a/samples/Playground/Playground.Droid/Playground.Droid.csproj +++ b/samples/Playground/Playground.Droid/Playground.Droid.csproj @@ -8,6 +8,7 @@ net7.0-android33.0 Exe + true android-arm;android-arm64;android-x86;android-x64 diff --git a/samples/Playground/Playground.iOS/CustomIosBootstrapper.cs b/samples/Playground/Playground.iOS/CustomIosBootstrapper.cs index f2123da3f..6b32c671f 100644 --- a/samples/Playground/Playground.iOS/CustomIosBootstrapper.cs +++ b/samples/Playground/Playground.iOS/CustomIosBootstrapper.cs @@ -15,7 +15,6 @@ using Softeq.XToolkit.WhiteLabel.Essentials.ImagePicker; using Softeq.XToolkit.WhiteLabel.Essentials.iOS.FullScreenImage; using Softeq.XToolkit.WhiteLabel.Essentials.iOS.ImagePicker; -using Softeq.XToolkit.WhiteLabel.Interfaces; using Softeq.XToolkit.WhiteLabel.iOS; using Softeq.XToolkit.WhiteLabel.iOS.Interfaces; using Softeq.XToolkit.WhiteLabel.iOS.Services; @@ -37,8 +36,6 @@ protected override void ConfigureIoc(IContainerBuilder builder) // core CustomBootstrapper.Configure(builder); - builder.Singleton(); - builder.Singleton(); builder.Singleton(); diff --git a/samples/Playground/Playground.iOS/Playground.iOS.csproj b/samples/Playground/Playground.iOS/Playground.iOS.csproj index 1c71c022d..b1c111c26 100644 --- a/samples/Playground/Playground.iOS/Playground.iOS.csproj +++ b/samples/Playground/Playground.iOS/Playground.iOS.csproj @@ -8,6 +8,7 @@ net7.0-ios16.2 Exe + true iPhoneSimulator;iPhone;AnyCPU 10.0 false diff --git a/samples/Playground/Playground/CustomBootstrapper.cs b/samples/Playground/Playground/CustomBootstrapper.cs index 3a9b11565..e8cfe399f 100644 --- a/samples/Playground/Playground/CustomBootstrapper.cs +++ b/samples/Playground/Playground/CustomBootstrapper.cs @@ -4,6 +4,8 @@ using Playground.Services; using Playground.ViewModels.Frames; using Softeq.XToolkit.WhiteLabel.Bootstrapper.Abstract; +using Softeq.XToolkit.WhiteLabel.Interfaces; +using Softeq.XToolkit.WhiteLabel.Services; namespace Playground { @@ -11,6 +13,8 @@ public static class CustomBootstrapper { public static void Configure(IContainerBuilder builder) { + builder.Singleton(); + // Playground builder.Singleton(); diff --git a/samples/Playground/Playground/Playground.csproj b/samples/Playground/Playground/Playground.csproj index 41bcd1ff4..b5e9b99d8 100644 --- a/samples/Playground/Playground/Playground.csproj +++ b/samples/Playground/Playground/Playground.csproj @@ -2,6 +2,7 @@ net7.0 + true diff --git a/samples/Playground/Playground/ViewModels/Components/PermissionViewModel.cs b/samples/Playground/Playground/ViewModels/Components/PermissionViewModel.cs index f630dc673..9e10f9888 100644 --- a/samples/Playground/Playground/ViewModels/Components/PermissionViewModel.cs +++ b/samples/Playground/Playground/ViewModels/Components/PermissionViewModel.cs @@ -7,7 +7,7 @@ using Softeq.XToolkit.Common.Commands; using Softeq.XToolkit.Common.Threading; using Softeq.XToolkit.Permissions; -using BasePermission = Xamarin.Essentials.Permissions.BasePermission; +using BasePermission = Microsoft.Maui.ApplicationModel.Permissions.BasePermission; using PermissionStatus = Softeq.XToolkit.Permissions.PermissionStatus; namespace Playground.ViewModels.Components diff --git a/samples/Playground/Playground/ViewModels/Components/PermissionsPageViewModel.cs b/samples/Playground/Playground/ViewModels/Components/PermissionsPageViewModel.cs index 04162da75..7c92040df 100644 --- a/samples/Playground/Playground/ViewModels/Components/PermissionsPageViewModel.cs +++ b/samples/Playground/Playground/ViewModels/Components/PermissionsPageViewModel.cs @@ -5,11 +5,11 @@ using Softeq.XToolkit.Common.Extensions; using Softeq.XToolkit.Permissions; using Softeq.XToolkit.WhiteLabel.Mvvm; -using CameraPermission = Xamarin.Essentials.Permissions.Camera; -using LocationAlwaysPermission = Xamarin.Essentials.Permissions.LocationAlways; -using LocationWhenInUsePermission = Xamarin.Essentials.Permissions.LocationWhenInUse; -using PhotosPermission = Xamarin.Essentials.Permissions.Photos; -using StoragePermission = Xamarin.Essentials.Permissions.StorageWrite; +using CameraPermission = Microsoft.Maui.ApplicationModel.Permissions.Camera; +using LocationAlwaysPermission = Microsoft.Maui.ApplicationModel.Permissions.LocationAlways; +using LocationWhenInUsePermission = Microsoft.Maui.ApplicationModel.Permissions.LocationWhenInUse; +using PhotosPermission = Microsoft.Maui.ApplicationModel.Permissions.Photos; +using StoragePermission = Microsoft.Maui.ApplicationModel.Permissions.StorageWrite; namespace Playground.ViewModels.Components { From 08ad4e54e1e4dc5028253473fe561d06955e265a Mon Sep 17 00:00:00 2001 From: Yauheni Pakala Date: Fri, 24 Feb 2023 17:36:06 +0200 Subject: [PATCH 05/26] Update documentation & rework nested namespaces (#514) --- azure-pipelines/Documentation.yml | 55 +++--- documentation/articles/configure-android.md | 4 +- documentation/articles/create-activity.md | 2 +- .../create-storyboard-viewcontroller.md | 4 +- documentation/articles/getting-started.md | 6 +- .../articles/xtoolkit/version-support.md | 49 ++--- documentation/build.sh | 19 +- documentation/docfx.json | 4 +- documentation/index.md | 7 +- documentation/nested-namespaces.ps1 | 176 ------------------ 10 files changed, 73 insertions(+), 253 deletions(-) delete mode 100644 documentation/nested-namespaces.ps1 diff --git a/azure-pipelines/Documentation.yml b/azure-pipelines/Documentation.yml index bb3700750..ea89019e6 100644 --- a/azure-pipelines/Documentation.yml +++ b/azure-pipelines/Documentation.yml @@ -15,31 +15,30 @@ jobs: pool: vmImage: $(MACOS_VM_IMAGE) steps: - # setup env - - task: UseDotNet@2 - displayName: Use .NET SDK - inputs: - version: $(DOTNET_SDK_VERSION) - - bash: sudo $AGENT_HOMEDIRECTORY/scripts/select-xamarin-sdk.sh $(MONO_VERSION) - displayName: Select Xamarin SDK - - bash: | - echo '##vso[task.setvariable variable=MD_APPLE_SDK_ROOT;]'/Applications/Xcode_$(XCODE_VERSION).app - sudo xcode-select -s /Applications/Xcode_$(XCODE_VERSION).app/Contents/Developer - displayName: Select Xcode - # restore, build - - script: 'msbuild XToolkit.sln -r /p:Configuration="Release" /p:Platform="Any CPU"' - displayName: Build Solution - - task: UseDotNet@2 - displayName: Use .NET 6 SDK - inputs: - version: 6.0.405 - - bash: 'cd documentation && sh build.sh' - displayName: Build Documentation - - task: CopyFiles@2 - inputs: - contents: 'documentation/_site/**' - targetFolder: $(Build.ArtifactStagingDirectory) - - task: PublishBuildArtifacts@1 - inputs: - pathToPublish: $(Build.ArtifactStagingDirectory) - artifactName: drop + - template: templates/setup-dotnet.yml + parameters: + version: $(DOTNET_SDK_VERSION) + + - template: templates/setup-xcode.yml + parameters: + version: $(XCODE_VERSION) + + - task: DotNetCoreCLI@2 + displayName: Build Solution + inputs: + command: build + configuration: Release + arguments: --verbosity Detailed + + - bash: 'cd documentation && sh build.sh' + displayName: Build Documentation + + - task: CopyFiles@2 + inputs: + contents: 'documentation/_site/**' + targetFolder: $(Build.ArtifactStagingDirectory) + + - task: PublishBuildArtifacts@1 + inputs: + pathToPublish: $(Build.ArtifactStagingDirectory) + artifactName: drop diff --git a/documentation/articles/configure-android.md b/documentation/articles/configure-android.md index 0b5010e4a..7c5b65c86 100644 --- a/documentation/articles/configure-android.md +++ b/documentation/articles/configure-android.md @@ -16,8 +16,8 @@ [Application] public class MainApplication : MainApplicationBase { - protected MainApplication(IntPtr handle, JniHandleOwnership transer) - : base(handle, transer) + protected MainApplication(IntPtr handle, JniHandleOwnership transfer) + : base(handle, transfer) { } diff --git a/documentation/articles/create-activity.md b/documentation/articles/create-activity.md index e5f98f0f0..99ff4fabc 100644 --- a/documentation/articles/create-activity.md +++ b/documentation/articles/create-activity.md @@ -10,7 +10,7 @@ First of all, see [Navigation Requirements](xtoolkit/whitelabel/navigation-requi [Activity] public class MainPageActivity : ActivityBase { - protected override void OnCreate(Bundle savedInstanceState) + protected override void OnCreate(Bundle? savedInstanceState) { base.OnCreate(savedInstanceState); diff --git a/documentation/articles/create-storyboard-viewcontroller.md b/documentation/articles/create-storyboard-viewcontroller.md index d8e6133c2..c6b197929 100644 --- a/documentation/articles/create-storyboard-viewcontroller.md +++ b/documentation/articles/create-storyboard-viewcontroller.md @@ -27,7 +27,7 @@ First of all, see [Navigation Requirements](xtoolkit/whitelabel/navigation-requi ```cs public partial class MainPageViewController : ViewControllerBase { - public MainPageViewController(IntPtr handle) + public MainPageViewController(NativeHandle handle) : base(handle) { } @@ -38,7 +38,7 @@ public partial class MainPageViewController : ViewControllerBase Date: Mon, 27 Feb 2023 14:03:49 +0200 Subject: [PATCH 06/26] Resolve warnings (#515) --- .../Extensions/ContextExtensions.cs | 6 ++--- .../EnumExtensionsTests/TestEnum.cs | 1 - .../EnumerableExtensionsTests.cs | 3 +-- .../Extensions/UIViewExtensions.cs | 1 - .../Api/IApiServiceFactory.cs | 6 +++++ .../Api/RefitApiServiceFactory.cs | 5 +++++ Softeq.XToolkit.Remote/Auth/AuthExtensions.cs | 3 +++ .../Client/ClientExtensions.cs | 3 +++ .../ExpiredRefreshTokenException.cs | 4 ++++ Softeq.XToolkit.Remote/RemoteService.cs | 5 +++++ .../RemoteServiceExtensions.cs | 3 +++ .../Controls/BadgeView.cs | 13 ++++++----- .../Controls/BusyOverlayView.cs | 2 +- .../Controls/ColoredClickableSpan.cs | 2 +- .../Controls/DroidContextMenuComponent.cs | 2 +- .../Controls/NavigationBarView.cs | 22 +++++++++---------- .../Controls/NonConsumingTouchesToolbar.cs | 2 +- .../Controls/ObservableStackView.cs | 2 +- .../Controls/SwipeControlViewPager.cs | 4 ++-- .../Dialogs/DialogFragmentBase.cs | 8 +++---- .../Navigation/BundleService.cs | 2 +- .../Services/DroidToastService.cs | 2 +- .../Services/KeyboardService.cs | 8 +++---- .../BottomNavigationComponentBase.cs | 4 ++-- .../Views/BottomNavigationActivityBase.cs | 2 +- .../Views/BottomNavigationFragmentBase.cs | 4 ++-- .../Views/ToolbarActivityBase.cs | 2 +- .../Views/ToolbarFragmentBase.cs | 2 +- .../FullScreenImageDialogFragment.cs | 2 +- .../ImagePicker/DroidImagePickerService.cs | 2 +- .../ImagePicker/ImagePickerUtils.cs | 2 +- .../DryIocContainerBuilderTests.cs | 2 -- .../Extended/DroidChooseBetterDateDialog.cs | 2 +- .../Playground.Droid/LinkerPleaseInclude.cs | 1 + .../BottomTabs/BottomTabsPageActivity.cs | 2 +- .../Views/BottomTabs/First/RedFragment.cs | 6 ++--- .../Views/BottomTabs/First/YellowActivity.cs | 2 +- .../Views/BottomTabs/Second/BlueFragment.cs | 6 ++--- .../Views/BottomTabs/Second/GreenFragment.cs | 6 ++--- .../GroupedCollectionPageActivity.cs | 2 +- .../Collections/GroupedTablePageActivity.cs | 2 +- .../Views/Collections/ProductViewHolder.cs | 8 +++---- .../Components/PermissionsPageActivity.cs | 6 ++--- .../Views/Controls/PhotoBrowserActivity.cs | 4 ++-- .../Views/Dialogs/DialogsPageActivity.cs | 12 +++++----- .../Views/Dialogs/SimpleDialogPageFragment.cs | 8 +++---- .../Views/EmptyPageActivity.cs | 2 +- .../Views/Frames/BlueFragment.cs | 6 ++--- .../Views/Frames/RedFragment.cs | 6 ++--- .../Views/Frames/SplitFrameFragment.cs | 2 +- .../Views/Frames/YellowFragment.cs | 6 ++--- .../Views/MainPageActivity.cs | 2 ++ .../Playground.iOS/LinkerPleaseInclude.cs | 1 + .../BottomTabs/First/RedViewController.cs | 6 ++--- .../BottomTabs/First/YellowViewController.cs | 6 ++--- .../BottomTabs/Second/BlueViewController.cs | 6 ++--- .../BottomTabs/Second/GreenViewController.cs | 6 ++--- .../CollectionPageViewController.cs | 4 ++-- .../CompositionalLayoutPageViewController.cs | 4 ++-- .../GroupedCollectionPageViewController.cs | 3 ++- ...GroupedCollectionViewDelegateFlowLayout.cs | 1 - .../GroupedListPageViewController.cs | 4 ++-- .../Collections/TablePageViewController.cs | 4 ++-- .../ConnectivityPageViewController.cs | 4 ++-- .../Components/GesturesPageViewController.cs | 4 ++-- .../PermissionsPageViewController.cs | 4 ++-- .../Controls/PhotoBrowserViewController.cs | 3 ++- .../Dialogs/DialogsPageViewController.cs | 4 ++-- .../Dialogs/SimpleDialogPageViewController.cs | 3 ++- .../EmptyPageViewController.cs | 4 ++-- .../Frames/FramesBlueViewController.cs | 4 ++-- .../Frames/FramesRedViewController.cs | 4 ++-- .../Frames/FramesViewController.cs | 4 ++-- .../Frames/FramesYellowViewController.cs | 4 ++-- .../Frames/SplitFrameViewController.cs | 4 ++-- .../ViewControllers/MainPageViewController.cs | 6 ++--- .../Pages/DetailsPageViewController.cs | 6 ++--- .../Views/Collections/DummyCell.cs | 4 ++-- .../Views/Collections/GroupedFooterView.cs | 3 ++- .../Views/Collections/GroupedHeaderView.cs | 3 ++- .../Views/Collections/ProductViewCell.cs | 3 ++- .../Views/MainPageGroupHeaderViewCell.cs | 4 ++-- .../Views/MainPageItemViewCell.cs | 4 ++-- .../Views/MovieCollectionViewCell.cs | 4 ++-- .../Views/MovieTableViewCell.cs | 4 ++-- .../Views/Table/GroupedTableFooterView.cs | 3 ++- .../Views/Table/GroupedTableHeaderView.cs | 3 ++- .../Views/Table/ProductTableViewCell.cs | 4 ++-- .../Products/ProductHeaderViewModel.cs | 9 +++++--- .../Components/ConnectivityPageViewModel.cs | 4 ++-- 90 files changed, 208 insertions(+), 169 deletions(-) diff --git a/Softeq.XToolkit.Common.Droid/Extensions/ContextExtensions.cs b/Softeq.XToolkit.Common.Droid/Extensions/ContextExtensions.cs index 91472d85e..499ce74e2 100644 --- a/Softeq.XToolkit.Common.Droid/Extensions/ContextExtensions.cs +++ b/Softeq.XToolkit.Common.Droid/Extensions/ContextExtensions.cs @@ -57,9 +57,9 @@ private static void SetupMetrics(Context context) return; } - using (var metrics = context.Resources.DisplayMetrics) + using (var metrics = context.Resources!.DisplayMetrics) { - _displayDensity = metrics.Density; + _displayDensity = metrics!.Density; } } @@ -121,7 +121,7 @@ public static bool IsInPowerSavingMode(this Context context) /// Intent that should be handled. public static bool TryStartActivity(this Context context, Intent intent) { - var canResolveActivity = intent.ResolveActivity(context.PackageManager) != null; + var canResolveActivity = intent.ResolveActivity(context.PackageManager!) != null; if (canResolveActivity) { context.StartActivity(intent); diff --git a/Softeq.XToolkit.Common.Tests/Extensions/EnumExtensionsTests/TestEnum.cs b/Softeq.XToolkit.Common.Tests/Extensions/EnumExtensionsTests/TestEnum.cs index e2035719f..224f473f3 100644 --- a/Softeq.XToolkit.Common.Tests/Extensions/EnumExtensionsTests/TestEnum.cs +++ b/Softeq.XToolkit.Common.Tests/Extensions/EnumExtensionsTests/TestEnum.cs @@ -1,7 +1,6 @@ // Developed by Softeq Development Corporation // http://www.softeq.com -using System; using System.ComponentModel; namespace Softeq.XToolkit.Common.Tests.Extensions.EnumExtensionsTests diff --git a/Softeq.XToolkit.Common.Tests/Extensions/EnumerableExtensionsTests/EnumerableExtensionsTests.cs b/Softeq.XToolkit.Common.Tests/Extensions/EnumerableExtensionsTests/EnumerableExtensionsTests.cs index 8aa1ad7f9..33494be00 100644 --- a/Softeq.XToolkit.Common.Tests/Extensions/EnumerableExtensionsTests/EnumerableExtensionsTests.cs +++ b/Softeq.XToolkit.Common.Tests/Extensions/EnumerableExtensionsTests/EnumerableExtensionsTests.cs @@ -1,11 +1,10 @@ // Developed by Softeq Development Corporation // http://www.softeq.com -using System; +using System; using System.Collections.Generic; using System.Linq; using NSubstitute; -using NSubstitute.ReceivedExtensions; using Softeq.XToolkit.Common.Extensions; using Xunit; diff --git a/Softeq.XToolkit.Common.iOS/Extensions/UIViewExtensions.cs b/Softeq.XToolkit.Common.iOS/Extensions/UIViewExtensions.cs index 9d9821637..0dff64888 100644 --- a/Softeq.XToolkit.Common.iOS/Extensions/UIViewExtensions.cs +++ b/Softeq.XToolkit.Common.iOS/Extensions/UIViewExtensions.cs @@ -1,7 +1,6 @@ // Developed by Softeq Development Corporation // http://www.softeq.com -using System; using CoreAnimation; using CoreGraphics; using UIKit; diff --git a/Softeq.XToolkit.Remote/Api/IApiServiceFactory.cs b/Softeq.XToolkit.Remote/Api/IApiServiceFactory.cs index e4a86fc5a..6256cd127 100644 --- a/Softeq.XToolkit.Remote/Api/IApiServiceFactory.cs +++ b/Softeq.XToolkit.Remote/Api/IApiServiceFactory.cs @@ -10,6 +10,12 @@ namespace Softeq.XToolkit.Remote.Api ///
public interface IApiServiceFactory { + /// + /// Creates API service implementation. + /// + /// Configured instance of HttpClient. + /// Type of API service. + /// API service implementation. TApiService CreateService(HttpClient httpClient); } } diff --git a/Softeq.XToolkit.Remote/Api/RefitApiServiceFactory.cs b/Softeq.XToolkit.Remote/Api/RefitApiServiceFactory.cs index edee2417a..b18f0e7cf 100644 --- a/Softeq.XToolkit.Remote/Api/RefitApiServiceFactory.cs +++ b/Softeq.XToolkit.Remote/Api/RefitApiServiceFactory.cs @@ -14,11 +14,16 @@ public class RefitApiServiceFactory : IApiServiceFactory { private readonly RefitSettings? _settings; + /// + /// Initializes a new instance of the class. + /// + /// Refit settings. public RefitApiServiceFactory(RefitSettings? settings = null) { _settings = settings; } + /// public TApiService CreateService(HttpClient httpClient) { if (httpClient == null) diff --git a/Softeq.XToolkit.Remote/Auth/AuthExtensions.cs b/Softeq.XToolkit.Remote/Auth/AuthExtensions.cs index d9804ccf6..e498d133a 100644 --- a/Softeq.XToolkit.Remote/Auth/AuthExtensions.cs +++ b/Softeq.XToolkit.Remote/Auth/AuthExtensions.cs @@ -8,6 +8,9 @@ namespace Softeq.XToolkit.Remote.Auth { + /// + /// Contains extension methods for related to auth. + /// public static class AuthExtensions { /// diff --git a/Softeq.XToolkit.Remote/Client/ClientExtensions.cs b/Softeq.XToolkit.Remote/Client/ClientExtensions.cs index 39d5afa1b..b656165f5 100644 --- a/Softeq.XToolkit.Remote/Client/ClientExtensions.cs +++ b/Softeq.XToolkit.Remote/Client/ClientExtensions.cs @@ -8,6 +8,9 @@ namespace Softeq.XToolkit.Remote.Client { + /// + /// Contains extension methods for . + /// public static class ClientExtensions { /// diff --git a/Softeq.XToolkit.Remote/Exceptions/ExpiredRefreshTokenException.cs b/Softeq.XToolkit.Remote/Exceptions/ExpiredRefreshTokenException.cs index b04920cc1..348ebe469 100644 --- a/Softeq.XToolkit.Remote/Exceptions/ExpiredRefreshTokenException.cs +++ b/Softeq.XToolkit.Remote/Exceptions/ExpiredRefreshTokenException.cs @@ -10,6 +10,10 @@ namespace Softeq.XToolkit.Remote.Exceptions /// public class ExpiredRefreshTokenException : Exception { + /// + /// Initializes a new instance of the class. + /// + /// Inner exception. public ExpiredRefreshTokenException(Exception innerException) : base("Refresh Token was expired", innerException) { diff --git a/Softeq.XToolkit.Remote/RemoteService.cs b/Softeq.XToolkit.Remote/RemoteService.cs index 60af48c84..0c08a13c2 100644 --- a/Softeq.XToolkit.Remote/RemoteService.cs +++ b/Softeq.XToolkit.Remote/RemoteService.cs @@ -57,6 +57,11 @@ public virtual async Task MakeRequest( .ConfigureAwait(false); } + /// + /// Creates custom Polly policy. + /// + /// Request options. + /// Polly policy. protected virtual IAsyncPolicy CreatePolicy(RequestOptions options) { return _executorFactory diff --git a/Softeq.XToolkit.Remote/RemoteServiceExtensions.cs b/Softeq.XToolkit.Remote/RemoteServiceExtensions.cs index 949e7ddfe..a41e60cb9 100644 --- a/Softeq.XToolkit.Remote/RemoteServiceExtensions.cs +++ b/Softeq.XToolkit.Remote/RemoteServiceExtensions.cs @@ -9,6 +9,9 @@ namespace Softeq.XToolkit.Remote { + /// + /// Contains extension methods for . + /// public static class RemoteServiceExtensions { /// diff --git a/Softeq.XToolkit.WhiteLabel.Droid/Controls/BadgeView.cs b/Softeq.XToolkit.WhiteLabel.Droid/Controls/BadgeView.cs index ca58c8639..3e832a6bc 100644 --- a/Softeq.XToolkit.WhiteLabel.Droid/Controls/BadgeView.cs +++ b/Softeq.XToolkit.WhiteLabel.Droid/Controls/BadgeView.cs @@ -41,11 +41,11 @@ public BadgeView(Context context, IAttributeSet attrs, int defStyle) public ColorStateList BackgroundColor { - get => ((GradientDrawable) _textView.Background).Color; - set => ((GradientDrawable) _textView.Background).SetColor(value); + get => ((GradientDrawable) _textView.Background!).Color!; + set => ((GradientDrawable) _textView.Background!).SetColor(value); } - public ColorStateList TextColor + public ColorStateList? TextColor { get => _textView.TextColors; set => _textView.SetTextColor(value); @@ -58,14 +58,15 @@ internal void SetViewModel(TabViewModel viewModel) _viewModelRef = new WeakReferenceEx>(viewModel); this.DetachBindings(); - this.Bind(() => _viewModelRef.Target.BadgeText, () => _textView.Text); - this.Bind(() => _viewModelRef.Target.IsBadgeVisible, () => Visibility, GoneConverter.Instance); + this.Bind(() => _viewModelRef.Target!.BadgeText, () => _textView.Text); + this.Bind(() => _viewModelRef.Target!.IsBadgeVisible, () => Visibility, VisibilityConverter.Gone); } private void Initialize(Context context) { Inflate(context, Resource.Layout.control_notification_badge, this); - _textView = FindViewById(Resource.Id.notification_badge_text_view); + + _textView = FindViewById(Resource.Id.notification_badge_text_view)!; } } } diff --git a/Softeq.XToolkit.WhiteLabel.Droid/Controls/BusyOverlayView.cs b/Softeq.XToolkit.WhiteLabel.Droid/Controls/BusyOverlayView.cs index 6b8dc7bc9..3c8310cc8 100644 --- a/Softeq.XToolkit.WhiteLabel.Droid/Controls/BusyOverlayView.cs +++ b/Softeq.XToolkit.WhiteLabel.Droid/Controls/BusyOverlayView.cs @@ -35,7 +35,7 @@ public BusyOverlayView(IntPtr handle, JniHandleOwnership owner) : base(handle, o public void SetOverlayBackgroundResource(int resourceId) { var view = FindViewById(Resource.Id.control_busy_overlay_container); - view.SetBackgroundResource(resourceId); + view!.SetBackgroundResource(resourceId); } private void Init(Context context) diff --git a/Softeq.XToolkit.WhiteLabel.Droid/Controls/ColoredClickableSpan.cs b/Softeq.XToolkit.WhiteLabel.Droid/Controls/ColoredClickableSpan.cs index deeb93bd1..0bb082bbf 100644 --- a/Softeq.XToolkit.WhiteLabel.Droid/Controls/ColoredClickableSpan.cs +++ b/Softeq.XToolkit.WhiteLabel.Droid/Controls/ColoredClickableSpan.cs @@ -30,7 +30,7 @@ public ColoredClickableSpan(Context context, Action clickAction, int? colorResou public override void OnClick(View widget) { - _clickAction?.Invoke(); + _clickAction.Invoke(); } public override void UpdateDrawState(TextPaint ds) diff --git a/Softeq.XToolkit.WhiteLabel.Droid/Controls/DroidContextMenuComponent.cs b/Softeq.XToolkit.WhiteLabel.Droid/Controls/DroidContextMenuComponent.cs index 11097df5f..9d030761a 100644 --- a/Softeq.XToolkit.WhiteLabel.Droid/Controls/DroidContextMenuComponent.cs +++ b/Softeq.XToolkit.WhiteLabel.Droid/Controls/DroidContextMenuComponent.cs @@ -32,7 +32,7 @@ public PopupMenu BuildMenu(Context context, View anchorView) return popup; } - private void Popup_MenuItemClick(object sender, PopupMenu.MenuItemClickEventArgs e) + private void Popup_MenuItemClick(object? sender, PopupMenu.MenuItemClickEventArgs e) { ExecuteCommand(e.Item.ItemId, null); } diff --git a/Softeq.XToolkit.WhiteLabel.Droid/Controls/NavigationBarView.cs b/Softeq.XToolkit.WhiteLabel.Droid/Controls/NavigationBarView.cs index 5d4690465..3c3b21080 100644 --- a/Softeq.XToolkit.WhiteLabel.Droid/Controls/NavigationBarView.cs +++ b/Softeq.XToolkit.WhiteLabel.Droid/Controls/NavigationBarView.cs @@ -49,7 +49,7 @@ public NavigationBarView(IntPtr handle, JniHandleOwnership owner) public ImageButton RightImageButton => _rightButton; - public void SetLeftButton(int resourceId, ICommand command, int? color = null) + public void SetLeftButton(int resourceId, ICommand? command, int? color = null) { _leftButton.SetImageResource(resourceId); _leftButton.Visibility = ViewStates.Visible; @@ -65,7 +65,7 @@ public void SetLeftButton(int resourceId, ICommand command, int? color = null) } } - public void SetRightButton(int resourceId, ICommand command) + public void SetRightButton(int resourceId, ICommand? command) { _rightButton.SetImageResource(resourceId); _rightButton.Visibility = ViewStates.Visible; @@ -76,7 +76,7 @@ public void SetRightButton(int resourceId, ICommand command) } } - public void SetRightButton(Drawable drawable, ICommand command) + public void SetRightButton(Drawable drawable, ICommand? command) { _rightButton.SetImageDrawable(drawable); _rightButton.Visibility = ViewStates.Visible; @@ -87,7 +87,7 @@ public void SetRightButton(Drawable drawable, ICommand command) } } - public void SetRightButton(string label, ICommand command) + public void SetRightButton(string label, ICommand? command) { RightTextButton.Text = label; RightTextButton.Visibility = ViewStates.Visible; @@ -98,7 +98,7 @@ public void SetRightButton(string label, ICommand command) } } - public void SetCenterImage(int resourceId, ICommand command) + public void SetCenterImage(int resourceId, ICommand? command) { _centerImageView.SetImageResource(resourceId); _centerImageView.Visibility = ViewStates.Visible; @@ -117,7 +117,7 @@ public void SetTitle(string text) public void SetBackground(int resourceId) { - var view = FindViewById(Resource.Id.control_navigation_bar_container); + var view = FindViewById(Resource.Id.control_navigation_bar_container)!; view.SetBackgroundResource(resourceId); } @@ -125,19 +125,19 @@ private void Init(Context context) { Inflate(context, Resource.Layout.control_navigation_bar, this); - _leftButton = FindViewById(Resource.Id.control_navigation_bar_left_button); + _leftButton = FindViewById(Resource.Id.control_navigation_bar_left_button)!; _leftButton.Visibility = ViewStates.Gone; - _rightButton = FindViewById(Resource.Id.control_navigation_bar_right_button); + _rightButton = FindViewById(Resource.Id.control_navigation_bar_right_button)!; _rightButton.Visibility = ViewStates.Gone; - RightTextButton = FindViewById - - - + + @@ -52,9 +58,11 @@ + + diff --git a/samples/Playground/Playground.iOS/ViewControllers/Components/PermissionsPageViewController.cs b/samples/Playground/Playground.iOS/ViewControllers/Components/PermissionsPageViewController.cs index a6066c89a..470c4e9fd 100644 --- a/samples/Playground/Playground.iOS/ViewControllers/Components/PermissionsPageViewController.cs +++ b/samples/Playground/Playground.iOS/ViewControllers/Components/PermissionsPageViewController.cs @@ -26,6 +26,8 @@ public override void ViewDidLoad() Camera.SetCommand(ViewModel.Camera.RequestPermissionCommand); LocationInUse.SetCommand(ViewModel.LocationInUse.RequestPermissionCommand); LocationAlways.SetCommand(ViewModel.LocationAlways.RequestPermissionCommand); + Notifications.SetCommand(ViewModel.Notifications.RequestPermissionCommand); + Bluetooth.SetCommand(ViewModel.Bluetooth.RequestPermissionCommand); } protected override void DoAttachBindings() @@ -38,6 +40,8 @@ protected override void DoAttachBindings() this.Bind(() => ViewModel.Camera.IsGranted, () => Camera.BackgroundColor, converter); this.Bind(() => ViewModel.LocationInUse.IsGranted, () => LocationInUse.BackgroundColor, converter); this.Bind(() => ViewModel.LocationAlways.IsGranted, () => LocationAlways.BackgroundColor, converter); + this.Bind(() => ViewModel.Notifications.IsGranted, () => Notifications.BackgroundColor, converter); + this.Bind(() => ViewModel.Bluetooth.IsGranted, () => Bluetooth.BackgroundColor, converter); } private class ColorConverter : IConverter diff --git a/samples/Playground/Playground.iOS/ViewControllers/Components/PermissionsPageViewController.designer.cs b/samples/Playground/Playground.iOS/ViewControllers/Components/PermissionsPageViewController.designer.cs index fca5d0536..0b2d50dd4 100644 --- a/samples/Playground/Playground.iOS/ViewControllers/Components/PermissionsPageViewController.designer.cs +++ b/samples/Playground/Playground.iOS/ViewControllers/Components/PermissionsPageViewController.designer.cs @@ -1,55 +1,70 @@ // WARNING // -// This file has been generated automatically by Visual Studio from the outlets and -// actions declared in your storyboard file. -// Manual changes to this file will not be maintained. +// This file has been generated automatically by Visual Studio to store outlets and +// actions made in the UI designer. If it is removed, they will be lost. +// Manual changes to this file may not be handled correctly. // - -using System.CodeDom.Compiler; using Foundation; +using System.CodeDom.Compiler; namespace Playground.iOS.ViewControllers.Components { - [Register ("PermissionsPageViewController")] - partial class PermissionsPageViewController - { - [Outlet] - [GeneratedCode ("iOS Designer", "1.0")] - UIKit.UIButton Camera { get; set; } - - [Outlet] - [GeneratedCode ("iOS Designer", "1.0")] - UIKit.UIButton LocationAlways { get; set; } - - [Outlet] - [GeneratedCode ("iOS Designer", "1.0")] - UIKit.UIButton LocationInUse { get; set; } - - [Outlet] - [GeneratedCode ("iOS Designer", "1.0")] - UIKit.UIButton Photos { get; set; } - - void ReleaseDesignerOutlets () - { - if (Camera != null) { - Camera.Dispose (); - Camera = null; - } - - if (LocationAlways != null) { - LocationAlways.Dispose (); - LocationAlways = null; - } - - if (LocationInUse != null) { - LocationInUse.Dispose (); - LocationInUse = null; - } - - if (Photos != null) { - Photos.Dispose (); - Photos = null; - } - } - } -} \ No newline at end of file + [Register ("PermissionsPageViewController")] + partial class PermissionsPageViewController + { + [Outlet] + UIKit.UIButton Bluetooth { get; set; } + + [Outlet] + [GeneratedCode ("iOS Designer", "1.0")] + UIKit.UIButton Camera { get; set; } + + [Outlet] + [GeneratedCode ("iOS Designer", "1.0")] + UIKit.UIButton LocationAlways { get; set; } + + [Outlet] + [GeneratedCode ("iOS Designer", "1.0")] + UIKit.UIButton LocationInUse { get; set; } + + [Outlet] + UIKit.UIButton Notifications { get; set; } + + [Outlet] + [GeneratedCode ("iOS Designer", "1.0")] + UIKit.UIButton Photos { get; set; } + + void ReleaseDesignerOutlets () + { + if (Camera != null) { + Camera.Dispose (); + Camera = null; + } + + if (LocationAlways != null) { + LocationAlways.Dispose (); + LocationAlways = null; + } + + if (LocationInUse != null) { + LocationInUse.Dispose (); + LocationInUse = null; + } + + if (Photos != null) { + Photos.Dispose (); + Photos = null; + } + + if (Notifications != null) { + Notifications.Dispose (); + Notifications = null; + } + + if (Bluetooth != null) { + Bluetooth.Dispose (); + Bluetooth = null; + } + } + } +} diff --git a/samples/Playground/Playground/CustomBootstrapper.cs b/samples/Playground/Playground/CustomBootstrapper.cs index e8cfe399f..1e2631744 100644 --- a/samples/Playground/Playground/CustomBootstrapper.cs +++ b/samples/Playground/Playground/CustomBootstrapper.cs @@ -3,6 +3,7 @@ using Playground.Services; using Playground.ViewModels.Frames; +using Softeq.XToolkit.Permissions; using Softeq.XToolkit.WhiteLabel.Bootstrapper.Abstract; using Softeq.XToolkit.WhiteLabel.Interfaces; using Softeq.XToolkit.WhiteLabel.Services; @@ -17,6 +18,7 @@ public static void Configure(IContainerBuilder builder) // Playground builder.Singleton(); + builder.Singleton(); builder.PerDependency(); } diff --git a/samples/Playground/Playground/ViewModels/Components/PermissionViewModel.cs b/samples/Playground/Playground/ViewModels/Components/PermissionViewModel.cs index 9e10f9888..dacdd4a72 100644 --- a/samples/Playground/Playground/ViewModels/Components/PermissionViewModel.cs +++ b/samples/Playground/Playground/ViewModels/Components/PermissionViewModel.cs @@ -51,4 +51,4 @@ private async Task RequestPermission() await CheckStatus(); } } -} +} \ No newline at end of file diff --git a/samples/Playground/Playground/ViewModels/Components/PermissionsPageViewModel.cs b/samples/Playground/Playground/ViewModels/Components/PermissionsPageViewModel.cs index 7c92040df..8f2825854 100644 --- a/samples/Playground/Playground/ViewModels/Components/PermissionsPageViewModel.cs +++ b/samples/Playground/Playground/ViewModels/Components/PermissionsPageViewModel.cs @@ -5,9 +5,11 @@ using Softeq.XToolkit.Common.Extensions; using Softeq.XToolkit.Permissions; using Softeq.XToolkit.WhiteLabel.Mvvm; +using BluetoothPermission = Softeq.XToolkit.Permissions.Permissions.Bluetooth; using CameraPermission = Microsoft.Maui.ApplicationModel.Permissions.Camera; using LocationAlwaysPermission = Microsoft.Maui.ApplicationModel.Permissions.LocationAlways; using LocationWhenInUsePermission = Microsoft.Maui.ApplicationModel.Permissions.LocationWhenInUse; +using NotificationsPermission = Softeq.XToolkit.Permissions.Permissions.Notifications; using PhotosPermission = Microsoft.Maui.ApplicationModel.Permissions.Photos; using StoragePermission = Microsoft.Maui.ApplicationModel.Permissions.StorageWrite; @@ -22,6 +24,8 @@ public PermissionsPageViewModel(IPermissionsManager permissionsManager) Storage = new PermissionViewModel(permissionsManager); LocationInUse = new PermissionViewModel(permissionsManager); LocationAlways = new PermissionViewModel(permissionsManager); + Notifications = new PermissionViewModel(permissionsManager); + Bluetooth = new PermissionViewModel(permissionsManager); } public PermissionViewModel Photos { get; } @@ -34,6 +38,10 @@ public PermissionsPageViewModel(IPermissionsManager permissionsManager) public PermissionViewModel LocationAlways { get; } + public PermissionViewModel Notifications { get; } + + public PermissionViewModel Bluetooth { get; } + public override void OnAppearing() { base.OnAppearing(); @@ -52,6 +60,10 @@ private async Task CheckAll() await LocationInUse.CheckStatus(); await LocationAlways.CheckStatus(); + + await Notifications.CheckStatus(); + + await Bluetooth.CheckStatus(); } } -} +} \ No newline at end of file From 174a957f0cbba6ab1afe8b5941be01ff82f8a750 Mon Sep 17 00:00:00 2001 From: Yauheni Pakala Date: Thu, 9 Mar 2023 14:06:55 +0200 Subject: [PATCH 08/26] Add System.Text.Json serializer and tests (#521) --- .../Interfaces/IJsonSerializer.cs | 6 +- .../DefaultJsonSerializerTests.cs | 106 ++++++++++ .../JsonDeserializationDataProvider.cs | 155 +++++++++++++++ .../JsonSerializationDataProvider.cs | 183 ++++++++++++++++++ .../JsonSerializationTestsStub.cs | 53 +++++ .../NewtonsoftJsonSerializerTests.cs | 106 ++++++++++ .../Services/DefaultJsonSerializer.cs | 115 +++++++++++ .../Services/NewtonsoftJsonSerializer.cs | 9 +- .../Softeq.XToolkit.WhiteLabel.csproj | 1 + 9 files changed, 724 insertions(+), 10 deletions(-) create mode 100644 Softeq.XToolkit.WhiteLabel.Tests/Services/DefaultJsonSerializerTests/DefaultJsonSerializerTests.cs create mode 100644 Softeq.XToolkit.WhiteLabel.Tests/Services/JsonSerializers/JsonDeserializationDataProvider.cs create mode 100644 Softeq.XToolkit.WhiteLabel.Tests/Services/JsonSerializers/JsonSerializationDataProvider.cs create mode 100644 Softeq.XToolkit.WhiteLabel.Tests/Services/JsonSerializers/JsonSerializationTestsStub.cs create mode 100644 Softeq.XToolkit.WhiteLabel.Tests/Services/NewtonsoftJsonSerializerTests/NewtonsoftJsonSerializerTests.cs create mode 100644 Softeq.XToolkit.WhiteLabel/Services/DefaultJsonSerializer.cs diff --git a/Softeq.XToolkit.Common/Interfaces/IJsonSerializer.cs b/Softeq.XToolkit.Common/Interfaces/IJsonSerializer.cs index 716e2a406..2b8a68e04 100644 --- a/Softeq.XToolkit.Common/Interfaces/IJsonSerializer.cs +++ b/Softeq.XToolkit.Common/Interfaces/IJsonSerializer.cs @@ -25,8 +25,7 @@ public interface IJsonSerializer /// The Stream that contains the JSON structure to deserialize. /// The deserialized object from the JSON string. /// Object type. - [return:MaybeNull] - TResult Deserialize(string value); + TResult? Deserialize(string value); /// /// Asynchronously serializes the specified object to a JSON string. @@ -48,7 +47,6 @@ public interface IJsonSerializer /// The value of the TResult parameter contains the deserialized object from the JSON string. /// /// Object type. - [return:MaybeNull] - Task DeserializeAsync(Stream stream); + Task DeserializeAsync(Stream stream); } } diff --git a/Softeq.XToolkit.WhiteLabel.Tests/Services/DefaultJsonSerializerTests/DefaultJsonSerializerTests.cs b/Softeq.XToolkit.WhiteLabel.Tests/Services/DefaultJsonSerializerTests/DefaultJsonSerializerTests.cs new file mode 100644 index 000000000..eeefb1487 --- /dev/null +++ b/Softeq.XToolkit.WhiteLabel.Tests/Services/DefaultJsonSerializerTests/DefaultJsonSerializerTests.cs @@ -0,0 +1,106 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using System.IO; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using Softeq.XToolkit.Common.Interfaces; +using Softeq.XToolkit.WhiteLabel.Services; +using Softeq.XToolkit.WhiteLabel.Tests.Services.JsonSerializers; +using Xunit; + +namespace Softeq.XToolkit.WhiteLabel.Tests.Services.DefaultJsonSerializerTests; + +public class DefaultJsonSerializerTests +{ + private readonly DefaultJsonSerializer _serializer; + + public DefaultJsonSerializerTests() + { + _serializer = new DefaultJsonSerializer(); + } + + [Fact] + public void Ctor_Default_ReturnsIJsonSerializer() + { + Assert.IsAssignableFrom(_serializer); + } + + [Fact] + public void DefaultOptions_StaticGet_ReturnsJsonSerializerOptions() + { + Assert.IsType(DefaultJsonSerializer.DefaultOptions); + } + + [Theory] + [MemberData(nameof(JsonSerializationDataProvider.Data), MemberType = typeof(JsonSerializationDataProvider))] + public void Serialize_WithValidData_ExpectedResult(object data, string expected) + { + var result = _serializer.Serialize(data); + + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(JsonDeserializationDataProvider.Data), MemberType = typeof(JsonDeserializationDataProvider))] + public void Deserialize_WithValidData_ExpectedResult(string data, T expected) + { + var result = _serializer.Deserialize(data); + + Assert.Equivalent(expected, result); + } + + [Fact] + public void Deserialize_Null_ThrowsArgumentNullException() + { + Assert.Throws(() => + { + _serializer.Deserialize(null!); + }); + } + + [Theory] + [MemberData(nameof(JsonDeserializationDataProvider.InvalidData), MemberType = typeof(JsonDeserializationDataProvider))] +#pragma warning disable xUnit1026 + public void Deserialize_WithInvalidData_ThrowsException(string data, T expected) +#pragma warning restore xUnit1026 + { + Assert.Throws(() => + { + _serializer.Deserialize(data); + }); + } + + [Theory] + [MemberData(nameof(JsonSerializationDataProvider.Data), MemberType = typeof(JsonSerializationDataProvider))] + public async Task SerializeAsync_WithValidData_ExpectedResult(object data, string expected) + { + using var stream = new MemoryStream(); + + await _serializer.SerializeAsync(data, stream); + + var result = Encoding.UTF8.GetString(stream.ToArray()); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(JsonDeserializationDataProvider.Data), MemberType = typeof(JsonDeserializationDataProvider))] + public async Task DeserializeAsync_WithValidData_ExpectedResult(string data, T expected) + { + var bytes = Encoding.UTF8.GetBytes(data); + using var stream = new MemoryStream(bytes); + + var result = await _serializer.DeserializeAsync(stream)!; + + Assert.Equivalent(expected, result); + } + + [Fact] + public async Task DeserializeAsync_Null_ThrowsArgumentNullException() + { + await Assert.ThrowsAsync(() => + _serializer.DeserializeAsync(null!)); + } +} diff --git a/Softeq.XToolkit.WhiteLabel.Tests/Services/JsonSerializers/JsonDeserializationDataProvider.cs b/Softeq.XToolkit.WhiteLabel.Tests/Services/JsonSerializers/JsonDeserializationDataProvider.cs new file mode 100644 index 000000000..b760603d4 --- /dev/null +++ b/Softeq.XToolkit.WhiteLabel.Tests/Services/JsonSerializers/JsonDeserializationDataProvider.cs @@ -0,0 +1,155 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using System.Collections.Generic; +using Newtonsoft.Json.Linq; + +namespace Softeq.XToolkit.WhiteLabel.Tests.Services.JsonSerializers; + +#pragma warning disable SA1122 + +internal static class JsonDeserializationDataProvider +{ + public static IEnumerable Data + { + get + { + yield return new object[] + { + "{\"name\":\"Test\",\"age\":1,\"date\":\"2022-09-18T00:00:00Z\",\"time\":\"10:25:38\"}", + new JsonSerializationTestsStub + { + Name = "Test", + Age = 1, + Date = new DateTime(2022, 09, 18), + Time = new TimeSpan(10, 25, 38) + } + }; + yield return new object[] + { + "{\"age\":\"2\"}", + new JsonSerializationTestsStub + { + Age = 2 + } + }; + yield return new object[] + { + "{\"name\":\"\"}", + new JsonSerializationTestsStub + { + Name = string.Empty + } + }; + yield return new object[] + { + "{\"first_name\":\"First\",\"last_name\":\"Last\"}", + new JsonSerializationTestsStub + { + FirstName = "First", + last_name = "Last" + } + }; + yield return new object[] + { + "{}", + new JObject() + }; + yield return new object[] + { + "", + null + }; + yield return new object[] + { + "1", + 1 + }; + yield return new object[] + { + "2.5", + 2.5 + }; + yield return new object[] + { + "\"123\"", + "123" + }; + yield return new object[] + { + "\"TestString\"", + "TestString" + }; + yield return new object[] + { + "{\"first\":\"TestString\"}", + new Dictionary + { + { "first", "TestString" } + } + }; + yield return new object[] + { + "\"2022-09-18T00:00:00Z\"", + new DateTime(2022, 09, 18) + }; + yield return new object[] + { + "\"fwAAAQ==\"", + new byte[] { 127, 0, 0, 1 }, + }; + yield return new object[] + { + "[127,0,0,1]", + new List { 127, 0, 0, 1 } + }; + yield return new object[] + { + "{\"asString\":\"StringValue\",\"asNumber\":123}", + new EnumJsonSerializationTestsStub + { + AsString = JsonSerializationTestsEnum.StringValue, + AsNumber = JsonSerializationTestsEnum.NumberValue + }, + }; + yield return new object[] + { + 123, + JsonSerializationTestsEnum.NumberValue + }; + yield return new object[] + { + 1, + JsonSerializationTestsEnum.StringValue + }; + } + } + + public static IEnumerable InvalidData + { + get + { + yield return new object[] + { + "2022-10-18T00:00:00Z", + new DateTime(2022, 10, 18) + }; + yield return new object[] + { + "TestString", + "ignore" + }; + yield return new object[] + { + "{\"a:123/* comment*/}", + "ignore" + }; + yield return new object[] + { + "{\"a:123 // comment}", + "ignore" + }; + } + } +} diff --git a/Softeq.XToolkit.WhiteLabel.Tests/Services/JsonSerializers/JsonSerializationDataProvider.cs b/Softeq.XToolkit.WhiteLabel.Tests/Services/JsonSerializers/JsonSerializationDataProvider.cs new file mode 100644 index 000000000..ba273c7d8 --- /dev/null +++ b/Softeq.XToolkit.WhiteLabel.Tests/Services/JsonSerializers/JsonSerializationDataProvider.cs @@ -0,0 +1,183 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using System.Collections.Generic; + +#pragma warning disable SA1122 + +namespace Softeq.XToolkit.WhiteLabel.Tests.Services.JsonSerializers; + +internal static class JsonSerializationDataProvider +{ + public static IEnumerable Data + { + get + { + yield return new object[] + { + new JsonSerializationTestsStub + { + Name = "Test", + Age = 1, + Date = new DateTime(2022, 09, 18), + Time = new TimeSpan(10, 25, 38) + }, + "{\"name\":\"Test\",\"age\":1,\"date\":\"2022-09-18T00:00:00Z\",\"time\":\"10:25:38\"}" + }; + yield return new object[] + { + new JsonSerializationTestsStub + { + Name = string.Empty + }, + "{\"name\":\"\"}" + }; + yield return new object[] + { + new JsonSerializationTestsStub + { + Date = TimeZoneInfo.ConvertTimeFromUtc( + new DateTime(2023, 08, 22, 20, 15, 00), + TimeZoneInfo.FindSystemTimeZoneById("Europe/Minsk")), // UTC+3 + }, + "{\"date\":\"2023-08-22T23:15:00Z\"}" + }; + yield return new object[] + { + new JsonSerializationTestsStub + { + Age = null, + Date = null, + Time = null, + }, + "{}" + }; + yield return new object[] + { + new JsonSerializationTestsStub + { + Name = "Test" + }, + "{\"name\":\"Test\"}" + }; + yield return new object[] + { + new JsonSerializationTestsStub + { + FirstName = "First", + last_name = "Last" + }, + "{\"first_name\":\"First\",\"last_name\":\"Last\"}" + }; + yield return new object[] + { + new JsonSerializationTestsStub + { + IgnoreData = "IgnoreData" + }, + "{}" + }; + yield return new object[] + { + new JsonSerializationTestsStub + { + Date = TimeZoneInfo.ConvertTimeFromUtc( + new DateTime(2022, 10, 16, 10, 34, 47), + TimeZoneInfo.FindSystemTimeZoneById("Europe/Minsk")) // UTC+3 + }, + "{\"date\":\"2022-10-16T13:34:47Z\"}" + }; + yield return new object[] + { + 1, + "1" + }; + yield return new object[] + { + 2.5, + "2.5" + }; + yield return new object[] + { + "123", + "\"123\"" + }; + yield return new object[] + { + "TestString", + "\"TestString\"" + }; + yield return new object[] + { + new Dictionary + { + { "first", "TestString" }, + { "Second", 123 }, + }, + "{\"first\":\"TestString\",\"Second\":123}" + }; + yield return new object[] + { + new DateTime(2022, 09, 18), + "\"2022-09-18T00:00:00Z\"" + }; + yield return new object[] + { + new byte[] { 127, 0, 0, 1 }, + "\"fwAAAQ==\"" + }; + yield return new object[] + { + new List { 127, 0, 0, 1 }, + "[127,0,0,1]" + }; + yield return new object[] + { + new EnumJsonSerializationTestsStub + { + AsString = JsonSerializationTestsEnum.StringValue, + AsNumber = JsonSerializationTestsEnum.NumberValue + }, + "{\"asString\":\"StringValue\",\"asNumber\":123}" + }; + yield return new object[] + { + JsonSerializationTestsEnum.NumberValue, + 123 + }; + yield return new object[] + { + JsonSerializationTestsEnum.StringValue, + 1 + }; + yield return new object[] + { + null, + "null" + }; + yield return new object[] + { + "", + "\"\"" + }; + yield return new object[] + { + new { total = "123" }, + "{\"total\":\"123\"}" + }; + + var child = new JsonSerializationTestsStubRef { Id = "child" }; + yield return new object[] + { + new JsonSerializationTestsStubRef + { + Id = "root", + Child = child, + Children = new List { child } + }, + "{\"id\":\"root\",\"child\":{\"id\":\"child\"},\"children\":[{\"id\":\"child\"}]}" + }; + } + } +} diff --git a/Softeq.XToolkit.WhiteLabel.Tests/Services/JsonSerializers/JsonSerializationTestsStub.cs b/Softeq.XToolkit.WhiteLabel.Tests/Services/JsonSerializers/JsonSerializationTestsStub.cs new file mode 100644 index 000000000..345db7b3a --- /dev/null +++ b/Softeq.XToolkit.WhiteLabel.Tests/Services/JsonSerializers/JsonSerializationTestsStub.cs @@ -0,0 +1,53 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace Softeq.XToolkit.WhiteLabel.Tests.Services.JsonSerializers; + +internal enum JsonSerializationTestsEnum +{ + Undefined, + StringValue, + NumberValue = 123 +} + +internal class EnumJsonSerializationTestsStub +{ + [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] + [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))] + public JsonSerializationTestsEnum AsString { get; set; } + + public JsonSerializationTestsEnum AsNumber { get; set; } +} + +[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global", Justification = "Reviewed.")] +internal class JsonSerializationTestsStub +{ + public string Name { get; set; } + public int? Age { get; set; } + public DateTime? Date { get; set; } + public TimeSpan? Time { get; set; } + + [Newtonsoft.Json.JsonProperty("first_name")] + [System.Text.Json.Serialization.JsonPropertyName("first_name")] + public string FirstName { get; set; } + +#pragma warning disable SA1300 + public string last_name { get; set; } +#pragma warning restore SA1300 + + [Newtonsoft.Json.JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + public string IgnoreData { get; set; } +} + +// circular references +internal class JsonSerializationTestsStubRef +{ + public string Id { get; set; } + public JsonSerializationTestsStubRef Child { get; set; } + public List Children { get; set; } +} diff --git a/Softeq.XToolkit.WhiteLabel.Tests/Services/NewtonsoftJsonSerializerTests/NewtonsoftJsonSerializerTests.cs b/Softeq.XToolkit.WhiteLabel.Tests/Services/NewtonsoftJsonSerializerTests/NewtonsoftJsonSerializerTests.cs new file mode 100644 index 000000000..cf907803e --- /dev/null +++ b/Softeq.XToolkit.WhiteLabel.Tests/Services/NewtonsoftJsonSerializerTests/NewtonsoftJsonSerializerTests.cs @@ -0,0 +1,106 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Softeq.XToolkit.Common.Interfaces; +using Softeq.XToolkit.WhiteLabel.Services; +using Softeq.XToolkit.WhiteLabel.Tests.Services.JsonSerializers; +using Xunit; + +namespace Softeq.XToolkit.WhiteLabel.Tests.Services.NewtonsoftJsonSerializerTests; + +public class NewtonsoftJsonSerializerTests +{ + private readonly NewtonsoftJsonSerializer _serializer; + + public NewtonsoftJsonSerializerTests() + { + _serializer = new NewtonsoftJsonSerializer(); + } + + [Fact] + public void Ctor_Default_ReturnsIJsonSerializer() + { + Assert.IsAssignableFrom(_serializer); + } + + [Fact] + public void DefaultSettings_StaticGet_ReturnsJsonSerializerSettings() + { + Assert.IsType(NewtonsoftJsonSerializer.DefaultSettings); + } + + [Theory] + [MemberData(nameof(JsonSerializationDataProvider.Data), MemberType = typeof(JsonSerializationDataProvider))] + public void Serialize_WithValidData_ExpectedResult(object data, string expected) + { + var result = _serializer.Serialize(data); + + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(JsonDeserializationDataProvider.Data), MemberType = typeof(JsonDeserializationDataProvider))] + public void Deserialize_WithValidData_ExpectedResult(string data, T expected) + { + var result = _serializer.Deserialize(data); + + Assert.Equivalent(expected, result); + } + + [Theory] + [MemberData(nameof(JsonDeserializationDataProvider.InvalidData), MemberType = typeof(JsonDeserializationDataProvider))] +#pragma warning disable xUnit1026 + public void Deserialize_WithInvalidData_ThrowsException(string data, T expected) +#pragma warning restore xUnit1026 + { + Assert.Throws(() => + { + _serializer.Deserialize(data); + }); + } + + [Fact] + public void Deserialize_Null_ThrowsArgumentNullException() + { + Assert.Throws(() => + { + _serializer.Deserialize(null!); + }); + } + + [Theory] + [MemberData(nameof(JsonSerializationDataProvider.Data), MemberType = typeof(JsonSerializationDataProvider))] + public async Task SerializeAsync_WithValidData_ExpectedResult(object data, string expected) + { + using var stream = new MemoryStream(); + + await _serializer.SerializeAsync(data, stream); + + var result = Encoding.UTF8.GetString(stream.ToArray()); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(JsonDeserializationDataProvider.Data), MemberType = typeof(JsonDeserializationDataProvider))] + public async Task DeserializeAsync_WithValidData_ExpectedResult(string data, T expected) + { + var bytes = Encoding.UTF8.GetBytes(data); + using var stream = new MemoryStream(bytes); + + var result = await _serializer.DeserializeAsync(stream)!; + + Assert.Equivalent(expected, result); + } + + [Fact] + public async Task DeserializeAsync_Null_ThrowsArgumentNullException() + { + await Assert.ThrowsAsync(() => + _serializer.DeserializeAsync(null!)); + } +} diff --git a/Softeq.XToolkit.WhiteLabel/Services/DefaultJsonSerializer.cs b/Softeq.XToolkit.WhiteLabel/Services/DefaultJsonSerializer.cs new file mode 100644 index 000000000..7ff3ac023 --- /dev/null +++ b/Softeq.XToolkit.WhiteLabel/Services/DefaultJsonSerializer.cs @@ -0,0 +1,115 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using System.Globalization; +using System.IO; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading.Tasks; +using Softeq.XToolkit.Common.Interfaces; +using JsonSerializer = System.Text.Json.JsonSerializer; + +namespace Softeq.XToolkit.WhiteLabel.Services; + +/// +/// A implementing using the System.Text.Json APIs. +/// +public class DefaultJsonSerializer : IJsonSerializer +{ + private readonly JsonSerializerOptions? _options; + + /// + /// Initializes a new instance of the class with the specified parameters. + /// + /// The serialization settings to use for the current instance. + public DefaultJsonSerializer(JsonSerializerOptions? options = null) + { + _options = options ?? DefaultOptions; + } + + /// + /// Gets default serialization options. + /// + public static JsonSerializerOptions DefaultOptions { get; } = CreateDefaultOptions(); + + private static JsonSerializerOptions CreateDefaultOptions() => new() + { + Converters = + { + new DateTimeUtcJsonConverter() + }, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + NumberHandling = JsonNumberHandling.AllowReadingFromString, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = false + }; + + /// + public string Serialize(object value) + { + return JsonSerializer.Serialize(value, _options); + } + + /// + public TResult? Deserialize(string? value) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (value.Length == 0) + { + return default; + } + + return JsonSerializer.Deserialize(value, _options); + } + + /// + public Task SerializeAsync(object value, Stream stream) + { + return JsonSerializer.SerializeAsync(stream, value, _options); + } + + /// + public async Task DeserializeAsync(Stream? stream) + { + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (stream.Length == 0) + { + return default; + } + + return await JsonSerializer.DeserializeAsync(stream, _options).AsTask().ConfigureAwait(false); + } +} + +/// +/// Custom System.Json converter that implements Newtonsoft.Json DateTime settings: +/// (DateFormatHandling.IsoDateFormat, DateTimeZoneHandling.Utc). +/// +public sealed class DateTimeUtcJsonConverter : JsonConverter +{ + // Source: https://github.com/JamesNK/Newtonsoft.Json/blob/0a2e291c0d9c0c7675d445703e51750363a549ef/Src/Newtonsoft.Json/Utilities/DateTimeUtils.cs#L85-L86 + private const string IsoDateFormat = "yyyy-MM-ddTHH:mm:ss.FFFFFFFZ"; + + /// + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var dateTime = reader.GetDateTime(); + return dateTime.ToUniversalTime(); + } + + /// + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + var dateTimeString = value.ToString(IsoDateFormat, CultureInfo.InvariantCulture); + writer.WriteStringValue(dateTimeString); + } +} diff --git a/Softeq.XToolkit.WhiteLabel/Services/NewtonsoftJsonSerializer.cs b/Softeq.XToolkit.WhiteLabel/Services/NewtonsoftJsonSerializer.cs index 530200b51..bbc76890b 100644 --- a/Softeq.XToolkit.WhiteLabel/Services/NewtonsoftJsonSerializer.cs +++ b/Softeq.XToolkit.WhiteLabel/Services/NewtonsoftJsonSerializer.cs @@ -1,7 +1,6 @@ // Developed by Softeq Development Corporation // http://www.softeq.com -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Threading.Tasks; using Newtonsoft.Json; @@ -49,8 +48,7 @@ public string Serialize(object value) } /// - [return:MaybeNull] - public T Deserialize(string value) + public T? Deserialize(string value) { return JsonConvert.DeserializeObject(value, _settings); } @@ -70,10 +68,9 @@ public Task SerializeAsync(object value, Stream stream) } /// - [return:MaybeNull] - public Task DeserializeAsync(Stream stream) + public Task DeserializeAsync(Stream stream) { - return Task.Run(() => + return Task.Run(() => { using (var streamReader = new StreamReader(stream)) using (var jsonTextReader = new JsonTextReader(streamReader)) diff --git a/Softeq.XToolkit.WhiteLabel/Softeq.XToolkit.WhiteLabel.csproj b/Softeq.XToolkit.WhiteLabel/Softeq.XToolkit.WhiteLabel.csproj index 4891d1148..21491c8fd 100644 --- a/Softeq.XToolkit.WhiteLabel/Softeq.XToolkit.WhiteLabel.csproj +++ b/Softeq.XToolkit.WhiteLabel/Softeq.XToolkit.WhiteLabel.csproj @@ -12,6 +12,7 @@ + From bce6fc34f6c670d7bb01a442c36984485153754e Mon Sep 17 00:00:00 2001 From: Yauheni Pakala Date: Fri, 24 Mar 2023 13:51:07 +0200 Subject: [PATCH 09/26] Show warning when ConfigureAwait should be used (#522) --- .../Commands/AsyncCommandBase.cs | 2 +- .../Files/BaseFileProvider.cs | 6 +- .../Threading/TaskDeferral.cs | 4 +- Softeq.XToolkit.Common/Timers/Timer.cs | 2 +- .../PermissionsService.cs | 6 +- .../GlobalSuppressions.cs | 5 + XToolkit.ruleset | 202 +++++++++--------- .../Playground.Droid/GlobalSuppressions.cs | 9 + .../Playground.iOS/GlobalSuppressions.cs | 9 + .../Playground/GlobalSuppressions.cs | 9 + 10 files changed, 142 insertions(+), 112 deletions(-) create mode 100644 samples/Playground/Playground.Droid/GlobalSuppressions.cs create mode 100644 samples/Playground/Playground.iOS/GlobalSuppressions.cs create mode 100644 samples/Playground/Playground/GlobalSuppressions.cs diff --git a/Softeq.XToolkit.Common/Commands/AsyncCommandBase.cs b/Softeq.XToolkit.Common/Commands/AsyncCommandBase.cs index 18231677a..00deaba7d 100644 --- a/Softeq.XToolkit.Common/Commands/AsyncCommandBase.cs +++ b/Softeq.XToolkit.Common/Commands/AsyncCommandBase.cs @@ -47,7 +47,7 @@ protected async Task DoExecuteAsync(Func executionProvider) try { - await executionProvider.Invoke(); + await executionProvider.Invoke().ConfigureAwait(false); } finally { diff --git a/Softeq.XToolkit.Common/Files/BaseFileProvider.cs b/Softeq.XToolkit.Common/Files/BaseFileProvider.cs index 8e0ea5b56..62a2c3721 100644 --- a/Softeq.XToolkit.Common/Files/BaseFileProvider.cs +++ b/Softeq.XToolkit.Common/Files/BaseFileProvider.cs @@ -32,7 +32,7 @@ public Task ClearFolderAsync(string path) { var tasks = _fileSystem.Directory.GetFiles(path). Select(async x => await RemoveFileAsync(x).ConfigureAwait(false)); - await Task.WhenAll(tasks); + await Task.WhenAll(tasks).ConfigureAwait(false); }); } @@ -69,10 +69,10 @@ public Task CopyFileAsync(string sourcePath, string destinationPath, bool overwr public async Task WriteFileAsync(string path, Stream stream) { path = GetAbsolutePath(path); - using (var outputStream = await OpenFileForWriteAsync(path)) + using (var outputStream = await OpenFileForWriteAsync(path).ConfigureAwait(false)) { stream.Position = 0; - await stream.CopyToAsync(outputStream); + await stream.CopyToAsync(outputStream).ConfigureAwait(false); stream.Dispose(); } diff --git a/Softeq.XToolkit.Common/Threading/TaskDeferral.cs b/Softeq.XToolkit.Common/Threading/TaskDeferral.cs index 72eb68655..eaf2fab24 100644 --- a/Softeq.XToolkit.Common/Threading/TaskDeferral.cs +++ b/Softeq.XToolkit.Common/Threading/TaskDeferral.cs @@ -41,7 +41,7 @@ public async Task DoWorkAsync(Func> taskFactory) if (_queue.IsEmpty) { _semaphoreSlim.Release(); - return await tcs.Task; + return await tcs.Task.ConfigureAwait(false); } var result = await ExecuteAsync(taskFactory).ConfigureAwait(false); @@ -54,7 +54,7 @@ public async Task DoWorkAsync(Func> taskFactory) _semaphoreSlim.Release(); - return await tcs.Task; + return await tcs.Task.ConfigureAwait(false); } private static async Task ExecuteAsync(Func> taskFactory) diff --git a/Softeq.XToolkit.Common/Timers/Timer.cs b/Softeq.XToolkit.Common/Timers/Timer.cs index 49dc78b92..18d060fd9 100644 --- a/Softeq.XToolkit.Common/Timers/Timer.cs +++ b/Softeq.XToolkit.Common/Timers/Timer.cs @@ -79,7 +79,7 @@ private async Task DoWork() { do { - await Task.Delay(_interval); + await Task.Delay(_interval).ConfigureAwait(false); if (IsActive && _taskFactory != null) { await _taskFactory().ConfigureAwait(false); diff --git a/Softeq.XToolkit.Permissions/PermissionsService.cs b/Softeq.XToolkit.Permissions/PermissionsService.cs index 460c64fb7..cf17adc9d 100644 --- a/Softeq.XToolkit.Permissions/PermissionsService.cs +++ b/Softeq.XToolkit.Permissions/PermissionsService.cs @@ -12,10 +12,10 @@ namespace Softeq.XToolkit.Permissions public class PermissionsService : IPermissionsService { /// - public async Task RequestPermissionsAsync() + public Task RequestPermissionsAsync() where T : BasePermission, new() { - return await MainThread.InvokeOnMainThreadAsync(async () => + return MainThread.InvokeOnMainThreadAsync(async () => { var result = await EssentialsPermissions.RequestAsync().ConfigureAwait(false); return result.ToPermissionStatus(); @@ -41,4 +41,4 @@ public void OpenSettings() return EssentialsPermissions.ShouldShowRationale(); } } -} \ No newline at end of file +} diff --git a/Softeq.XToolkit.WhiteLabel.Tests/GlobalSuppressions.cs b/Softeq.XToolkit.WhiteLabel.Tests/GlobalSuppressions.cs index ab8b5b25c..1c80e631d 100644 --- a/Softeq.XToolkit.WhiteLabel.Tests/GlobalSuppressions.cs +++ b/Softeq.XToolkit.WhiteLabel.Tests/GlobalSuppressions.cs @@ -12,3 +12,8 @@ "StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "Mixed types allowed for tests")] + +[assembly: SuppressMessage( + "Reliability", + "CA2007:Consider calling ConfigureAwait on the awaited task", + Justification = "Allowed for tests")] diff --git a/XToolkit.ruleset b/XToolkit.ruleset index 3ab2aa800..49e438af8 100755 --- a/XToolkit.ruleset +++ b/XToolkit.ruleset @@ -1,19 +1,22 @@ + + + - - - - - + + + + + + + + + + + + - + - + + + + - - - + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - + - - - - - + + + + + + + + + + + + + + + + + + + @@ -109,49 +107,49 @@ - + - - + + - + - - - - - + + + + + + + + + + + + + + - + - + + - - - - - + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Playground/Playground.Droid/GlobalSuppressions.cs b/samples/Playground/Playground.Droid/GlobalSuppressions.cs new file mode 100644 index 000000000..13d3ec9a5 --- /dev/null +++ b/samples/Playground/Playground.Droid/GlobalSuppressions.cs @@ -0,0 +1,9 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage( + "Reliability", + "CA2007:Consider calling ConfigureAwait on the awaited task", + Justification = "Allowed for playground project")] diff --git a/samples/Playground/Playground.iOS/GlobalSuppressions.cs b/samples/Playground/Playground.iOS/GlobalSuppressions.cs new file mode 100644 index 000000000..13d3ec9a5 --- /dev/null +++ b/samples/Playground/Playground.iOS/GlobalSuppressions.cs @@ -0,0 +1,9 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage( + "Reliability", + "CA2007:Consider calling ConfigureAwait on the awaited task", + Justification = "Allowed for playground project")] diff --git a/samples/Playground/Playground/GlobalSuppressions.cs b/samples/Playground/Playground/GlobalSuppressions.cs new file mode 100644 index 000000000..13d3ec9a5 --- /dev/null +++ b/samples/Playground/Playground/GlobalSuppressions.cs @@ -0,0 +1,9 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage( + "Reliability", + "CA2007:Consider calling ConfigureAwait on the awaited task", + Justification = "Allowed for playground project")] From 27a1124781e6abbcf3923ac6a7899ea107108cce Mon Sep 17 00:00:00 2001 From: Yauheni Pakala Date: Tue, 28 Mar 2023 17:56:58 +0300 Subject: [PATCH 10/26] Remove Chunkify method (#523) --- .../EnumerableExtensionsDataProvider.cs | 41 --------------- .../EnumerableExtensionsTests.cs | 41 --------------- .../Extensions/EnumerableExtensions.cs | 50 ------------------- 3 files changed, 132 deletions(-) delete mode 100644 Softeq.XToolkit.Common.Tests/Extensions/EnumerableExtensionsTests/EnumerableExtensionsDataProvider.cs diff --git a/Softeq.XToolkit.Common.Tests/Extensions/EnumerableExtensionsTests/EnumerableExtensionsDataProvider.cs b/Softeq.XToolkit.Common.Tests/Extensions/EnumerableExtensionsTests/EnumerableExtensionsDataProvider.cs deleted file mode 100644 index ec27d5d29..000000000 --- a/Softeq.XToolkit.Common.Tests/Extensions/EnumerableExtensionsTests/EnumerableExtensionsDataProvider.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Collections.Generic; - -namespace Softeq.XToolkit.Common.Tests.Extensions.EnumerableExtensionsTests -{ - public class EnumerableExtensionsDataProvider - { - public static IEnumerable ChunkifyData - { - get - { - yield return new object[] - { - new List() { 1, 2, 3, 4 }, - 1, - new List() { new int[] { 1 }, new int[] { 2 }, new int[] { 3 }, new int[] { 4 } } - }; - yield return new object[] - { - new List() { 1, 2, 3, 4 }, - 2, - new List() { new int[] { 1, 2 }, new int[] { 3, 4 } } - }; - yield return new object[] - { - new List() { 1, 2, 3, 4, 5, 6, 7, 8 }, - 3, - new List() { new int[] { 1, 2, 3 }, new int[] { 4, 5, 6 }, new int[] { 7, 8 } } - }; - yield return new object[] - { - new List() { 1, 2, 3, 4 }, - 10, - new List() { new int[] { 1, 2, 3, 4 } } - }; - } - } - } -} diff --git a/Softeq.XToolkit.Common.Tests/Extensions/EnumerableExtensionsTests/EnumerableExtensionsTests.cs b/Softeq.XToolkit.Common.Tests/Extensions/EnumerableExtensionsTests/EnumerableExtensionsTests.cs index 33494be00..22af20c16 100644 --- a/Softeq.XToolkit.Common.Tests/Extensions/EnumerableExtensionsTests/EnumerableExtensionsTests.cs +++ b/Softeq.XToolkit.Common.Tests/Extensions/EnumerableExtensionsTests/EnumerableExtensionsTests.cs @@ -87,46 +87,5 @@ public void Apply_NotNullParams_ActionIsCalledForEveryElement() _testAction.ReceivedWithAnyArgs(_testEnumerable.Count()).Invoke(Arg.Any()); } - - [Theory] - [InlineData(-1)] - [InlineData(0)] - [InlineData(1)] - public void Chunkify_NullEnumerable_ThrowsArgumentNullException(int size) - { - IEnumerable enumerable = null; - - Assert.Throws(() => - { - enumerable.Chunkify(size).ToList(); - }); - } - - [Theory] - [InlineData(-1)] - [InlineData(0)] - public void Chunkify_IncorrectSize_ThrowsOutOfRangeException(int size) - { - Assert.Throws(() => - { - _testEnumerable.Chunkify(size).ToList(); - }); - } - - [Theory] - [MemberData(nameof(EnumerableExtensionsDataProvider.ChunkifyData), MemberType = typeof(EnumerableExtensionsDataProvider))] - public void Chunkify_CorrectSize_ReturnsExpectedChunks(IEnumerable data, int size, IEnumerable expectedChunks) - { - var result = data.Chunkify(size).ToList(); - - Assert.Equal(expectedChunks.Count(), result.Count()); - - var i = 0; - foreach (var expectedChunk in expectedChunks) - { - Assert.Equal(expectedChunk, result[i]); - i++; - } - } } } diff --git a/Softeq.XToolkit.Common/Extensions/EnumerableExtensions.cs b/Softeq.XToolkit.Common/Extensions/EnumerableExtensions.cs index 245288a0b..ce798a450 100644 --- a/Softeq.XToolkit.Common/Extensions/EnumerableExtensions.cs +++ b/Softeq.XToolkit.Common/Extensions/EnumerableExtensions.cs @@ -52,55 +52,5 @@ public static void Apply(this IEnumerable enumerable, Action action) action(item); } } - - /// - /// Splits the current into chunks of a specified size. - /// - /// instance. - /// Chunk size. - /// Item type. - /// - /// Collection of arrays. Each array is a chunk of the source collection and will have the specified size, - /// the last chunk might have size less than specified. - /// - /// - /// parameter cannot be . - /// - /// - /// must be greater than 0. - /// - public static IEnumerable Chunkify(this IEnumerable source, int size) - { - if (source == null) - { - throw new ArgumentNullException(nameof(source)); - } - - if (size < 1) - { - throw new ArgumentOutOfRangeException(nameof(size)); - } - - var chunkIndex = 0; - var lastChunkIndex = Math.Ceiling(source.Count() / (double) size) - 1; - var incompleteChunkSize = source.Count() % size; - var lastChunkSize = incompleteChunkSize == 0 ? size : incompleteChunkSize; - - using (var iter = source.GetEnumerator()) - { - while (iter.MoveNext()) - { - var chunk = new T[chunkIndex == lastChunkIndex ? lastChunkSize : size]; - chunk[0] = iter.Current; - for (var i = 1; i < size && iter.MoveNext(); i++) - { - chunk[i] = iter.Current; - } - - chunkIndex++; - yield return chunk; - } - } - } } } From 107c72d485a1275590a7d54f14732d3d91659bdb Mon Sep 17 00:00:00 2001 From: Yauheni Pakala Date: Wed, 29 Mar 2023 12:33:30 +0300 Subject: [PATCH 11/26] Remove Newtonsoft.Json (#524) --- .../Interfaces/IJsonSerializer.cs | 5 +- .../Softeq.XToolkit.Remote.csproj | 1 - .../ActivityBase.cs | 2 +- .../DroidBootstrapperBase.cs | 3 + .../Interfaces/IBundleService.cs | 46 ++++++++ .../ActivityPageNavigationService.cs | 1 + .../Navigation/BundleService.cs | 99 ++++++++-------- .../Extensions/ExpressionExtensionsTests.cs | 65 +++++++++++ ...entNavigatorExtensionsTestsDataProvider.cs | 8 +- .../NavigationParameterModelDataProvider.cs | 6 +- .../NavigationParameterModelTests.cs | 2 +- ...ests.cs => NavigationPropertyInfoTests.cs} | 41 +++---- .../DefaultJsonSerializerTests.cs | 2 +- ...JsonSerializerTestsNavigationSerializer.cs | 48 ++++++++ .../JsonDeserializationDataProvider.cs | 42 +++++++ .../NewtonsoftJsonSerializerTests.cs | 106 ------------------ .../AppDelegateBase.cs | 2 + .../Bootstrapper/BootstrapperBase.cs | 3 +- Softeq.XToolkit.WhiteLabel/Dependencies.cs | 2 +- .../Extensions/ExpressionExtensions.cs | 3 + .../FluentNavigators/FluentNavigatorBase.cs | 2 +- .../Navigation/INavigationSerializer.cs | 46 ++++++++ .../Navigation/NavigationExtensions.cs | 3 + .../Navigation/NavigationParameterModel.cs | 8 +- ...InfoModel.cs => NavigationPropertyInfo.cs} | 31 ++--- .../Services/DefaultJsonSerializer.cs | 31 +++-- .../Services/NewtonsoftJsonSerializer.cs | 84 -------------- .../Softeq.XToolkit.WhiteLabel.csproj | 1 - .../Playground.iOS/Playground.iOS.csproj | 1 - 29 files changed, 392 insertions(+), 302 deletions(-) create mode 100644 Softeq.XToolkit.WhiteLabel.Droid/Interfaces/IBundleService.cs create mode 100644 Softeq.XToolkit.WhiteLabel.Tests/Extensions/ExpressionExtensionsTests.cs rename Softeq.XToolkit.WhiteLabel.Tests/Navigation/PropertyInfoModelTests/{PropertyInfoModelTests.cs => NavigationPropertyInfoTests.cs} (65%) create mode 100644 Softeq.XToolkit.WhiteLabel.Tests/Services/DefaultJsonSerializerTests/DefaultJsonSerializerTestsNavigationSerializer.cs delete mode 100644 Softeq.XToolkit.WhiteLabel.Tests/Services/NewtonsoftJsonSerializerTests/NewtonsoftJsonSerializerTests.cs create mode 100644 Softeq.XToolkit.WhiteLabel/Navigation/INavigationSerializer.cs rename Softeq.XToolkit.WhiteLabel/Navigation/{PropertyInfoModel.cs => NavigationPropertyInfo.cs} (82%) delete mode 100644 Softeq.XToolkit.WhiteLabel/Services/NewtonsoftJsonSerializer.cs diff --git a/Softeq.XToolkit.Common/Interfaces/IJsonSerializer.cs b/Softeq.XToolkit.Common/Interfaces/IJsonSerializer.cs index 2b8a68e04..04b8a21d8 100644 --- a/Softeq.XToolkit.Common/Interfaces/IJsonSerializer.cs +++ b/Softeq.XToolkit.Common/Interfaces/IJsonSerializer.cs @@ -1,7 +1,6 @@ // Developed by Softeq Development Corporation // http://www.softeq.com -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Threading.Tasks; @@ -22,8 +21,8 @@ public interface IJsonSerializer /// /// Deserializes the JSON to a .NET object. /// - /// The Stream that contains the JSON structure to deserialize. - /// The deserialized object from the JSON string. + /// The string that contains the JSON structure to deserialize. + /// The deserialized object. /// Object type. TResult? Deserialize(string value); diff --git a/Softeq.XToolkit.Remote/Softeq.XToolkit.Remote.csproj b/Softeq.XToolkit.Remote/Softeq.XToolkit.Remote.csproj index 4eb6fe2cb..f7d89d821 100644 --- a/Softeq.XToolkit.Remote/Softeq.XToolkit.Remote.csproj +++ b/Softeq.XToolkit.Remote/Softeq.XToolkit.Remote.csproj @@ -11,7 +11,6 @@ - diff --git a/Softeq.XToolkit.WhiteLabel.Droid/ActivityBase.cs b/Softeq.XToolkit.WhiteLabel.Droid/ActivityBase.cs index 7d9a6d242..892d5b674 100644 --- a/Softeq.XToolkit.WhiteLabel.Droid/ActivityBase.cs +++ b/Softeq.XToolkit.WhiteLabel.Droid/ActivityBase.cs @@ -11,7 +11,7 @@ using Softeq.XToolkit.Bindings.Abstract; using Softeq.XToolkit.Bindings.Extensions; using Softeq.XToolkit.Common.Droid.Permissions; -using Softeq.XToolkit.WhiteLabel.Droid.Navigation; +using Softeq.XToolkit.WhiteLabel.Droid.Interfaces; using Softeq.XToolkit.WhiteLabel.Droid.ViewComponents; using Softeq.XToolkit.WhiteLabel.Mvvm; using Softeq.XToolkit.WhiteLabel.Navigation; diff --git a/Softeq.XToolkit.WhiteLabel.Droid/DroidBootstrapperBase.cs b/Softeq.XToolkit.WhiteLabel.Droid/DroidBootstrapperBase.cs index 117652ac1..3b8ad09b7 100644 --- a/Softeq.XToolkit.WhiteLabel.Droid/DroidBootstrapperBase.cs +++ b/Softeq.XToolkit.WhiteLabel.Droid/DroidBootstrapperBase.cs @@ -6,9 +6,11 @@ using AndroidX.Fragment.App; using Softeq.XToolkit.WhiteLabel.Bootstrapper; using Softeq.XToolkit.WhiteLabel.Bootstrapper.Abstract; +using Softeq.XToolkit.WhiteLabel.Droid.Interfaces; using Softeq.XToolkit.WhiteLabel.Droid.Navigation; using Softeq.XToolkit.WhiteLabel.Droid.Providers; using Softeq.XToolkit.WhiteLabel.Navigation; +using Softeq.XToolkit.WhiteLabel.Services; namespace Softeq.XToolkit.WhiteLabel.Droid { @@ -29,6 +31,7 @@ protected override void RegisterInternalServices(IContainerBuilder builder) // navigation builder.Singleton(IfRegistered.Keep); + builder.Singleton(IfRegistered.Keep); builder.Singleton(IfRegistered.Keep); builder.Singleton(IfRegistered.Keep); builder.PerDependency(IfRegistered.Keep); diff --git a/Softeq.XToolkit.WhiteLabel.Droid/Interfaces/IBundleService.cs b/Softeq.XToolkit.WhiteLabel.Droid/Interfaces/IBundleService.cs new file mode 100644 index 000000000..785bd0807 --- /dev/null +++ b/Softeq.XToolkit.WhiteLabel.Droid/Interfaces/IBundleService.cs @@ -0,0 +1,46 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System.Collections.Generic; +using Android.Content; +using Android.OS; +using Softeq.XToolkit.WhiteLabel.Mvvm; +using Softeq.XToolkit.WhiteLabel.Navigation; + +namespace Softeq.XToolkit.WhiteLabel.Droid.Interfaces; + +/// +/// Represents methods for saving/restoring ViewModel navigation parameters on Android. +/// +public interface IBundleService +{ + /// + /// Marks that we have a state that should be restored. + /// + /// Android Bundle. + void SaveInstanceState(Bundle bundle); + + /// + /// Saves navigation into the . + /// + /// Android Intent object. + /// ViewModel navigation parameters. + void TryToSetParams(Intent intent, IReadOnlyList? parameters); + + /// + /// Restores navigation parameters from + /// if was marked for restore. + /// + /// + /// Skip restore when: + /// + /// ViewModel was alive + /// Activity never been destroyed + /// we don't have data to restore. + /// + /// + /// Target ViewModel. + /// Android Intent. + /// Android Bundle. + void TryToRestoreParams(ViewModelBase viewModel, Intent? intent, Bundle? bundle); +} diff --git a/Softeq.XToolkit.WhiteLabel.Droid/Navigation/ActivityPageNavigationService.cs b/Softeq.XToolkit.WhiteLabel.Droid/Navigation/ActivityPageNavigationService.cs index aaba84d2b..c66cc278c 100644 --- a/Softeq.XToolkit.WhiteLabel.Droid/Navigation/ActivityPageNavigationService.cs +++ b/Softeq.XToolkit.WhiteLabel.Droid/Navigation/ActivityPageNavigationService.cs @@ -6,6 +6,7 @@ using System.Reflection; using Android.Content; using Softeq.XToolkit.Common.Threading; +using Softeq.XToolkit.WhiteLabel.Droid.Interfaces; using Softeq.XToolkit.WhiteLabel.Droid.Providers; using Softeq.XToolkit.WhiteLabel.Mvvm; using Softeq.XToolkit.WhiteLabel.Navigation; diff --git a/Softeq.XToolkit.WhiteLabel.Droid/Navigation/BundleService.cs b/Softeq.XToolkit.WhiteLabel.Droid/Navigation/BundleService.cs index 4faa21f50..576138233 100644 --- a/Softeq.XToolkit.WhiteLabel.Droid/Navigation/BundleService.cs +++ b/Softeq.XToolkit.WhiteLabel.Droid/Navigation/BundleService.cs @@ -4,69 +4,70 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using Android.Content; using Android.OS; -using Newtonsoft.Json.Linq; using Softeq.XToolkit.Common.Extensions; -using Softeq.XToolkit.Common.Interfaces; +using Softeq.XToolkit.WhiteLabel.Droid.Interfaces; using Softeq.XToolkit.WhiteLabel.Mvvm; using Softeq.XToolkit.WhiteLabel.Navigation; namespace Softeq.XToolkit.WhiteLabel.Droid.Navigation { - public interface IBundleService - { - void TryToSetParams(Intent intent, IReadOnlyList? parameters); - - /// - /// - /// Skip restore when: - /// 1) ViewModel was alive - /// 2) Activity never been destroyed - /// 3) we don't have data to restore. - /// - /// ViewModel. - /// Android Intent. - /// Android Bundle. - void TryToRestoreParams(ViewModelBase viewModel, Intent intent, Bundle? bundle); - - void SaveInstanceState(Bundle bundle); - } - + /// public class BundleService : IBundleService { private const string ShouldRestoreStateKey = "WL_ShouldRestore"; private const string ParametersKey = "WL_Parameters"; - private readonly IJsonSerializer _jsonSerializer; + private readonly INavigationSerializer _serializer; + + public BundleService(INavigationSerializer serializer) + { + _serializer = serializer; + } - public BundleService( - IJsonSerializer jsonSerializer) + /// + public void SaveInstanceState(Bundle bundle) { - _jsonSerializer = jsonSerializer; + bundle.PutBoolean(ShouldRestoreStateKey, true); } + /// public void TryToSetParams(Intent intent, IReadOnlyList? parameters) { if (parameters != null && parameters.Any()) { - intent.PutExtra(ParametersKey, _jsonSerializer.Serialize(parameters)); + var serializedParameters = _serializer.Serialize(parameters); + intent.PutExtra(ParametersKey, serializedParameters); } } /// - public void TryToRestoreParams(ViewModelBase viewModel, Intent intent, Bundle? bundle) + public void TryToRestoreParams(ViewModelBase viewModel, Intent? intent, Bundle? bundle) { - if (viewModel.IsInitialized - || bundle == null - || !bundle.ContainsKey(ShouldRestoreStateKey) - || !intent.HasExtra(ParametersKey)) + if (viewModel.IsInitialized) + { + return; + } + + if (bundle == null || !bundle.ContainsKey(ShouldRestoreStateKey)) + { + return; + } + + if (intent == null || !intent.HasExtra(ParametersKey)) { return; } var parametersObject = intent.GetStringExtra(ParametersKey); - var parameters = _jsonSerializer + if (string.IsNullOrEmpty(parametersObject)) + { + return; + } + + var parameters = _serializer .Deserialize>(parametersObject) .EmptyIfNull(); @@ -78,31 +79,31 @@ public void TryToRestoreParams(ViewModelBase viewModel, Intent intent, Bundle? b intent.RemoveExtra(ShouldRestoreStateKey); } - public void SaveInstanceState(Bundle bundle) + private void SetValueToProperty(ViewModelBase viewModel, NavigationParameterModel parameter) { - bundle.PutBoolean(ShouldRestoreStateKey, true); + var property = parameter.PropertyInfo?.ToPropertyInfo(); + + if (property != null) + { + var value = DeserializeValueFromProperty(parameter.Value, property); + + property.SetValue(viewModel, value, null); + } } - private static void SetValueToProperty(ViewModelBase viewModel, NavigationParameterModel parameter) + private object? DeserializeValueFromProperty(object? value, PropertyInfo propertyInfo) { - var property = parameter.PropertyInfo.ToPropertyInfo(); - - object? GetValue(object? value) + if (value == null) { - if (value == null) - { - return null; - } - - if (property.PropertyType.IsEnum) - { - return Enum.ToObject(property.PropertyType, value); - } + return null; + } - return ((JObject) value).ToObject(property.PropertyType); + if (propertyInfo.PropertyType.IsEnum) + { + return Enum.ToObject(propertyInfo.PropertyType, value); } - property.SetValue(viewModel, GetValue(parameter.Value), null); + return _serializer.Deserialize(value, propertyInfo.PropertyType); } } } diff --git a/Softeq.XToolkit.WhiteLabel.Tests/Extensions/ExpressionExtensionsTests.cs b/Softeq.XToolkit.WhiteLabel.Tests/Extensions/ExpressionExtensionsTests.cs new file mode 100644 index 000000000..57ec81b1c --- /dev/null +++ b/Softeq.XToolkit.WhiteLabel.Tests/Extensions/ExpressionExtensionsTests.cs @@ -0,0 +1,65 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using System.Linq.Expressions; +using System.Reflection; +using Softeq.XToolkit.WhiteLabel.Extensions; +using Xunit; + +namespace Softeq.XToolkit.WhiteLabel.Tests.Extensions; + +public class ExpressionExtensionsTests +{ + [Fact] + public void GetMemberInfo_Null_ThrowsNullReferenceException() + { + Assert.Throws(() => + { + ExpressionExtensions.GetMemberInfo(null!); + }); + } + + [Fact] + public void GetMemberInfo_ConstantExpression_ThrowsInvalidCastException() + { + const string TestStr = "test"; + + Assert.Throws(() => + { + ExpressionExtensions.GetMemberInfo(() => TestStr); + }); + } + + [Fact] + public void GetMemberInfo_ParameterExpression_ThrowsInvalidCastException() + { + Assert.Throws(() => + { + ExpressionExtensions.GetMemberInfo((bool a) => !a); + }); + } + + [Fact] + public void GetMemberInfo_FieldExpression_ReturnsMemberInfoWithCorrectName() + { + string testField = "test"; + Expression> expression = () => testField; + MemberInfo expected = ((MemberExpression)expression.Body).Member; + + var result = expression.GetMemberInfo(); + + Assert.Equal(expected, result); + } + + [Fact] + public void GetMemberInfo_UnaryExpression_ReturnsMemberInfoWithCorrectName() + { + Expression, bool>> expression = arg => !arg.Item1; + MemberInfo expected = ((MemberExpression)((UnaryExpression)expression.Body).Operand).Member; + + var result = expression.GetMemberInfo(); + + Assert.Equal(expected, result); + } +} diff --git a/Softeq.XToolkit.WhiteLabel.Tests/Navigation/FluentNavigators/FluentNavigatorExtensionsTests/FluentNavigatorExtensionsTestsDataProvider.cs b/Softeq.XToolkit.WhiteLabel.Tests/Navigation/FluentNavigators/FluentNavigatorExtensionsTests/FluentNavigatorExtensionsTestsDataProvider.cs index a36c9fbe0..05df8362e 100644 --- a/Softeq.XToolkit.WhiteLabel.Tests/Navigation/FluentNavigators/FluentNavigatorExtensionsTests/FluentNavigatorExtensionsTestsDataProvider.cs +++ b/Softeq.XToolkit.WhiteLabel.Tests/Navigation/FluentNavigators/FluentNavigatorExtensionsTests/FluentNavigatorExtensionsTestsDataProvider.cs @@ -16,13 +16,13 @@ internal static class FluentNavigatorExtensionsTestsDataProvider private static readonly PropertyInfo ValidPropertyInfo1 = typeof(ViewModelStub).GetProperty(nameof(ViewModelStub.StringParameter)); - private static readonly PropertyInfoModel ValidPropertyInfoModel1 - = new PropertyInfoModel(ValidPropertyInfo1); + private static readonly NavigationPropertyInfo ValidPropertyInfoModel1 + = NavigationPropertyInfo.FromPropertyInfo(ValidPropertyInfo1); private static readonly PropertyInfo ValidPropertyInfo2 = typeof(ViewModelStub).GetProperty(nameof(ViewModelStub.IntParameter)); - private static readonly PropertyInfoModel ValidPropertyInfoModel2 - = new PropertyInfoModel(ValidPropertyInfo2); + private static readonly NavigationPropertyInfo ValidPropertyInfoModel2 + = NavigationPropertyInfo.FromPropertyInfo(ValidPropertyInfo2); private static readonly NavigationParameterModel ValidNavigationParameter1 = new NavigationParameterModel(PropertyValue1, ValidPropertyInfoModel1); diff --git a/Softeq.XToolkit.WhiteLabel.Tests/Navigation/NavigationParameterModelTests/NavigationParameterModelDataProvider.cs b/Softeq.XToolkit.WhiteLabel.Tests/Navigation/NavigationParameterModelTests/NavigationParameterModelDataProvider.cs index 57a3facd9..44ef01561 100644 --- a/Softeq.XToolkit.WhiteLabel.Tests/Navigation/NavigationParameterModelTests/NavigationParameterModelDataProvider.cs +++ b/Softeq.XToolkit.WhiteLabel.Tests/Navigation/NavigationParameterModelTests/NavigationParameterModelDataProvider.cs @@ -8,11 +8,11 @@ namespace Softeq.XToolkit.WhiteLabel.Tests.Navigation.NavigationParameterModelTe { internal static class NavigationParameterModelDataProvider { - private static readonly PropertyInfoModel EmptyPropertyInfoModel = new PropertyInfoModel(null, null); + private static readonly NavigationPropertyInfo EmptyPropertyInfoModel = new NavigationPropertyInfo(null, null); private static readonly object EmptyObject = new object(); - public static TheoryData CtorData - => new TheoryData + public static TheoryData CtorData + => new TheoryData { { null, null }, { null, EmptyPropertyInfoModel }, diff --git a/Softeq.XToolkit.WhiteLabel.Tests/Navigation/NavigationParameterModelTests/NavigationParameterModelTests.cs b/Softeq.XToolkit.WhiteLabel.Tests/Navigation/NavigationParameterModelTests/NavigationParameterModelTests.cs index ec449f971..f0d56ce04 100644 --- a/Softeq.XToolkit.WhiteLabel.Tests/Navigation/NavigationParameterModelTests/NavigationParameterModelTests.cs +++ b/Softeq.XToolkit.WhiteLabel.Tests/Navigation/NavigationParameterModelTests/NavigationParameterModelTests.cs @@ -12,7 +12,7 @@ public class NavigationParameterModelTests [MemberData( nameof(NavigationParameterModelDataProvider.CtorData), MemberType = typeof(NavigationParameterModelDataProvider))] - public void Ctor_InitializesProperties(object value, PropertyInfoModel propertyInfo) + public void Ctor_InitializesProperties(object value, NavigationPropertyInfo propertyInfo) { var navigationParameterModel = new NavigationParameterModel(value, propertyInfo); diff --git a/Softeq.XToolkit.WhiteLabel.Tests/Navigation/PropertyInfoModelTests/PropertyInfoModelTests.cs b/Softeq.XToolkit.WhiteLabel.Tests/Navigation/PropertyInfoModelTests/NavigationPropertyInfoTests.cs similarity index 65% rename from Softeq.XToolkit.WhiteLabel.Tests/Navigation/PropertyInfoModelTests/PropertyInfoModelTests.cs rename to Softeq.XToolkit.WhiteLabel.Tests/Navigation/PropertyInfoModelTests/NavigationPropertyInfoTests.cs index d08163aa5..53f9b1cae 100644 --- a/Softeq.XToolkit.WhiteLabel.Tests/Navigation/PropertyInfoModelTests/PropertyInfoModelTests.cs +++ b/Softeq.XToolkit.WhiteLabel.Tests/Navigation/PropertyInfoModelTests/NavigationPropertyInfoTests.cs @@ -1,51 +1,54 @@ // Developed by Softeq Development Corporation // http://www.softeq.com - + using System.Reflection; using Softeq.XToolkit.WhiteLabel.Navigation; using Xunit; namespace Softeq.XToolkit.WhiteLabel.Tests.Navigation.PropertyInfoModelTests { - public class PropertyInfoModelTests + public class NavigationPropertyInfoTests { [Theory] [PairwiseData] public void Ctor_WithPropertyAndTypeNames_InitializesProperties( - [CombinatorialValues(null, "", "a", "abc")] string propertyName, - [CombinatorialValues(null, "", "a", "abc")] string typeName) + [CombinatorialValues("", "abc")] string propertyName, + [CombinatorialValues("", "abc")] string typeName) { - var model = new PropertyInfoModel(propertyName, typeName); + var model = new NavigationPropertyInfo(propertyName, typeName); + + Assert.Equal(propertyName, model.PropertyName); + Assert.Equal(typeName, model.AssemblyQualifiedTypeName); + } - Assert.NotNull(model.PropertyName); - Assert.NotNull(model.AssemblyQualifiedTypeName); + [Fact] + public void Ctor_WithNullArgs_InitializesPropertiesToEmptyStrings() + { + var model = new NavigationPropertyInfo(null, null); - Assert.Equal(propertyName ?? string.Empty, model.PropertyName); - Assert.Equal(typeName ?? string.Empty, model.AssemblyQualifiedTypeName); + Assert.Empty(model.PropertyName); + Assert.Empty(model.AssemblyQualifiedTypeName); } [Theory] [MemberData( nameof(PropertyInfoModelDataProvider.ValidPropertyInfoData), MemberType = typeof(PropertyInfoModelDataProvider))] - public void Ctor_WithPropertyInfo_InitializesProperties( + public void FromPropertyInfo_WithPropertyInfo_InitializesProperties( PropertyInfo propertyInfo, string propertyName, string typeName) { - var model = new PropertyInfoModel(propertyInfo); - - Assert.NotNull(model.PropertyName); - Assert.NotNull(model.AssemblyQualifiedTypeName); + var model = NavigationPropertyInfo.FromPropertyInfo(propertyInfo); Assert.Equal(propertyName, model.PropertyName); Assert.Equal(typeName, model.AssemblyQualifiedTypeName); } [Fact] - public void Ctor_WithNullPropertyInfo_InitializesPropertiesToEmptyStrings() + public void FromPropertyInfo_WithNullPropertyInfo_InitializesPropertiesToEmptyStrings() { - var model = new PropertyInfoModel(null); + var model = NavigationPropertyInfo.FromPropertyInfo(null); Assert.Empty(model.PropertyName); Assert.Empty(model.AssemblyQualifiedTypeName); @@ -59,7 +62,7 @@ public void ToPropertyInfo_WithInvalidTypeName_ThrowsCorrectException( string propertyName, string typeName) { - var model = new PropertyInfoModel(propertyName, typeName); + var model = new NavigationPropertyInfo(propertyName, typeName); Assert.Throws(() => model.ToPropertyInfo()); } @@ -72,7 +75,7 @@ public void ToPropertyInfo_WithInvalidPropertyName_ThrowsCorrectException( string propertyName, string typeName) { - var model = new PropertyInfoModel(propertyName, typeName); + var model = new NavigationPropertyInfo(propertyName, typeName); Assert.Throws(() => model.ToPropertyInfo()); } @@ -86,7 +89,7 @@ public void ToPropertyInfo_WithValidData_ReturnsValidProperty( string propertyName, string typeName) { - var model = new PropertyInfoModel(propertyName, typeName); + var model = new NavigationPropertyInfo(propertyName, typeName); var propertyInfo = model.ToPropertyInfo(); diff --git a/Softeq.XToolkit.WhiteLabel.Tests/Services/DefaultJsonSerializerTests/DefaultJsonSerializerTests.cs b/Softeq.XToolkit.WhiteLabel.Tests/Services/DefaultJsonSerializerTests/DefaultJsonSerializerTests.cs index eeefb1487..0890fbf32 100644 --- a/Softeq.XToolkit.WhiteLabel.Tests/Services/DefaultJsonSerializerTests/DefaultJsonSerializerTests.cs +++ b/Softeq.XToolkit.WhiteLabel.Tests/Services/DefaultJsonSerializerTests/DefaultJsonSerializerTests.cs @@ -13,7 +13,7 @@ namespace Softeq.XToolkit.WhiteLabel.Tests.Services.DefaultJsonSerializerTests; -public class DefaultJsonSerializerTests +public partial class DefaultJsonSerializerTests { private readonly DefaultJsonSerializer _serializer; diff --git a/Softeq.XToolkit.WhiteLabel.Tests/Services/DefaultJsonSerializerTests/DefaultJsonSerializerTestsNavigationSerializer.cs b/Softeq.XToolkit.WhiteLabel.Tests/Services/DefaultJsonSerializerTests/DefaultJsonSerializerTestsNavigationSerializer.cs new file mode 100644 index 000000000..47718f834 --- /dev/null +++ b/Softeq.XToolkit.WhiteLabel.Tests/Services/DefaultJsonSerializerTests/DefaultJsonSerializerTestsNavigationSerializer.cs @@ -0,0 +1,48 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using System.Text.Json; +using Softeq.XToolkit.WhiteLabel.Navigation; +using Softeq.XToolkit.WhiteLabel.Tests.Services.JsonSerializers; +using Xunit; + +namespace Softeq.XToolkit.WhiteLabel.Tests.Services.DefaultJsonSerializerTests; + +public partial class DefaultJsonSerializerTests +{ + [Fact] + public void Ctor_Default_ReturnsINavigationSerializer() + { + Assert.IsAssignableFrom(_serializer); + } + + [Theory] + [MemberData(nameof(JsonDeserializationDataProvider.CustomObjects), MemberType = typeof(JsonDeserializationDataProvider))] + public void Deserialize_WithCustomObject_ResultsExpectedType(string json, T expected) + { + var jsonElement = JsonDocument.Parse(json).RootElement; + + var result = _serializer.Deserialize(jsonElement, typeof(T)); + + Assert.Equivalent(expected, result); + } + + [Fact] + public void Deserialize_WithNullCustomObject_ThrowsArgumentNullException() + { + Assert.Throws(() => + { + _serializer.Deserialize(null, typeof(object)); + }); + } + + [Fact] + public void Deserialize_WithUnsupportedCustomObject_ThrowsArgumentNullException() + { + Assert.Throws(() => + { + _serializer.Deserialize("test string", typeof(string)); + }); + } +} diff --git a/Softeq.XToolkit.WhiteLabel.Tests/Services/JsonSerializers/JsonDeserializationDataProvider.cs b/Softeq.XToolkit.WhiteLabel.Tests/Services/JsonSerializers/JsonDeserializationDataProvider.cs index b760603d4..e29d64b73 100644 --- a/Softeq.XToolkit.WhiteLabel.Tests/Services/JsonSerializers/JsonDeserializationDataProvider.cs +++ b/Softeq.XToolkit.WhiteLabel.Tests/Services/JsonSerializers/JsonDeserializationDataProvider.cs @@ -152,4 +152,46 @@ public static IEnumerable InvalidData }; } } + + public static IEnumerable CustomObjects + { + get + { + yield return new object[] + { + "{\"name\":\"Test\",\"age\":1}", + new JsonSerializationTestsStub + { + Name = "Test", + Age = 1, + } + }; + yield return new object[] + { + "{\"age\":\"2\"}", + new JsonSerializationTestsStub + { + Age = 2 + } + }; + yield return new object[] + { + "{\"name\":\"\"}", + new JsonSerializationTestsStub + { + Name = string.Empty + } + }; + yield return new object[] + { + "2.5", + 2.5 + }; + yield return new object[] + { + "\"123\"", + "123" + }; + } + } } diff --git a/Softeq.XToolkit.WhiteLabel.Tests/Services/NewtonsoftJsonSerializerTests/NewtonsoftJsonSerializerTests.cs b/Softeq.XToolkit.WhiteLabel.Tests/Services/NewtonsoftJsonSerializerTests/NewtonsoftJsonSerializerTests.cs deleted file mode 100644 index cf907803e..000000000 --- a/Softeq.XToolkit.WhiteLabel.Tests/Services/NewtonsoftJsonSerializerTests/NewtonsoftJsonSerializerTests.cs +++ /dev/null @@ -1,106 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System; -using System.IO; -using System.Text; -using System.Threading.Tasks; -using Newtonsoft.Json; -using Softeq.XToolkit.Common.Interfaces; -using Softeq.XToolkit.WhiteLabel.Services; -using Softeq.XToolkit.WhiteLabel.Tests.Services.JsonSerializers; -using Xunit; - -namespace Softeq.XToolkit.WhiteLabel.Tests.Services.NewtonsoftJsonSerializerTests; - -public class NewtonsoftJsonSerializerTests -{ - private readonly NewtonsoftJsonSerializer _serializer; - - public NewtonsoftJsonSerializerTests() - { - _serializer = new NewtonsoftJsonSerializer(); - } - - [Fact] - public void Ctor_Default_ReturnsIJsonSerializer() - { - Assert.IsAssignableFrom(_serializer); - } - - [Fact] - public void DefaultSettings_StaticGet_ReturnsJsonSerializerSettings() - { - Assert.IsType(NewtonsoftJsonSerializer.DefaultSettings); - } - - [Theory] - [MemberData(nameof(JsonSerializationDataProvider.Data), MemberType = typeof(JsonSerializationDataProvider))] - public void Serialize_WithValidData_ExpectedResult(object data, string expected) - { - var result = _serializer.Serialize(data); - - Assert.Equal(expected, result); - } - - [Theory] - [MemberData(nameof(JsonDeserializationDataProvider.Data), MemberType = typeof(JsonDeserializationDataProvider))] - public void Deserialize_WithValidData_ExpectedResult(string data, T expected) - { - var result = _serializer.Deserialize(data); - - Assert.Equivalent(expected, result); - } - - [Theory] - [MemberData(nameof(JsonDeserializationDataProvider.InvalidData), MemberType = typeof(JsonDeserializationDataProvider))] -#pragma warning disable xUnit1026 - public void Deserialize_WithInvalidData_ThrowsException(string data, T expected) -#pragma warning restore xUnit1026 - { - Assert.Throws(() => - { - _serializer.Deserialize(data); - }); - } - - [Fact] - public void Deserialize_Null_ThrowsArgumentNullException() - { - Assert.Throws(() => - { - _serializer.Deserialize(null!); - }); - } - - [Theory] - [MemberData(nameof(JsonSerializationDataProvider.Data), MemberType = typeof(JsonSerializationDataProvider))] - public async Task SerializeAsync_WithValidData_ExpectedResult(object data, string expected) - { - using var stream = new MemoryStream(); - - await _serializer.SerializeAsync(data, stream); - - var result = Encoding.UTF8.GetString(stream.ToArray()); - Assert.Equal(expected, result); - } - - [Theory] - [MemberData(nameof(JsonDeserializationDataProvider.Data), MemberType = typeof(JsonDeserializationDataProvider))] - public async Task DeserializeAsync_WithValidData_ExpectedResult(string data, T expected) - { - var bytes = Encoding.UTF8.GetBytes(data); - using var stream = new MemoryStream(bytes); - - var result = await _serializer.DeserializeAsync(stream)!; - - Assert.Equivalent(expected, result); - } - - [Fact] - public async Task DeserializeAsync_Null_ThrowsArgumentNullException() - { - await Assert.ThrowsAsync(() => - _serializer.DeserializeAsync(null!)); - } -} diff --git a/Softeq.XToolkit.WhiteLabel.iOS/AppDelegateBase.cs b/Softeq.XToolkit.WhiteLabel.iOS/AppDelegateBase.cs index f6ddeb0a0..2035f199d 100644 --- a/Softeq.XToolkit.WhiteLabel.iOS/AppDelegateBase.cs +++ b/Softeq.XToolkit.WhiteLabel.iOS/AppDelegateBase.cs @@ -19,8 +19,10 @@ public abstract class AppDelegateBase : UIApplicationDelegate { private UIViewController _rootViewController = default!; + /// public override UIWindow? Window { get; set; } + /// public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions) { // YP: Hard reference kept because StoryboardNavigation service uses weak references. diff --git a/Softeq.XToolkit.WhiteLabel/Bootstrapper/BootstrapperBase.cs b/Softeq.XToolkit.WhiteLabel/Bootstrapper/BootstrapperBase.cs index 029c1b218..5d9297877 100644 --- a/Softeq.XToolkit.WhiteLabel/Bootstrapper/BootstrapperBase.cs +++ b/Softeq.XToolkit.WhiteLabel/Bootstrapper/BootstrapperBase.cs @@ -10,6 +10,7 @@ using Softeq.XToolkit.WhiteLabel.Bootstrapper.Abstract; using Softeq.XToolkit.WhiteLabel.Bootstrapper.Containers; using Softeq.XToolkit.WhiteLabel.Navigation; +using Softeq.XToolkit.WhiteLabel.Services; namespace Softeq.XToolkit.WhiteLabel.Bootstrapper { @@ -55,7 +56,7 @@ protected virtual void RegisterInternalServices(IContainerBuilder builder) builder.Singleton(IfRegistered.Keep); // json - builder.Singleton(); + builder.Singleton(); } /// diff --git a/Softeq.XToolkit.WhiteLabel/Dependencies.cs b/Softeq.XToolkit.WhiteLabel/Dependencies.cs index c24e1a1f6..83ff18fe0 100644 --- a/Softeq.XToolkit.WhiteLabel/Dependencies.cs +++ b/Softeq.XToolkit.WhiteLabel/Dependencies.cs @@ -10,7 +10,7 @@ namespace Softeq.XToolkit.WhiteLabel { /// - /// Static class that contains referencies to application services. + /// Static class that contains references to application services. /// /// Please, avoid using this class. /// Use it only if you don't have other options to add service reference. diff --git a/Softeq.XToolkit.WhiteLabel/Extensions/ExpressionExtensions.cs b/Softeq.XToolkit.WhiteLabel/Extensions/ExpressionExtensions.cs index 19268be75..5dbf58a5d 100644 --- a/Softeq.XToolkit.WhiteLabel/Extensions/ExpressionExtensions.cs +++ b/Softeq.XToolkit.WhiteLabel/Extensions/ExpressionExtensions.cs @@ -6,6 +6,9 @@ namespace Softeq.XToolkit.WhiteLabel.Extensions { + /// + /// Extension methods for . + /// public static class ExpressionExtensions { /// diff --git a/Softeq.XToolkit.WhiteLabel/Navigation/FluentNavigators/FluentNavigatorBase.cs b/Softeq.XToolkit.WhiteLabel/Navigation/FluentNavigators/FluentNavigatorBase.cs index 26c83cce2..48f5c833e 100644 --- a/Softeq.XToolkit.WhiteLabel/Navigation/FluentNavigators/FluentNavigatorBase.cs +++ b/Softeq.XToolkit.WhiteLabel/Navigation/FluentNavigators/FluentNavigatorBase.cs @@ -38,7 +38,7 @@ protected FluentNavigatorBase ApplyParameter( TValue value) { var propertyInfo = (PropertyInfo) propertyExpression.GetMemberInfo(); - var parameter = new NavigationParameterModel(value, new PropertyInfoModel(propertyInfo)); + var parameter = new NavigationParameterModel(value, NavigationPropertyInfo.FromPropertyInfo(propertyInfo)); _parameters.Add(parameter); diff --git a/Softeq.XToolkit.WhiteLabel/Navigation/INavigationSerializer.cs b/Softeq.XToolkit.WhiteLabel/Navigation/INavigationSerializer.cs new file mode 100644 index 000000000..8c6eae7f2 --- /dev/null +++ b/Softeq.XToolkit.WhiteLabel/Navigation/INavigationSerializer.cs @@ -0,0 +1,46 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; + +namespace Softeq.XToolkit.WhiteLabel.Navigation; + +/// +/// Represents methods for serialization and deserialization of objects into and from the text format. +/// +/// +/// Related to navigation and uses internally to save some of ViewModel states (currently only on Android). +/// +public interface INavigationSerializer +{ + /// + /// Serializes the specified object to a string. + /// + /// The object to serialize. + /// A string representation of the object. + string Serialize(object value); + + /// + /// Deserializes to a .NET object. + /// + /// The string that contains data to deserialize. + /// The deserialized object. + /// Result object type. + TResult? Deserialize(string value); + + /// + /// Deserializes the custom object to a .NET object of the . + /// + /// The custom object that contains data to deserialize. + /// Result object type. + /// In case: + /// - to serialize Model as `object` we will receive json string. + /// + /// - to deserialize json string to Model back when the type was `object` + /// we should explicitly deserialize/unwrap the received object (object of serializer like JsonElement, etc.) + /// to convert this object to Model on runtime. + /// + /// + /// The deserialized object of type . + object? Deserialize(object? value, Type returnType); +} diff --git a/Softeq.XToolkit.WhiteLabel/Navigation/NavigationExtensions.cs b/Softeq.XToolkit.WhiteLabel/Navigation/NavigationExtensions.cs index 6ddd6c86e..9b7c97836 100644 --- a/Softeq.XToolkit.WhiteLabel/Navigation/NavigationExtensions.cs +++ b/Softeq.XToolkit.WhiteLabel/Navigation/NavigationExtensions.cs @@ -8,6 +8,9 @@ namespace Softeq.XToolkit.WhiteLabel.Navigation { + /// + /// Extension methods regarding navigation. + /// public static class NavigationExtensions { /// diff --git a/Softeq.XToolkit.WhiteLabel/Navigation/NavigationParameterModel.cs b/Softeq.XToolkit.WhiteLabel/Navigation/NavigationParameterModel.cs index ee0d39ee3..09b70488a 100644 --- a/Softeq.XToolkit.WhiteLabel/Navigation/NavigationParameterModel.cs +++ b/Softeq.XToolkit.WhiteLabel/Navigation/NavigationParameterModel.cs @@ -16,7 +16,7 @@ public class NavigationParameterModel /// Model representing information about /// a property that should store . /// - public NavigationParameterModel(object? value, PropertyInfoModel propertyInfo) + public NavigationParameterModel(object? value, NavigationPropertyInfo? propertyInfo) { Value = value; PropertyInfo = propertyInfo; @@ -29,8 +29,8 @@ public NavigationParameterModel(object? value, PropertyInfoModel propertyInfo) /// /// Gets the model representing information about - /// a property that should store . + /// a property that should store . /// - public PropertyInfoModel PropertyInfo { get; } + public NavigationPropertyInfo? PropertyInfo { get; } } -} \ No newline at end of file +} diff --git a/Softeq.XToolkit.WhiteLabel/Navigation/PropertyInfoModel.cs b/Softeq.XToolkit.WhiteLabel/Navigation/NavigationPropertyInfo.cs similarity index 82% rename from Softeq.XToolkit.WhiteLabel/Navigation/PropertyInfoModel.cs rename to Softeq.XToolkit.WhiteLabel/Navigation/NavigationPropertyInfo.cs index cd1b0f083..1fa778463 100644 --- a/Softeq.XToolkit.WhiteLabel/Navigation/PropertyInfoModel.cs +++ b/Softeq.XToolkit.WhiteLabel/Navigation/NavigationPropertyInfo.cs @@ -11,10 +11,10 @@ namespace Softeq.XToolkit.WhiteLabel.Navigation /// that contains only property and type names. /// Used for serialization. /// - public class PropertyInfoModel + public class NavigationPropertyInfo { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The string containing the name of the public property. /// @@ -24,21 +24,12 @@ public class PropertyInfoModel /// If the type is in the currently executing assembly or in mscorlib.dll/System.Private.CoreLib.dll, /// it is sufficient to supply the type name qualified by its namespace. /// - public PropertyInfoModel(string? propertyName, string? assemblyQualifiedTypeName) + public NavigationPropertyInfo(string? propertyName, string? assemblyQualifiedTypeName) { PropertyName = propertyName ?? string.Empty; AssemblyQualifiedTypeName = assemblyQualifiedTypeName ?? string.Empty; } - /// - /// Initializes a new instance of the class. - /// - /// Property information. - public PropertyInfoModel(PropertyInfo propertyInfo) - : this(propertyInfo?.Name, propertyInfo?.DeclaringType?.AssemblyQualifiedName) - { - } - /// /// Gets the name of the public property. /// @@ -53,6 +44,18 @@ public PropertyInfoModel(PropertyInfo propertyInfo) /// public string AssemblyQualifiedTypeName { get; } + /// + /// Creates a new instance of the class. + /// + /// Property information. + /// New instance of the class. + public static NavigationPropertyInfo FromPropertyInfo(PropertyInfo? propertyInfo) + { + return new NavigationPropertyInfo( + propertyInfo?.Name, + propertyInfo?.DeclaringType?.AssemblyQualifiedName); + } + /// /// Converts current model to the . /// @@ -64,8 +67,8 @@ public PropertyInfoModel(PropertyInfo propertyInfo) /// public PropertyInfo ToPropertyInfo() { - Type type; - PropertyInfo propertyInfo; + Type? type; + PropertyInfo? propertyInfo; try { diff --git a/Softeq.XToolkit.WhiteLabel/Services/DefaultJsonSerializer.cs b/Softeq.XToolkit.WhiteLabel/Services/DefaultJsonSerializer.cs index 7ff3ac023..929c14eee 100644 --- a/Softeq.XToolkit.WhiteLabel/Services/DefaultJsonSerializer.cs +++ b/Softeq.XToolkit.WhiteLabel/Services/DefaultJsonSerializer.cs @@ -8,14 +8,15 @@ using System.Text.Json.Serialization; using System.Threading.Tasks; using Softeq.XToolkit.Common.Interfaces; -using JsonSerializer = System.Text.Json.JsonSerializer; +using Softeq.XToolkit.WhiteLabel.Navigation; namespace Softeq.XToolkit.WhiteLabel.Services; /// -/// A implementing using the System.Text.Json APIs. +/// A implementing and +/// using the System.Text.Json APIs. /// -public class DefaultJsonSerializer : IJsonSerializer +public class DefaultJsonSerializer : IJsonSerializer, INavigationSerializer { private readonly JsonSerializerOptions? _options; @@ -45,14 +46,14 @@ public DefaultJsonSerializer(JsonSerializerOptions? options = null) PropertyNameCaseInsensitive = false }; - /// + /// public string Serialize(object value) { return JsonSerializer.Serialize(value, _options); } - /// - public TResult? Deserialize(string? value) + /// + public TResult? Deserialize(string value) { if (value == null) { @@ -67,6 +68,22 @@ public string Serialize(object value) return JsonSerializer.Deserialize(value, _options); } + /// + public object? Deserialize(object? value, Type returnType) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (value is not JsonElement jsonElement) + { + throw new NotSupportedException("Value can't be deserialized by this serializer."); + } + + return jsonElement.Deserialize(returnType, DefaultOptions); + } + /// public Task SerializeAsync(object value, Stream stream) { @@ -74,7 +91,7 @@ public Task SerializeAsync(object value, Stream stream) } /// - public async Task DeserializeAsync(Stream? stream) + public async Task DeserializeAsync(Stream stream) { if (stream == null) { diff --git a/Softeq.XToolkit.WhiteLabel/Services/NewtonsoftJsonSerializer.cs b/Softeq.XToolkit.WhiteLabel/Services/NewtonsoftJsonSerializer.cs deleted file mode 100644 index bbc76890b..000000000 --- a/Softeq.XToolkit.WhiteLabel/Services/NewtonsoftJsonSerializer.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.IO; -using System.Threading.Tasks; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; -using Softeq.XToolkit.Common.Interfaces; - -namespace Softeq.XToolkit.WhiteLabel.Services -{ - /// - /// A implementing using the Newtonsoft.Json APIs. - /// - public class NewtonsoftJsonSerializer : IJsonSerializer - { - private readonly JsonSerializerSettings _settings; - - /// - /// Initializes a new instance of the class with the specified parameters. - /// - /// The serialization settings to use for the current instance. - public NewtonsoftJsonSerializer(JsonSerializerSettings? settings = null) - { - _settings = settings ?? DefaultSettings; - } - - /// - /// Gets default serialization settings. - /// - public static JsonSerializerSettings DefaultSettings { get; } = new JsonSerializerSettings - { - Formatting = Formatting.None, - TypeNameHandling = TypeNameHandling.None, - NullValueHandling = NullValueHandling.Ignore, - ContractResolver = new DefaultContractResolver - { - NamingStrategy = new CamelCaseNamingStrategy() - }, - DateFormatHandling = DateFormatHandling.IsoDateFormat, - DateTimeZoneHandling = DateTimeZoneHandling.Utc - }; - - /// - public string Serialize(object value) - { - return JsonConvert.SerializeObject(value, _settings); - } - - /// - public T? Deserialize(string value) - { - return JsonConvert.DeserializeObject(value, _settings); - } - - /// - public Task SerializeAsync(object value, Stream stream) - { - return Task.Run(() => - { - using (var streamWriter = new StreamWriter(stream)) - using (var jsonTextWriter = new JsonTextWriter(streamWriter)) - { - var serializer = JsonSerializer.Create(_settings); - serializer.Serialize(jsonTextWriter, value); - } - }); - } - - /// - public Task DeserializeAsync(Stream stream) - { - return Task.Run(() => - { - using (var streamReader = new StreamReader(stream)) - using (var jsonTextReader = new JsonTextReader(streamReader)) - { - var serializer = JsonSerializer.Create(_settings); - return serializer.Deserialize(jsonTextReader)!; - } - }); - } - } -} diff --git a/Softeq.XToolkit.WhiteLabel/Softeq.XToolkit.WhiteLabel.csproj b/Softeq.XToolkit.WhiteLabel/Softeq.XToolkit.WhiteLabel.csproj index 21491c8fd..8e06b1132 100644 --- a/Softeq.XToolkit.WhiteLabel/Softeq.XToolkit.WhiteLabel.csproj +++ b/Softeq.XToolkit.WhiteLabel/Softeq.XToolkit.WhiteLabel.csproj @@ -11,7 +11,6 @@ - diff --git a/samples/Playground/Playground.iOS/Playground.iOS.csproj b/samples/Playground/Playground.iOS/Playground.iOS.csproj index 657589b2d..817447169 100644 --- a/samples/Playground/Playground.iOS/Playground.iOS.csproj +++ b/samples/Playground/Playground.iOS/Playground.iOS.csproj @@ -61,7 +61,6 @@ - From 0f589bba63b3844990b9506514ee5d928073b08f Mon Sep 17 00:00:00 2001 From: Yauheni Pakala Date: Wed, 2 Aug 2023 16:53:26 +0200 Subject: [PATCH 12/26] Update CI environment (#530) --- azure-pipelines/templates/vars.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines/templates/vars.yml b/azure-pipelines/templates/vars.yml index 97c2374ff..4a2c9f8c3 100644 --- a/azure-pipelines/templates/vars.yml +++ b/azure-pipelines/templates/vars.yml @@ -3,6 +3,6 @@ # https://github.com/actions/runner-images variables: - XCODE_VERSION: 14.2 + XCODE_VERSION: 14.3.1 DOTNET_SDK_VERSION: 6.0.402 - MACOS_VM_IMAGE: 'macos-12' + MACOS_VM_IMAGE: 'macos-13' From da8d58b463d27b567930cedc7d542d7152d024a3 Mon Sep 17 00:00:00 2001 From: Yauheni Pakala Date: Wed, 2 Aug 2023 23:30:09 +0200 Subject: [PATCH 13/26] Migrate Common.iOS & Common.Droid Tests to .NET6 (#525) --- .../Assets/xunit.runner.json | 5 - .../Converters/VisibilityConverterTests.cs | 80 ------ .../DroidMainThreadExecutorTests.cs | 34 --- .../ContextExtensionsTests.cs | 34 --- .../EditTextExtensionsTests.cs | 94 ------- .../MockInputFilter.cs | 16 -- .../StringExtensionsTests.cs | 230 ------------------ Softeq.XToolkit.Common.Droid.Tests/Helpers.cs | 29 --- .../MainActivity.cs | 43 ---- .../MauiProgram.cs | 31 +++ .../Platforms/Android/AndroidManifest.xml | 6 + .../Converters/VisibilityConverterTests.cs | 79 ++++++ .../DroidMainThreadExecutorTests.cs | 33 +++ .../ContextExtensionsTests.cs | 33 +++ .../EditTextExtensionsTests.cs | 93 +++++++ .../MockInputFilter.cs | 16 ++ .../StringExtensionsDataProvider.cs | 0 .../StringExtensionsTests.cs | 229 +++++++++++++++++ .../Platforms/Android/Helpers.cs | 28 +++ .../Platforms/Android/MainActivity.cs | 24 ++ .../Platforms/Android/MainApplication.cs | 21 ++ .../Android/Resources/values/colors.xml | 6 + .../ForbiddenCharsInputFilterTests.cs | 101 ++++++++ .../Properties/AndroidManifest.xml | 5 - .../Properties/launchSettings.json | 8 + .../Resources/AppIcon/appicon.svg | 4 + .../Resources/AppIcon/appiconfg.svg | 8 + .../Resources/Raw/AboutAssets.txt | 15 ++ .../Resources/Splash/splash.svg | 8 + .../Resources/mipmap-mdpi/ic_launcher.png | Bin 1362 -> 0 bytes .../Resources/mipmap-xhdpi/ic_launcher.png | Bin 2307 -> 0 bytes .../Resources/mipmap-xxhdpi/ic_launcher.png | Bin 3871 -> 0 bytes .../Resources/mipmap-xxxhdpi/ic_launcher.png | Bin 5016 -> 0 bytes .../Resources/values/strings.xml | 4 - .../Softeq.XToolkit.Common.Droid.Tests.csproj | 83 ++++--- .../ForbiddenCharsInputFilterTests.cs | 102 -------- .../AppDelegate.cs | 37 --- .../Entitlements.plist | 6 - .../NSDateExtensionsTests.cs | 78 ------ .../NSLocaleExtensionsTests.cs | 43 ---- .../NSRangeExtensionsTests.cs | 48 ---- .../NSStringExtensionsTests.cs | 193 --------------- .../UIColorExtensionsTests.cs | 70 ------ .../UITextViewExtensionsTests.cs | 106 -------- Softeq.XToolkit.Common.iOS.Tests/Helpers.cs | 30 --- Softeq.XToolkit.Common.iOS.Tests/Info.plist | 38 --- .../IosMainThreadExecutorTests.cs | 34 --- .../LaunchScreen.storyboard | 27 -- Softeq.XToolkit.Common.iOS.Tests/Main.cs | 17 -- .../MauiProgram.cs | 31 +++ .../Platforms/iOS/AppDelegate.cs | 14 ++ .../NSDateExtensionsTests.cs | 78 ++++++ .../NSLocaleExtensionsTests.cs | 42 ++++ .../NSStringExtensionsTests.cs | 193 +++++++++++++++ .../NSRangeExtensionsTests.cs | 47 ++++ .../UIColorExtensionsTests.cs | 69 ++++++ .../UITextViewExtensionsTests.cs | 105 ++++++++ .../Platforms/iOS/Helpers.cs | 29 +++ .../Platforms/iOS/Info.plist | 32 +++ .../IosMainThreadExecutorTests.cs | 33 +++ .../Platforms/iOS/Program.cs | 17 ++ .../ForbiddenCharsFilterTests.cs | 91 +++++++ .../GroupFilterTests/GroupFilterTests.cs | 65 +++++ .../GroupFilterTests/MockTextFilter.cs | 23 ++ .../LengthFilterTests/LengthFilterTests.cs | 115 +++++++++ .../Properties/launchSettings.json | 8 + .../Resources/AppIcon/appicon.svg | 4 + .../Resources/AppIcon/appiconfg.svg | 8 + .../Resources/Raw/AboutAssets.txt | 15 ++ .../Resources/Splash/splash.svg | 8 + .../Softeq.XToolkit.Common.iOS.Tests.csproj | 89 +++---- .../ForbiddenCharsFilterTests.cs | 92 ------- .../GroupFilterTests/GroupFilterTests.cs | 66 ----- .../GroupFilterTests/MockTextFilter.cs | 24 -- .../LengthFilterTests/LengthFilterTests.cs | 116 --------- XToolkit.sln | 26 +- _Tests.Platform.targets | 3 +- 77 files changed, 1886 insertions(+), 1786 deletions(-) delete mode 100644 Softeq.XToolkit.Common.Droid.Tests/Assets/xunit.runner.json delete mode 100644 Softeq.XToolkit.Common.Droid.Tests/Converters/VisibilityConverterTests.cs delete mode 100644 Softeq.XToolkit.Common.Droid.Tests/DroidMainThreadExecutorTests/DroidMainThreadExecutorTests.cs delete mode 100644 Softeq.XToolkit.Common.Droid.Tests/Extensions/ContextExtensionsTests/ContextExtensionsTests.cs delete mode 100644 Softeq.XToolkit.Common.Droid.Tests/Extensions/EditTextExtensionsTests/EditTextExtensionsTests.cs delete mode 100644 Softeq.XToolkit.Common.Droid.Tests/Extensions/EditTextExtensionsTests/MockInputFilter.cs delete mode 100644 Softeq.XToolkit.Common.Droid.Tests/Extensions/StringExtensionsTests/StringExtensionsTests.cs delete mode 100644 Softeq.XToolkit.Common.Droid.Tests/Helpers.cs delete mode 100644 Softeq.XToolkit.Common.Droid.Tests/MainActivity.cs create mode 100644 Softeq.XToolkit.Common.Droid.Tests/MauiProgram.cs create mode 100644 Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/AndroidManifest.xml create mode 100644 Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Converters/VisibilityConverterTests.cs create mode 100644 Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/DroidMainThreadExecutorTests/DroidMainThreadExecutorTests.cs create mode 100644 Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Extensions/ContextExtensionsTests/ContextExtensionsTests.cs create mode 100644 Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Extensions/EditTextExtensionsTests/EditTextExtensionsTests.cs create mode 100644 Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Extensions/EditTextExtensionsTests/MockInputFilter.cs rename Softeq.XToolkit.Common.Droid.Tests/{ => Platforms/Android}/Extensions/StringExtensionsTests/StringExtensionsDataProvider.cs (100%) create mode 100644 Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Extensions/StringExtensionsTests/StringExtensionsTests.cs create mode 100644 Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Helpers.cs create mode 100644 Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/MainActivity.cs create mode 100644 Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/MainApplication.cs create mode 100644 Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Resources/values/colors.xml create mode 100644 Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/TextFilters/ForbiddenCharsInputFilterTests.cs delete mode 100644 Softeq.XToolkit.Common.Droid.Tests/Properties/AndroidManifest.xml create mode 100644 Softeq.XToolkit.Common.Droid.Tests/Properties/launchSettings.json create mode 100644 Softeq.XToolkit.Common.Droid.Tests/Resources/AppIcon/appicon.svg create mode 100644 Softeq.XToolkit.Common.Droid.Tests/Resources/AppIcon/appiconfg.svg create mode 100644 Softeq.XToolkit.Common.Droid.Tests/Resources/Raw/AboutAssets.txt create mode 100644 Softeq.XToolkit.Common.Droid.Tests/Resources/Splash/splash.svg delete mode 100644 Softeq.XToolkit.Common.Droid.Tests/Resources/mipmap-mdpi/ic_launcher.png delete mode 100644 Softeq.XToolkit.Common.Droid.Tests/Resources/mipmap-xhdpi/ic_launcher.png delete mode 100644 Softeq.XToolkit.Common.Droid.Tests/Resources/mipmap-xxhdpi/ic_launcher.png delete mode 100644 Softeq.XToolkit.Common.Droid.Tests/Resources/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 Softeq.XToolkit.Common.Droid.Tests/Resources/values/strings.xml delete mode 100644 Softeq.XToolkit.Common.Droid.Tests/TextFilters/ForbiddenCharsInputFilterTests.cs delete mode 100644 Softeq.XToolkit.Common.iOS.Tests/AppDelegate.cs delete mode 100644 Softeq.XToolkit.Common.iOS.Tests/Entitlements.plist delete mode 100644 Softeq.XToolkit.Common.iOS.Tests/Extensions/NSDateExtensionsTests/NSDateExtensionsTests.cs delete mode 100644 Softeq.XToolkit.Common.iOS.Tests/Extensions/NSLocaleExtensionsTests/NSLocaleExtensionsTests.cs delete mode 100644 Softeq.XToolkit.Common.iOS.Tests/Extensions/NSRangeExtensionsTests/NSRangeExtensionsTests.cs delete mode 100644 Softeq.XToolkit.Common.iOS.Tests/Extensions/NSStringExtensions/NSStringExtensionsTests.cs delete mode 100644 Softeq.XToolkit.Common.iOS.Tests/Extensions/UIColorExtensionsTests/UIColorExtensionsTests.cs delete mode 100644 Softeq.XToolkit.Common.iOS.Tests/Extensions/UITextViewExtensionsTests/UITextViewExtensionsTests.cs delete mode 100644 Softeq.XToolkit.Common.iOS.Tests/Helpers.cs delete mode 100644 Softeq.XToolkit.Common.iOS.Tests/Info.plist delete mode 100644 Softeq.XToolkit.Common.iOS.Tests/IosMainThreadExecutorTests/IosMainThreadExecutorTests.cs delete mode 100644 Softeq.XToolkit.Common.iOS.Tests/LaunchScreen.storyboard delete mode 100644 Softeq.XToolkit.Common.iOS.Tests/Main.cs create mode 100644 Softeq.XToolkit.Common.iOS.Tests/MauiProgram.cs create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/AppDelegate.cs create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/NSDateExtensionsTests/NSDateExtensionsTests.cs create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/NSLocaleExtensionsTests/NSLocaleExtensionsTests.cs create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/NSStringExtensions/NSStringExtensionsTests.cs create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/NsRangeExtensionsTests/NSRangeExtensionsTests.cs create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/UIColorExtensionsTests/UIColorExtensionsTests.cs create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/UITextViewExtensionsTests/UITextViewExtensionsTests.cs create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Helpers.cs create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Info.plist create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/IosMainThreadExecutorTests/IosMainThreadExecutorTests.cs create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Program.cs create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/TextFilters/ForbiddenCharsFilterTests/ForbiddenCharsFilterTests.cs create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/TextFilters/GroupFilterTests/GroupFilterTests.cs create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/TextFilters/GroupFilterTests/MockTextFilter.cs create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/TextFilters/LengthFilterTests/LengthFilterTests.cs create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Properties/launchSettings.json create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Resources/AppIcon/appicon.svg create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Resources/AppIcon/appiconfg.svg create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Resources/Raw/AboutAssets.txt create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Resources/Splash/splash.svg delete mode 100644 Softeq.XToolkit.Common.iOS.Tests/TextFilters/ForbiddenCharsFilterTests/ForbiddenCharsFilterTests.cs delete mode 100644 Softeq.XToolkit.Common.iOS.Tests/TextFilters/GroupFilterTests/GroupFilterTests.cs delete mode 100644 Softeq.XToolkit.Common.iOS.Tests/TextFilters/GroupFilterTests/MockTextFilter.cs delete mode 100644 Softeq.XToolkit.Common.iOS.Tests/TextFilters/LengthFilterTests/LengthFilterTests.cs diff --git a/Softeq.XToolkit.Common.Droid.Tests/Assets/xunit.runner.json b/Softeq.XToolkit.Common.Droid.Tests/Assets/xunit.runner.json deleted file mode 100644 index 426a7cdfb..000000000 --- a/Softeq.XToolkit.Common.Droid.Tests/Assets/xunit.runner.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "diagnosticMessages": true, - "longRunningTestSeconds": 30, - "methodDisplay": "classAndMethod" -} \ No newline at end of file diff --git a/Softeq.XToolkit.Common.Droid.Tests/Converters/VisibilityConverterTests.cs b/Softeq.XToolkit.Common.Droid.Tests/Converters/VisibilityConverterTests.cs deleted file mode 100644 index 9f39b98b3..000000000 --- a/Softeq.XToolkit.Common.Droid.Tests/Converters/VisibilityConverterTests.cs +++ /dev/null @@ -1,80 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using Android.Views; -using Softeq.XToolkit.Common.Droid.Converters; -using Xunit; - -namespace Softeq.XToolkit.Common.Droid.Tests.Converters -{ - public class VisibilityConverterTests - { - [Fact] - public void Gone_ReturnsNotNull() - { - Assert.NotNull(VisibilityConverter.Gone); - } - - [Fact] - public void Invisible_ReturnsNotNull() - { - Assert.NotNull(VisibilityConverter.Invisible); - } - - [Fact] - public void Instance_ReturnsInvisible() - { - Assert.Same(VisibilityConverter.Invisible, VisibilityConverter.Instance); - } - - [Theory] - [InlineData(true, ViewStates.Visible)] - [InlineData(false, ViewStates.Gone)] - public void ConvertValue_Gone_ReturnsExpectedValue(bool value, ViewStates expectedResult) - { - var converter = VisibilityConverter.Gone; - - var result = converter.ConvertValue(value); - - Assert.Equal(expectedResult, result); - } - - [Theory] - [InlineData(true, ViewStates.Visible)] - [InlineData(false, ViewStates.Invisible)] - public void ConvertValue_Invisible_ReturnsExpectedValue(bool value, ViewStates expectedResult) - { - var converter = VisibilityConverter.Invisible; - - var result = converter.ConvertValue(value); - - Assert.Equal(expectedResult, result); - } - - [Theory] - [InlineData(ViewStates.Visible, true)] - [InlineData(ViewStates.Invisible, false)] - [InlineData(ViewStates.Gone, false)] - public void ConvertValueBack_Gone_ReturnsExpectedValue(ViewStates value, bool expectedResult) - { - var converter = VisibilityConverter.Gone; - - var result = converter.ConvertValueBack(value); - - Assert.Equal(expectedResult, result); - } - - [Theory] - [InlineData(ViewStates.Visible, true)] - [InlineData(ViewStates.Invisible, false)] - [InlineData(ViewStates.Gone, false)] - public void ConvertValueBack_Invisible_ReturnsExpectedValue(ViewStates value, bool expectedResult) - { - var converter = VisibilityConverter.Invisible; - - var result = converter.ConvertValueBack(value); - - Assert.Equal(expectedResult, result); - } - } -} diff --git a/Softeq.XToolkit.Common.Droid.Tests/DroidMainThreadExecutorTests/DroidMainThreadExecutorTests.cs b/Softeq.XToolkit.Common.Droid.Tests/DroidMainThreadExecutorTests/DroidMainThreadExecutorTests.cs deleted file mode 100644 index ff76de6f2..000000000 --- a/Softeq.XToolkit.Common.Droid.Tests/DroidMainThreadExecutorTests/DroidMainThreadExecutorTests.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Threading.Tasks; -using Xunit; - -namespace Softeq.XToolkit.Common.Droid.Tests.DroidMainThreadExecutorTests -{ - public class DroidMainThreadExecutorTests - { - private readonly DroidMainThreadExecutor _executor; - - public DroidMainThreadExecutorTests() - { - _executor = new DroidMainThreadExecutor(); - } - - [Fact] - public async Task IsMainThread_InMainThread_ReturnsTrue() - { - var result = await Helpers.RunOnUIThreadAsync(() => _executor.IsMainThread); - - Assert.True(result); - } - - [Fact] - public async Task IsMainThread_NotInMainThread_ReturnsFalse() - { - var result = await Task.Run(() => _executor.IsMainThread); - - Assert.False(result); - } - } -} diff --git a/Softeq.XToolkit.Common.Droid.Tests/Extensions/ContextExtensionsTests/ContextExtensionsTests.cs b/Softeq.XToolkit.Common.Droid.Tests/Extensions/ContextExtensionsTests/ContextExtensionsTests.cs deleted file mode 100644 index cb8c1fe28..000000000 --- a/Softeq.XToolkit.Common.Droid.Tests/Extensions/ContextExtensionsTests/ContextExtensionsTests.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using Android.Content; -using Softeq.XToolkit.Common.Droid.Extensions; -using Xunit; - -namespace Softeq.XToolkit.Common.Droid.Tests.Extensions.ContextExtensionsTests -{ - public class ContextExtensionsTests - { - private readonly Context _context = MainActivity.Current; - - [Theory] - [InlineData(-1)] - [InlineData(0)] - [InlineData(10)] - public void PxToDp_DpToPx_Equivalence(double pixels) - { - var resultDp = _context.PxToDp(pixels); - var resultPx = _context.DpToPx(resultDp); - - Assert.Equal(pixels, resultPx); - } - - [Fact] - public void GetStatusBarHeight_Default_PositiveValue() - { - var result = _context.GetStatusBarHeight(); - - Assert.True(result > 0); - } - } -} diff --git a/Softeq.XToolkit.Common.Droid.Tests/Extensions/EditTextExtensionsTests/EditTextExtensionsTests.cs b/Softeq.XToolkit.Common.Droid.Tests/Extensions/EditTextExtensionsTests/EditTextExtensionsTests.cs deleted file mode 100644 index bdd1ab4fc..000000000 --- a/Softeq.XToolkit.Common.Droid.Tests/Extensions/EditTextExtensionsTests/EditTextExtensionsTests.cs +++ /dev/null @@ -1,94 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using Android.Text; -using Android.Widget; -using Softeq.XToolkit.Common.Droid.Extensions; -using Xunit; - -namespace Softeq.XToolkit.Common.Droid.Tests.Extensions.EditTextExtensionsTests -{ - public class EditTextExtensionsTests - { - [Fact] - public void SetFilters_WhenCalledWithoutFilters_DoNothing() - { - EditText editText = new EditText(MainActivity.Current); - editText.SetFilters(); - - var appliedFilters = editText.GetFilters(); - if (appliedFilters != null) - { - Assert.Empty(appliedFilters); - } - } - - [Fact] - public void SetFilters_WhenCalledWithMultipleFilters_AppliesAllFilters() - { - EditText editText = new EditText(MainActivity.Current); - - var filter1 = new MockInputFilter(); - var filter2 = new MockInputFilter(); - var filter3 = new MockInputFilter(); - var filters = new IInputFilter[] { filter1, filter2, filter3 }; - - editText.SetFilters(filter1, filter2, filter3); - - var appliedFilters = editText.GetFilters(); - Assert.Equal(filters, appliedFilters); - } - - [Fact] - public void SetFilters_WhenCalledWithMultipleFiltersWithNullAndDuplicates_AppliesAllFilters() - { - EditText editText = new EditText(MainActivity.Current); - - var filter1 = new MockInputFilter(); - var filter2 = new MockInputFilter(); - var filter3 = new MockInputFilter(); - var nullFilter = null as IInputFilter; - var filters = new[] { filter1, filter2, nullFilter, filter3, filter2, nullFilter, filter1, filter2 }; - - editText.SetFilters(filter1, filter2, nullFilter!, filter3, filter2, nullFilter!, filter1, filter2); - - var appliedFilters = editText.GetFilters(); - Assert.Equal(filters, appliedFilters); - } - - [Theory] - [InlineData("")] - [InlineData("a")] - [InlineData("abcd")] - [InlineData("abcdefg")] - public void KeepFocusAtTheEndOfField_WhenTextChangedWithoutFocus_WhenFocused_MovesCursorToTheEndOfField(string str) - { - EditText editText = new EditText(MainActivity.Current); - editText.ClearFocus(); - - editText.Text = str; - editText.RequestFocus(); - - Assert.Equal(editText.SelectionEnd, editText.SelectionStart); - Assert.Equal(editText.SelectionStart, str.Length); - } - - [Theory] - [InlineData("")] - [InlineData("a")] - [InlineData("abcd")] - [InlineData("abcdefg")] - public void KeepFocusAtTheEndOfField_WhenTextChangedWithFocus_WhenRefocused_MovesCursorToTheEndOfField(string str) - { - EditText editText = new EditText(MainActivity.Current); - editText.RequestFocus(); - - editText.Text = str; - editText.ClearFocus(); - editText.RequestFocus(); - - Assert.Equal(editText.SelectionEnd, editText.SelectionStart); - Assert.Equal(editText.SelectionStart, str.Length); - } - } -} diff --git a/Softeq.XToolkit.Common.Droid.Tests/Extensions/EditTextExtensionsTests/MockInputFilter.cs b/Softeq.XToolkit.Common.Droid.Tests/Extensions/EditTextExtensionsTests/MockInputFilter.cs deleted file mode 100644 index eeba9693e..000000000 --- a/Softeq.XToolkit.Common.Droid.Tests/Extensions/EditTextExtensionsTests/MockInputFilter.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using Android.Text; -using Java.Lang; - -namespace Softeq.XToolkit.Common.Droid.Tests.Extensions.EditTextExtensionsTests -{ - public class MockInputFilter : Object, IInputFilter - { - public ICharSequence? FilterFormatted(ICharSequence? source, int start, int end, ISpanned? dest, int dstart, int dend) - { - return null; - } - } -} diff --git a/Softeq.XToolkit.Common.Droid.Tests/Extensions/StringExtensionsTests/StringExtensionsTests.cs b/Softeq.XToolkit.Common.Droid.Tests/Extensions/StringExtensionsTests/StringExtensionsTests.cs deleted file mode 100644 index c3f1e25c7..000000000 --- a/Softeq.XToolkit.Common.Droid.Tests/Extensions/StringExtensionsTests/StringExtensionsTests.cs +++ /dev/null @@ -1,230 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System; -using System.Linq; -using Android.Text; -using Android.Text.Style; -using Softeq.XToolkit.Common.Droid.Extensions; -using Softeq.XToolkit.Common.Helpers; -using Xunit; -using Object = Java.Lang.Object; - -namespace Softeq.XToolkit.Common.Droid.Tests.Extensions.StringExtensionsTests -{ - public class StringExtensionsTests - { - #region Null string - - [Theory] - [MemberData( - nameof(StringExtensionsDataProvider.FormatSpannableNullStringWithRangeAndSpansTestData), - MemberType = typeof(StringExtensionsDataProvider))] - public void FormatSpannable_WhenCalledOnNullString_WithAnyTextRange_WithSpans_ThrowsCorrectException( - TextRange textRange, Object[] spans) - { - var str = null as string; - Assert.Throws(() => str!.FormatSpannable(textRange, spans)); - } - - [Theory] - [MemberData( - nameof(StringExtensionsDataProvider.FormatSpannableNullStringWithoutSpansTestData), - MemberType = typeof(StringExtensionsDataProvider))] - public void FormatSpannable_WhenCalledOnNullString_WithAnyTextRange_WithoutSpans_ThrowsCorrectException( - TextRange textRange) - { - var str = null as string; - Assert.Throws(() => str!.FormatSpannable(textRange)); - } - - [Theory] - [MemberData( - nameof(StringExtensionsDataProvider.FormatSpannableNullStringWithSpansOnlyTestData), - MemberType = typeof(StringExtensionsDataProvider))] - public void FormatSpannable_WhenCalledOnNullString_WithoutTextRange_WithSpans_ThrowsCorrectException( - Object[] spans) - { - var str = null as string; - Assert.Throws(() => str!.FormatSpannable(spans)); - } - - [Fact] - public void FormatSpannable_WhenCalledOnNullString_WithoutTextRange_WithoutSpans_ThrowsCorrectException() - { - var str = null as string; - Assert.Throws(() => str!.FormatSpannable()); - } - - #endregion - - [Theory] - [MemberData( - nameof(StringExtensionsDataProvider.FormatSpannableStringsTestData), - MemberType = typeof(StringExtensionsDataProvider))] - public void FormatSpannable_WhenCalledOnCorrectString_WithNullTextRange_ThrowsCorrectException( - string str) - { - var spans = new Object[] { }; - Assert.Throws(() => str.FormatSpannable(StringExtensionsDataProvider.NullTextRange, spans)); - } - - [Theory] - [MemberData( - nameof(StringExtensionsDataProvider.FormatSpannableWithNonNullTextRangeWithoutSpansTestData), - MemberType = typeof(StringExtensionsDataProvider))] - public void FormatSpannable_WhenCalledOnCorrectString_WithNonNullTextRange_WithoutSpans_ThrowsCorrectException( - string str, TextRange textRange) - { - var spans = new Object[] { }; - Assert.Throws(() => str.FormatSpannable(textRange, spans)); - } - - [Theory] - [MemberData( - nameof(StringExtensionsDataProvider.FormatSpannableWithIncorrectTextRangeWithSpansTestData), - MemberType = typeof(StringExtensionsDataProvider))] - public void FormatSpannable_WhenCalledOnCorrectString_WithIncorrectTextRange_WithSpans_ThrowsCorrectException( - string str, TextRange textRange, Object[] spans) - { - Assert.Throws(() => str.FormatSpannable(textRange, spans)); - } - - [Theory] - [MemberData( - nameof(StringExtensionsDataProvider.FormatSpannableWithCorrectTextRangeWithStyleSpansTestData), - MemberType = typeof(StringExtensionsDataProvider))] - public void FormatSpannable_WhenCalledOnCorrectString_WithCorrectTextRange_WithStyleSpans_AppliesSpans( - string str, TextRange textRange, Object[] spans) - { - var spannable = str.FormatSpannable(textRange, spans); - var appliedSpans = GetSpans(spannable, textRange); - - AssertAppliedSpans(spans, appliedSpans); - AssertNoSpansOutsideSpecifiedIntervalApplied(spannable, textRange); - } - - [Theory] - [MemberData( - nameof(StringExtensionsDataProvider.FormatSpannableWithCorrectTextRangeWithForegroundColorSpansTestData), - MemberType = typeof(StringExtensionsDataProvider))] - public void FormatSpannable_WhenCalledOnCorrectString_WithCorrectTextRange_WithForegroundColorSpans_AppliesSpans( - string str, TextRange textRange, Object[] spans) - { - var spannable = str.FormatSpannable(textRange, spans); - var appliedSpans = GetSpans(spannable, textRange); - - AssertAppliedSpans(spans, appliedSpans); - AssertNoSpansOutsideSpecifiedIntervalApplied(spannable, textRange); - } - - [Theory] - [MemberData( - nameof(StringExtensionsDataProvider.FormatSpannableWithCorrectTextRangeWithDifferentSpansTestData), - MemberType = typeof(StringExtensionsDataProvider))] - public void FormatSpannable_WhenCalledOnCorrectString_WithCorrectTextRange_WithDifferentSpans_AppliesSpans( - string str, TextRange textRange, Object[] spans) - { - var spannable = str.FormatSpannable(textRange, spans); - var appliedStyleSpans = GetSpans(spannable, textRange); - var appliedForegroundColorSpans = GetSpans(spannable, textRange); - var appliedSpans = appliedStyleSpans.Concat(appliedForegroundColorSpans).ToArray(); - - AssertAppliedSpans(spans, appliedSpans); - AssertNoSpansOutsideSpecifiedIntervalApplied(spannable, textRange); - AssertNoSpansOutsideSpecifiedIntervalApplied(spannable, textRange); - } - - [Theory] - [MemberData( - nameof(StringExtensionsDataProvider.FormatSpannableWithoutTextRangeWithStyleSpansTestData), - MemberType = typeof(StringExtensionsDataProvider))] - public void FormatSpannable_WhenCalledOnCorrectString_WithoutTextRange_WithStyleSpans_AppliesSpans( - string str, Object[] spans) - { - var spannable = str.FormatSpannable(spans); - var appliedSpans = GetSpans(spannable, new TextRange(0, str.Length)); - - AssertAppliedSpans(spans, appliedSpans); - } - - [Theory] - [MemberData( - nameof(StringExtensionsDataProvider.FormatSpannableWithoutTextRangeWithForegroundColorSpansTestData), - MemberType = typeof(StringExtensionsDataProvider))] - public void FormatSpannable_WhenCalledOnCorrectString_WithoutTextRange_WithForegroundColorSpans_AppliesSpans( - string str, Object[] spans) - { - var spannable = str.FormatSpannable(spans); - var appliedSpans = GetSpans(spannable, new TextRange(0, str.Length)); - - AssertAppliedSpans(spans, appliedSpans); - } - - [Theory] - [MemberData( - nameof(StringExtensionsDataProvider.FormatSpannableWithoutTextRangeWithDifferentSpansTestData), - MemberType = typeof(StringExtensionsDataProvider))] - public void FormatSpannable_WhenCalledOnCorrectString_WithoutTextRange_WithDifferentSpans_AppliesSpans( - string str, Object[] spans) - { - var spannable = str.FormatSpannable(spans); - var appliedStyleSpans = GetSpans(spannable, new TextRange(0, str.Length)); - var appliedForegroundColorSpans = GetSpans(spannable, new TextRange(0, str.Length)); - var appliedSpans = appliedStyleSpans.Concat(appliedForegroundColorSpans).ToArray(); - - AssertAppliedSpans(spans, appliedSpans); - } - - [Theory] - [MemberData( - nameof(StringExtensionsDataProvider.FormatSpannableStringsTestData), - MemberType = typeof(StringExtensionsDataProvider))] - public void FormatSpannable_WhenCalledOnCorrectString_WithoutTextRange_WithoutSpans_ThrowsCorrectException( - string str) - { - Assert.Throws(() => str.FormatSpannable()); - } - - private void AssertAppliedSpans(Object[] spans, Object[] appliedSpans) - { - var nonNullSpans = spans.Where(t => t != null).Distinct(); - var nonNullAppliedSpans = appliedSpans.Where(t => t != null); - - Assert.Equal(nonNullSpans.Count(), nonNullAppliedSpans.Count()); - foreach (var span in nonNullSpans) - { - Assert.Contains(span, appliedSpans); - } - - foreach (var appliedSpan in nonNullAppliedSpans) - { - Assert.Contains(appliedSpan, spans); - } - } - - private void AssertNoSpansOutsideSpecifiedIntervalApplied(SpannableString? spannable, TextRange textRange) - { - int spannableLength = spannable?.Length() ?? 0; - if (textRange.Position > 0) - { - var spansBefore = GetSpans(spannable, new TextRange(0, textRange.Position - 1)); - Assert.Empty(spansBefore); - } - - if (textRange.Position + textRange.Length < spannableLength - 1) - { - var spansAfter = GetSpans(spannable, new TextRange(textRange.Position + textRange.Length + 1, spannableLength - 1)); - Assert.Empty(spansAfter); - } - } - - private Object[] GetSpans(SpannableString? spannable, TextRange textRange) - { - return spannable?.GetSpans( - textRange.Position, - textRange.Position + textRange.Length, - Java.Lang.Class.FromType(typeof(T))) ?? new Object[] { }; - } - } -} diff --git a/Softeq.XToolkit.Common.Droid.Tests/Helpers.cs b/Softeq.XToolkit.Common.Droid.Tests/Helpers.cs deleted file mode 100644 index b4c3f22c0..000000000 --- a/Softeq.XToolkit.Common.Droid.Tests/Helpers.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System; -using System.Threading.Tasks; - -namespace Softeq.XToolkit.Common.Droid.Tests -{ - public static class Helpers - { - public static Task RunOnUIThreadAsync(Func func) - { - var tcs = new TaskCompletionSource(); - MainActivity.Current.RunOnUiThread(() => - { - try - { - var result = func(); - tcs.SetResult(result); - } - catch (Exception e) - { - tcs.SetException(e); - } - }); - return tcs.Task; - } - } -} diff --git a/Softeq.XToolkit.Common.Droid.Tests/MainActivity.cs b/Softeq.XToolkit.Common.Droid.Tests/MainActivity.cs deleted file mode 100644 index be02fddde..000000000 --- a/Softeq.XToolkit.Common.Droid.Tests/MainActivity.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Reflection; -using Android.App; -using Android.OS; -using Xunit.Runners.UI; -using Xunit.Sdk; - -namespace Softeq.XToolkit.Common.Droid.Tests -{ - [Activity(MainLauncher = true, Theme = "@android:style/Theme.Material.Light")] - public class MainActivity : RunnerActivity - { - public static MainActivity Current { get; private set; } = null!; - - protected override void OnCreate(Bundle bundle) - { - Current = this; - - // tests can be inside the main assembly - AddTestAssembly(Assembly.GetExecutingAssembly()); - - AddExecutionAssembly(typeof(ExtensibilityPointFactory).Assembly); - - // or in any reference assemblies - // AddTestAssembly(typeof(PortableTests).Assembly); - // or in any assembly that you load (since JIT is available) - -#if false - // you can use the default or set your own custom writer (e.g. save to web site and tweet it ;-) - Writer = new TcpTextWriter ("10.0.1.2", 16384); - // start running the test suites as soon as the application is loaded - AutoStart = true; - // crash the application (to ensure it's ended) and return to springboard - TerminateAfterExecution = true; -#endif - - // you cannot add more assemblies once calling base - base.OnCreate(bundle); - } - } -} diff --git a/Softeq.XToolkit.Common.Droid.Tests/MauiProgram.cs b/Softeq.XToolkit.Common.Droid.Tests/MauiProgram.cs new file mode 100644 index 000000000..cedd72d84 --- /dev/null +++ b/Softeq.XToolkit.Common.Droid.Tests/MauiProgram.cs @@ -0,0 +1,31 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using Microsoft.Extensions.Logging; +using Microsoft.Maui.Hosting; +using Xunit.Runners.Maui; + +namespace Softeq.XToolkit.Common.Droid.Tests; + +public static class MauiProgram +{ + public static MauiApp CreateMauiApp() + { + var builder = MauiApp + .CreateBuilder() + .ConfigureTests(new TestOptions + { + Assemblies = + { + typeof(MauiProgram).Assembly + } + }) + .UseVisualRunner(); + +#if DEBUG + builder.Logging.AddDebug(); +#endif + + return builder.Build(); + } +} diff --git a/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/AndroidManifest.xml b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/AndroidManifest.xml new file mode 100644 index 000000000..e9937ad77 --- /dev/null +++ b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Converters/VisibilityConverterTests.cs b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Converters/VisibilityConverterTests.cs new file mode 100644 index 000000000..be423e820 --- /dev/null +++ b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Converters/VisibilityConverterTests.cs @@ -0,0 +1,79 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using Android.Views; +using Softeq.XToolkit.Common.Droid.Converters; +using Xunit; + +namespace Softeq.XToolkit.Common.Droid.Tests.Converters; + +public class VisibilityConverterTests +{ + [Fact] + public void Gone_ReturnsNotNull() + { + Assert.NotNull(VisibilityConverter.Gone); + } + + [Fact] + public void Invisible_ReturnsNotNull() + { + Assert.NotNull(VisibilityConverter.Invisible); + } + + [Fact] + public void Instance_ReturnsInvisible() + { + Assert.Same(VisibilityConverter.Invisible, VisibilityConverter.Instance); + } + + [Theory] + [InlineData(true, ViewStates.Visible)] + [InlineData(false, ViewStates.Gone)] + public void ConvertValue_Gone_ReturnsExpectedValue(bool value, ViewStates expectedResult) + { + var converter = VisibilityConverter.Gone; + + var result = converter.ConvertValue(value); + + Assert.Equal(expectedResult, result); + } + + [Theory] + [InlineData(true, ViewStates.Visible)] + [InlineData(false, ViewStates.Invisible)] + public void ConvertValue_Invisible_ReturnsExpectedValue(bool value, ViewStates expectedResult) + { + var converter = VisibilityConverter.Invisible; + + var result = converter.ConvertValue(value); + + Assert.Equal(expectedResult, result); + } + + [Theory] + [InlineData(ViewStates.Visible, true)] + [InlineData(ViewStates.Invisible, false)] + [InlineData(ViewStates.Gone, false)] + public void ConvertValueBack_Gone_ReturnsExpectedValue(ViewStates value, bool expectedResult) + { + var converter = VisibilityConverter.Gone; + + var result = converter.ConvertValueBack(value); + + Assert.Equal(expectedResult, result); + } + + [Theory] + [InlineData(ViewStates.Visible, true)] + [InlineData(ViewStates.Invisible, false)] + [InlineData(ViewStates.Gone, false)] + public void ConvertValueBack_Invisible_ReturnsExpectedValue(ViewStates value, bool expectedResult) + { + var converter = VisibilityConverter.Invisible; + + var result = converter.ConvertValueBack(value); + + Assert.Equal(expectedResult, result); + } +} diff --git a/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/DroidMainThreadExecutorTests/DroidMainThreadExecutorTests.cs b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/DroidMainThreadExecutorTests/DroidMainThreadExecutorTests.cs new file mode 100644 index 000000000..c2710f329 --- /dev/null +++ b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/DroidMainThreadExecutorTests/DroidMainThreadExecutorTests.cs @@ -0,0 +1,33 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System.Threading.Tasks; +using Xunit; + +namespace Softeq.XToolkit.Common.Droid.Tests.DroidMainThreadExecutorTests; + +public class DroidMainThreadExecutorTests +{ + private readonly DroidMainThreadExecutor _executor; + + public DroidMainThreadExecutorTests() + { + _executor = new DroidMainThreadExecutor(); + } + + [Fact] + public async Task IsMainThread_InMainThread_ReturnsTrue() + { + var result = await Helpers.RunOnUIThreadAsync(() => _executor.IsMainThread); + + Assert.True(result); + } + + [Fact] + public async Task IsMainThread_NotInMainThread_ReturnsFalse() + { + var result = await Task.Run(() => _executor.IsMainThread); + + Assert.False(result); + } +} diff --git a/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Extensions/ContextExtensionsTests/ContextExtensionsTests.cs b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Extensions/ContextExtensionsTests/ContextExtensionsTests.cs new file mode 100644 index 000000000..adfeb0d9c --- /dev/null +++ b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Extensions/ContextExtensionsTests/ContextExtensionsTests.cs @@ -0,0 +1,33 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using Android.Content; +using Softeq.XToolkit.Common.Droid.Extensions; +using Xunit; + +namespace Softeq.XToolkit.Common.Droid.Tests.Extensions.ContextExtensionsTests; + +public class ContextExtensionsTests +{ + private readonly Context _context = MainActivity.Current; + + [Theory] + [InlineData(-1)] + [InlineData(0)] + [InlineData(10)] + public void PxToDp_DpToPx_Equivalence(double pixels) + { + var resultDp = _context.PxToDp(pixels); + var resultPx = _context.DpToPx(resultDp); + + Assert.Equal(pixels, resultPx); + } + + [Fact] + public void GetStatusBarHeight_Default_PositiveValue() + { + var result = _context.GetStatusBarHeight(); + + Assert.True(result > 0); + } +} diff --git a/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Extensions/EditTextExtensionsTests/EditTextExtensionsTests.cs b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Extensions/EditTextExtensionsTests/EditTextExtensionsTests.cs new file mode 100644 index 000000000..cd5f9d1af --- /dev/null +++ b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Extensions/EditTextExtensionsTests/EditTextExtensionsTests.cs @@ -0,0 +1,93 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using Android.Text; +using Android.Widget; +using Softeq.XToolkit.Common.Droid.Extensions; +using Xunit; + +namespace Softeq.XToolkit.Common.Droid.Tests.Extensions.EditTextExtensionsTests; + +public class EditTextExtensionsTests +{ + [Fact] + public void SetFilters_WhenCalledWithoutFilters_DoNothing() + { + EditText editText = new EditText(MainActivity.Current); + editText.SetFilters(); + + var appliedFilters = editText.GetFilters(); + if (appliedFilters != null) + { + Assert.Empty(appliedFilters); + } + } + + [Fact] + public void SetFilters_WhenCalledWithMultipleFilters_AppliesAllFilters() + { + EditText editText = new EditText(MainActivity.Current); + + var filter1 = new MockInputFilter(); + var filter2 = new MockInputFilter(); + var filter3 = new MockInputFilter(); + var filters = new IInputFilter[] { filter1, filter2, filter3 }; + + editText.SetFilters(filter1, filter2, filter3); + + var appliedFilters = editText.GetFilters(); + Assert.Equal(filters, appliedFilters); + } + + [Fact] + public void SetFilters_WhenCalledWithMultipleFiltersWithNullAndDuplicates_AppliesAllFilters() + { + EditText editText = new EditText(MainActivity.Current); + + var filter1 = new MockInputFilter(); + var filter2 = new MockInputFilter(); + var filter3 = new MockInputFilter(); + var nullFilter = null as IInputFilter; + var filters = new[] { filter1, filter2, nullFilter, filter3, filter2, nullFilter, filter1, filter2 }; + + editText.SetFilters(filter1, filter2, nullFilter!, filter3, filter2, nullFilter!, filter1, filter2); + + var appliedFilters = editText.GetFilters(); + Assert.Equal(filters, appliedFilters); + } + + [Theory] + [InlineData("")] + [InlineData("a")] + [InlineData("abcd")] + [InlineData("abcdefg")] + public void KeepFocusAtTheEndOfField_WhenTextChangedWithoutFocus_WhenFocused_MovesCursorToTheEndOfField(string str) + { + EditText editText = new EditText(MainActivity.Current); + editText.ClearFocus(); + + editText.Text = str; + editText.RequestFocus(); + + Assert.Equal(editText.SelectionEnd, editText.SelectionStart); + Assert.Equal(editText.SelectionStart, str.Length); + } + + [Theory] + [InlineData("")] + [InlineData("a")] + [InlineData("abcd")] + [InlineData("abcdefg")] + public void KeepFocusAtTheEndOfField_WhenTextChangedWithFocus_WhenRefocused_MovesCursorToTheEndOfField(string str) + { + EditText editText = new EditText(MainActivity.Current); + editText.RequestFocus(); + + editText.Text = str; + editText.ClearFocus(); + editText.RequestFocus(); + + Assert.Equal(editText.SelectionEnd, editText.SelectionStart); + Assert.Equal(editText.SelectionStart, str.Length); + } +} diff --git a/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Extensions/EditTextExtensionsTests/MockInputFilter.cs b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Extensions/EditTextExtensionsTests/MockInputFilter.cs new file mode 100644 index 000000000..b2a5172f4 --- /dev/null +++ b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Extensions/EditTextExtensionsTests/MockInputFilter.cs @@ -0,0 +1,16 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using Android.Text; +using Java.Lang; +using JObject = Java.Lang.Object; + +namespace Softeq.XToolkit.Common.Droid.Tests.Extensions.EditTextExtensionsTests; + +public class MockInputFilter : JObject, IInputFilter +{ + public ICharSequence? FilterFormatted(ICharSequence? source, int start, int end, ISpanned? dest, int dstart, int dend) + { + return null; + } +} diff --git a/Softeq.XToolkit.Common.Droid.Tests/Extensions/StringExtensionsTests/StringExtensionsDataProvider.cs b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Extensions/StringExtensionsTests/StringExtensionsDataProvider.cs similarity index 100% rename from Softeq.XToolkit.Common.Droid.Tests/Extensions/StringExtensionsTests/StringExtensionsDataProvider.cs rename to Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Extensions/StringExtensionsTests/StringExtensionsDataProvider.cs diff --git a/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Extensions/StringExtensionsTests/StringExtensionsTests.cs b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Extensions/StringExtensionsTests/StringExtensionsTests.cs new file mode 100644 index 000000000..ffc163908 --- /dev/null +++ b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Extensions/StringExtensionsTests/StringExtensionsTests.cs @@ -0,0 +1,229 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using System.Linq; +using Android.Text; +using Android.Text.Style; +using Softeq.XToolkit.Common.Droid.Extensions; +using Softeq.XToolkit.Common.Helpers; +using Xunit; +using Object = Java.Lang.Object; + +namespace Softeq.XToolkit.Common.Droid.Tests.Extensions.StringExtensionsTests; + +public class StringExtensionsTests +{ + #region Null string + + [Theory] + [MemberData( + nameof(StringExtensionsDataProvider.FormatSpannableNullStringWithRangeAndSpansTestData), + MemberType = typeof(StringExtensionsDataProvider))] + public void FormatSpannable_WhenCalledOnNullString_WithAnyTextRange_WithSpans_ThrowsCorrectException( + TextRange textRange, Object[] spans) + { + var str = null as string; + Assert.Throws(() => str!.FormatSpannable(textRange, spans)); + } + + [Theory] + [MemberData( + nameof(StringExtensionsDataProvider.FormatSpannableNullStringWithoutSpansTestData), + MemberType = typeof(StringExtensionsDataProvider))] + public void FormatSpannable_WhenCalledOnNullString_WithAnyTextRange_WithoutSpans_ThrowsCorrectException( + TextRange textRange) + { + var str = null as string; + Assert.Throws(() => str!.FormatSpannable(textRange)); + } + + [Theory] + [MemberData( + nameof(StringExtensionsDataProvider.FormatSpannableNullStringWithSpansOnlyTestData), + MemberType = typeof(StringExtensionsDataProvider))] + public void FormatSpannable_WhenCalledOnNullString_WithoutTextRange_WithSpans_ThrowsCorrectException( + Object[] spans) + { + var str = null as string; + Assert.Throws(() => str!.FormatSpannable(spans)); + } + + [Fact] + public void FormatSpannable_WhenCalledOnNullString_WithoutTextRange_WithoutSpans_ThrowsCorrectException() + { + var str = null as string; + Assert.Throws(() => str!.FormatSpannable()); + } + + #endregion + + [Theory] + [MemberData( + nameof(StringExtensionsDataProvider.FormatSpannableStringsTestData), + MemberType = typeof(StringExtensionsDataProvider))] + public void FormatSpannable_WhenCalledOnCorrectString_WithNullTextRange_ThrowsCorrectException( + string str) + { + var spans = new Object[] { }; + Assert.Throws(() => str.FormatSpannable(StringExtensionsDataProvider.NullTextRange, spans)); + } + + [Theory] + [MemberData( + nameof(StringExtensionsDataProvider.FormatSpannableWithNonNullTextRangeWithoutSpansTestData), + MemberType = typeof(StringExtensionsDataProvider))] + public void FormatSpannable_WhenCalledOnCorrectString_WithNonNullTextRange_WithoutSpans_ThrowsCorrectException( + string str, TextRange textRange) + { + var spans = new Object[] { }; + Assert.Throws(() => str.FormatSpannable(textRange, spans)); + } + + [Theory] + [MemberData( + nameof(StringExtensionsDataProvider.FormatSpannableWithIncorrectTextRangeWithSpansTestData), + MemberType = typeof(StringExtensionsDataProvider))] + public void FormatSpannable_WhenCalledOnCorrectString_WithIncorrectTextRange_WithSpans_ThrowsCorrectException( + string str, TextRange textRange, Object[] spans) + { + Assert.Throws(() => str.FormatSpannable(textRange, spans)); + } + + [Theory] + [MemberData( + nameof(StringExtensionsDataProvider.FormatSpannableWithCorrectTextRangeWithStyleSpansTestData), + MemberType = typeof(StringExtensionsDataProvider))] + public void FormatSpannable_WhenCalledOnCorrectString_WithCorrectTextRange_WithStyleSpans_AppliesSpans( + string str, TextRange textRange, Object[] spans) + { + var spannable = str.FormatSpannable(textRange, spans); + var appliedSpans = GetSpans(spannable, textRange); + + AssertAppliedSpans(spans, appliedSpans); + AssertNoSpansOutsideSpecifiedIntervalApplied(spannable, textRange); + } + + [Theory] + [MemberData( + nameof(StringExtensionsDataProvider.FormatSpannableWithCorrectTextRangeWithForegroundColorSpansTestData), + MemberType = typeof(StringExtensionsDataProvider))] + public void FormatSpannable_WhenCalledOnCorrectString_WithCorrectTextRange_WithForegroundColorSpans_AppliesSpans( + string str, TextRange textRange, Object[] spans) + { + var spannable = str.FormatSpannable(textRange, spans); + var appliedSpans = GetSpans(spannable, textRange); + + AssertAppliedSpans(spans, appliedSpans); + AssertNoSpansOutsideSpecifiedIntervalApplied(spannable, textRange); + } + + [Theory] + [MemberData( + nameof(StringExtensionsDataProvider.FormatSpannableWithCorrectTextRangeWithDifferentSpansTestData), + MemberType = typeof(StringExtensionsDataProvider))] + public void FormatSpannable_WhenCalledOnCorrectString_WithCorrectTextRange_WithDifferentSpans_AppliesSpans( + string str, TextRange textRange, Object[] spans) + { + var spannable = str.FormatSpannable(textRange, spans); + var appliedStyleSpans = GetSpans(spannable, textRange); + var appliedForegroundColorSpans = GetSpans(spannable, textRange); + var appliedSpans = appliedStyleSpans.Concat(appliedForegroundColorSpans).ToArray(); + + AssertAppliedSpans(spans, appliedSpans); + AssertNoSpansOutsideSpecifiedIntervalApplied(spannable, textRange); + AssertNoSpansOutsideSpecifiedIntervalApplied(spannable, textRange); + } + + [Theory] + [MemberData( + nameof(StringExtensionsDataProvider.FormatSpannableWithoutTextRangeWithStyleSpansTestData), + MemberType = typeof(StringExtensionsDataProvider))] + public void FormatSpannable_WhenCalledOnCorrectString_WithoutTextRange_WithStyleSpans_AppliesSpans( + string str, Object[] spans) + { + var spannable = str.FormatSpannable(spans); + var appliedSpans = GetSpans(spannable, new TextRange(0, str.Length)); + + AssertAppliedSpans(spans, appliedSpans); + } + + [Theory] + [MemberData( + nameof(StringExtensionsDataProvider.FormatSpannableWithoutTextRangeWithForegroundColorSpansTestData), + MemberType = typeof(StringExtensionsDataProvider))] + public void FormatSpannable_WhenCalledOnCorrectString_WithoutTextRange_WithForegroundColorSpans_AppliesSpans( + string str, Object[] spans) + { + var spannable = str.FormatSpannable(spans); + var appliedSpans = GetSpans(spannable, new TextRange(0, str.Length)); + + AssertAppliedSpans(spans, appliedSpans); + } + + [Theory] + [MemberData( + nameof(StringExtensionsDataProvider.FormatSpannableWithoutTextRangeWithDifferentSpansTestData), + MemberType = typeof(StringExtensionsDataProvider))] + public void FormatSpannable_WhenCalledOnCorrectString_WithoutTextRange_WithDifferentSpans_AppliesSpans( + string str, Object[] spans) + { + var spannable = str.FormatSpannable(spans); + var appliedStyleSpans = GetSpans(spannable, new TextRange(0, str.Length)); + var appliedForegroundColorSpans = GetSpans(spannable, new TextRange(0, str.Length)); + var appliedSpans = appliedStyleSpans.Concat(appliedForegroundColorSpans).ToArray(); + + AssertAppliedSpans(spans, appliedSpans); + } + + [Theory] + [MemberData( + nameof(StringExtensionsDataProvider.FormatSpannableStringsTestData), + MemberType = typeof(StringExtensionsDataProvider))] + public void FormatSpannable_WhenCalledOnCorrectString_WithoutTextRange_WithoutSpans_ThrowsCorrectException( + string str) + { + Assert.Throws(() => str.FormatSpannable()); + } + + private void AssertAppliedSpans(Object[] spans, Object[] appliedSpans) + { + var nonNullSpans = spans.Where(t => t != null).Distinct(); + var nonNullAppliedSpans = appliedSpans.Where(t => t != null); + + Assert.Equal(nonNullSpans.Count(), nonNullAppliedSpans.Count()); + foreach (var span in nonNullSpans) + { + Assert.Contains(span, appliedSpans); + } + + foreach (var appliedSpan in nonNullAppliedSpans) + { + Assert.Contains(appliedSpan, spans); + } + } + + private void AssertNoSpansOutsideSpecifiedIntervalApplied(SpannableString? spannable, TextRange textRange) + { + int spannableLength = spannable?.Length() ?? 0; + if (textRange.Position > 0) + { + var spansBefore = GetSpans(spannable, new TextRange(0, textRange.Position - 1)); + Assert.Empty(spansBefore); + } + + if (textRange.Position + textRange.Length < spannableLength - 1) + { + var spansAfter = GetSpans(spannable, new TextRange(textRange.Position + textRange.Length + 1, spannableLength - 1)); + Assert.Empty(spansAfter); + } + } + + private Object[] GetSpans(SpannableString? spannable, TextRange textRange) + { + return spannable?.GetSpans( + textRange.Position, + textRange.Position + textRange.Length, + Java.Lang.Class.FromType(typeof(T))) ?? new Object[] { }; + } +} diff --git a/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Helpers.cs b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Helpers.cs new file mode 100644 index 000000000..71454ce38 --- /dev/null +++ b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Helpers.cs @@ -0,0 +1,28 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using System.Threading.Tasks; + +namespace Softeq.XToolkit.Common.Droid.Tests; + +public static class Helpers +{ + public static Task RunOnUIThreadAsync(Func func) + { + var tcs = new TaskCompletionSource(); + MainActivity.Current.RunOnUiThread(() => + { + try + { + var result = func(); + tcs.SetResult(result); + } + catch (Exception e) + { + tcs.SetException(e); + } + }); + return tcs.Task; + } +} diff --git a/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/MainActivity.cs b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/MainActivity.cs new file mode 100644 index 000000000..0e8b5aa69 --- /dev/null +++ b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/MainActivity.cs @@ -0,0 +1,24 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using Android.App; +using Android.Content.PM; +using Android.OS; +using Microsoft.Maui; + +namespace Softeq.XToolkit.Common.Droid.Tests; + +[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, + ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | + ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)] +public class MainActivity : MauiAppCompatActivity +{ + public static MainActivity Current { get; private set; } = null!; + + protected override void OnCreate(Bundle? savedInstanceState) + { + Current = this; + + base.OnCreate(savedInstanceState); + } +} diff --git a/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/MainApplication.cs b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/MainApplication.cs new file mode 100644 index 000000000..9cfc2866d --- /dev/null +++ b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/MainApplication.cs @@ -0,0 +1,21 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using Android.App; +using Android.Runtime; +using Microsoft.Maui; +using Microsoft.Maui.Hosting; + +namespace Softeq.XToolkit.Common.Droid.Tests; + +[Application] +public class MainApplication : MauiApplication +{ + public MainApplication(IntPtr handle, JniHandleOwnership ownership) + : base(handle, ownership) + { + } + + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); +} diff --git a/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Resources/values/colors.xml b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Resources/values/colors.xml new file mode 100644 index 000000000..c04d7492a --- /dev/null +++ b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Resources/values/colors.xml @@ -0,0 +1,6 @@ + + + #512BD4 + #2B0B98 + #2B0B98 + \ No newline at end of file diff --git a/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/TextFilters/ForbiddenCharsInputFilterTests.cs b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/TextFilters/ForbiddenCharsInputFilterTests.cs new file mode 100644 index 000000000..66d30da35 --- /dev/null +++ b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/TextFilters/ForbiddenCharsInputFilterTests.cs @@ -0,0 +1,101 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using Android.Text; +using Softeq.XToolkit.Common.Droid.TextFilters; +using Xunit; +using JString = Java.Lang.String; + +namespace Softeq.XToolkit.Common.Droid.Tests.TextFilters; + +public class ForbiddenCharsInputFilterTests +{ + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("a")] + [InlineData("aaa")] + [InlineData("aA%@2-")] + public void Ctor_WhenCalledWithAnyChars_CreatesFilter(string? chars) + { + var obj = new ForbiddenCharsInputFilter(chars?.ToCharArray() ?? null!); + + Assert.IsAssignableFrom(obj); + } + + [Theory] + [PairwiseData] + public void FilterFormatted_ForFilterWithNullChars_ReturnsNull( + [CombinatorialValues(null, "", "b", "bbc", "+3b^ gb^")] string sourceStr, + [CombinatorialValues(-1, 0, 1, 3)] int start, + [CombinatorialValues(-1, 0, 1, 3)] int end, + [CombinatorialValues(null, "", "a", "abc")] string? destStr, + [CombinatorialValues(-1, 0, 1, 3)] int dstart, + [CombinatorialValues(-1, 0, 1, 3)] int dend) + { + var source = new JString(sourceStr); + var dest = destStr == null ? null : new SpannableString(destStr); + var filter = new ForbiddenCharsInputFilter(null!); + + var result = filter.FilterFormatted(source, start, end, dest, dstart, dend); + + Assert.Null(result); + } + + [Theory] + [PairwiseData] + public void FilterFormatted_ForFilterWithNonNullChars_WhenCalledWithNullSource_ReturnsNull( + [CombinatorialValues("", "a", "aaA%@2-")] string chars, + [CombinatorialValues(-1, 0, 1, 3)] int start, + [CombinatorialValues(-1, 0, 1, 3)] int end, + [CombinatorialValues(null, "", "a", "abc")] string? destStr, + [CombinatorialValues(-1, 0, 1, 3)] int dstart, + [CombinatorialValues(-1, 0, 1, 3)] int dend) + { + var dest = destStr == null ? null : new SpannableString(destStr); + var filter = new ForbiddenCharsInputFilter(chars.ToCharArray()); + + var result = filter.FilterFormatted(null, start, end, dest, dstart, dend); + + Assert.Null(result); + } + + [Theory] + [PairwiseData] + public void FilterFormatted_ForFilterWithNonNullChars_WhenCalledWithNonNullSource_IfSourceDoesNotContainForbiddenChars_ReturnsNull( + [CombinatorialValues("", "a", "aaA%@2-")] string chars, + [CombinatorialValues("", "b", "bbc", "+3b^ gb^")] string sourceStr, + [CombinatorialValues(-1, 0, 1, 3)] int start, + [CombinatorialValues(-1, 0, 1, 3)] int end, + [CombinatorialValues(null, "", "a", "abc")] string? destStr, + [CombinatorialValues(-1, 0, 1, 3)] int dstart, + [CombinatorialValues(-1, 0, 1, 3)] int dend) + { + var source = new JString(sourceStr); + var dest = destStr == null ? null : new SpannableString(destStr); + var filter = new ForbiddenCharsInputFilter(chars.ToCharArray()); + + var result = filter.FilterFormatted(source, start, end, dest, dstart, dend); + + Assert.Null(result); + } + + [Theory] + [PairwiseData] + public void FilterFormatted_ForFilterWithNonEmptyChars_WhenCalledWithNonNullSource_IfSourceContainForbiddenChars_ReturnsEmptyString( + [CombinatorialValues("a", "aaabc", "%klp", "jd2ye")] string sourceStr, + [CombinatorialValues(-1, 0, 1, 3)] int start, + [CombinatorialValues(-1, 0, 1, 3)] int end, + [CombinatorialValues(null, "", "a", "abc")] string? destStr, + [CombinatorialValues(-1, 0, 1, 3)] int dstart, + [CombinatorialValues(-1, 0, 1, 3)] int dend) + { + var source = new JString(sourceStr); + var dest = destStr == null ? null : new SpannableString(destStr); + var filter = new ForbiddenCharsInputFilter("aaAA%@2@-".ToCharArray()); + + var result = filter.FilterFormatted(source, start, end, dest, dstart, dend); + + Assert.Empty(result!); + } +} diff --git a/Softeq.XToolkit.Common.Droid.Tests/Properties/AndroidManifest.xml b/Softeq.XToolkit.Common.Droid.Tests/Properties/AndroidManifest.xml deleted file mode 100644 index 07b561a06..000000000 --- a/Softeq.XToolkit.Common.Droid.Tests/Properties/AndroidManifest.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/Softeq.XToolkit.Common.Droid.Tests/Properties/launchSettings.json b/Softeq.XToolkit.Common.Droid.Tests/Properties/launchSettings.json new file mode 100644 index 000000000..edf8aadcc --- /dev/null +++ b/Softeq.XToolkit.Common.Droid.Tests/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Windows Machine": { + "commandName": "MsixPackage", + "nativeDebugging": false + } + } +} \ No newline at end of file diff --git a/Softeq.XToolkit.Common.Droid.Tests/Resources/AppIcon/appicon.svg b/Softeq.XToolkit.Common.Droid.Tests/Resources/AppIcon/appicon.svg new file mode 100644 index 000000000..9d63b6513 --- /dev/null +++ b/Softeq.XToolkit.Common.Droid.Tests/Resources/AppIcon/appicon.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Softeq.XToolkit.Common.Droid.Tests/Resources/AppIcon/appiconfg.svg b/Softeq.XToolkit.Common.Droid.Tests/Resources/AppIcon/appiconfg.svg new file mode 100644 index 000000000..21dfb25f1 --- /dev/null +++ b/Softeq.XToolkit.Common.Droid.Tests/Resources/AppIcon/appiconfg.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Softeq.XToolkit.Common.Droid.Tests/Resources/Raw/AboutAssets.txt b/Softeq.XToolkit.Common.Droid.Tests/Resources/Raw/AboutAssets.txt new file mode 100644 index 000000000..15d624484 --- /dev/null +++ b/Softeq.XToolkit.Common.Droid.Tests/Resources/Raw/AboutAssets.txt @@ -0,0 +1,15 @@ +Any raw assets you want to be deployed with your application can be placed in +this directory (and child directories). Deployment of the asset to your application +is automatically handled by the following `MauiAsset` Build Action within your `.csproj`. + + + +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/Softeq.XToolkit.Common.Droid.Tests/Resources/Splash/splash.svg b/Softeq.XToolkit.Common.Droid.Tests/Resources/Splash/splash.svg new file mode 100644 index 000000000..21dfb25f1 --- /dev/null +++ b/Softeq.XToolkit.Common.Droid.Tests/Resources/Splash/splash.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Softeq.XToolkit.Common.Droid.Tests/Resources/mipmap-mdpi/ic_launcher.png b/Softeq.XToolkit.Common.Droid.Tests/Resources/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 795ea7c005880b08ffe2ce47e0c07045675ac13d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1362 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 - - XT.Common.Tests - \ No newline at end of file diff --git a/Softeq.XToolkit.Common.Droid.Tests/Softeq.XToolkit.Common.Droid.Tests.csproj b/Softeq.XToolkit.Common.Droid.Tests/Softeq.XToolkit.Common.Droid.Tests.csproj index b4fb843a8..75be754c3 100644 --- a/Softeq.XToolkit.Common.Droid.Tests/Softeq.XToolkit.Common.Droid.Tests.csproj +++ b/Softeq.XToolkit.Common.Droid.Tests/Softeq.XToolkit.Common.Droid.Tests.csproj @@ -1,36 +1,51 @@  - - net6.0-android32.0 - Exe - false - false - - - - true - None - - - - SdkOnly - True - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + net6.0-android32.0 + Exe + true + true + disable + + + Softeq.XToolkit.Common.Droid.Tests + + + com.softeq.xtoolkit.common.droid.tests + + + 1.0 + 1 + + 21.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Softeq.XToolkit.Common.Droid.Tests/TextFilters/ForbiddenCharsInputFilterTests.cs b/Softeq.XToolkit.Common.Droid.Tests/TextFilters/ForbiddenCharsInputFilterTests.cs deleted file mode 100644 index 34371d97e..000000000 --- a/Softeq.XToolkit.Common.Droid.Tests/TextFilters/ForbiddenCharsInputFilterTests.cs +++ /dev/null @@ -1,102 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using Android.Text; -using Java.Lang; -using Softeq.XToolkit.Common.Droid.TextFilters; -using Xunit; - -namespace Softeq.XToolkit.Common.Droid.Tests.TextFilters -{ - public class ForbiddenCharsInputFilterTests - { - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData("a")] - [InlineData("aaa")] - [InlineData("aA%@2-")] - public void Ctor_WhenCalledWithAnyChars_CreatesFilter(string? chars) - { - var obj = new ForbiddenCharsInputFilter(chars?.ToCharArray() ?? null!); - - Assert.IsAssignableFrom(obj); - } - - [Theory] - [PairwiseData] - public void FilterFormatted_ForFilterWithNullChars_ReturnsNull( - [CombinatorialValues(null, "", "b", "bbc", "+3b^ gb^")] string sourceStr, - [CombinatorialValues(-1, 0, 1, 3)] int start, - [CombinatorialValues(-1, 0, 1, 3)] int end, - [CombinatorialValues(null, "", "a", "abc")] string? destStr, - [CombinatorialValues(-1, 0, 1, 3)] int dstart, - [CombinatorialValues(-1, 0, 1, 3)] int dend) - { - var source = new String(sourceStr); - var dest = destStr == null ? null : new SpannableString(destStr); - var filter = new ForbiddenCharsInputFilter(null!); - - var result = filter.FilterFormatted(source, start, end, dest, dstart, dend); - - Assert.Null(result); - } - - [Theory] - [PairwiseData] - public void FilterFormatted_ForFilterWithNonNullChars_WhenCalledWithNullSource_ReturnsNull( - [CombinatorialValues("", "a", "aaA%@2-")] string chars, - [CombinatorialValues(-1, 0, 1, 3)] int start, - [CombinatorialValues(-1, 0, 1, 3)] int end, - [CombinatorialValues(null, "", "a", "abc")] string? destStr, - [CombinatorialValues(-1, 0, 1, 3)] int dstart, - [CombinatorialValues(-1, 0, 1, 3)] int dend) - { - var dest = destStr == null ? null : new SpannableString(destStr); - var filter = new ForbiddenCharsInputFilter(chars.ToCharArray()); - - var result = filter.FilterFormatted(null, start, end, dest, dstart, dend); - - Assert.Null(result); - } - - [Theory] - [PairwiseData] - public void FilterFormatted_ForFilterWithNonNullChars_WhenCalledWithNonNullSource_IfSourceDoesNotContainForbiddenChars_ReturnsNull( - [CombinatorialValues("", "a", "aaA%@2-")] string chars, - [CombinatorialValues("", "b", "bbc", "+3b^ gb^")] string sourceStr, - [CombinatorialValues(-1, 0, 1, 3)] int start, - [CombinatorialValues(-1, 0, 1, 3)] int end, - [CombinatorialValues(null, "", "a", "abc")] string? destStr, - [CombinatorialValues(-1, 0, 1, 3)] int dstart, - [CombinatorialValues(-1, 0, 1, 3)] int dend) - { - var source = new String(sourceStr); - var dest = destStr == null ? null : new SpannableString(destStr); - var filter = new ForbiddenCharsInputFilter(chars.ToCharArray()); - - var result = filter.FilterFormatted(source, start, end, dest, dstart, dend); - - Assert.Null(result); - } - - [Theory] - [PairwiseData] - public void FilterFormatted_ForFilterWithNonEmptyChars_WhenCalledWithNonNullSource_IfSourceContainForbiddenChars_ReturnsEmptyString( - [CombinatorialValues("a", "aaabc", "%klp", "jd2ye")] string sourceStr, - [CombinatorialValues(-1, 0, 1, 3)] int start, - [CombinatorialValues(-1, 0, 1, 3)] int end, - [CombinatorialValues(null, "", "a", "abc")] string? destStr, - [CombinatorialValues(-1, 0, 1, 3)] int dstart, - [CombinatorialValues(-1, 0, 1, 3)] int dend) - { - var source = new String(sourceStr); - var dest = destStr == null ? null : new SpannableString(destStr); - var filter = new ForbiddenCharsInputFilter("aaAA%@2@-".ToCharArray()); - - var result = filter.FilterFormatted(source, start, end, dest, dstart, dend); - - Assert.Empty(result!); - } - } -} diff --git a/Softeq.XToolkit.Common.iOS.Tests/AppDelegate.cs b/Softeq.XToolkit.Common.iOS.Tests/AppDelegate.cs deleted file mode 100644 index 3a5e97d2a..000000000 --- a/Softeq.XToolkit.Common.iOS.Tests/AppDelegate.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Reflection; -using Foundation; -using UIKit; -using Xunit.Runner; -using Xunit.Sdk; - -namespace Softeq.XToolkit.Common.iOS.Tests -{ - [Register("AppDelegate")] - public class AppDelegate : RunnerAppDelegate - { - public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions) - { - // We need this to ensure the execution assembly is part of the app bundle - AddExecutionAssembly(typeof(ExtensibilityPointFactory).Assembly); - - // tests can be inside the main assembly - AddTestAssembly(Assembly.GetExecutingAssembly()); - - // otherwise you need to ensure that the test assemblies will - // become part of the app bundle - // AddTestAssembly(typeof(PortableTests).Assembly); -#if false - // you can use the default or set your own custom writer (e.g. save to web site and tweet it ;-) - Writer = new TcpTextWriter ("10.0.1.2", 16384); - // start running the test suites as soon as the application is loaded - AutoStart = true; - // crash the application (to ensure it's ended) and return to springboard - TerminateAfterExecution = true; -#endif - return base.FinishedLaunching(application, launchOptions); - } - } -} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Entitlements.plist b/Softeq.XToolkit.Common.iOS.Tests/Entitlements.plist deleted file mode 100644 index 5ea1ec76e..000000000 --- a/Softeq.XToolkit.Common.iOS.Tests/Entitlements.plist +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/Softeq.XToolkit.Common.iOS.Tests/Extensions/NSDateExtensionsTests/NSDateExtensionsTests.cs b/Softeq.XToolkit.Common.iOS.Tests/Extensions/NSDateExtensionsTests/NSDateExtensionsTests.cs deleted file mode 100644 index b882404f4..000000000 --- a/Softeq.XToolkit.Common.iOS.Tests/Extensions/NSDateExtensionsTests/NSDateExtensionsTests.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System; -using Foundation; -using Softeq.XToolkit.Common.iOS.Extensions; -using Xunit; - -namespace Softeq.XToolkit.Common.iOS.Tests.Extensions.NSDateExtensionsTests -{ - public class NSDateExtensionsTests - { - [Theory] - [InlineData(-100000)] - [InlineData(0)] - [InlineData(100000)] - public void ToUtcDateTime_ForNsDate_ConvertsToUtcDateTime(double secondsSinceNow) - { - var nsDate = NSDate.FromTimeIntervalSinceNow(secondsSinceNow); - var calendar = NSCalendar.CurrentCalendar; - calendar.TimeZone = NSTimeZone.FromGMT(0); - var utcComponents = CreateComponents(nsDate, calendar); - - var dateTime = nsDate.ToUtcDateTime(); - - Assert.IsType(dateTime); - Assert.Equal(DateTimeKind.Utc, dateTime.Kind); - Assert.Equal(utcComponents.Year, dateTime.Year); - Assert.Equal(utcComponents.Month, dateTime.Month); - Assert.Equal(utcComponents.Day, dateTime.Day); - Assert.Equal(utcComponents.Hour, dateTime.Hour); - Assert.Equal(utcComponents.Minute, dateTime.Minute); - Assert.Equal(utcComponents.Second, dateTime.Second); - } - - [Theory] - [InlineData(-100000)] - [InlineData(0)] - [InlineData(100000)] - public void ToLocalDateTime_ForNsDate_ConvertsToLocalDateTime(double secondsSinceNow) - { - var nsDate = NSDate.FromTimeIntervalSinceNow(secondsSinceNow); - var localComponents = CreateComponents(nsDate, NSCalendar.CurrentCalendar); - - var dateTime = nsDate.ToLocalDateTime(); - - Assert.IsType(dateTime); - Assert.Equal(DateTimeKind.Local, dateTime.Kind); - Assert.Equal(localComponents.Year, dateTime.Year); - Assert.Equal(localComponents.Month, dateTime.Month); - Assert.Equal(localComponents.Day, dateTime.Day); - Assert.Equal(localComponents.Hour, dateTime.Hour); - Assert.Equal(localComponents.Minute, dateTime.Minute); - Assert.Equal(localComponents.Second, dateTime.Second); - } - - [Theory] - [InlineData(-100000)] - [InlineData(0)] - [InlineData(100000)] - public void ToLocalDateTimeAndBack_ForNsDate_ReturnsSameNsDate(double secondsSinceNow) - { - var nsDate = NSDate.FromTimeIntervalSinceNow(secondsSinceNow); - - var dateTime = nsDate.ToLocalDateTime(); - var newNsDate = dateTime.ToNsDate(); - - Assert.Equal(nsDate.SecondsSince1970, newNsDate.SecondsSince1970, 3); - } - - private NSDateComponents CreateComponents(NSDate date, NSCalendar calendar) - { - return calendar.Components( - NSCalendarUnit.Second | NSCalendarUnit.Minute | NSCalendarUnit.Hour | - NSCalendarUnit.Day | NSCalendarUnit.Month | NSCalendarUnit.Year, date); - } - } -} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Extensions/NSLocaleExtensionsTests/NSLocaleExtensionsTests.cs b/Softeq.XToolkit.Common.iOS.Tests/Extensions/NSLocaleExtensionsTests/NSLocaleExtensionsTests.cs deleted file mode 100644 index 346928753..000000000 --- a/Softeq.XToolkit.Common.iOS.Tests/Extensions/NSLocaleExtensionsTests/NSLocaleExtensionsTests.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System; -using System.Diagnostics.CodeAnalysis; -using Foundation; -using Softeq.XToolkit.Common.iOS.Extensions; -using Xunit; - -namespace Softeq.XToolkit.Common.iOS.Tests.Extensions.NSLocaleExtensionsTests -{ - [SuppressMessage("ReSharper", "InvokeAsExtensionMethod", Justification = "Need for tests")] - public class NSLocaleExtensionsTests - { - [Fact] - public void Is24HourFormat_Null_ThrowsArgumentNullException() - { - var locale = null as NSLocale; - - Assert.Throws(() => - { - NSLocaleExtensions.Is24HourFormat(locale!); - }); - } - - [Theory] - [InlineData("en_US", false)] - [InlineData("en_CA", false)] - [InlineData("fil_PH", false)] - [InlineData("ru_RU", true)] - [InlineData("en_BY", true)] - [InlineData("de_DE", true)] - [InlineData("it_IT", true)] - public void Is24HourFormat_24HourLocaleFormat_ReturnsTrue(string localeId, bool expectedResult) - { - var locale = NSLocale.FromLocaleIdentifier(localeId); - - var result = locale.Is24HourFormat(); - - Assert.Equal(expectedResult, result); - } - } -} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Extensions/NSRangeExtensionsTests/NSRangeExtensionsTests.cs b/Softeq.XToolkit.Common.iOS.Tests/Extensions/NSRangeExtensionsTests/NSRangeExtensionsTests.cs deleted file mode 100644 index 4268f9eec..000000000 --- a/Softeq.XToolkit.Common.iOS.Tests/Extensions/NSRangeExtensionsTests/NSRangeExtensionsTests.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using Foundation; -using Softeq.XToolkit.Common.Helpers; -using Softeq.XToolkit.Common.iOS.Extensions; -using Xunit; - -namespace Softeq.XToolkit.Common.iOS.Tests.Extensions.NSRangeExtensionsTests -{ - public class NSRangeExtensionsTests - { - [Theory] - [InlineData(0, 0)] - [InlineData(0, 10)] - [InlineData(5, 10)] - [InlineData(9, 10)] - [InlineData(15, 10)] - public void ToTextRange_ForNsRange_Converts(int position, int length) - { - var nsRange = new NSRange(position, length); - - var textRange = nsRange.ToTextRange(); - - Assert.NotNull(textRange); - Assert.IsType(textRange); - Assert.Equal(position, textRange.Position); - Assert.Equal(length, textRange.Length); - } - - [Theory] - [InlineData(0, 0)] - [InlineData(0, 10)] - [InlineData(5, 10)] - [InlineData(9, 10)] - [InlineData(15, 10)] - public void ToNsRange_ForTextRange_Converts(int position, int length) - { - var textRange = new TextRange(position, length); - - var nsRange = textRange.ToNSRange(); - - Assert.IsType(nsRange); - Assert.Equal(position, nsRange.Location); - Assert.Equal(length, nsRange.Length); - } - } -} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Extensions/NSStringExtensions/NSStringExtensionsTests.cs b/Softeq.XToolkit.Common.iOS.Tests/Extensions/NSStringExtensions/NSStringExtensionsTests.cs deleted file mode 100644 index d5c94bb6f..000000000 --- a/Softeq.XToolkit.Common.iOS.Tests/Extensions/NSStringExtensions/NSStringExtensionsTests.cs +++ /dev/null @@ -1,193 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System; -using System.Diagnostics.CodeAnalysis; -using Foundation; -using Softeq.XToolkit.Common.iOS.Extensions; -using UIKit; -using Xunit; - -namespace Softeq.XToolkit.Common.iOS.Tests.Extensions.NSStringExtensions -{ - [SuppressMessage("ReSharper", "InvokeAsExtensionMethod", Justification = "Need for tests")] - public class NSStringExtensionsTests - { - [Fact] - public void NewParagraphStyle_CreateNewInstance_ReturnsInstance() - { - var result = iOS.Extensions.NSStringExtensions.NewParagraphStyle; - - Assert.IsType(result); - } - - [Fact] - public void ToNSUrl_Null_ThrowsArgumentNullException() - { - var link = null as string; - - Assert.Throws(() => - { - iOS.Extensions.NSStringExtensions.ToNSUrl(link!); - }); - } - - [Theory] - [InlineData("")] - public void ToNSUrl_InvalidLink_ThrowsUriFormatException(string link) - { - Assert.Throws(() => - { - link.ToNSUrl(); - }); - } - - [Theory] - [InlineData("localhost", "http://localhost/")] - [InlineData("softeq.com", "http://softeq.com/")] - [InlineData("www.softeq.com", "http://www.softeq.com/")] - [InlineData("http://softeq.com", "http://softeq.com/")] - [InlineData("https://softeq.com", "https://softeq.com/")] - [InlineData("https://www.softeq.com/", "https://www.softeq.com/")] - [InlineData("https://softeq.com//", "https://softeq.com//")] - [InlineData("https://example.com/??a", "https://example.com/??a")] - [InlineData("https://example.com/?arg=1", "https://example.com/?arg=1")] - [InlineData("https://api.example.com:3000/", "https://api.example.com:3000/")] - [InlineData("https://example.com/?arg=1&asd=&q=тест+-+test", "https://example.com/?arg=1&asd=&q=%D1%82%D0%B5%D1%81%D1%82+-+test")] - [InlineData("https://com.dev.api.example.com/&a=1?b=2", "https://com.dev.api.example.com/&a=1?b=2")] - [InlineData("https://com.dev.api.example.com/?a=b=", "https://com.dev.api.example.com/?a=b=")] - [InlineData("http://localhost/../..", "http://localhost/")] - [InlineData("http://localhost/%2E%2E/%2E%2E", "http://localhost/")] - [InlineData("htTP://localhost/", "http://localhost/")] - [InlineData("http://localHOST/", "http://localhost/")] - [InlineData("https://localhost:3000", "https://localhost:3000/")] - [InlineData("https://127.0.0.1", "https://127.0.0.1/")] - [InlineData("https://127.0.0.1:8080", "https://127.0.0.1:8080/")] - [InlineData("https://user:password@www.example.com:80/Home/Index.htm?q1=v1&q2=v2#FragmentName", "https://user:password@www.example.com:80/Home/Index.htm?q1=v1&q2=v2#FragmentName")] - [InlineData("ftp://example.com", "ftp://example.com/")] - [InlineData("mailto://example.com", "mailto://example.com")] - [InlineData("mailto:test@example.com", "mailto:test@example.com")] - [InlineData("test://localhost/", "test://localhost/")] - [InlineData("localhost:3000", "localhost:3000")] - [InlineData(@"\\some\directory\name\", "file://some/directory/name/")] - public void ToNSUrl_ValidLink_ReturnsExpectedNSUrl(string link, string expectedLink) - { - var result = link.ToNSUrl(); - - Assert.IsType(result); - Assert.Equal(expectedLink, result.AbsoluteString); - } - - [Fact] - public void BuildAttributedString_Null_ThrowsArgumentNullException() - { - var input = null as string; - - Assert.Throws(() => - { - iOS.Extensions.NSStringExtensions.BuildAttributedString(input!); - }); - } - - [Theory] - [InlineData("")] - [InlineData("test string")] - public void BuildAttributedString_NotNull_ReturnsAttributedString(string input) - { - var result = input.BuildAttributedString(); - - Assert.IsType(result); - Assert.Equal(input, result.Value); - } - - [Fact] - public void BuildAttributedStringFromHtml_Null_ThrowsArgumentNullException() - { - var input = null as string; - - Assert.Throws(() => - { - iOS.Extensions.NSStringExtensions.BuildAttributedStringFromHtml(input!); - }); - } - - [Theory] - [InlineData("", "")] - [InlineData("test string", "test string")] - [InlineData("

test string

", "test string\n")] - [InlineData("test string", "test string")] - [InlineData("test string", "test string")] - [InlineData("

test - string

", "test - string\n")] - [InlineData("test string", "test string")] - [InlineData("test string", "test string")] - public void BuildAttributedStringFromHtml_NotNull_ReturnsAttributedString(string html, string expectedResult) - { - var result = html.BuildAttributedStringFromHtml(); - - Assert.IsType(result); - Assert.Equal(expectedResult, result.Value); - } - - [Theory] - [InlineData("", NSStringEncoding.Unicode, "")] - [InlineData("test строка \u2022", NSStringEncoding.UTF8, "test строка •")] - [InlineData("test строка 👍", NSStringEncoding.UTF8, "test строка 👍")] - [InlineData("“test” string", NSStringEncoding.UTF8, "“test” string")] - [InlineData("test \u203C string", NSStringEncoding.UTF8, "test ‼ string")] - public void BuildAttributedStringFromHtml_SpecificEncoding_ReturnsAttributedString( - string html, - NSStringEncoding encoding, - string expectedResult) - { - var result = html.BuildAttributedStringFromHtml(encoding); - - Assert.IsType(result); - Assert.Equal(expectedResult, result.Value); - } - - [Fact] - public void DetectLinks_NullAttributedString_ThrowsNullReferenceException() - { - var obj = null as NSMutableAttributedString; - - Assert.Throws(() => - { - iOS.Extensions.NSStringExtensions.DetectLinks( - obj!, - UIColor.Red, - NSUnderlineStyle.Single, - false, - out _); - }); - } - - [Fact] - public void DetectLinks_NullColor_ExecutesWithoutExceptions() - { - var obj = "test string".BuildAttributedString(); - var color = null as UIColor; - - var modifiedObj = obj.DetectLinks(color!, NSUnderlineStyle.Single, false, out _); - - Assert.IsType(modifiedObj); - } - - [Theory] - [InlineData("link: https://softeq.com", 1)] - [InlineData("link: https://softeq.com, google: google.com", 2)] - [InlineData("test string: https://www.softeq.com/featured_projects#mobile, example: http://example.com/test/page", 2)] - public void DetectLinks_TextWithLinks_ExecutesWithoutExceptions(string text, int linkCount) - { - var obj = text.BuildAttributedString(); - - var modifiedObj = obj.DetectLinks( - UIColor.Red, - NSUnderlineStyle.Single, - true, - out var tags); - - Assert.IsType(modifiedObj); - Assert.Equal(linkCount, tags.Length); - } - } -} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Extensions/UIColorExtensionsTests/UIColorExtensionsTests.cs b/Softeq.XToolkit.Common.iOS.Tests/Extensions/UIColorExtensionsTests/UIColorExtensionsTests.cs deleted file mode 100644 index 07d6586e9..000000000 --- a/Softeq.XToolkit.Common.iOS.Tests/Extensions/UIColorExtensionsTests/UIColorExtensionsTests.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System; -using Softeq.XToolkit.Common.iOS.Extensions; -using UIKit; -using Xunit; - -namespace Softeq.XToolkit.Common.iOS.Tests.Extensions.UIColorExtensionsTests -{ - public class UIColorExtensionsTests - { - [Theory] - [InlineData("#333333", "UIColor [A=255, R=51, G=51, B=51]")] - [InlineData("0000FF", "UIColor [A=255, R=0, G=0, B=255]")] - [InlineData("00FF00", "UIColor [A=255, R=0, G=255, B=0]")] - [InlineData("F00", "UIColor [A=255, R=255, G=0, B=0]")] - [InlineData("#008080", "UIColor [A=255, R=0, G=128, B=128]")] - [InlineData("FA8072", "UIColor [A=255, R=250, G=128, B=114]")] - [InlineData("350027", "UIColor [A=255, R=53, G=0, B=39]")] - public void UIColorFromHex_CorrectValueWithoutAlpha_ReturnsValidColor(string hexColor, string expected) - { - var result = hexColor.UIColorFromHex(); - - Assert.Equal(expected, result.ToString()); - } - - [Theory] - [InlineData(1, 1)] - [InlineData(2, 1)] - [InlineData(0, 0)] - [InlineData(-2, 0)] - [InlineData(0.5, 0.5)] - [InlineData(0.33, 0.33)] - public void UIColorFromHex_CorrectValueWithAlpha_ReturnsValidColor(float alpha, float expectedAlpha) - { - var result = "#FFF".UIColorFromHex(alpha); - - Assert.Equal(expectedAlpha, result.CGColor.Alpha); - } - - [Theory] - [InlineData("")] - [InlineData("#00")] - [InlineData("1122")] - [InlineData("#11223344")] - [InlineData("11223344")] - public void UIColorFromHex_IncorrectValue_ThrowsArgumentOutOfRangeException(string hexColor) - { - Assert.Throws(() => - { - hexColor.UIColorFromHex(); - }); - } - - [Theory] - [InlineData(255, 255, 255, "#FFFFFF")] - [InlineData(0, 0, 0, "#000000")] - [InlineData(6, 27, 250, "#061BFA")] - [InlineData(0, 52, -1, "#0034FF")] - public void ToHex_UIColor_ReturnsHexColor(int red, int green, int blue, string expectedHex) - { - var color = UIColor.FromRGB(red, green, blue); - - var result = color.ToHex(); - - Assert.Equal(expectedHex, result); - } - } -} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Extensions/UITextViewExtensionsTests/UITextViewExtensionsTests.cs b/Softeq.XToolkit.Common.iOS.Tests/Extensions/UITextViewExtensionsTests/UITextViewExtensionsTests.cs deleted file mode 100644 index 23d42da63..000000000 --- a/Softeq.XToolkit.Common.iOS.Tests/Extensions/UITextViewExtensionsTests/UITextViewExtensionsTests.cs +++ /dev/null @@ -1,106 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System; -using System.Diagnostics.CodeAnalysis; -using System.Threading.Tasks; -using Foundation; -using Softeq.XToolkit.Common.iOS.Extensions; -using Softeq.XToolkit.Common.iOS.Tests.TextFilters.GroupFilterTests; -using UIKit; -using Xunit; - -namespace Softeq.XToolkit.Common.iOS.Tests.Extensions.UITextViewExtensionsTests -{ - [SuppressMessage("ReSharper", "InvokeAsExtensionMethod", Justification = "Need for tests")] - public class UITextViewExtensionsTests - { - [Fact] - public void SetFilter_UITextFieldIsNull_ThrowsNullReferenceException() - { - var textField = (UITextField) null!; - - Assert.Throws(() => - { - UITextViewExtensions.SetFilter(textField, new MockTextFilter(true)); - }); - } - - [Fact] - public async Task SetFilter_UITextFieldWithNullFilter_ThrowsArgumentNullException() - { - var result = Helpers.RunOnUIThreadAsync(() => - { - var textField = new UITextField(); - - textField.SetFilter(null!); - - return false; - }); - - await Assert.ThrowsAsync(() => result); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task SetFilter_UITextFieldWithFilter_ReturnsExpected(bool expectedResult) - { - var result = await Helpers.RunOnUIThreadAsync(() => - { - var textField = new UITextField(); - var filter = new MockTextFilter(expectedResult); - - textField.SetFilter(filter); - - return textField.ShouldChangeCharacters(textField, new NSRange(1, 0), "new"); - }); - - Assert.Equal(expectedResult, result); - } - - [Fact] - public void SetFilter_UITextViewIsNull_ThrowsNullReferenceException() - { - var textView = (UITextView) null!; - - Assert.Throws(() => - { - UITextViewExtensions.SetFilter(textView, new MockTextFilter(true)); - }); - } - - [Fact] - public async Task SetFilter_UITextViewWithNullFilter_ThrowsArgumentNullException() - { - var result = Helpers.RunOnUIThreadAsync(() => - { - var textField = new UITextField(); - - textField.SetFilter(null!); - - return false; - }); - - await Assert.ThrowsAsync(() => result); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task SetFilter_UITextViewWithFilter_ReturnsExpected(bool expectedResult) - { - var result = await Helpers.RunOnUIThreadAsync(() => - { - var textView = new UITextView(); - var filter = new MockTextFilter(expectedResult); - - textView.SetFilter(filter); - - return textView.ShouldChangeText!(textView, new NSRange(1, 0), "new"); - }); - - Assert.Equal(expectedResult, result); - } - } -} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Helpers.cs b/Softeq.XToolkit.Common.iOS.Tests/Helpers.cs deleted file mode 100644 index c412eac6e..000000000 --- a/Softeq.XToolkit.Common.iOS.Tests/Helpers.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System; -using System.Threading.Tasks; -using UIKit; - -namespace Softeq.XToolkit.Common.iOS.Tests -{ - public static class Helpers - { - public static Task RunOnUIThreadAsync(Func func) - { - var tcs = new TaskCompletionSource(); - UIApplication.SharedApplication.BeginInvokeOnMainThread(() => - { - try - { - var result = func(); - tcs.SetResult(result); - } - catch (Exception e) - { - tcs.SetException(e); - } - }); - return tcs.Task; - } - } -} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Info.plist b/Softeq.XToolkit.Common.iOS.Tests/Info.plist deleted file mode 100644 index 56b0926cd..000000000 --- a/Softeq.XToolkit.Common.iOS.Tests/Info.plist +++ /dev/null @@ -1,38 +0,0 @@ - - - - - CFBundleName - XT.Common.Tests - CFBundleIdentifier - com.softeq.xtoolkit-common-ios-tests - CFBundleShortVersionString - 1.0 - CFBundleVersion - 1.0 - LSRequiresIPhoneOS - - MinimumOSVersion - 10.0 - UIDeviceFamily - - 1 - 2 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UILaunchStoryboardName - LaunchScreen - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - - UIUserInterfaceStyle - Light - - diff --git a/Softeq.XToolkit.Common.iOS.Tests/IosMainThreadExecutorTests/IosMainThreadExecutorTests.cs b/Softeq.XToolkit.Common.iOS.Tests/IosMainThreadExecutorTests/IosMainThreadExecutorTests.cs deleted file mode 100644 index 91d7091d5..000000000 --- a/Softeq.XToolkit.Common.iOS.Tests/IosMainThreadExecutorTests/IosMainThreadExecutorTests.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Threading.Tasks; -using Xunit; - -namespace Softeq.XToolkit.Common.iOS.Tests.IosMainThreadExecutorTests -{ - public class IosMainThreadExecutorTests - { - private readonly IosMainThreadExecutor _executor; - - public IosMainThreadExecutorTests() - { - _executor = new IosMainThreadExecutor(); - } - - [Fact] - public async Task IsMainThread_InMainThread_ReturnsTrue() - { - var result = await Helpers.RunOnUIThreadAsync(() => _executor.IsMainThread); - - Assert.True(result); - } - - [Fact] - public async Task IsMainThread_NotInMainThread_ReturnsFalse() - { - var result = await Task.Run(() => _executor.IsMainThread); - - Assert.False(result); - } - } -} diff --git a/Softeq.XToolkit.Common.iOS.Tests/LaunchScreen.storyboard b/Softeq.XToolkit.Common.iOS.Tests/LaunchScreen.storyboard deleted file mode 100644 index 535551fca..000000000 --- a/Softeq.XToolkit.Common.iOS.Tests/LaunchScreen.storyboard +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Softeq.XToolkit.Common.iOS.Tests/Main.cs b/Softeq.XToolkit.Common.iOS.Tests/Main.cs deleted file mode 100644 index 6023e350d..000000000 --- a/Softeq.XToolkit.Common.iOS.Tests/Main.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using UIKit; - -namespace Softeq.XToolkit.Common.iOS.Tests -{ -#pragma warning disable SA1649 - public class Application -#pragma warning restore SA1649 - { - private static void Main(string[] args) - { - UIApplication.Main(args, null, typeof(AppDelegate)); - } - } -} diff --git a/Softeq.XToolkit.Common.iOS.Tests/MauiProgram.cs b/Softeq.XToolkit.Common.iOS.Tests/MauiProgram.cs new file mode 100644 index 000000000..ba2105586 --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/MauiProgram.cs @@ -0,0 +1,31 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using Microsoft.Extensions.Logging; +using Microsoft.Maui.Hosting; +using Xunit.Runners.Maui; + +namespace Softeq.XToolkit.Common.iOS.Tests; + +public static class MauiProgram +{ + public static MauiApp CreateMauiApp() + { + var builder = MauiApp + .CreateBuilder() + .ConfigureTests(new TestOptions + { + Assemblies = + { + typeof(MauiProgram).Assembly + } + }) + .UseVisualRunner(); + +#if DEBUG + builder.Logging.AddDebug(); +#endif + + return builder.Build(); + } +} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/AppDelegate.cs b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/AppDelegate.cs new file mode 100644 index 000000000..fc53d8a47 --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/AppDelegate.cs @@ -0,0 +1,14 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using Foundation; +using Microsoft.Maui; +using Microsoft.Maui.Hosting; + +namespace Softeq.XToolkit.Common.iOS.Tests; + +[Register(nameof(AppDelegate))] +public class AppDelegate : MauiUIApplicationDelegate +{ + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); +} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/NSDateExtensionsTests/NSDateExtensionsTests.cs b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/NSDateExtensionsTests/NSDateExtensionsTests.cs new file mode 100644 index 000000000..be0137190 --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/NSDateExtensionsTests/NSDateExtensionsTests.cs @@ -0,0 +1,78 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using Foundation; +using Softeq.XToolkit.Common.iOS.Extensions; +using Xunit; + +namespace Softeq.XToolkit.Common.iOS.Tests.Extensions.NSDateExtensionsTests; + +public class NSDateExtensionsTests +{ + [Theory] + [InlineData(-100000)] + [InlineData(0)] + [InlineData(100000)] + public void ToUtcDateTime_ForNsDate_ConvertsToUtcDateTime(double secondsSinceNow) + { + var nsDate = NSDate.FromTimeIntervalSinceNow(secondsSinceNow); + var calendar = NSCalendar.CurrentCalendar; + calendar.TimeZone = NSTimeZone.FromGMT(0); + var utcComponents = CreateComponents(nsDate, calendar); + + var dateTime = nsDate.ToUtcDateTime(); + + Assert.IsType(dateTime); + Assert.Equal(DateTimeKind.Utc, dateTime.Kind); + Assert.Equal(utcComponents.Year, dateTime.Year); + Assert.Equal(utcComponents.Month, dateTime.Month); + Assert.Equal(utcComponents.Day, dateTime.Day); + Assert.Equal(utcComponents.Hour, dateTime.Hour); + Assert.Equal(utcComponents.Minute, dateTime.Minute); + Assert.Equal(utcComponents.Second, dateTime.Second); + } + + [Theory] + [InlineData(-100000)] + [InlineData(0)] + [InlineData(100000)] + public void ToLocalDateTime_ForNsDate_ConvertsToLocalDateTime(double secondsSinceNow) + { + var nsDate = NSDate.FromTimeIntervalSinceNow(secondsSinceNow); + var localComponents = CreateComponents(nsDate, NSCalendar.CurrentCalendar); + + var dateTime = nsDate.ToLocalDateTime(); + + Assert.IsType(dateTime); + Assert.Equal(DateTimeKind.Local, dateTime.Kind); + Assert.Equal(localComponents.Year, dateTime.Year); + Assert.Equal(localComponents.Month, dateTime.Month); + Assert.Equal(localComponents.Day, dateTime.Day); + Assert.Equal(localComponents.Hour, dateTime.Hour); + Assert.Equal(localComponents.Minute, dateTime.Minute); + Assert.Equal(localComponents.Second, dateTime.Second); + } + + [Theory] + [InlineData(-100000)] + [InlineData(0)] + [InlineData(100000)] + public void ToLocalDateTimeAndBack_ForNsDate_ReturnsSameNsDate(double secondsSinceNow) + { + var nsDate = NSDate.FromTimeIntervalSinceNow(secondsSinceNow); + + var dateTime = nsDate.ToLocalDateTime(); + var newNsDate = dateTime.ToNsDate(); + + Assert.Equal(nsDate.SecondsSince1970, newNsDate.SecondsSince1970, 3); + } + + private NSDateComponents CreateComponents(NSDate date, NSCalendar calendar) + { + return calendar.Components( + NSCalendarUnit.Second | NSCalendarUnit.Minute | NSCalendarUnit.Hour | + NSCalendarUnit.Day | NSCalendarUnit.Month | NSCalendarUnit.Year, + date); + } +} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/NSLocaleExtensionsTests/NSLocaleExtensionsTests.cs b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/NSLocaleExtensionsTests/NSLocaleExtensionsTests.cs new file mode 100644 index 000000000..cc6fe6df1 --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/NSLocaleExtensionsTests/NSLocaleExtensionsTests.cs @@ -0,0 +1,42 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using System.Diagnostics.CodeAnalysis; +using Foundation; +using Softeq.XToolkit.Common.iOS.Extensions; +using Xunit; + +namespace Softeq.XToolkit.Common.iOS.Tests.Extensions.NSLocaleExtensionsTests; + +[SuppressMessage("ReSharper", "InvokeAsExtensionMethod", Justification = "Need for tests")] +public class NSLocaleExtensionsTests +{ + [Fact] + public void Is24HourFormat_Null_ThrowsArgumentNullException() + { + var locale = null as NSLocale; + + Assert.Throws(() => + { + NSLocaleExtensions.Is24HourFormat(locale!); + }); + } + + [Theory] + [InlineData("en_US", false)] + [InlineData("en_CA", false)] + [InlineData("fil_PH", false)] + [InlineData("ru_RU", true)] + [InlineData("en_BY", true)] + [InlineData("de_DE", true)] + [InlineData("it_IT", true)] + public void Is24HourFormat_24HourLocaleFormat_ReturnsTrue(string localeId, bool expectedResult) + { + var locale = NSLocale.FromLocaleIdentifier(localeId); + + var result = locale.Is24HourFormat(); + + Assert.Equal(expectedResult, result); + } +} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/NSStringExtensions/NSStringExtensionsTests.cs b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/NSStringExtensions/NSStringExtensionsTests.cs new file mode 100644 index 000000000..0484191af --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/NSStringExtensions/NSStringExtensionsTests.cs @@ -0,0 +1,193 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using System.Diagnostics.CodeAnalysis; +using Foundation; +using Softeq.XToolkit.Common.iOS.Extensions; +using UIKit; +using Xunit; +using CommonIosExtensions = Softeq.XToolkit.Common.iOS.Extensions; + +namespace Softeq.XToolkit.Common.iOS.Tests.Extensions.NSStringExtensions; + +[SuppressMessage("ReSharper", "InvokeAsExtensionMethod", Justification = "Need for tests")] +public class NSStringExtensionsTests +{ + [Fact] + public void NewParagraphStyle_CreateNewInstance_ReturnsInstance() + { + var result = CommonIosExtensions.NSStringExtensions.NewParagraphStyle; + + Assert.IsType(result); + } + + [Fact] + public void ToNSUrl_Null_ThrowsArgumentNullException() + { + var link = null as string; + + Assert.Throws(() => + { + CommonIosExtensions.NSStringExtensions.ToNSUrl(link!); + }); + } + + [Theory] + [InlineData("")] + public void ToNSUrl_InvalidLink_ThrowsUriFormatException(string link) + { + Assert.Throws(() => + { + link.ToNSUrl(); + }); + } + + [Theory] + [InlineData("localhost", "http://localhost/")] + [InlineData("softeq.com", "http://softeq.com/")] + [InlineData("www.softeq.com", "http://www.softeq.com/")] + [InlineData("http://softeq.com", "http://softeq.com/")] + [InlineData("https://softeq.com", "https://softeq.com/")] + [InlineData("https://www.softeq.com/", "https://www.softeq.com/")] + [InlineData("https://softeq.com//", "https://softeq.com//")] + [InlineData("https://example.com/??a", "https://example.com/??a")] + [InlineData("https://example.com/?arg=1", "https://example.com/?arg=1")] + [InlineData("https://api.example.com:3000/", "https://api.example.com:3000/")] + [InlineData("https://example.com/?arg=1&asd=&q=тест+-+test", "https://example.com/?arg=1&asd=&q=%D1%82%D0%B5%D1%81%D1%82+-+test")] + [InlineData("https://com.dev.api.example.com/&a=1?b=2", "https://com.dev.api.example.com/&a=1?b=2")] + [InlineData("https://com.dev.api.example.com/?a=b=", "https://com.dev.api.example.com/?a=b=")] + [InlineData("http://localhost/../..", "http://localhost/")] + [InlineData("http://localhost/%2E%2E/%2E%2E", "http://localhost/")] + [InlineData("htTP://localhost/", "http://localhost/")] + [InlineData("http://localHOST/", "http://localhost/")] + [InlineData("https://localhost:3000", "https://localhost:3000/")] + [InlineData("https://127.0.0.1", "https://127.0.0.1/")] + [InlineData("https://127.0.0.1:8080", "https://127.0.0.1:8080/")] + [InlineData("https://user:password@www.example.com:80/Home/Index.htm?q1=v1&q2=v2#FragmentName", "https://user:password@www.example.com:80/Home/Index.htm?q1=v1&q2=v2#FragmentName")] + [InlineData("ftp://example.com", "ftp://example.com/")] + [InlineData("mailto://example.com", "mailto://example.com")] + [InlineData("mailto:test@example.com", "mailto:test@example.com")] + [InlineData("test://localhost/", "test://localhost/")] + [InlineData("localhost:3000", "localhost:3000")] + [InlineData(@"\\some\directory\name\", "file://some/directory/name/")] + public void ToNSUrl_ValidLink_ReturnsExpectedNSUrl(string link, string expectedLink) + { + var result = link.ToNSUrl(); + + Assert.IsType(result); + Assert.Equal(expectedLink, result.AbsoluteString); + } + + [Fact] + public void BuildAttributedString_Null_ThrowsArgumentNullException() + { + var input = null as string; + + Assert.Throws(() => + { + CommonIosExtensions.NSStringExtensions.BuildAttributedString(input!); + }); + } + + [Theory] + [InlineData("")] + [InlineData("test string")] + public void BuildAttributedString_NotNull_ReturnsAttributedString(string input) + { + var result = input.BuildAttributedString(); + + Assert.IsType(result); + Assert.Equal(input, result.Value); + } + + [Fact] + public void BuildAttributedStringFromHtml_Null_ThrowsArgumentNullException() + { + var input = null as string; + + Assert.Throws(() => + { + CommonIosExtensions.NSStringExtensions.BuildAttributedStringFromHtml(input!); + }); + } + + [Theory] + [InlineData("", "")] + [InlineData("test string", "test string")] + [InlineData("

test string

", "test string\n")] + [InlineData("test string", "test string")] + [InlineData("test string", "test string")] + [InlineData("

test - string

", "test - string\n")] + [InlineData("test string", "test string")] + [InlineData("test string", "test string")] + public void BuildAttributedStringFromHtml_NotNull_ReturnsAttributedString(string html, string expectedResult) + { + var result = html.BuildAttributedStringFromHtml(); + + Assert.IsType(result); + Assert.Equal(expectedResult, result.Value); + } + + [Theory] + [InlineData("", NSStringEncoding.Unicode, "")] + [InlineData("test строка \u2022", NSStringEncoding.UTF8, "test строка •")] + [InlineData("test строка 👍", NSStringEncoding.UTF8, "test строка 👍")] + [InlineData("“test” string", NSStringEncoding.UTF8, "“test” string")] + [InlineData("test \u203C string", NSStringEncoding.UTF8, "test ‼ string")] + public void BuildAttributedStringFromHtml_SpecificEncoding_ReturnsAttributedString( + string html, + NSStringEncoding encoding, + string expectedResult) + { + var result = html.BuildAttributedStringFromHtml(encoding); + + Assert.IsType(result); + Assert.Equal(expectedResult, result.Value); + } + + [Fact] + public void DetectLinks_NullAttributedString_ThrowsNullReferenceException() + { + var obj = null as NSMutableAttributedString; + + Assert.Throws(() => + { + CommonIosExtensions.NSStringExtensions.DetectLinks( + obj!, + UIColor.Red, + NSUnderlineStyle.Single, + false, + out _); + }); + } + + [Fact] + public void DetectLinks_NullColor_ExecutesWithoutExceptions() + { + var obj = "test string".BuildAttributedString(); + var color = null as UIColor; + + var modifiedObj = obj.DetectLinks(color!, NSUnderlineStyle.Single, false, out _); + + Assert.IsType(modifiedObj); + } + + [Theory] + [InlineData("link: https://softeq.com", 1)] + [InlineData("link: https://softeq.com, google: google.com", 2)] + [InlineData("test string: https://www.softeq.com/featured_projects#mobile, example: http://example.com/test/page", 2)] + public void DetectLinks_TextWithLinks_ExecutesWithoutExceptions(string text, int linkCount) + { + var obj = text.BuildAttributedString(); + + var modifiedObj = obj.DetectLinks( + UIColor.Red, + NSUnderlineStyle.Single, + true, + out var tags); + + Assert.IsType(modifiedObj); + Assert.Equal(linkCount, tags.Length); + } +} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/NsRangeExtensionsTests/NSRangeExtensionsTests.cs b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/NsRangeExtensionsTests/NSRangeExtensionsTests.cs new file mode 100644 index 000000000..6dc66e71a --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/NsRangeExtensionsTests/NSRangeExtensionsTests.cs @@ -0,0 +1,47 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using Foundation; +using Softeq.XToolkit.Common.Helpers; +using Softeq.XToolkit.Common.iOS.Extensions; +using Xunit; + +namespace Softeq.XToolkit.Common.iOS.Tests.Extensions.NsRangeExtensionsTests; + +public class NSRangeExtensionsTests +{ + [Theory] + [InlineData(0, 0)] + [InlineData(0, 10)] + [InlineData(5, 10)] + [InlineData(9, 10)] + [InlineData(15, 10)] + public void ToTextRange_ForNsRange_Converts(int position, int length) + { + var nsRange = new NSRange(position, length); + + var textRange = nsRange.ToTextRange(); + + Assert.NotNull(textRange); + Assert.IsType(textRange); + Assert.Equal(position, textRange.Position); + Assert.Equal(length, textRange.Length); + } + + [Theory] + [InlineData(0, 0)] + [InlineData(0, 10)] + [InlineData(5, 10)] + [InlineData(9, 10)] + [InlineData(15, 10)] + public void ToNsRange_ForTextRange_Converts(int position, int length) + { + var textRange = new TextRange(position, length); + + var nsRange = textRange.ToNSRange(); + + Assert.IsType(nsRange); + Assert.Equal(position, nsRange.Location); + Assert.Equal(length, nsRange.Length); + } +} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/UIColorExtensionsTests/UIColorExtensionsTests.cs b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/UIColorExtensionsTests/UIColorExtensionsTests.cs new file mode 100644 index 000000000..990fe478f --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/UIColorExtensionsTests/UIColorExtensionsTests.cs @@ -0,0 +1,69 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using Softeq.XToolkit.Common.iOS.Extensions; +using UIKit; +using Xunit; + +namespace Softeq.XToolkit.Common.iOS.Tests.Extensions.UIColorExtensionsTests; + +public class UIColorExtensionsTests +{ + [Theory] + [InlineData("#333333", "UIColor [A=255, R=51, G=51, B=51]")] + [InlineData("0000FF", "UIColor [A=255, R=0, G=0, B=255]")] + [InlineData("00FF00", "UIColor [A=255, R=0, G=255, B=0]")] + [InlineData("F00", "UIColor [A=255, R=255, G=0, B=0]")] + [InlineData("#008080", "UIColor [A=255, R=0, G=128, B=128]")] + [InlineData("FA8072", "UIColor [A=255, R=250, G=128, B=114]")] + [InlineData("350027", "UIColor [A=255, R=53, G=0, B=39]")] + public void UIColorFromHex_CorrectValueWithoutAlpha_ReturnsValidColor(string hexColor, string expected) + { + var result = hexColor.UIColorFromHex(); + + Assert.Equal(expected, result.ToString()); + } + + [Theory] + [InlineData(1, 1)] + [InlineData(2, 1)] + [InlineData(0, 0)] + [InlineData(-2, 0)] + [InlineData(0.5, 0.5)] + [InlineData(0.33, 0.33)] + public void UIColorFromHex_CorrectValueWithAlpha_ReturnsValidColor(float alpha, float expectedAlpha) + { + var result = "#FFF".UIColorFromHex(alpha); + + Assert.Equal(expectedAlpha, result.CGColor.Alpha); + } + + [Theory] + [InlineData("")] + [InlineData("#00")] + [InlineData("1122")] + [InlineData("#11223344")] + [InlineData("11223344")] + public void UIColorFromHex_IncorrectValue_ThrowsArgumentOutOfRangeException(string hexColor) + { + Assert.Throws(() => + { + hexColor.UIColorFromHex(); + }); + } + + [Theory] + [InlineData(255, 255, 255, "#FFFFFF")] + [InlineData(0, 0, 0, "#000000")] + [InlineData(6, 27, 250, "#061BFA")] + [InlineData(0, 52, -1, "#0034FF")] + public void ToHex_UIColor_ReturnsHexColor(int red, int green, int blue, string expectedHex) + { + var color = UIColor.FromRGB(red, green, blue); + + var result = color.ToHex(); + + Assert.Equal(expectedHex, result); + } +} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/UITextViewExtensionsTests/UITextViewExtensionsTests.cs b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/UITextViewExtensionsTests/UITextViewExtensionsTests.cs new file mode 100644 index 000000000..128f34c53 --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/UITextViewExtensionsTests/UITextViewExtensionsTests.cs @@ -0,0 +1,105 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using Foundation; +using Softeq.XToolkit.Common.iOS.Extensions; +using Softeq.XToolkit.Common.iOS.Tests.TextFilters.GroupFilterTests; +using UIKit; +using Xunit; + +namespace Softeq.XToolkit.Common.iOS.Tests.Extensions.UITextViewExtensionsTests; + +[SuppressMessage("ReSharper", "InvokeAsExtensionMethod", Justification = "Need for tests")] +public class UITextViewExtensionsTests +{ + [Fact] + public void SetFilter_UITextFieldIsNull_ThrowsNullReferenceException() + { + var textField = (UITextField) null!; + + Assert.Throws(() => + { + UITextViewExtensions.SetFilter(textField, new MockTextFilter(true)); + }); + } + + [Fact] + public async Task SetFilter_UITextFieldWithNullFilter_ThrowsArgumentNullException() + { + var result = Helpers.RunOnUIThreadAsync(() => + { + var textField = new UITextField(); + + textField.SetFilter(null!); + + return false; + }); + + await Assert.ThrowsAsync(() => result); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task SetFilter_UITextFieldWithFilter_ReturnsExpected(bool expectedResult) + { + var result = await Helpers.RunOnUIThreadAsync(() => + { + var textField = new UITextField(); + var filter = new MockTextFilter(expectedResult); + + textField.SetFilter(filter); + + return textField.ShouldChangeCharacters(textField, new NSRange(1, 0), "new"); + }); + + Assert.Equal(expectedResult, result); + } + + [Fact] + public void SetFilter_UITextViewIsNull_ThrowsNullReferenceException() + { + var textView = (UITextView) null!; + + Assert.Throws(() => + { + UITextViewExtensions.SetFilter(textView, new MockTextFilter(true)); + }); + } + + [Fact] + public async Task SetFilter_UITextViewWithNullFilter_ThrowsArgumentNullException() + { + var result = Helpers.RunOnUIThreadAsync(() => + { + var textField = new UITextField(); + + textField.SetFilter(null!); + + return false; + }); + + await Assert.ThrowsAsync(() => result); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task SetFilter_UITextViewWithFilter_ReturnsExpected(bool expectedResult) + { + var result = await Helpers.RunOnUIThreadAsync(() => + { + var textView = new UITextView(); + var filter = new MockTextFilter(expectedResult); + + textView.SetFilter(filter); + + return textView.ShouldChangeText!(textView, new NSRange(1, 0), "new"); + }); + + Assert.Equal(expectedResult, result); + } +} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Helpers.cs b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Helpers.cs new file mode 100644 index 000000000..873dbc476 --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Helpers.cs @@ -0,0 +1,29 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using System.Threading.Tasks; +using UIKit; + +namespace Softeq.XToolkit.Common.iOS.Tests; + +public static class Helpers +{ + public static Task RunOnUIThreadAsync(Func func) + { + var tcs = new TaskCompletionSource(); + UIApplication.SharedApplication.BeginInvokeOnMainThread(() => + { + try + { + var result = func(); + tcs.SetResult(result); + } + catch (Exception e) + { + tcs.SetException(e); + } + }); + return tcs.Task; + } +} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Info.plist b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Info.plist new file mode 100644 index 000000000..0004a4fde --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/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/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/IosMainThreadExecutorTests/IosMainThreadExecutorTests.cs b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/IosMainThreadExecutorTests/IosMainThreadExecutorTests.cs new file mode 100644 index 000000000..32e090758 --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/IosMainThreadExecutorTests/IosMainThreadExecutorTests.cs @@ -0,0 +1,33 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System.Threading.Tasks; +using Xunit; + +namespace Softeq.XToolkit.Common.iOS.Tests.IosMainThreadExecutorTests; + +public class IosMainThreadExecutorTests +{ + private readonly IosMainThreadExecutor _executor; + + public IosMainThreadExecutorTests() + { + _executor = new IosMainThreadExecutor(); + } + + [Fact] + public async Task IsMainThread_InMainThread_ReturnsTrue() + { + var result = await Helpers.RunOnUIThreadAsync(() => _executor.IsMainThread); + + Assert.True(result); + } + + [Fact] + public async Task IsMainThread_NotInMainThread_ReturnsFalse() + { + var result = await Task.Run(() => _executor.IsMainThread); + + Assert.False(result); + } +} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Program.cs b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Program.cs new file mode 100644 index 000000000..d12ce8e7d --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Program.cs @@ -0,0 +1,17 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using UIKit; + +namespace Softeq.XToolkit.Common.iOS.Tests; + +public class Program +{ + // This is the main entry point of the application. + 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/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/TextFilters/ForbiddenCharsFilterTests/ForbiddenCharsFilterTests.cs b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/TextFilters/ForbiddenCharsFilterTests/ForbiddenCharsFilterTests.cs new file mode 100644 index 000000000..4d1494cd7 --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/TextFilters/ForbiddenCharsFilterTests/ForbiddenCharsFilterTests.cs @@ -0,0 +1,91 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using System.Threading.Tasks; +using Foundation; +using Softeq.XToolkit.Common.iOS.TextFilters; +using UIKit; +using Xunit; + +namespace Softeq.XToolkit.Common.iOS.Tests.TextFilters.ForbiddenCharsFilterTests; + +public class ForbiddenCharsFilterTests +{ + [Fact] + public void Ctor_Empty_ReturnsITextFilter() + { + var obj = new ForbiddenCharsFilter(); + + Assert.IsAssignableFrom(obj); + } + + [Fact] + public void Ctor_Null_ThrowsArgumentNullException() + { + Assert.Throws(() => new ForbiddenCharsFilter(null!)); + } + + [Fact] + public void Ctor_SingleChar_ReturnsITextFilter() + { + var obj = new ForbiddenCharsFilter('@'); + + Assert.IsAssignableFrom(obj); + } + + [Fact] + public void Ctor_Chars_ReturnsITextFilter() + { + var obj = new ForbiddenCharsFilter('@', '+', '#'); + + Assert.IsAssignableFrom(obj); + } + + [Theory] + [InlineData("", "", "", true)] + [InlineData("", "old", "new", true)] + [InlineData("#$", "old", "new#a", false)] + [InlineData("#$@", "new", "@", false)] + [InlineData("#$@", "old", "new", true)] + public async Task ShouldChangeText_UITextFieldForbiddenChars_ReturnsExpectedResult( + string forbiddenChars, + string oldText, + string newText, + bool expectedResult) + { + var result = await Helpers.RunOnUIThreadAsync(() => + { + var textField = new UITextField(); + var range = new NSRange(oldText.Length - 1, newText.Length); + var chars = forbiddenChars.ToCharArray(); + var filter = new ForbiddenCharsFilter(chars); + + return filter.ShouldChangeText(textField, oldText, range, newText); + }); + + Assert.Equal(expectedResult, result); + } + + [Theory] + [InlineData(null, "old")] + [InlineData(null, null)] + public void ShouldChangeText_NullResponderAndOldText_ReturnsExpectedResult(UIResponder responder, string oldText) + { + var filter = new ForbiddenCharsFilter('%'); + + var result = filter.ShouldChangeText(responder, oldText, new NSRange(1, 1), "new"); + + Assert.True(result); + } + + [Fact] + public void ShouldChangeText_DefaultRange_ReturnsExpectedResult() + { + var filter = new ForbiddenCharsFilter('%'); + + var result = filter.ShouldChangeText(null!, "old", default, "new"); + + Assert.True(result); + } +} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/TextFilters/GroupFilterTests/GroupFilterTests.cs b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/TextFilters/GroupFilterTests/GroupFilterTests.cs new file mode 100644 index 000000000..d766c5cd9 --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/TextFilters/GroupFilterTests/GroupFilterTests.cs @@ -0,0 +1,65 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using Softeq.XToolkit.Common.iOS.TextFilters; +using Xunit; + +namespace Softeq.XToolkit.Common.iOS.Tests.TextFilters.GroupFilterTests; + +public class GroupFilterTests +{ + [Fact] + public void Ctor_Empty_ReturnsITextFilter() + { + var obj = new GroupFilter(); + + Assert.IsAssignableFrom(obj); + } + + [Fact] + public void Ctor_Null_ThrowsArgumentNullException() + { + Assert.Throws(() => new GroupFilter(null!)); + } + + [Fact] + public void Ctor_SingleFilter_ReturnsITextFilter() + { + var filter = new MockTextFilter(true); + + var obj = new GroupFilter(filter); + + Assert.IsAssignableFrom(obj); + } + + [Fact] + public void Ctor_Filters_ReturnsITextFilter() + { + var filter1 = new MockTextFilter(true); + var filter2 = new MockTextFilter(true); + + var obj = new GroupFilter(filter1, filter2); + + Assert.IsAssignableFrom(obj); + } + + [Theory] + [InlineData(true, true, true)] + [InlineData(true, false, false)] + [InlineData(false, true, false)] + [InlineData(false, false, false)] + public void ShouldChangeText_FilterReturnsResult_ReturnsExpected( + bool filter1Result, + bool filter2Result, + bool expectedResult) + { + var filter1 = new MockTextFilter(filter1Result); + var filter2 = new MockTextFilter(filter2Result); + var groupFilter = new GroupFilter(filter1, filter2); + + var result = groupFilter.ShouldChangeText(null!, string.Empty, default, string.Empty); + + Assert.Equal(expectedResult, result); + } +} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/TextFilters/GroupFilterTests/MockTextFilter.cs b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/TextFilters/GroupFilterTests/MockTextFilter.cs new file mode 100644 index 000000000..b465a5b2d --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/TextFilters/GroupFilterTests/MockTextFilter.cs @@ -0,0 +1,23 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using Foundation; +using Softeq.XToolkit.Common.iOS.TextFilters; +using UIKit; + +namespace Softeq.XToolkit.Common.iOS.Tests.TextFilters.GroupFilterTests; + +internal class MockTextFilter : ITextFilter +{ + private readonly bool _result; + + public MockTextFilter(bool result) + { + _result = result; + } + + public bool ShouldChangeText(UIResponder responder, string? oldText, NSRange range, string replacementString) + { + return _result; + } +} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/TextFilters/LengthFilterTests/LengthFilterTests.cs b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/TextFilters/LengthFilterTests/LengthFilterTests.cs new file mode 100644 index 000000000..bb49fb98b --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/TextFilters/LengthFilterTests/LengthFilterTests.cs @@ -0,0 +1,115 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using System.Threading.Tasks; +using Foundation; +using Softeq.XToolkit.Common.iOS.TextFilters; +using UIKit; +using Xunit; + +namespace Softeq.XToolkit.Common.iOS.Tests.TextFilters.LengthFilterTests; + +public class LengthFilterTests +{ + [Theory] + [InlineData(0)] + [InlineData(1000)] + public void Ctor_Positive_ReturnsITextFilter(int maxLength) + { + var obj = new LengthFilter(maxLength); + + Assert.IsAssignableFrom(obj); + } + + [Fact] + public void Ctor_Negative_ThrowsArgumentException() + { + Assert.Throws(() => new LengthFilter(-1)); + } + + [Fact] + public void IsCharacterOverwritingEnabled_Default_ReturnsFalse() + { + var obj = new LengthFilter(0); + + var result = obj.IsCharacterOverwritingEnabled; + + Assert.False(result); + } + + [Theory] + [InlineData("old", 3, "new", 6, true)] + [InlineData("old", 3, "new", 3, false)] + [InlineData("old", 1, "new", 2, false)] + public async Task ShouldChangeText_UITextField_ReturnsExpected( + string oldText, + int insertPosition, + string newText, + int maxLength, + bool expectedResult) + { + var result = await Helpers.RunOnUIThreadAsync(() => + { + var textField = new UITextField(); + var range = new NSRange(insertPosition, 0); + var filter = new LengthFilter(maxLength); + + return filter.ShouldChangeText(textField, oldText, range, newText); + }); + + Assert.Equal(expectedResult, result); + } + + [Theory] + [InlineData("old", 0, "new", 3, "new")] + [InlineData("old-extra-text", 4, "new", 7, "old-new")] + public async Task ShouldChangeText_UITextFieldWithCharacterOverwritingEnabled_ReturnsExpected( + string oldText, + int insertPosition, + string newText, + int maxLength, + string expectedResult) + { + var result = await Helpers.RunOnUIThreadAsync(() => + { + var textField = new UITextField { Text = oldText }; + var range = new NSRange(insertPosition, 0); + var filter = new LengthFilter(maxLength); + + filter.IsCharacterOverwritingEnabled = true; + filter.ShouldChangeText(textField, oldText, range, newText); + + return textField.Text; + }); + + Assert.Equal(expectedResult, result); + } + + [Fact] + public void ShouldChangeText_NullResponderWithCharacterOverwritingEnabled_ReturnsExpected() + { + var range = new NSRange(2, 0); + var filter = new LengthFilter(0); + + filter.IsCharacterOverwritingEnabled = true; + var result = filter.ShouldChangeText(null!, "old", range, "new"); + + Assert.False(result); + } + + [Fact] + public async Task ShouldChangeText_InvalidRange_ThrowsArgumentOutOfRangeException() + { + var result = Helpers.RunOnUIThreadAsync(() => + { + var textField = new UITextField(); + var filter = new LengthFilter(0); + + var range = new NSRange(0, 1000); + return filter.ShouldChangeText(textField, "old", range, "new"); + }); + + await Assert.ThrowsAsync(() => result); + } +} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Properties/launchSettings.json b/Softeq.XToolkit.Common.iOS.Tests/Properties/launchSettings.json new file mode 100644 index 000000000..edf8aadcc --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Windows Machine": { + "commandName": "MsixPackage", + "nativeDebugging": false + } + } +} \ No newline at end of file diff --git a/Softeq.XToolkit.Common.iOS.Tests/Resources/AppIcon/appicon.svg b/Softeq.XToolkit.Common.iOS.Tests/Resources/AppIcon/appicon.svg new file mode 100644 index 000000000..9d63b6513 --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Resources/AppIcon/appicon.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Softeq.XToolkit.Common.iOS.Tests/Resources/AppIcon/appiconfg.svg b/Softeq.XToolkit.Common.iOS.Tests/Resources/AppIcon/appiconfg.svg new file mode 100644 index 000000000..21dfb25f1 --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Resources/AppIcon/appiconfg.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Softeq.XToolkit.Common.iOS.Tests/Resources/Raw/AboutAssets.txt b/Softeq.XToolkit.Common.iOS.Tests/Resources/Raw/AboutAssets.txt new file mode 100644 index 000000000..15d624484 --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Resources/Raw/AboutAssets.txt @@ -0,0 +1,15 @@ +Any raw assets you want to be deployed with your application can be placed in +this directory (and child directories). Deployment of the asset to your application +is automatically handled by the following `MauiAsset` Build Action within your `.csproj`. + + + +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/Softeq.XToolkit.Common.iOS.Tests/Resources/Splash/splash.svg b/Softeq.XToolkit.Common.iOS.Tests/Resources/Splash/splash.svg new file mode 100644 index 000000000..21dfb25f1 --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Resources/Splash/splash.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Softeq.XToolkit.Common.iOS.Tests/Softeq.XToolkit.Common.iOS.Tests.csproj b/Softeq.XToolkit.Common.iOS.Tests/Softeq.XToolkit.Common.iOS.Tests.csproj index e0de69a46..7c3136981 100644 --- a/Softeq.XToolkit.Common.iOS.Tests/Softeq.XToolkit.Common.iOS.Tests.csproj +++ b/Softeq.XToolkit.Common.iOS.Tests/Softeq.XToolkit.Common.iOS.Tests.csproj @@ -1,42 +1,47 @@ - - - - net6.0-ios16.0 - Exe - iPhoneSimulator;iPhone;AnyCPU - 10.0 - false - true - iPhone Developer - - - - true - None - iossimulator-x64 - - - - true - SdkOnly - ios-arm64 - - - - SdkOnly - iossimulator-x64 - - - - SdkOnly - ios-arm64 - - - - - - - - - - \ No newline at end of file + + + + net6.0-ios15.0 + Exe + true + true + disable + + + Softeq.XToolkit.Common.iOS.Tests + + + com.softeq.xtoolkit.common.ios.tests + + + 1.0 + 1 + + 11.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Softeq.XToolkit.Common.iOS.Tests/TextFilters/ForbiddenCharsFilterTests/ForbiddenCharsFilterTests.cs b/Softeq.XToolkit.Common.iOS.Tests/TextFilters/ForbiddenCharsFilterTests/ForbiddenCharsFilterTests.cs deleted file mode 100644 index 71907d0f6..000000000 --- a/Softeq.XToolkit.Common.iOS.Tests/TextFilters/ForbiddenCharsFilterTests/ForbiddenCharsFilterTests.cs +++ /dev/null @@ -1,92 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System; -using System.Threading.Tasks; -using Foundation; -using Softeq.XToolkit.Common.iOS.TextFilters; -using UIKit; -using Xunit; - -namespace Softeq.XToolkit.Common.iOS.Tests.TextFilters.ForbiddenCharsFilterTests -{ - public class ForbiddenCharsFilterTests - { - [Fact] - public void Ctor_Empty_ReturnsITextFilter() - { - var obj = new ForbiddenCharsFilter(); - - Assert.IsAssignableFrom(obj); - } - - [Fact] - public void Ctor_Null_ThrowsArgumentNullException() - { - Assert.Throws(() => new ForbiddenCharsFilter(null!)); - } - - [Fact] - public void Ctor_SingleChar_ReturnsITextFilter() - { - var obj = new ForbiddenCharsFilter('@'); - - Assert.IsAssignableFrom(obj); - } - - [Fact] - public void Ctor_Chars_ReturnsITextFilter() - { - var obj = new ForbiddenCharsFilter('@', '+', '#'); - - Assert.IsAssignableFrom(obj); - } - - [Theory] - [InlineData("", "", "", true)] - [InlineData("", "old", "new", true)] - [InlineData("#$", "old", "new#a", false)] - [InlineData("#$@", "new", "@", false)] - [InlineData("#$@", "old", "new", true)] - public async Task ShouldChangeText_UITextFieldForbiddenChars_ReturnsExpectedResult( - string forbiddenChars, - string oldText, - string newText, - bool expectedResult) - { - var result = await Helpers.RunOnUIThreadAsync(() => - { - var textField = new UITextField(); - var range = new NSRange(oldText.Length - 1, newText.Length); - var chars = forbiddenChars.ToCharArray(); - var filter = new ForbiddenCharsFilter(chars); - - return filter.ShouldChangeText(textField, oldText, range, newText); - }); - - Assert.Equal(expectedResult, result); - } - - [Theory] - [InlineData(null, "old")] - [InlineData(null, null)] - public void ShouldChangeText_NullResponderAndOldText_ReturnsExpectedResult(UIResponder responder, string oldText) - { - var filter = new ForbiddenCharsFilter('%'); - - var result = filter.ShouldChangeText(responder, oldText, new NSRange(1, 1), "new"); - - Assert.True(result); - } - - [Fact] - public void ShouldChangeText_DefaultRange_ReturnsExpectedResult() - { - var filter = new ForbiddenCharsFilter('%'); - - var result = filter.ShouldChangeText(null!, "old", default, "new"); - - Assert.True(result); - } - } -} diff --git a/Softeq.XToolkit.Common.iOS.Tests/TextFilters/GroupFilterTests/GroupFilterTests.cs b/Softeq.XToolkit.Common.iOS.Tests/TextFilters/GroupFilterTests/GroupFilterTests.cs deleted file mode 100644 index 60c5426b2..000000000 --- a/Softeq.XToolkit.Common.iOS.Tests/TextFilters/GroupFilterTests/GroupFilterTests.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System; -using Softeq.XToolkit.Common.iOS.TextFilters; -using Xunit; - -namespace Softeq.XToolkit.Common.iOS.Tests.TextFilters.GroupFilterTests -{ - public class GroupFilterTests - { - [Fact] - public void Ctor_Empty_ReturnsITextFilter() - { - var obj = new GroupFilter(); - - Assert.IsAssignableFrom(obj); - } - - [Fact] - public void Ctor_Null_ThrowsArgumentNullException() - { - Assert.Throws(() => new GroupFilter(null!)); - } - - [Fact] - public void Ctor_SingleFilter_ReturnsITextFilter() - { - var filter = new MockTextFilter(true); - - var obj = new GroupFilter(filter); - - Assert.IsAssignableFrom(obj); - } - - [Fact] - public void Ctor_Filters_ReturnsITextFilter() - { - var filter1 = new MockTextFilter(true); - var filter2 = new MockTextFilter(true); - - var obj = new GroupFilter(filter1, filter2); - - Assert.IsAssignableFrom(obj); - } - - [Theory] - [InlineData(true, true, true)] - [InlineData(true, false, false)] - [InlineData(false, true, false)] - [InlineData(false, false, false)] - public void ShouldChangeText_FilterReturnsResult_ReturnsExpected( - bool filter1Result, - bool filter2Result, - bool expectedResult) - { - var filter1 = new MockTextFilter(filter1Result); - var filter2 = new MockTextFilter(filter2Result); - var groupFilter = new GroupFilter(filter1, filter2); - - var result = groupFilter.ShouldChangeText(null!, string.Empty, default, string.Empty); - - Assert.Equal(expectedResult, result); - } - } -} diff --git a/Softeq.XToolkit.Common.iOS.Tests/TextFilters/GroupFilterTests/MockTextFilter.cs b/Softeq.XToolkit.Common.iOS.Tests/TextFilters/GroupFilterTests/MockTextFilter.cs deleted file mode 100644 index 57550120c..000000000 --- a/Softeq.XToolkit.Common.iOS.Tests/TextFilters/GroupFilterTests/MockTextFilter.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using Foundation; -using Softeq.XToolkit.Common.iOS.TextFilters; -using UIKit; - -namespace Softeq.XToolkit.Common.iOS.Tests.TextFilters.GroupFilterTests -{ - internal class MockTextFilter : ITextFilter - { - private readonly bool _result; - - public MockTextFilter(bool result) - { - _result = result; - } - - public bool ShouldChangeText(UIResponder responder, string? oldText, NSRange range, string replacementString) - { - return _result; - } - } -} diff --git a/Softeq.XToolkit.Common.iOS.Tests/TextFilters/LengthFilterTests/LengthFilterTests.cs b/Softeq.XToolkit.Common.iOS.Tests/TextFilters/LengthFilterTests/LengthFilterTests.cs deleted file mode 100644 index 2b7888ba4..000000000 --- a/Softeq.XToolkit.Common.iOS.Tests/TextFilters/LengthFilterTests/LengthFilterTests.cs +++ /dev/null @@ -1,116 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System; -using System.Threading.Tasks; -using Foundation; -using Softeq.XToolkit.Common.iOS.TextFilters; -using UIKit; -using Xunit; - -namespace Softeq.XToolkit.Common.iOS.Tests.TextFilters.LengthFilterTests -{ - public class LengthFilterTests - { - [Theory] - [InlineData(0)] - [InlineData(1000)] - public void Ctor_Positive_ReturnsITextFilter(int maxLength) - { - var obj = new LengthFilter(maxLength); - - Assert.IsAssignableFrom(obj); - } - - [Fact] - public void Ctor_Negative_ThrowsArgumentException() - { - Assert.Throws(() => new LengthFilter(-1)); - } - - [Fact] - public void IsCharacterOverwritingEnabled_Default_ReturnsFalse() - { - var obj = new LengthFilter(0); - - var result = obj.IsCharacterOverwritingEnabled; - - Assert.False(result); - } - - [Theory] - [InlineData("old", 3, "new", 6, true)] - [InlineData("old", 3, "new", 3, false)] - [InlineData("old", 1, "new", 2, false)] - public async Task ShouldChangeText_UITextField_ReturnsExpected( - string oldText, - int insertPosition, - string newText, - int maxLength, - bool expectedResult) - { - var result = await Helpers.RunOnUIThreadAsync(() => - { - var textField = new UITextField(); - var range = new NSRange(insertPosition, 0); - var filter = new LengthFilter(maxLength); - - return filter.ShouldChangeText(textField, oldText, range, newText); - }); - - Assert.Equal(expectedResult, result); - } - - [Theory] - [InlineData("old", 0, "new", 3, "new")] - [InlineData("old-extra-text", 4, "new", 7, "old-new")] - public async Task ShouldChangeText_UITextFieldWithCharacterOverwritingEnabled_ReturnsExpected( - string oldText, - int insertPosition, - string newText, - int maxLength, - string expectedResult) - { - var result = await Helpers.RunOnUIThreadAsync(() => - { - var textField = new UITextField { Text = oldText }; - var range = new NSRange(insertPosition, 0); - var filter = new LengthFilter(maxLength); - - filter.IsCharacterOverwritingEnabled = true; - filter.ShouldChangeText(textField, oldText, range, newText); - - return textField.Text; - }); - - Assert.Equal(expectedResult, result); - } - - [Fact] - public void ShouldChangeText_NullResponderWithCharacterOverwritingEnabled_ReturnsExpected() - { - var range = new NSRange(2, 0); - var filter = new LengthFilter(0); - - filter.IsCharacterOverwritingEnabled = true; - var result = filter.ShouldChangeText(null!, "old", range, "new"); - - Assert.False(result); - } - - [Fact] - public async Task ShouldChangeText_InvalidRange_ThrowsArgumentOutOfRangeException() - { - var result = Helpers.RunOnUIThreadAsync(() => - { - var textField = new UITextField(); - var filter = new LengthFilter(0); - - var range = new NSRange(0, 1000); - return filter.ShouldChangeText(textField, "old", range, "new"); - }); - - await Assert.ThrowsAsync(() => result); - } - } -} diff --git a/XToolkit.sln b/XToolkit.sln index 2443d5108..11ada7ae8 100644 --- a/XToolkit.sln +++ b/XToolkit.sln @@ -77,7 +77,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_Playground", "_Playground" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Playground", "Playground", "{6A926916-1067-40F9-A266-A2F71C3B2DD4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Softeq.XToolkit.Common.Droid.Tests", "Softeq.XToolkit.Common.Droid.Tests\Softeq.XToolkit.Common.Droid.Tests.csproj", "{69A56CFD-E5A6-4A7D-9F41-25EB88975BF8}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Softeq.XToolkit.Common.iOS.Tests", "Softeq.XToolkit.Common.iOS.Tests\Softeq.XToolkit.Common.iOS.Tests.csproj", "{F0224985-D9FF-4E50-992F-C78079DC3DDB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Softeq.XToolkit.Common.Droid.Tests", "Softeq.XToolkit.Common.Droid.Tests\Softeq.XToolkit.Common.Droid.Tests.csproj", "{73ABD70F-00C8-4EFC-B743-F85C1B4A50F4}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -289,12 +291,19 @@ Global {1CD42D28-1118-4FEF-8DA9-8C14A47098B4}.Debug.iOS|iPhoneSimulator.Build.0 = Debug|Any CPU {1CD42D28-1118-4FEF-8DA9-8C14A47098B4}.Debug.iOS|iPhone.ActiveCfg = Debug|Any CPU {1CD42D28-1118-4FEF-8DA9-8C14A47098B4}.Debug.iOS|iPhone.Build.0 = Debug|Any CPU - {69A56CFD-E5A6-4A7D-9F41-25EB88975BF8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {69A56CFD-E5A6-4A7D-9F41-25EB88975BF8}.Release|Any CPU.Build.0 = Release|Any CPU - {69A56CFD-E5A6-4A7D-9F41-25EB88975BF8}.Debug.Droid|Any CPU.ActiveCfg = Debug|Any CPU - {69A56CFD-E5A6-4A7D-9F41-25EB88975BF8}.Debug.Droid|Any CPU.Build.0 = Debug|Any CPU - {69A56CFD-E5A6-4A7D-9F41-25EB88975BF8}.Debug.iOS|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {69A56CFD-E5A6-4A7D-9F41-25EB88975BF8}.Debug.iOS|iPhone.ActiveCfg = Debug|Any CPU + {F0224985-D9FF-4E50-992F-C78079DC3DDB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F0224985-D9FF-4E50-992F-C78079DC3DDB}.Release|Any CPU.Build.0 = Release|Any CPU + {F0224985-D9FF-4E50-992F-C78079DC3DDB}.Debug.Droid|Any CPU.ActiveCfg = Debug|Any CPU + {F0224985-D9FF-4E50-992F-C78079DC3DDB}.Debug.iOS|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {F0224985-D9FF-4E50-992F-C78079DC3DDB}.Debug.iOS|iPhoneSimulator.Build.0 = Debug|Any CPU + {F0224985-D9FF-4E50-992F-C78079DC3DDB}.Debug.iOS|iPhone.ActiveCfg = Debug|Any CPU + {F0224985-D9FF-4E50-992F-C78079DC3DDB}.Debug.iOS|iPhone.Build.0 = Debug|Any CPU + {73ABD70F-00C8-4EFC-B743-F85C1B4A50F4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {73ABD70F-00C8-4EFC-B743-F85C1B4A50F4}.Release|Any CPU.Build.0 = Release|Any CPU + {73ABD70F-00C8-4EFC-B743-F85C1B4A50F4}.Debug.Droid|Any CPU.ActiveCfg = Debug|Any CPU + {73ABD70F-00C8-4EFC-B743-F85C1B4A50F4}.Debug.Droid|Any CPU.Build.0 = Debug|Any CPU + {73ABD70F-00C8-4EFC-B743-F85C1B4A50F4}.Debug.iOS|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {73ABD70F-00C8-4EFC-B743-F85C1B4A50F4}.Debug.iOS|iPhone.ActiveCfg = Debug|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {24588814-B93D-4528-8917-9C2A3C4E85CA} = {2CC5305B-22A3-48B0-858A-98478AC14EF9} @@ -326,6 +335,7 @@ Global {00250664-56DC-4B3D-93B9-7C1E8B381F5F} = {6A926916-1067-40F9-A266-A2F71C3B2DD4} {EDFA0566-1772-4442-86F8-828E5EB98B16} = {6A926916-1067-40F9-A266-A2F71C3B2DD4} {6B7105F6-6F18-4999-BA4E-8069D92F3150} = {6A926916-1067-40F9-A266-A2F71C3B2DD4} - {69A56CFD-E5A6-4A7D-9F41-25EB88975BF8} = {2CC5305B-22A3-48B0-858A-98478AC14EF9} + {F0224985-D9FF-4E50-992F-C78079DC3DDB} = {2CC5305B-22A3-48B0-858A-98478AC14EF9} + {73ABD70F-00C8-4EFC-B743-F85C1B4A50F4} = {2CC5305B-22A3-48B0-858A-98478AC14EF9} EndGlobalSection EndGlobal diff --git a/_Tests.Platform.targets b/_Tests.Platform.targets index 5a6a53322..3cf078722 100644 --- a/_Tests.Platform.targets +++ b/_Tests.Platform.targets @@ -3,7 +3,8 @@ - + + \ No newline at end of file From 5645e219b1a5514f6f70385db397b53d529189e4 Mon Sep 17 00:00:00 2001 From: Yauheni Pakala Date: Thu, 3 Aug 2023 16:25:51 +0200 Subject: [PATCH 14/26] Replace Connectivity plugin to MAUI Essentials (#531) --- .../IosConnectivityService.cs | 68 ++++++++------ .../Softeq.XToolkit.Connectivity.iOS.csproj | 7 +- .../ConnectivityService.cs | 58 ------------ .../EssentialsConnectivityService.cs | 91 +++++++++++++++++++ .../IConnectivityService.cs | 22 +++-- .../Softeq.XToolkit.Connectivity.csproj | 5 +- .../CustomDroidBootstrapper.cs | 2 +- .../Properties/AndroidManifest.xml | 1 + .../Components/ConnectivityPageActivity.cs | 2 +- .../ConnectivityPageViewController.cs | 2 +- .../Components/ConnectivityPageViewModel.cs | 19 ++-- 11 files changed, 159 insertions(+), 118 deletions(-) delete mode 100644 Softeq.XToolkit.Connectivity/ConnectivityService.cs create mode 100644 Softeq.XToolkit.Connectivity/EssentialsConnectivityService.cs diff --git a/Softeq.XToolkit.Connectivity.iOS/IosConnectivityService.cs b/Softeq.XToolkit.Connectivity.iOS/IosConnectivityService.cs index a9b1d3482..a1ec7a17b 100644 --- a/Softeq.XToolkit.Connectivity.iOS/IosConnectivityService.cs +++ b/Softeq.XToolkit.Connectivity.iOS/IosConnectivityService.cs @@ -6,23 +6,28 @@ using System.Linq; using System.Threading; using CoreFoundation; +using Microsoft.Maui.Networking; using Network; -using Plugin.Connectivity.Abstractions; namespace Softeq.XToolkit.Connectivity.iOS { - public class IosConnectivityService : IConnectivityService + /// + /// iOS implementation of . + /// Uses iOS 12+ Network framework: https://developer.apple.com/documentation/network?language=objc + /// MAUI.Essentials issue: https://github.com/dotnet/maui/issues/2574 . + /// + public class IosConnectivityService : IConnectivityService, IDisposable { - private readonly ReaderWriterLockSlim _statusesLock = new ReaderWriterLockSlim(); - + private readonly ReaderWriterLockSlim _statusesLock; private readonly Dictionary _connectionStatuses; private readonly IList _monitors; - public event EventHandler? ConnectivityChanged; - public event EventHandler? ConnectivityTypeChanged; - + /// + /// Initializes a new instance of the class. + /// public IosConnectivityService() { + _statusesLock = new ReaderWriterLockSlim(); _connectionStatuses = new Dictionary(); _monitors = new List { @@ -39,6 +44,10 @@ public IosConnectivityService() Dispose(false); } + /// + public event EventHandler? ConnectivityChanged; + + /// public bool IsConnected { get @@ -55,9 +64,8 @@ public bool IsConnected } } - public bool IsSupported => true; - - public IEnumerable ConnectionTypes + /// + public IEnumerable ConnectionProfiles { get { @@ -74,12 +82,19 @@ public IEnumerable ConnectionTypes } } + /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } + /// + /// Releases the unmanaged and optionally the managed resources. + /// + /// true to dispose managed state. + /// + /// protected virtual void Dispose(bool disposing) { if (disposing) @@ -101,7 +116,7 @@ protected virtual bool CheckConnectivity(IReadOnlyDictionary x.Value); } - protected virtual IEnumerable FilterConnectionTypes(IList activeNetworkTypes) + protected virtual IEnumerable FilterConnectionTypes(IList activeNetworkTypes) { if (activeNetworkTypes.Contains(NWInterfaceType.Other) && activeNetworkTypes.Count > 1) { @@ -111,13 +126,15 @@ protected virtual IEnumerable FilterConnectionTypes(IList ConnectionType.Cellular, - NWInterfaceType.Wifi => ConnectionType.WiFi, - _ => ConnectionType.Other + NWInterfaceType.Cellular => ConnectionProfile.Cellular, + NWInterfaceType.Wifi => ConnectionProfile.WiFi, + NWInterfaceType.Wired => ConnectionProfile.Ethernet, + NWInterfaceType.Loopback => ConnectionProfile.Unknown, + _ => ConnectionProfile.Unknown }; } @@ -149,7 +166,7 @@ private NWPathMonitor CreateMonitor(NWInterfaceType type, Action action) private void HandleUpdateSnapshot(NWPath nWPath, NWInterfaceType type) { var isConnectedOld = IsConnected; - var connectionTypesOld = ConnectionTypes; + var connectionTypesOld = ConnectionProfiles; _statusesLock.EnterWriteLock(); try @@ -161,21 +178,14 @@ private void HandleUpdateSnapshot(NWPath nWPath, NWInterfaceType type) _statusesLock.ExitWriteLock(); } - if (isConnectedOld != IsConnected) - { - ConnectivityChanged?.Invoke(this, new ConnectivityChangedEventArgs - { - IsConnected = IsConnected - }); - } + var isConnectedNew = IsConnected; + var connectionTypesNew = ConnectionProfiles; - if (!ConnectionTypes.SequenceEqual(connectionTypesOld)) + if (isConnectedOld != isConnectedNew || + !connectionTypesNew.SequenceEqual(connectionTypesOld)) { - ConnectivityTypeChanged?.Invoke(this, new ConnectivityTypeChangedEventArgs - { - IsConnected = IsConnected, - ConnectionTypes = ConnectionTypes - }); + var access = isConnectedNew ? NetworkAccess.Internet : NetworkAccess.None; + ConnectivityChanged?.Invoke(this, new ConnectivityChangedEventArgs(access, ConnectionProfiles)); } } } diff --git a/Softeq.XToolkit.Connectivity.iOS/Softeq.XToolkit.Connectivity.iOS.csproj b/Softeq.XToolkit.Connectivity.iOS/Softeq.XToolkit.Connectivity.iOS.csproj index 01458f3cb..b2b3e0cc4 100644 --- a/Softeq.XToolkit.Connectivity.iOS/Softeq.XToolkit.Connectivity.iOS.csproj +++ b/Softeq.XToolkit.Connectivity.iOS/Softeq.XToolkit.Connectivity.iOS.csproj @@ -1,7 +1,8 @@  - net6.0-ios10.0 + net6.0-ios12.0 + true false @@ -18,8 +19,4 @@ - - - - \ No newline at end of file diff --git a/Softeq.XToolkit.Connectivity/ConnectivityService.cs b/Softeq.XToolkit.Connectivity/ConnectivityService.cs deleted file mode 100644 index b9fdfbbd1..000000000 --- a/Softeq.XToolkit.Connectivity/ConnectivityService.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System; -using System.Collections.Generic; -using Plugin.Connectivity; -using Plugin.Connectivity.Abstractions; - -namespace Softeq.XToolkit.Connectivity -{ - public class ConnectivityService : IConnectivityService - { - public virtual event EventHandler? ConnectivityChanged; - public virtual event EventHandler? ConnectivityTypeChanged; - - public ConnectivityService() - { - CrossConnectivity.Current.ConnectivityChanged += CurrentConnectivityChanged; - CrossConnectivity.Current.ConnectivityTypeChanged += CurrentConnectivityTypeChanged; - } - - ~ConnectivityService() - { - Dispose(false); - } - - public virtual bool IsConnected => CrossConnectivity.Current.IsConnected; - - public bool IsSupported => CrossConnectivity.IsSupported; - - public IEnumerable ConnectionTypes => CrossConnectivity.Current.ConnectionTypes; - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected void Dispose(bool disposing) - { - if (disposing) - { - CrossConnectivity.Current.ConnectivityChanged -= CurrentConnectivityChanged; - CrossConnectivity.Current.ConnectivityTypeChanged -= CurrentConnectivityTypeChanged; - } - } - - private void CurrentConnectivityChanged(object sender, ConnectivityChangedEventArgs e) - { - ConnectivityChanged?.Invoke(sender, e); - } - - private void CurrentConnectivityTypeChanged(object sender, ConnectivityTypeChangedEventArgs e) - { - ConnectivityTypeChanged?.Invoke(sender, e); - } - } -} diff --git a/Softeq.XToolkit.Connectivity/EssentialsConnectivityService.cs b/Softeq.XToolkit.Connectivity/EssentialsConnectivityService.cs new file mode 100644 index 000000000..e14bde167 --- /dev/null +++ b/Softeq.XToolkit.Connectivity/EssentialsConnectivityService.cs @@ -0,0 +1,91 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Maui.Networking; + +namespace Softeq.XToolkit.Connectivity +{ + /// + /// MAUI.Essentials cross-platform implementation of . + /// + public class EssentialsConnectivityService : IConnectivityService, IDisposable + { + private readonly IConnectivity _connectivity; + + /// + /// Initializes a new instance of the class. + /// + /// + /// Custom instance of + /// or you can use static method. + /// + public EssentialsConnectivityService(IConnectivity connectivity) + { + _connectivity = connectivity; + + _connectivity.ConnectivityChanged += CurrentConnectivityChanged; + } + + ~EssentialsConnectivityService() + { + Dispose(false); + } + + /// + public event EventHandler? ConnectivityChanged; + + /// + public virtual bool IsConnected + { + get + { + var profiles = _connectivity.ConnectionProfiles; + var access = _connectivity.NetworkAccess; + + var hasAnyConnection = profiles.Any(); + var hasInternet = access == NetworkAccess.Internet; + + return hasAnyConnection && hasInternet; + } + } + + /// + public IEnumerable ConnectionProfiles => _connectivity.ConnectionProfiles; + + /// + /// Initializes a new instance of the class + /// with a default instance of . + /// + /// instance. + public static EssentialsConnectivityService Default() => new(Microsoft.Maui.Networking.Connectivity.Current); + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases the unmanaged and optionally the managed resources. + /// + /// true to dispose managed state. + /// + /// + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _connectivity.ConnectivityChanged -= CurrentConnectivityChanged; + } + } + + private void CurrentConnectivityChanged(object? sender, ConnectivityChangedEventArgs e) + { + ConnectivityChanged?.Invoke(sender, e); + } + } +} diff --git a/Softeq.XToolkit.Connectivity/IConnectivityService.cs b/Softeq.XToolkit.Connectivity/IConnectivityService.cs index df8c9fe01..01d5fecfa 100644 --- a/Softeq.XToolkit.Connectivity/IConnectivityService.cs +++ b/Softeq.XToolkit.Connectivity/IConnectivityService.cs @@ -3,20 +3,28 @@ using System; using System.Collections.Generic; -using Plugin.Connectivity.Abstractions; +using Microsoft.Maui.Networking; namespace Softeq.XToolkit.Connectivity { - public interface IConnectivityService : IDisposable + /// + /// Interface for Connectivity Service. + /// + public interface IConnectivityService { + /// + /// Event handler when connection state changes. + /// event EventHandler ConnectivityChanged; - event EventHandler ConnectivityTypeChanged; - + /// + /// Gets a value indicating whether there is an active internet connection. + /// bool IsConnected { get; } - bool IsSupported { get; } - - IEnumerable ConnectionTypes { get; } + /// + /// Gets the active connectivity types for the device. + /// + IEnumerable ConnectionProfiles { get; } } } diff --git a/Softeq.XToolkit.Connectivity/Softeq.XToolkit.Connectivity.csproj b/Softeq.XToolkit.Connectivity/Softeq.XToolkit.Connectivity.csproj index dbd57e59e..800ef1918 100644 --- a/Softeq.XToolkit.Connectivity/Softeq.XToolkit.Connectivity.csproj +++ b/Softeq.XToolkit.Connectivity/Softeq.XToolkit.Connectivity.csproj @@ -2,16 +2,13 @@ net6.0 + true True - - - - diff --git a/samples/Playground/Playground.Droid/CustomDroidBootstrapper.cs b/samples/Playground/Playground.Droid/CustomDroidBootstrapper.cs index 4150a90fa..cb3e0aeb0 100644 --- a/samples/Playground/Playground.Droid/CustomDroidBootstrapper.cs +++ b/samples/Playground/Playground.Droid/CustomDroidBootstrapper.cs @@ -49,7 +49,7 @@ protected override void ConfigureIoc(IContainerBuilder builder) builder.Singleton(); // connectivity - builder.Singleton(); + builder.Singleton(_ => EssentialsConnectivityService.Default()); } } } diff --git a/samples/Playground/Playground.Droid/Properties/AndroidManifest.xml b/samples/Playground/Playground.Droid/Properties/AndroidManifest.xml index a42c82992..0c8889f0d 100644 --- a/samples/Playground/Playground.Droid/Properties/AndroidManifest.xml +++ b/samples/Playground/Playground.Droid/Properties/AndroidManifest.xml @@ -1,6 +1,7 @@  + diff --git a/samples/Playground/Playground.Droid/Views/Components/ConnectivityPageActivity.cs b/samples/Playground/Playground.Droid/Views/Components/ConnectivityPageActivity.cs index e1e4b9b0d..b655a32b6 100644 --- a/samples/Playground/Playground.Droid/Views/Components/ConnectivityPageActivity.cs +++ b/samples/Playground/Playground.Droid/Views/Components/ConnectivityPageActivity.cs @@ -31,7 +31,7 @@ protected override void DoAttachBindings() base.DoAttachBindings(); this.Bind(() => ViewModel.ConnectionStatus, () => _connectionTextView!.Text); - this.Bind(() => ViewModel.ConnectionTypes, () => _typesTextView!.Text); + this.Bind(() => ViewModel.ConnectionProfiles, () => _typesTextView!.Text); } } } diff --git a/samples/Playground/Playground.iOS/ViewControllers/Components/ConnectivityPageViewController.cs b/samples/Playground/Playground.iOS/ViewControllers/Components/ConnectivityPageViewController.cs index 2eebefafd..1ac38c530 100644 --- a/samples/Playground/Playground.iOS/ViewControllers/Components/ConnectivityPageViewController.cs +++ b/samples/Playground/Playground.iOS/ViewControllers/Components/ConnectivityPageViewController.cs @@ -20,7 +20,7 @@ public override void ViewDidLoad() base.ViewDidLoad(); this.Bind(() => ViewModel.ConnectionStatus, () => ConnectionStatusLabel.Text); - this.Bind(() => ViewModel.ConnectionTypes, () => ConnectionTypeLabel.Text); + this.Bind(() => ViewModel.ConnectionProfiles, () => ConnectionTypeLabel.Text); } } } diff --git a/samples/Playground/Playground/ViewModels/Components/ConnectivityPageViewModel.cs b/samples/Playground/Playground/ViewModels/Components/ConnectivityPageViewModel.cs index dc5951213..4041c6e36 100644 --- a/samples/Playground/Playground/ViewModels/Components/ConnectivityPageViewModel.cs +++ b/samples/Playground/Playground/ViewModels/Components/ConnectivityPageViewModel.cs @@ -1,7 +1,7 @@ // Developed by Softeq Development Corporation // http://www.softeq.com -using Plugin.Connectivity.Abstractions; +using Microsoft.Maui.Networking; using Softeq.XToolkit.Common.Threading; using Softeq.XToolkit.Connectivity; using Softeq.XToolkit.WhiteLabel.Mvvm; @@ -19,17 +19,15 @@ public ConnectivityPageViewModel(IConnectivityService connectivityService) public string ConnectionStatus => _connectivityService.IsConnected ? "Connected" : "No Connection"; - public string ConnectionTypes => string.Join(", ", _connectivityService.ConnectionTypes); + public string ConnectionProfiles => string.Join(", ", _connectivityService.ConnectionProfiles); public override void OnAppearing() { base.OnAppearing(); _connectivityService.ConnectivityChanged += ConnectivityServiceConnectivityChanged; - _connectivityService.ConnectivityTypeChanged += ConnectivityServiceConnectivityTypeChanged; - RaisePropertyChanged(nameof(ConnectionStatus)); - RaisePropertyChanged(nameof(ConnectionTypes)); + UpdateStates(); } public override void OnDisappearing() @@ -37,22 +35,19 @@ public override void OnDisappearing() base.OnDisappearing(); _connectivityService.ConnectivityChanged -= ConnectivityServiceConnectivityChanged; - _connectivityService.ConnectivityTypeChanged -= ConnectivityServiceConnectivityTypeChanged; } private void ConnectivityServiceConnectivityChanged(object? sender, ConnectivityChangedEventArgs e) { - Execute.BeginOnUIThread(() => - { - RaisePropertyChanged(nameof(ConnectionStatus)); - }); + UpdateStates(); } - private void ConnectivityServiceConnectivityTypeChanged(object? sender, ConnectivityTypeChangedEventArgs e) + private void UpdateStates() { Execute.BeginOnUIThread(() => { - RaisePropertyChanged(nameof(ConnectionTypes)); + RaisePropertyChanged(nameof(ConnectionStatus)); + RaisePropertyChanged(nameof(ConnectionProfiles)); }); } } From 70783d4bd1bedc409fd6309fcc2f5940bc918da2 Mon Sep 17 00:00:00 2001 From: Kirill Akulich Date: Fri, 13 Oct 2023 11:36:49 +0300 Subject: [PATCH 15/26] location eq added --- .../Location/LocationModel.cs | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/Softeq.XToolkit.WhiteLabel/Location/LocationModel.cs b/Softeq.XToolkit.WhiteLabel/Location/LocationModel.cs index 1b60e3344..11ab68b62 100644 --- a/Softeq.XToolkit.WhiteLabel/Location/LocationModel.cs +++ b/Softeq.XToolkit.WhiteLabel/Location/LocationModel.cs @@ -1,9 +1,11 @@ // Developed by Softeq Development Corporation // http://www.softeq.com +using System; + namespace Softeq.XToolkit.WhiteLabel.Location { - public class LocationModel + public class LocationModel : IEquatable { public LocationModel() { @@ -17,5 +19,32 @@ public LocationModel(double latitude, double longitude) public double Latitude { get; set; } public double Longitude { get; set; } + + public override bool Equals(object obj) + { + return Equals(obj as LocationModel); + } + + public bool Equals(LocationModel other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return other is LocationModel model && + Latitude == model.Latitude && + Longitude == model.Longitude; + } + + public override int GetHashCode() + { + return HashCode.Combine(Latitude, Longitude); + } } } \ No newline at end of file From ee61471743fc4f380af645754cb7a0116fc23d47 Mon Sep 17 00:00:00 2001 From: Kirill Akulich Date: Mon, 16 Oct 2023 16:28:08 +0300 Subject: [PATCH 16/26] comment fixed --- .../Location/LocationModel.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Softeq.XToolkit.WhiteLabel/Location/LocationModel.cs b/Softeq.XToolkit.WhiteLabel/Location/LocationModel.cs index 11ab68b62..ca19a1e55 100644 --- a/Softeq.XToolkit.WhiteLabel/Location/LocationModel.cs +++ b/Softeq.XToolkit.WhiteLabel/Location/LocationModel.cs @@ -25,6 +25,11 @@ public override bool Equals(object obj) return Equals(obj as LocationModel); } + public override int GetHashCode() + { + return HashCode.Combine(Latitude, Longitude); + } + public bool Equals(LocationModel other) { if (ReferenceEquals(null, other)) @@ -37,14 +42,8 @@ public bool Equals(LocationModel other) return true; } - return other is LocationModel model && - Latitude == model.Latitude && - Longitude == model.Longitude; - } - - public override int GetHashCode() - { - return HashCode.Combine(Latitude, Longitude); + return Latitude == other.Latitude && + Longitude == other.Longitude; } } } \ No newline at end of file From 27f1b7aa23f5fb94d8fd933653d2414cdaf4613f Mon Sep 17 00:00:00 2001 From: Yauheni Pakala Date: Wed, 25 Oct 2023 14:49:29 +0200 Subject: [PATCH 17/26] Refactor permissions library (#535) --- .../PermissionsManager.cs | 76 +++++++++---------- .../Permissions/Bluetooth.cs | 16 ++-- .../Permissions/Notifications.cs | 10 ++- .../PermissionsManager.cs | 26 +++---- 4 files changed, 65 insertions(+), 63 deletions(-) diff --git a/Softeq.XToolkit.Permissions.Droid/PermissionsManager.cs b/Softeq.XToolkit.Permissions.Droid/PermissionsManager.cs index 6282ff85b..de5eb2f94 100644 --- a/Softeq.XToolkit.Permissions.Droid/PermissionsManager.cs +++ b/Softeq.XToolkit.Permissions.Droid/PermissionsManager.cs @@ -2,12 +2,12 @@ // http://www.softeq.com using System; -using System.Diagnostics; +using System.Diagnostics; using System.Threading.Tasks; using Microsoft.Maui.Storage; using BasePermission = Microsoft.Maui.ApplicationModel.Permissions.BasePermission; -using XToolkitPermissions = Softeq.XToolkit.Permissions.Permissions; -using XToolkitPermissionsDroid = Softeq.XToolkit.Permissions.Droid.Permissions; +using CustomPermissions = Softeq.XToolkit.Permissions.Permissions; +using PlatformCustomPermissions = Softeq.XToolkit.Permissions.Droid.Permissions; namespace Softeq.XToolkit.Permissions.Droid { @@ -31,15 +31,15 @@ public virtual Task CheckAsync() where T : BasePermission, new() { var permissionType = typeof(T); - if (permissionType == typeof(XToolkitPermissions.Notifications)) + if (permissionType == typeof(CustomPermissions.Notifications)) { - return _permissionsService.CheckPermissionsAsync(); + return _permissionsService.CheckPermissionsAsync(); } - else if (permissionType == typeof(XToolkitPermissions.Bluetooth)) + else if (permissionType == typeof(CustomPermissions.Bluetooth)) { - return _permissionsService.CheckPermissionsAsync(); + return _permissionsService.CheckPermissionsAsync(); } - else + else { return _permissionsService.CheckPermissionsAsync(); } @@ -50,15 +50,15 @@ public Task CheckWithRequestAsync() where T : BasePermission, new() { var permissionType = typeof(T); - if (permissionType == typeof(XToolkitPermissions.Notifications)) + if (permissionType == typeof(CustomPermissions.Notifications)) { - return CommonCheckWithRequestAsync(); + return CommonCheckWithRequestAsync(); } - else if (permissionType == typeof(XToolkitPermissions.Bluetooth)) + else if (permissionType == typeof(CustomPermissions.Bluetooth)) { - return CommonCheckWithRequestAsync(); + return CommonCheckWithRequestAsync(); } - else + else { return CommonCheckWithRequestAsync(); } @@ -76,31 +76,31 @@ private void OpenSettings() _permissionsService.OpenSettings(); } - private void RemoveOldKeys() - where T : BasePermission, new() - { - var requestedKey = GetPermissionRequestedKey(); + private void RemoveOldKeys() + where T : BasePermission, new() + { + var requestedKey = GetPermissionRequestedKey(); if (Preferences.ContainsKey(requestedKey)) - { - Preferences.Remove(requestedKey); - } - - var deniedEverKey = GetPermissionDeniedEverKey(); + { + Preferences.Remove(requestedKey); + } + + var deniedEverKey = GetPermissionDeniedEverKey(); if (Preferences.ContainsKey(deniedEverKey)) - { - Preferences.Remove(deniedEverKey); - } - - string GetPermissionRequestedKey() - where TPermission : BasePermission - { - return $"{nameof(PermissionsManager)}_IsPermissionRequested_{typeof(T).Name}"; - } - + { + Preferences.Remove(deniedEverKey); + } + + string GetPermissionRequestedKey() + where TPermission : BasePermission + { + return $"{nameof(PermissionsManager)}_IsPermissionRequested_{typeof(T).Name}"; + } + string GetPermissionDeniedEverKey() - where T : BasePermission - { - return $"{nameof(PermissionsManager)}_IsPermissionDeniedEver_{typeof(T).Name}"; + where T : BasePermission + { + return $"{nameof(PermissionsManager)}_IsPermissionDeniedEver_{typeof(T).Name}"; } } @@ -115,7 +115,7 @@ private async Task CommonCheckWithRequestAsync() RemoveOldKeys(); - // Timer are used for confirm a fact of showing a request of permission access popup + // Timer are used for confirm a fact of showing a request of permission access popup // in another case user should see screen with settings for changing permission state var timer = new Stopwatch(); timer.Start(); @@ -126,7 +126,7 @@ private async Task CommonCheckWithRequestAsync() permissionStatus = await _permissionsService.RequestPermissionsAsync().ConfigureAwait(false); } - if (permissionStatus == PermissionStatus.Denied + if (permissionStatus == PermissionStatus.Denied && timer.Elapsed < _showPermissionDialogThreshold) { await OpenSettingsWithConfirmationAsync().ConfigureAwait(false); @@ -147,4 +147,4 @@ private async Task OpenSettingsWithConfirmationAsync() } } } -} \ No newline at end of file +} diff --git a/Softeq.XToolkit.Permissions.iOS/Permissions/Bluetooth.cs b/Softeq.XToolkit.Permissions.iOS/Permissions/Bluetooth.cs index dd8080425..1a2d66878 100644 --- a/Softeq.XToolkit.Permissions.iOS/Permissions/Bluetooth.cs +++ b/Softeq.XToolkit.Permissions.iOS/Permissions/Bluetooth.cs @@ -3,9 +3,6 @@ using System; using System.Collections.Generic; -using System.Reactive.Linq; -using System.Reactive.Subjects; -using System.Reactive.Threading.Tasks; using System.Threading.Tasks; using CoreBluetooth; using BasePlatformPermission = Microsoft.Maui.ApplicationModel.Permissions.BasePlatformPermission; @@ -15,9 +12,11 @@ namespace Softeq.XToolkit.Permissions.iOS.Permissions { public class Bluetooth : BasePlatformPermission { + /// protected override Func> RequiredInfoPlistKeys => () => new string[] { "NSBluetoothAlwaysUsageDescription" }; + /// public override Task CheckStatusAsync() { EnsureDeclared(); @@ -25,11 +24,12 @@ public override Task CheckStatusAsync() return Task.FromResult(ParseAuthorization(CBManager.Authorization)); } + /// public override async Task RequestAsync() { EnsureDeclared(); - var status = await CheckStatusAsync(); + var status = await CheckStatusAsync().ConfigureAwait(false); if (status == EssentialsPermissionStatus.Granted) { @@ -39,7 +39,7 @@ public override async Task RequestAsync() if (CBManager.Authorization == CBManagerAuthorization.NotDetermined) { var centralManagerDelegate = new CentralManagerDelegate(); - await centralManagerDelegate.RequestAccessAsync(); + await centralManagerDelegate.RequestAccessAsync().ConfigureAwait(false); } return ParseAuthorization(CBManager.Authorization); @@ -61,7 +61,7 @@ private class CentralManagerDelegate : CBCentralManagerDelegate { private readonly CBCentralManager _centralManager; - private TaskCompletionSource _statusRequest = + private readonly TaskCompletionSource _statusRequest = new TaskCompletionSource(); public CentralManagerDelegate() @@ -71,7 +71,7 @@ public CentralManagerDelegate() public async Task RequestAccessAsync() { - var state = await _statusRequest.Task; + var state = await _statusRequest.Task.ConfigureAwait(false); return state; } @@ -84,4 +84,4 @@ public override void UpdatedState(CBCentralManager centralManager) } } } -} \ No newline at end of file +} diff --git a/Softeq.XToolkit.Permissions.iOS/Permissions/Notifications.cs b/Softeq.XToolkit.Permissions.iOS/Permissions/Notifications.cs index 98da08553..99af89862 100644 --- a/Softeq.XToolkit.Permissions.iOS/Permissions/Notifications.cs +++ b/Softeq.XToolkit.Permissions.iOS/Permissions/Notifications.cs @@ -2,9 +2,7 @@ // http://www.softeq.com using System; -using System.Collections.Generic; using System.Threading.Tasks; -using CoreBluetooth; using UserNotifications; using BasePlatformPermission = Microsoft.Maui.ApplicationModel.Permissions.BasePlatformPermission; using EssentialsPermissionStatus = Microsoft.Maui.ApplicationModel.PermissionStatus; @@ -16,6 +14,7 @@ public class Notifications : BasePlatformPermission public static UNAuthorizationOptions AuthorizationOptions = UNAuthorizationOptions.Alert | UNAuthorizationOptions.Sound; + /// public override async Task CheckStatusAsync() { var notificationSettings = await UNUserNotificationCenter.Current @@ -24,10 +23,13 @@ public override async Task CheckStatusAsync() return ParseAuthorization(notificationSettings.AuthorizationStatus); } + /// public override async Task RequestAsync() { var notificationCenter = UNUserNotificationCenter.Current; - var (isGranted, _) = await notificationCenter.RequestAuthorizationAsync(AuthorizationOptions); + var (isGranted, _) = await notificationCenter + .RequestAuthorizationAsync(AuthorizationOptions) + .ConfigureAwait(false); return isGranted ? EssentialsPermissionStatus.Granted : EssentialsPermissionStatus.Denied; @@ -45,4 +47,4 @@ private static EssentialsPermissionStatus ParseAuthorization(UNAuthorizationStat }; } } -} \ No newline at end of file +} diff --git a/Softeq.XToolkit.Permissions.iOS/PermissionsManager.cs b/Softeq.XToolkit.Permissions.iOS/PermissionsManager.cs index fa4e197f8..dea76e881 100644 --- a/Softeq.XToolkit.Permissions.iOS/PermissionsManager.cs +++ b/Softeq.XToolkit.Permissions.iOS/PermissionsManager.cs @@ -5,8 +5,8 @@ using System.Threading.Tasks; using Microsoft.Maui.Storage; using BasePermission = Microsoft.Maui.ApplicationModel.Permissions.BasePermission; -using XToolkitPermissions = Softeq.XToolkit.Permissions.Permissions; -using XToolkitPermissionsIos = Softeq.XToolkit.Permissions.iOS.Permissions; +using CustomPermissions = Softeq.XToolkit.Permissions.Permissions; +using PlatformCustomPermissions = Softeq.XToolkit.Permissions.iOS.Permissions; namespace Softeq.XToolkit.Permissions.iOS { @@ -39,15 +39,15 @@ public virtual Task CheckWithRequestAsync() { var permissionType = typeof(T); - if (permissionType == typeof(XToolkitPermissions.Notifications)) + if (permissionType == typeof(CustomPermissions.Notifications)) { - return CommonCheckWithRequestAsync(); + return CommonCheckWithRequestAsync(); } - else if (permissionType == typeof(XToolkitPermissions.Bluetooth)) + else if (permissionType == typeof(CustomPermissions.Bluetooth)) { - return CommonCheckWithRequestAsync(); + return CommonCheckWithRequestAsync(); } - else + else { return CommonCheckWithRequestAsync(); } @@ -59,15 +59,15 @@ public Task CheckAsync() { var permissionType = typeof(T); - if (permissionType == typeof(XToolkitPermissions.Notifications)) + if (permissionType == typeof(CustomPermissions.Notifications)) { - return _permissionsService.CheckPermissionsAsync(); + return _permissionsService.CheckPermissionsAsync(); } - else if (permissionType == typeof(XToolkitPermissions.Bluetooth)) + else if (permissionType == typeof(CustomPermissions.Bluetooth)) { - return _permissionsService.CheckPermissionsAsync(); + return _permissionsService.CheckPermissionsAsync(); } - else + else { return _permissionsService.CheckPermissionsAsync(); } @@ -124,4 +124,4 @@ private async Task OpenSettingsWithConfirmationAsync() return PermissionStatus.Unknown; } } -} \ No newline at end of file +} From f87e2c88796209771e155ab6958759e66d726e1e Mon Sep 17 00:00:00 2001 From: Yauheni Pakala Date: Wed, 25 Oct 2023 21:41:40 +0200 Subject: [PATCH 18/26] Add binding support for Count property (ObservableKeyGroupsCollection) (#538) --- ...bleKeyGroupsCollectionTestsCountChanged.cs | 80 +++++++++++++++++++ .../ObservableKeyGroupsCollection.cs | 38 +++++++-- 2 files changed, 112 insertions(+), 6 deletions(-) create mode 100644 Softeq.XToolkit.Common.Tests/Collections/ObservableKeyGroupsCollectionTest/ObservableKeyGroupsCollectionTestsCountChanged.cs diff --git a/Softeq.XToolkit.Common.Tests/Collections/ObservableKeyGroupsCollectionTest/ObservableKeyGroupsCollectionTestsCountChanged.cs b/Softeq.XToolkit.Common.Tests/Collections/ObservableKeyGroupsCollectionTest/ObservableKeyGroupsCollectionTestsCountChanged.cs new file mode 100644 index 000000000..3abf739ac --- /dev/null +++ b/Softeq.XToolkit.Common.Tests/Collections/ObservableKeyGroupsCollectionTest/ObservableKeyGroupsCollectionTestsCountChanged.cs @@ -0,0 +1,80 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using Xunit; +using CollectionHelper = Softeq.XToolkit.Common.Tests.Collections.ObservableKeyGroupsCollectionTest.ObservableKeyGroupsCollectionHelper; + +namespace Softeq.XToolkit.Common.Tests.Collections.ObservableKeyGroupsCollectionTest; + +public class ObservableKeyGroupsCollectionTestsCountChanged +{ + [Fact] + public void AddGroups_KeysOnlyForbidEmptyGroupCorrectKeys_RaisesPropertyChangedForCount() + { + var collection = CollectionHelper.CreateFilledGroupsWithoutEmpty(); + var pairs = CollectionHelper.PairNotContainedKeyWithItems; + + Assert.PropertyChanged(collection, nameof(collection.Count), () => + { + collection.AddGroups(pairs); + }); + } + + [Fact] + public void InsertGroups_KeysOnlyForbidEmptyGroupCorrectKeys_RaisesPropertyChangedForCount() + { + var collection = CollectionHelper.CreateFilledGroupsWithoutEmpty(); + var pairs = CollectionHelper.PairNotContainedKeyWithItems; + + Assert.PropertyChanged(collection, nameof(collection.Count), () => + { + collection.InsertGroups(0, pairs); + }); + } + + [Fact] + public void ReplaceAllGroups_KeysOnlyAllowEmptyGroupCorrectKeys_RaisesPropertyChangedForCount() + { + var collection = CollectionHelper.CreateFilledGroupsWithEmpty(); + var keys = CollectionHelper.KeysEmpty; + + Assert.PropertyChanged(collection, nameof(collection.Count), () => + { + collection.ReplaceAllGroups(keys); + }); + } + + [Fact] + public void ReplaceAllGroups_KeysOnlyAllowEmptyGroupEmptyCollection_RaisesPropertyChangedForCount() + { + var collection = CollectionHelper.CreateFilledGroupsWithEmpty(); + var pairs = CollectionHelper.PairsEmpty; + + Assert.PropertyChanged(collection, nameof(collection.Count), () => + { + collection.ReplaceAllGroups(pairs); + }); + } + + [Fact] + public void RemoveGroups_KeysOnlyForbidEmptyGroupContainsKey_RaisesPropertyChangedForCount() + { + var collection = CollectionHelper.CreateFilledGroupsWithoutEmpty(); + + Assert.PropertyChanged(collection, nameof(collection.Count), () => + { + collection.RemoveGroups(CollectionHelper.KeysOneFill); + }); + } + + [Fact] + public void Clear_FilledCollection_RaisesPropertyChangedForCount() + { + var collection = CollectionHelper.CreateFilledGroupsWithoutEmpty(); + + Assert.PropertyChanged(collection, nameof(collection.Count), () => + { + collection.Clear(); + }); + } +} diff --git a/Softeq.XToolkit.Common/Collections/ObservableKeyGroupsCollection.cs b/Softeq.XToolkit.Common/Collections/ObservableKeyGroupsCollection.cs index 9ab70ff45..4eab2c6fa 100644 --- a/Softeq.XToolkit.Common/Collections/ObservableKeyGroupsCollection.cs +++ b/Softeq.XToolkit.Common/Collections/ObservableKeyGroupsCollection.cs @@ -6,7 +6,9 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; +using System.ComponentModel; using System.Linq; +using System.Runtime.CompilerServices; using Softeq.XToolkit.Common.Collections.EventArgs; using Softeq.XToolkit.Common.Extensions; @@ -21,7 +23,8 @@ namespace Softeq.XToolkit.Common.Collections public sealed class ObservableKeyGroupsCollection : IObservableKeyGroupsCollection, INotifyKeyGroupCollectionChanged, - INotifyCollectionChanged + INotifyCollectionChanged, + INotifyPropertyChanged where TKey : notnull where TValue : notnull { @@ -42,6 +45,7 @@ public ObservableKeyGroupsCollection(bool allowEmptyGroups = true) public event NotifyCollectionChangedEventHandler? CollectionChanged; public event EventHandler>? ItemsChanged; + public event PropertyChangedEventHandler? PropertyChanged; public IList Keys => _groups.Select(item => item.Key).ToList(); @@ -161,14 +165,11 @@ public void ReplaceAllGroups(IEnumerable>> item _groups.Clear(); var insertedGroups = InsertGroupsWithoutNotify(0, items, _emptyGroupsDisabled); - if (insertedGroups == null) - { - return; - } + var insertedGroupKeys = insertedGroups?.Select(x => x.Key).ToList() ?? new List(); var newItems = new Collection<(int, IReadOnlyList)> { - (0, insertedGroups.Select(x => x.Key).ToList()) + (0, insertedGroupKeys) }; OnChanged( @@ -534,6 +535,8 @@ private void RaiseEvents(NotifyKeyGroupCollectionChangedEventArgs } ItemsChanged?.Invoke(this, args); + + NotifyCountIfNeeded(args); } private IEnumerable? InsertGroupsWithoutNotify( @@ -697,6 +700,29 @@ private void DecrementIndex(IList> indexes) } } + private void NotifyCountIfNeeded(NotifyKeyGroupCollectionChangedEventArgs args) + { + var isCountOfGroupsChanged = IsActionCanModifyGroup(args.Action); + if (isCountOfGroupsChanged) + { + OnPropertyChanged(nameof(Count)); + } + } + + private bool IsActionCanModifyGroup(NotifyCollectionChangedAction? action) + { + return action + is NotifyCollectionChangedAction.Add + or NotifyCollectionChangedAction.Remove + or NotifyCollectionChangedAction.Replace + or NotifyCollectionChangedAction.Reset; + } + + private void OnPropertyChanged([CallerMemberName] string? propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + private class Group : List, IGrouping { public Group(KeyValuePair> keyValuePair) From 4fdd0daeae66fe097ddb57e6c1fcb12bfdcd99ca Mon Sep 17 00:00:00 2001 From: Yauheni Pakala Date: Fri, 27 Oct 2023 14:56:06 +0200 Subject: [PATCH 19/26] Update docfx (#534) --- documentation/build.sh | 2 +- documentation/docfx.json | 37 +++- documentation/filterConfig.yml | 4 + .../styles/main.css | 87 ++++++-- .../templates/material-modern/public/main.css | 204 ++++++++++++++++++ .../material/partials/head.tmpl.partial | 20 -- 6 files changed, 313 insertions(+), 41 deletions(-) create mode 100644 documentation/filterConfig.yml rename documentation/templates/{material => material-classic}/styles/main.css (69%) create mode 100644 documentation/templates/material-modern/public/main.css delete mode 100644 documentation/templates/material/partials/head.tmpl.partial diff --git a/documentation/build.sh b/documentation/build.sh index d988599a9..221854d5c 100644 --- a/documentation/build.sh +++ b/documentation/build.sh @@ -1,6 +1,6 @@ #!/bin/bash -DOCFX_VERSION="2.62.1" +DOCFX_VERSION="2.71.1" DOCFX_CONFIG_PATH="docfx.json" # install tool diff --git a/documentation/docfx.json b/documentation/docfx.json index 2a7916bb4..5d091e4b7 100644 --- a/documentation/docfx.json +++ b/documentation/docfx.json @@ -4,19 +4,43 @@ "src": [ { "files": [ - "**/Softeq.XToolkit.*.csproj" + "Softeq.XToolkit.Bindings/bin/Release/*/Softeq.XToolkit.Bindings.dll", + "Softeq.XToolkit.Bindings.Droid/bin/Release/*/Softeq.XToolkit.Bindings.Droid.dll", + "Softeq.XToolkit.Bindings.iOS/bin/Release/*/Softeq.XToolkit.Bindings.iOS.dll", + + "Softeq.XToolkit.Common/bin/Release/*/Softeq.XToolkit.Common.dll", + "Softeq.XToolkit.Common.Droid/bin/Release/*/Softeq.XToolkit.Common.Droid.dll", + "Softeq.XToolkit.Common.iOS/bin/Release/*/Softeq.XToolkit.Common.iOS.dll", + + "Softeq.XToolkit.Connectivity/bin/Release/*/Softeq.XToolkit.Connectivity.dll", + "Softeq.XToolkit.Connectivity.iOS/bin/Release/*/Softeq.XToolkit.Connectivity.iOS.dll", + + "Softeq.XToolkit.Permissions/bin/Release/*/Softeq.XToolkit.Permissions.dll", + "Softeq.XToolkit.Permissions.Droid/bin/Release/*/Softeq.XToolkit.Permissions.Droid.dll", + "Softeq.XToolkit.Permissions.iOS/bin/Release/*/Softeq.XToolkit.Permissions.iOS.dll", + + "Softeq.XToolkit.PushNotifications/bin/Release/*/Softeq.XToolkit.PushNotifications.dll", + "Softeq.XToolkit.PushNotifications.Droid/bin/Release/*/Softeq.XToolkit.PushNotifications.Droid.dll", + "Softeq.XToolkit.PushNotifications.iOS/bin/Release/*/Softeq.XToolkit.PushNotifications.iOS.dll", + + "Softeq.XToolkit.Remote/bin/Release/*/Softeq.XToolkit.Remote.dll", + + "Softeq.XToolkit.WhiteLabel/bin/Release/*/Softeq.XToolkit.WhiteLabel.dll", + "Softeq.XToolkit.WhiteLabel.Droid/bin/Release/*/Softeq.XToolkit.WhiteLabel.Droid.dll", + "Softeq.XToolkit.WhiteLabel.iOS/bin/Release/*/Softeq.XToolkit.WhiteLabel.iOS.dll", + + "Softeq.XToolkit.WhiteLabel.Essentials/bin/Release/*/Softeq.XToolkit.WhiteLabel.Essentials.dll", + "Softeq.XToolkit.WhiteLabel.Essentials.Droid/bin/Release/*/Softeq.XToolkit.WhiteLabel.Essentials.Droid.dll", + "Softeq.XToolkit.WhiteLabel.Essentials.iOS/bin/Release/*/Softeq.XToolkit.WhiteLabel.Essentials.iOS.dll" ], "src": "..", "exclude": [ - "**/obj/**", - "**/bin/**", - "**/*.Tests.csproj", - "**/*.Forms.csproj", "_site/**" ] } ], "dest": "obj/docfxapi", + "filter": "filterConfig.yml", "namespaceLayout": "nested" } ], @@ -94,7 +118,8 @@ "dest": "_site", "template": [ "statictoc", - "templates/material" + "modern", + "templates/material-modern" ] } } \ No newline at end of file diff --git a/documentation/filterConfig.yml b/documentation/filterConfig.yml new file mode 100644 index 000000000..f1d3324da --- /dev/null +++ b/documentation/filterConfig.yml @@ -0,0 +1,4 @@ +apiRules: +- exclude: + uidRegex: Droid\.Resource + type: Class \ No newline at end of file diff --git a/documentation/templates/material/styles/main.css b/documentation/templates/material-classic/styles/main.css similarity index 69% rename from documentation/templates/material/styles/main.css rename to documentation/templates/material-classic/styles/main.css index a37a4df96..d100f389d 100644 --- a/documentation/templates/material/styles/main.css +++ b/documentation/templates/material-classic/styles/main.css @@ -1,16 +1,22 @@ +@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@100;400;700&display=swap'); + /* COLOR VARIABLES*/ :root { --header-bg-color: #00adff; --header-ft-color: #fff; --highlight-light: #45c3ff; --highlight-dark: #00adff; - --font-color: #000; - --custom-box-shadow: 0 1px 2px 0 rgba(61, 65, 68, 0.06), 0 1px 3px 1px rgba(61, 65, 68, 0.16); + --accent-dim: #e0e0e0; + --accent-super-dim: #f3f3f3; + --font-color: #34393e; + --card-box-shadow: 0 1px 2px 0 rgba(61, 65, 68, 0.06), 0 1px 3px 1px rgba(61, 65, 68, 0.16); + --search-box-shadow: 0 1px 2px 0 rgba(41, 45, 48, 0.36), 0 1px 3px 1px rgba(41, 45, 48, 0.46); + --transition: 350ms; } body { color: var(--font-color); - font-family: sans-serif; + font-family: 'Roboto', sans-serif; line-height: 1.5; font-size: 16px; -ms-text-size-adjust: 100%; @@ -104,9 +110,9 @@ article h4 { .navbar { border: none; /* Both navbars use box-shadow */ - -webkit-box-shadow: var(--custom-box-shadow); - -moz-box-shadow: var(--custom-box-shadow); - box-shadow: var(--custom-box-shadow); + -webkit-box-shadow: var(--card-box-shadow); + -moz-box-shadow: var(--card-box-shadow); + box-shadow: var(--card-box-shadow); } .subnav { @@ -125,6 +131,7 @@ article h4 { background-color: var(--header-bg-color); border-bottom: 3px solid transparent; padding-bottom: 12px; + transition: 350ms; } .navbar-inverse .navbar-nav > li > a:focus, @@ -143,8 +150,30 @@ article h4 { } .navbar-form .form-control { + border: 0; + border-radius: 4px; + box-shadow: var(--search-box-shadow); + transition: var(--transition); +} + +.navbar-form .form-control:hover { + background-color: var(--accent-dim); +} + +/* NAVBAR TOGGLED (small screens) */ + +.navbar-inverse .navbar-collapse, +.navbar-inverse .navbar-form { + border: none; +} +.navbar-inverse .navbar-toggle { + box-shadow: var(--card-box-shadow); border: none; - border-radius: 20px; +} + +.navbar-inverse .navbar-toggle:focus, +.navbar-inverse .navbar-toggle:hover { + background-color: var(--highlight-dark); } /* SIDEBAR */ @@ -170,13 +199,20 @@ article h4 { } .toc-filter { - padding: 10px; + padding: 5px; margin: 0; + box-shadow: var(--card-box-shadow); + transition: var(--transition); +} + +.toc-filter:hover { + background-color: var(--accent-super-dim); } .toc-filter > input { - border: 2px solid #ddd; - border-radius: 20px; + border: none; + background-color: inherit; + transition: inherit; } .toc-filter > .filter-icon { @@ -200,7 +236,7 @@ article h4 { color: inherit; background-color: inherit; border: none; - box-shadow: var(--custom-box-shadow); + box-shadow: var(--card-box-shadow); } .alert > p { @@ -249,13 +285,36 @@ pre { background-color: #fffaef; border-radius: 4px; border: none; - box-shadow: var(--custom-box-shadow); + box-shadow: var(--card-box-shadow); +} + +/* STYLE FOR IMAGES */ + +.article .small-image { + margin-top: 15px; + box-shadow: var(--card-box-shadow); + max-width: 350px; } +.article .medium-image { + margin-top: 15px; + box-shadow: var(--card-box-shadow); + max-width: 550px; +} + +.article .large-image { + margin-top: 15px; + box-shadow: var(--card-box-shadow); + max-width: 700px; +} + + /* SOFTEQ CUSTOM STYLES */ + .navbar-brand > img { - height: 40px; - padding-top: 10px; + width: 20px; + margin-top: 12px; + margin-right: 20px; } .toc ul.nav.level3 { margin: 0 0 0 10px; diff --git a/documentation/templates/material-modern/public/main.css b/documentation/templates/material-modern/public/main.css new file mode 100644 index 000000000..670011b94 --- /dev/null +++ b/documentation/templates/material-modern/public/main.css @@ -0,0 +1,204 @@ +@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@100;400;700&display=swap'); + +:root { + --bs-font-sans-serif: 'Roboto'; + --bs-border-radius: 10px; + + --border-radius-button: 40px; + --card-box-shadow: 0 1px 2px 0 #3d41440f, 0 1px 3px 1px #3d414429; + + --material-yellow-light: #e6dfbf; + --material-yellow-dark: #5a5338; + + --material-blue-light: #c4d9f1; + --material-blue-dark: #383e5a; + + --material-red-light: #f1c4c4; + --material-red-dark: #5a3838; + + --material-warning-header: #f57f171a; + --material-warning-background: #f6e8bd; + --material-warning-background-dark: #57502c; + + --material-info-header: #1976d21a; + --material-info-background: #e3f2fd; + --material-info-background-dark: #2c4557; + + --material-danger-header: #d32f2f1a; + --material-danger-background: #ffebee; + --material-danger-background-dark: #572c2c; +} + +/* HEADINGS */ + +h1 { + font-weight: 600; + font-size: 32px; +} + +h2 { + font-weight: 600; + font-size: 24px; + line-height: 1.8; +} + +h3 { + font-weight: 600; + font-size: 20px; + line-height: 1.8; +} + +h5 { + font-size: 14px; + padding: 10px 0px; +} + +article h2, +article h3, +article h4 { + margin-top: 15px; + margin-bottom: 15px; +} + +article h4 { + padding-bottom: 8px; + border-bottom: 2px solid #ddd; +} + +/** IMAGES **/ +img { + border-radius: var(--bs-border-radius); + box-shadow: var(--card-box-shadow); +} + +/** NAVBAR **/ +.navbar-brand > img { + box-shadow: none; + color: var(--bs-nav-link-color); +} + +[data-bs-theme='light'] nav.navbar { + background-color: var(--bs-primary-bg-subtle); +} + +[data-bs-theme='dark'] nav.navbar { + background-color: var(--bs-tertiary-bg); +} + +.navbar-nav > li > a { + border-radius: var(--border-radius-button); + transition: 200ms; +} + +.navbar-nav a.nav-link:focus, +.navbar-nav a.nav-link:hover { + background-color: var(--bs-primary-border-subtle); +} + +.navbar-nav .nav-link.active, +.navbar-nav .nav-link.show { + color: var(--bs-link-hover-color); +} + +/** SEARCH AND FILTER **/ +input.form-control { + border-radius: var(--border-radius-button); +} + +form.filter { + margin: 0.3rem; +} + +/** ALERTS **/ +.alert { + padding: 0; + border: none; + box-shadow: var(--card-box-shadow); +} + +.alert > p { + padding: 0.2rem 0.7rem 0.7rem 1rem; +} + +.alert > ul { + margin-bottom: 0; + padding: 5px 40px; +} + +.alert > h5 { + padding: 0.5rem 0.7rem 0.7rem 1rem; + border-radius: var(--bs-border-radius) var(--bs-border-radius) 0 0; + font-weight: bold; + text-transform: capitalize; +} + +.alert-info { + color: var(--material-blue-dark); + background-color: var(--material-info-background); +} + +[data-bs-theme='dark'] .alert-info { + color: var(--material-blue-light); + background-color: var(--material-info-background-dark); +} + +.alert-info > h5 { + background-color: var(--material-info-header); +} + +.alert-warning { + color: var(--material-yellow-dark); + background-color: var(--material-warning-background); +} + +[data-bs-theme='dark'] .alert-warning { + color: var(--material-yellow-light); + background-color: var(--material-warning-background-dark); +} + +.alert-warning > h5 { + background-color: var(--material-warning-header); +} + +.alert-danger { + color: var(--material-red-dark); + background-color: var(--material-danger-background); +} + +[data-bs-theme='dark'] .alert-danger { + color: var(--material-red-light); + background-color: var(--material-danger-background-dark); +} + +.alert-danger > h5 { + background-color: var(--material-danger-header); +} + +/* CODE HIGHLIGHT */ +code { + border-radius: var(--bs-border-radius); + margin: 4px 2px; + box-shadow: var(--card-box-shadow); +} + +/* SOFTEQ CUSTOM STYLES */ + +.navbar-brand > img { + width: 30px; + margin-left: 10px; + border-radius: 0; +} +/* .toc ul.nav.level3 { + margin: 0 0 0 10px; +} +.toc ul.nav li { + margin: 4px 0 4px 0; +} */ +.footer-docfx { + color: #ccc; + display: block; + font-size: 12px; +} +code { + padding: 2px 8px; +} diff --git a/documentation/templates/material/partials/head.tmpl.partial b/documentation/templates/material/partials/head.tmpl.partial deleted file mode 100644 index 83fc5f91a..000000000 --- a/documentation/templates/material/partials/head.tmpl.partial +++ /dev/null @@ -1,20 +0,0 @@ -{{!Copyright (c) Oscar Vasquez. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} - - - - - {{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}} - - - - {{#_description}}{{/_description}} - - - - - - - {{#_noindex}}{{/_noindex}} - {{#_enableSearch}}{{/_enableSearch}} - {{#_enableNewTab}}{{/_enableNewTab}} - \ No newline at end of file From 84e4ef3189636da9f3428f742c57a901940879f7 Mon Sep 17 00:00:00 2001 From: Yauheni Pakala Date: Wed, 20 Dec 2023 12:32:45 +0100 Subject: [PATCH 20/26] Improvements before migration to .NET8 (#539) * Add supported os versions * Bump year * Update target framework for nuspecs * Sync wl.essentials platform target frameworks * Add install missed android sdk for api 32 * Bump android target api * Revert "Add install missed android sdk for api 32" This reverts commit a89a7c424a11e61181a1f391d2e6c51dd1d1fb61. --- .../Softeq.XToolkit.Bindings.Droid.csproj | 3 ++- .../Softeq.XToolkit.Bindings.iOS.csproj | 1 + .../Softeq.XToolkit.Common.Droid.Tests.csproj | 2 +- .../Softeq.XToolkit.Common.Droid.csproj | 3 ++- .../Softeq.XToolkit.Common.iOS.csproj | 1 + .../Softeq.XToolkit.Connectivity.iOS.csproj | 1 + .../Softeq.XToolkit.Permissions.Droid.csproj | 1 + .../Softeq.XToolkit.Permissions.iOS.csproj | 1 + ...eq.XToolkit.PushNotifications.Droid.csproj | 3 ++- ...fteq.XToolkit.PushNotifications.iOS.csproj | 1 + .../Softeq.XToolkit.WhiteLabel.Droid.csproj | 3 ++- ...Toolkit.WhiteLabel.Essentials.Droid.csproj | 3 ++- ....XToolkit.WhiteLabel.Essentials.iOS.csproj | 3 ++- .../Softeq.XToolkit.WhiteLabel.iOS.csproj | 1 + nuget/Softeq.XToolkit.Bindings.nuspec | 24 ++++++++--------- nuget/Softeq.XToolkit.Common.nuspec | 24 ++++++++--------- nuget/Softeq.XToolkit.Permissions.nuspec | 24 ++++++++--------- .../Softeq.XToolkit.PushNotifications.nuspec | 27 +++++++++---------- nuget/Softeq.XToolkit.Remote.nuspec | 6 ++--- ...fteq.XToolkit.WhiteLabel.Essentials.nuspec | 25 ++++++++--------- nuget/Softeq.XToolkit.WhiteLabel.Forms.nuspec | 8 +++--- nuget/Softeq.XToolkit.WhiteLabel.nuspec | 24 ++++++++--------- 22 files changed, 100 insertions(+), 89 deletions(-) diff --git a/Softeq.XToolkit.Bindings.Droid/Softeq.XToolkit.Bindings.Droid.csproj b/Softeq.XToolkit.Bindings.Droid/Softeq.XToolkit.Bindings.Droid.csproj index 5b875deae..da03e628b 100644 --- a/Softeq.XToolkit.Bindings.Droid/Softeq.XToolkit.Bindings.Droid.csproj +++ b/Softeq.XToolkit.Bindings.Droid/Softeq.XToolkit.Bindings.Droid.csproj @@ -1,7 +1,8 @@ - net6.0-android32.0 + net6.0-android33.0 + 21.0 diff --git a/Softeq.XToolkit.Bindings.iOS/Softeq.XToolkit.Bindings.iOS.csproj b/Softeq.XToolkit.Bindings.iOS/Softeq.XToolkit.Bindings.iOS.csproj index 4393f4b45..960ce8b26 100644 --- a/Softeq.XToolkit.Bindings.iOS/Softeq.XToolkit.Bindings.iOS.csproj +++ b/Softeq.XToolkit.Bindings.iOS/Softeq.XToolkit.Bindings.iOS.csproj @@ -2,6 +2,7 @@ net6.0-ios12.0 + 10.0 false diff --git a/Softeq.XToolkit.Common.Droid.Tests/Softeq.XToolkit.Common.Droid.Tests.csproj b/Softeq.XToolkit.Common.Droid.Tests/Softeq.XToolkit.Common.Droid.Tests.csproj index 75be754c3..fc09041e2 100644 --- a/Softeq.XToolkit.Common.Droid.Tests/Softeq.XToolkit.Common.Droid.Tests.csproj +++ b/Softeq.XToolkit.Common.Droid.Tests/Softeq.XToolkit.Common.Droid.Tests.csproj @@ -1,7 +1,7 @@  - net6.0-android32.0 + net6.0-android33.0 Exe true true diff --git a/Softeq.XToolkit.Common.Droid/Softeq.XToolkit.Common.Droid.csproj b/Softeq.XToolkit.Common.Droid/Softeq.XToolkit.Common.Droid.csproj index 0a1d306e8..606e94b19 100644 --- a/Softeq.XToolkit.Common.Droid/Softeq.XToolkit.Common.Droid.csproj +++ b/Softeq.XToolkit.Common.Droid/Softeq.XToolkit.Common.Droid.csproj @@ -1,7 +1,8 @@  - net6.0-android32.0 + net6.0-android33.0 + 21.0 diff --git a/Softeq.XToolkit.Common.iOS/Softeq.XToolkit.Common.iOS.csproj b/Softeq.XToolkit.Common.iOS/Softeq.XToolkit.Common.iOS.csproj index 55767c699..2c95cc8d7 100644 --- a/Softeq.XToolkit.Common.iOS/Softeq.XToolkit.Common.iOS.csproj +++ b/Softeq.XToolkit.Common.iOS/Softeq.XToolkit.Common.iOS.csproj @@ -2,6 +2,7 @@ net6.0-ios12.0 + 10.0 false diff --git a/Softeq.XToolkit.Connectivity.iOS/Softeq.XToolkit.Connectivity.iOS.csproj b/Softeq.XToolkit.Connectivity.iOS/Softeq.XToolkit.Connectivity.iOS.csproj index b2b3e0cc4..89e81ee1f 100644 --- a/Softeq.XToolkit.Connectivity.iOS/Softeq.XToolkit.Connectivity.iOS.csproj +++ b/Softeq.XToolkit.Connectivity.iOS/Softeq.XToolkit.Connectivity.iOS.csproj @@ -2,6 +2,7 @@ net6.0-ios12.0 + 12.0 true false diff --git a/Softeq.XToolkit.Permissions.Droid/Softeq.XToolkit.Permissions.Droid.csproj b/Softeq.XToolkit.Permissions.Droid/Softeq.XToolkit.Permissions.Droid.csproj index a206ccda0..07c4191b9 100644 --- a/Softeq.XToolkit.Permissions.Droid/Softeq.XToolkit.Permissions.Droid.csproj +++ b/Softeq.XToolkit.Permissions.Droid/Softeq.XToolkit.Permissions.Droid.csproj @@ -2,6 +2,7 @@ net6.0-android33.0 + 21.0 true diff --git a/Softeq.XToolkit.Permissions.iOS/Softeq.XToolkit.Permissions.iOS.csproj b/Softeq.XToolkit.Permissions.iOS/Softeq.XToolkit.Permissions.iOS.csproj index fa4e2a115..7e5b55bc6 100644 --- a/Softeq.XToolkit.Permissions.iOS/Softeq.XToolkit.Permissions.iOS.csproj +++ b/Softeq.XToolkit.Permissions.iOS/Softeq.XToolkit.Permissions.iOS.csproj @@ -2,6 +2,7 @@ net6.0-ios13.0 + 13.0 true false diff --git a/Softeq.XToolkit.PushNotifications.Droid/Softeq.XToolkit.PushNotifications.Droid.csproj b/Softeq.XToolkit.PushNotifications.Droid/Softeq.XToolkit.PushNotifications.Droid.csproj index 50dcfe3db..63dd6c269 100644 --- a/Softeq.XToolkit.PushNotifications.Droid/Softeq.XToolkit.PushNotifications.Droid.csproj +++ b/Softeq.XToolkit.PushNotifications.Droid/Softeq.XToolkit.PushNotifications.Droid.csproj @@ -1,7 +1,8 @@ - net6.0-android32.0 + net6.0-android33.0 + 26.0 diff --git a/Softeq.XToolkit.PushNotifications.iOS/Softeq.XToolkit.PushNotifications.iOS.csproj b/Softeq.XToolkit.PushNotifications.iOS/Softeq.XToolkit.PushNotifications.iOS.csproj index 145be86a8..0394fd5a0 100644 --- a/Softeq.XToolkit.PushNotifications.iOS/Softeq.XToolkit.PushNotifications.iOS.csproj +++ b/Softeq.XToolkit.PushNotifications.iOS/Softeq.XToolkit.PushNotifications.iOS.csproj @@ -2,6 +2,7 @@ net6.0-ios10.0 + 10.0 false diff --git a/Softeq.XToolkit.WhiteLabel.Droid/Softeq.XToolkit.WhiteLabel.Droid.csproj b/Softeq.XToolkit.WhiteLabel.Droid/Softeq.XToolkit.WhiteLabel.Droid.csproj index e2ad7ec51..fc258d188 100644 --- a/Softeq.XToolkit.WhiteLabel.Droid/Softeq.XToolkit.WhiteLabel.Droid.csproj +++ b/Softeq.XToolkit.WhiteLabel.Droid/Softeq.XToolkit.WhiteLabel.Droid.csproj @@ -1,7 +1,8 @@ - net6.0-android32.0 + net6.0-android33.0 + 24.0 true diff --git a/Softeq.XToolkit.WhiteLabel.Essentials.Droid/Softeq.XToolkit.WhiteLabel.Essentials.Droid.csproj b/Softeq.XToolkit.WhiteLabel.Essentials.Droid/Softeq.XToolkit.WhiteLabel.Essentials.Droid.csproj index 4d18917b6..20ecd8b01 100644 --- a/Softeq.XToolkit.WhiteLabel.Essentials.Droid/Softeq.XToolkit.WhiteLabel.Essentials.Droid.csproj +++ b/Softeq.XToolkit.WhiteLabel.Essentials.Droid/Softeq.XToolkit.WhiteLabel.Essentials.Droid.csproj @@ -1,7 +1,8 @@  - net6.0-android32.0 + net6.0-android33.0 + 21.0 true diff --git a/Softeq.XToolkit.WhiteLabel.Essentials.iOS/Softeq.XToolkit.WhiteLabel.Essentials.iOS.csproj b/Softeq.XToolkit.WhiteLabel.Essentials.iOS/Softeq.XToolkit.WhiteLabel.Essentials.iOS.csproj index ea273283e..b98d14c9d 100644 --- a/Softeq.XToolkit.WhiteLabel.Essentials.iOS/Softeq.XToolkit.WhiteLabel.Essentials.iOS.csproj +++ b/Softeq.XToolkit.WhiteLabel.Essentials.iOS/Softeq.XToolkit.WhiteLabel.Essentials.iOS.csproj @@ -1,7 +1,8 @@  - net6.0-ios12.0 + net6.0-ios13.0 + 10.0 true false diff --git a/Softeq.XToolkit.WhiteLabel.iOS/Softeq.XToolkit.WhiteLabel.iOS.csproj b/Softeq.XToolkit.WhiteLabel.iOS/Softeq.XToolkit.WhiteLabel.iOS.csproj index aff63a8b2..1e8b40e7c 100644 --- a/Softeq.XToolkit.WhiteLabel.iOS/Softeq.XToolkit.WhiteLabel.iOS.csproj +++ b/Softeq.XToolkit.WhiteLabel.iOS/Softeq.XToolkit.WhiteLabel.iOS.csproj @@ -2,6 +2,7 @@ net6.0-ios12.0 + 10.0 true false diff --git a/nuget/Softeq.XToolkit.Bindings.nuspec b/nuget/Softeq.XToolkit.Bindings.nuspec index e36c23400..1bab00941 100644 --- a/nuget/Softeq.XToolkit.Bindings.nuspec +++ b/nuget/Softeq.XToolkit.Bindings.nuspec @@ -11,18 +11,18 @@ https://softeq.github.io/XToolkit.WhiteLabel/articles/xtoolkit/bindings.html Bindings implementation based on INotifyPropertyChanged interface. - Copyright 2022 Softeq Development Corp. + Copyright 2023 Softeq Development Corp. softeq, xtoolkit, xamarin, ios, android, mvvm, bindings Releases: https://github.com/Softeq/XToolkit.WhiteLabel/releases - + - + - + @@ -33,29 +33,29 @@ + target="lib\net6.0" /> + target="lib\net6.0-android33.0" /> + target="lib\net6.0-android33.0" /> + target="lib\net6.0-ios12.0" /> + target="lib\net6.0-ios12.0" /> \ No newline at end of file diff --git a/nuget/Softeq.XToolkit.Common.nuspec b/nuget/Softeq.XToolkit.Common.nuspec index e097aec1b..c99771411 100644 --- a/nuget/Softeq.XToolkit.Common.nuspec +++ b/nuget/Softeq.XToolkit.Common.nuspec @@ -11,13 +11,13 @@ https://softeq.github.io/XToolkit.WhiteLabel/articles/xtoolkit/common.html The most common components without dependencies that can be reused in any project. - Copyright 2022 Softeq Development Corp. + Copyright 2023 Softeq Development Corp. softeq, xtoolkit, xamarin, ios, android, mvvm, toolkit, kit Releases: https://github.com/Softeq/XToolkit.WhiteLabel/releases - - - + + + @@ -26,29 +26,29 @@ + target="lib\net6.0" /> + target="lib\net6.0-android33.0" /> + target="lib\net6.0-android33.0" /> + target="lib\net6.0-ios12.0" /> + target="lib\net6.0-ios12.0" /> \ No newline at end of file diff --git a/nuget/Softeq.XToolkit.Permissions.nuspec b/nuget/Softeq.XToolkit.Permissions.nuspec index 9fd96131d..310aaa272 100644 --- a/nuget/Softeq.XToolkit.Permissions.nuspec +++ b/nuget/Softeq.XToolkit.Permissions.nuspec @@ -11,18 +11,18 @@ https://softeq.github.io/XToolkit.WhiteLabel/articles/xtoolkit/permissions.html Simple cross platform plugin to request and check permissions for Android and iOS. - Copyright 2022 Softeq Development Corp. + Copyright 2023 Softeq Development Corp. softeq, xtoolkit, xamarin, ios, android, permissions - + - + - + @@ -33,29 +33,29 @@ + target="lib\net6.0"/> + target="lib\net6.0-android33.0"/> + target="lib\net6.0-android33.0"/> + target="lib\net6.0-ios13.0"/> + target="lib\net6.0-ios13.0"/> \ No newline at end of file diff --git a/nuget/Softeq.XToolkit.PushNotifications.nuspec b/nuget/Softeq.XToolkit.PushNotifications.nuspec index 02642ee93..569d55e25 100644 --- a/nuget/Softeq.XToolkit.PushNotifications.nuspec +++ b/nuget/Softeq.XToolkit.PushNotifications.nuspec @@ -11,14 +11,14 @@ https://softeq.github.io/XToolkit.WhiteLabel/articles/xtoolkit/push-notifications.html Simple cross platform plugin to use push-notifications for Android and iOS. - Copyright 2022 Softeq Development Corp. + Copyright 2023 Softeq Development Corp. softeq, xtoolkit, xamarin, ios, android, push-notifications, firebase, apns, fcm Releases: https://github.com/Softeq/XToolkit.WhiteLabel/releases - + - + @@ -26,13 +26,10 @@ - + - - - @@ -40,29 +37,29 @@ + target="lib\net6.0" /> + target="lib\net6.0-android33.0" /> + target="lib\net6.0-android33.0" /> + target="lib\net6.0-ios10.0" /> + target="lib\net6.0-ios10.0" /> \ No newline at end of file diff --git a/nuget/Softeq.XToolkit.Remote.nuspec b/nuget/Softeq.XToolkit.Remote.nuspec index 4b1bee3ba..c371da4c5 100644 --- a/nuget/Softeq.XToolkit.Remote.nuspec +++ b/nuget/Softeq.XToolkit.Remote.nuspec @@ -11,7 +11,7 @@ https://softeq.github.io/XToolkit.WhiteLabel/articles/xtoolkit/remote.html Advanced HttpClient infrastructure for mobile applications. - Copyright 2022 Softeq Development Corp. + Copyright 2023 Softeq Development Corp. softeq, xtoolkit, xamarin, ios, android, mvvm, toolkit, remote, http, rest, api, auth Releases: https://github.com/Softeq/XToolkit.WhiteLabel/releases @@ -28,8 +28,8 @@ + target="lib\net6.0" /> \ No newline at end of file diff --git a/nuget/Softeq.XToolkit.WhiteLabel.Essentials.nuspec b/nuget/Softeq.XToolkit.WhiteLabel.Essentials.nuspec index f1717d9a7..b1b804652 100644 --- a/nuget/Softeq.XToolkit.WhiteLabel.Essentials.nuspec +++ b/nuget/Softeq.XToolkit.WhiteLabel.Essentials.nuspec @@ -11,16 +11,16 @@ https://softeq.github.io/XToolkit.WhiteLabel/articles/xtoolkit/whitelabel/essentials.html Library over the Softeq.XToolkit.WhiteLabel that contains optional components for any application. - Copyright 2022 Softeq Development Corp. + Copyright 2023 Softeq Development Corp. softeq, xtoolkit, android, ios, whitelabel, xamarin, essentials, imagepicker, picker Releases: https://github.com/Softeq/XToolkit.WhiteLabel/releases - + - + @@ -28,7 +28,7 @@ - + @@ -42,28 +42,29 @@ + target="lib\net6.0" /> + target="lib\net6.0-android33.0" /> + target="lib\net6.0-android33.0" /> + + target="lib\net6.0-ios13.0" /> + target="lib\net6.0-ios13.0" /> diff --git a/nuget/Softeq.XToolkit.WhiteLabel.Forms.nuspec b/nuget/Softeq.XToolkit.WhiteLabel.Forms.nuspec index 2385c68b0..6596141ca 100644 --- a/nuget/Softeq.XToolkit.WhiteLabel.Forms.nuspec +++ b/nuget/Softeq.XToolkit.WhiteLabel.Forms.nuspec @@ -11,11 +11,11 @@ https://softeq.github.io/XToolkit.WhiteLabel/articles/xtoolkit/whitelabel/forms.html Integration library for using Softeq.XToolkit.WhiteLabel in Xamarin.Forms projects. - Copyright 2022 Softeq Development Corp. + Copyright 2023 Softeq Development Corp. softeq, xtoolkit, whitelabel, xamarin, forms, xamarinforms, xamarin.forms Releases: https://github.com/Softeq/XToolkit.WhiteLabel/releases - + @@ -28,9 +28,9 @@ + target="lib\net6.0" /> diff --git a/nuget/Softeq.XToolkit.WhiteLabel.nuspec b/nuget/Softeq.XToolkit.WhiteLabel.nuspec index 216b94893..4a51b69e3 100644 --- a/nuget/Softeq.XToolkit.WhiteLabel.nuspec +++ b/nuget/Softeq.XToolkit.WhiteLabel.nuspec @@ -11,18 +11,18 @@ https://softeq.github.io/XToolkit.WhiteLabel/articles/xtoolkit/whitelabel.html XToolkit.WhiteLabel is a collection of "lego" components for fast create cross-platform mobile applications with Xamarin, based on XToolkit. - Copyright 2022 Softeq Development Corp. + Copyright 2023 Softeq Development Corp. softeq, xtoolkit, xamarin, ios, android, mvvm, toolkit, whitelabel Releases: https://github.com/Softeq/XToolkit.WhiteLabel/releases - + - + @@ -34,7 +34,7 @@ - + @@ -51,29 +51,29 @@ + target="lib\net6.0" /> + target="lib\net6.0-android33.0" /> + target="lib\net6.0-android33.0" /> + target="lib\net6.0-ios12.0" /> + target="lib\net6.0-ios12.0" /> From 1e59f30f4f1259886ed70019740aa0cadefc9a8a Mon Sep 17 00:00:00 2001 From: Yauheni Pakala Date: Wed, 27 Dec 2023 18:47:43 +0100 Subject: [PATCH 21/26] Migrate to .NET8 (#536) * Bump dotnet version * Bump target framework to .net8 * Migrate Microsoft.Maui.Essentials to use as explicit NuGet packages * Remove obsoleted binary serialization for BiDictionary * Cleanup * Migrate Maui to use as explicit NuGet package * Sync target frameworks and min supported os * Use the same MAUI version in all projects * Bump .NET8 to stable * Update pipelines env * Bump iOS target framework for latest maui & essentials * Resolve warnings related to supported versions * Update nuget package * Sync versions --- Directory.Build.props | 3 +- .../Softeq.XToolkit.Bindings.Droid.csproj | 2 +- .../Softeq.XToolkit.Bindings.Tests.csproj | 2 +- .../Softeq.XToolkit.Bindings.iOS.csproj | 2 +- .../Softeq.XToolkit.Bindings.csproj | 2 +- .../Softeq.XToolkit.Common.Droid.Tests.csproj | 9 +-- .../Softeq.XToolkit.Common.Droid.csproj | 2 +- .../BiDictionaryTests/BiDictionaryTests.cs | 26 --------- .../Softeq.XToolkit.Common.Tests.csproj | 2 +- .../Softeq.XToolkit.Common.iOS.Tests.csproj | 5 +- .../Softeq.XToolkit.Common.iOS.csproj | 2 +- .../Collections/BiDictionary.cs | 55 +------------------ .../Softeq.XToolkit.Common.csproj | 2 +- .../Softeq.XToolkit.Connectivity.iOS.csproj | 7 ++- .../Softeq.XToolkit.Connectivity.csproj | 7 ++- .../Permissions/Notifications.cs | 4 +- .../Softeq.XToolkit.Permissions.Droid.csproj | 7 ++- .../Softeq.XToolkit.Permissions.iOS.csproj | 4 +- .../Softeq.XToolkit.Permissions.csproj | 10 ++-- ...eq.XToolkit.PushNotifications.Droid.csproj | 4 +- ...fteq.XToolkit.PushNotifications.iOS.csproj | 2 +- .../Softeq.XToolkit.PushNotifications.csproj | 2 +- .../Softeq.XToolkit.Remote.Tests.csproj | 2 +- .../Softeq.XToolkit.Remote.csproj | 2 +- .../Softeq.XToolkit.WhiteLabel.Droid.csproj | 4 +- .../ImagePickerActivityResultHandler.cs | 5 +- .../ImagePicker/ImagePickerUtils.cs | 2 + ...Toolkit.WhiteLabel.Essentials.Droid.csproj | 4 +- ....XToolkit.WhiteLabel.Essentials.iOS.csproj | 7 ++- ...fteq.XToolkit.WhiteLabel.Essentials.csproj | 2 +- .../Softeq.XToolkit.WhiteLabel.Tests.csproj | 2 +- .../Softeq.XToolkit.WhiteLabel.iOS.csproj | 7 ++- .../Softeq.XToolkit.WhiteLabel.csproj | 4 +- azure-pipelines/templates/vars.yml | 4 +- global.json | 2 +- .../Playground.Droid/Playground.Droid.csproj | 5 +- .../Properties/AndroidManifest.xml | 2 +- .../Playground.iOS/Playground.iOS.csproj | 5 +- .../Playground/Playground/Playground.csproj | 7 ++- 39 files changed, 85 insertions(+), 142 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 6514dac69..7eba25610 100755 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,9 +2,10 @@ - 10.0 + 12.0 enable portable + 8.0.3 diff --git a/Softeq.XToolkit.Bindings.Droid/Softeq.XToolkit.Bindings.Droid.csproj b/Softeq.XToolkit.Bindings.Droid/Softeq.XToolkit.Bindings.Droid.csproj index da03e628b..7f35df664 100644 --- a/Softeq.XToolkit.Bindings.Droid/Softeq.XToolkit.Bindings.Droid.csproj +++ b/Softeq.XToolkit.Bindings.Droid/Softeq.XToolkit.Bindings.Droid.csproj @@ -1,7 +1,7 @@ - net6.0-android33.0 + net8.0-android34.0 21.0 diff --git a/Softeq.XToolkit.Bindings.Tests/Softeq.XToolkit.Bindings.Tests.csproj b/Softeq.XToolkit.Bindings.Tests/Softeq.XToolkit.Bindings.Tests.csproj index a7eca7ccf..4bbaec837 100644 --- a/Softeq.XToolkit.Bindings.Tests/Softeq.XToolkit.Bindings.Tests.csproj +++ b/Softeq.XToolkit.Bindings.Tests/Softeq.XToolkit.Bindings.Tests.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 Exe disable diff --git a/Softeq.XToolkit.Bindings.iOS/Softeq.XToolkit.Bindings.iOS.csproj b/Softeq.XToolkit.Bindings.iOS/Softeq.XToolkit.Bindings.iOS.csproj index 960ce8b26..4bff76dee 100644 --- a/Softeq.XToolkit.Bindings.iOS/Softeq.XToolkit.Bindings.iOS.csproj +++ b/Softeq.XToolkit.Bindings.iOS/Softeq.XToolkit.Bindings.iOS.csproj @@ -1,7 +1,7 @@  - net6.0-ios12.0 + net8.0-ios12.0 10.0 false diff --git a/Softeq.XToolkit.Bindings/Softeq.XToolkit.Bindings.csproj b/Softeq.XToolkit.Bindings/Softeq.XToolkit.Bindings.csproj index a39c9f858..cd0e00201 100644 --- a/Softeq.XToolkit.Bindings/Softeq.XToolkit.Bindings.csproj +++ b/Softeq.XToolkit.Bindings/Softeq.XToolkit.Bindings.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 diff --git a/Softeq.XToolkit.Common.Droid.Tests/Softeq.XToolkit.Common.Droid.Tests.csproj b/Softeq.XToolkit.Common.Droid.Tests/Softeq.XToolkit.Common.Droid.Tests.csproj index fc09041e2..69456169d 100644 --- a/Softeq.XToolkit.Common.Droid.Tests/Softeq.XToolkit.Common.Droid.Tests.csproj +++ b/Softeq.XToolkit.Common.Droid.Tests/Softeq.XToolkit.Common.Droid.Tests.csproj @@ -1,9 +1,8 @@  - net6.0-android33.0 + net8.0-android34.0 Exe - true true disable @@ -36,16 +35,14 @@ + + - - - - diff --git a/Softeq.XToolkit.Common.Droid/Softeq.XToolkit.Common.Droid.csproj b/Softeq.XToolkit.Common.Droid/Softeq.XToolkit.Common.Droid.csproj index 606e94b19..835b75bb8 100644 --- a/Softeq.XToolkit.Common.Droid/Softeq.XToolkit.Common.Droid.csproj +++ b/Softeq.XToolkit.Common.Droid/Softeq.XToolkit.Common.Droid.csproj @@ -1,7 +1,7 @@  - net6.0-android33.0 + net8.0-android34.0 21.0 diff --git a/Softeq.XToolkit.Common.Tests/Collections/BiDictionaryTests/BiDictionaryTests.cs b/Softeq.XToolkit.Common.Tests/Collections/BiDictionaryTests/BiDictionaryTests.cs index 6f065437d..f9562725b 100644 --- a/Softeq.XToolkit.Common.Tests/Collections/BiDictionaryTests/BiDictionaryTests.cs +++ b/Softeq.XToolkit.Common.Tests/Collections/BiDictionaryTests/BiDictionaryTests.cs @@ -3,10 +3,7 @@ using System; using System.Collections.Generic; -using System.IO; -using System.Runtime.Serialization.Formatters.Binary; using System.Text; -using Softeq.XToolkit.Common.Collections; using Xunit; namespace Softeq.XToolkit.Common.Tests.Collections.BiDictionaryTests @@ -141,29 +138,6 @@ public void Remove_Negative() Assert.Equal("[0, 0],[1, 1]-[0, 0],[1, 1]", dictionary.GetResult()); } - [Fact] - public void Serialization() - { - var dictionary = BiDictionaryHelper.CreateWithTwoItems(); - - var sb = new StringBuilder(); - - using (var memoryStream = new MemoryStream()) - { - var binaryFormatter = new BinaryFormatter(); - binaryFormatter.Serialize(memoryStream, dictionary); - memoryStream.Position = 0; - - var newDict = (BiDictionary) binaryFormatter.Deserialize(memoryStream); - - sb.AppendJoin(",", newDict); - sb.Append("-"); - sb.AppendJoin(",", newDict.Reverse); - } - - Assert.Equal("[0, 0],[1, 1]-[0, 0],[1, 1]", sb.ToString()); - } - [Fact] public void Set() { diff --git a/Softeq.XToolkit.Common.Tests/Softeq.XToolkit.Common.Tests.csproj b/Softeq.XToolkit.Common.Tests/Softeq.XToolkit.Common.Tests.csproj index d04cb66bd..c0600b9f4 100644 --- a/Softeq.XToolkit.Common.Tests/Softeq.XToolkit.Common.Tests.csproj +++ b/Softeq.XToolkit.Common.Tests/Softeq.XToolkit.Common.Tests.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net8.0 disable diff --git a/Softeq.XToolkit.Common.iOS.Tests/Softeq.XToolkit.Common.iOS.Tests.csproj b/Softeq.XToolkit.Common.iOS.Tests/Softeq.XToolkit.Common.iOS.Tests.csproj index 7c3136981..5d391770b 100644 --- a/Softeq.XToolkit.Common.iOS.Tests/Softeq.XToolkit.Common.iOS.Tests.csproj +++ b/Softeq.XToolkit.Common.iOS.Tests/Softeq.XToolkit.Common.iOS.Tests.csproj @@ -1,9 +1,8 @@  - net6.0-ios15.0 + net8.0-ios17.0 Exe - true true disable @@ -36,6 +35,8 @@ + + diff --git a/Softeq.XToolkit.Common.iOS/Softeq.XToolkit.Common.iOS.csproj b/Softeq.XToolkit.Common.iOS/Softeq.XToolkit.Common.iOS.csproj index 2c95cc8d7..1ef220853 100644 --- a/Softeq.XToolkit.Common.iOS/Softeq.XToolkit.Common.iOS.csproj +++ b/Softeq.XToolkit.Common.iOS/Softeq.XToolkit.Common.iOS.csproj @@ -1,7 +1,7 @@  - net6.0-ios12.0 + net8.0-ios12.0 10.0 false diff --git a/Softeq.XToolkit.Common/Collections/BiDictionary.cs b/Softeq.XToolkit.Common/Collections/BiDictionary.cs index 0739279bf..695f1f10f 100644 --- a/Softeq.XToolkit.Common/Collections/BiDictionary.cs +++ b/Softeq.XToolkit.Common/Collections/BiDictionary.cs @@ -4,20 +4,14 @@ using System; using System.Collections; using System.Collections.Generic; -using System.IO; -using System.Runtime.Serialization; -using System.Runtime.Serialization.Formatters.Binary; -using System.Security.Permissions; #pragma warning disable CS1591 namespace Softeq.XToolkit.Common.Collections { - [Serializable] public class BiDictionary : IDictionary, IReadOnlyDictionary, - IDictionary, - ISerializable + IDictionary { private readonly IDictionary _firstToSecond = new Dictionary(); @@ -33,24 +27,6 @@ public BiDictionary() _reverseDictionary = new ReverseDictionary(this); } - protected BiDictionary(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw new ArgumentNullException(nameof(info)); - } - - var data = (byte[]) info.GetValue("firstToSecond", typeof(byte[])); - using (var memoryStream = new MemoryStream(data)) - { - var binaryFormatter = new BinaryFormatter(); - _firstToSecond = (Dictionary) binaryFormatter.Deserialize(memoryStream); - } - - _secondToFirst = new Dictionary(); - _reverseDictionary = new ReverseDictionary(this); - } - public int Count => _firstToSecond.Count; public bool IsReadOnly => _firstToSecond.IsReadOnly || _secondToFirst.IsReadOnly; @@ -196,35 +172,6 @@ void ICollection>.CopyTo(KeyValuePair ReverseItem(KeyValuePair item) { return new KeyValuePair(item.Value, item.Key); diff --git a/Softeq.XToolkit.Common/Softeq.XToolkit.Common.csproj b/Softeq.XToolkit.Common/Softeq.XToolkit.Common.csproj index b62727fd7..cf562f7bf 100644 --- a/Softeq.XToolkit.Common/Softeq.XToolkit.Common.csproj +++ b/Softeq.XToolkit.Common/Softeq.XToolkit.Common.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 diff --git a/Softeq.XToolkit.Connectivity.iOS/Softeq.XToolkit.Connectivity.iOS.csproj b/Softeq.XToolkit.Connectivity.iOS/Softeq.XToolkit.Connectivity.iOS.csproj index 89e81ee1f..26d93a3c8 100644 --- a/Softeq.XToolkit.Connectivity.iOS/Softeq.XToolkit.Connectivity.iOS.csproj +++ b/Softeq.XToolkit.Connectivity.iOS/Softeq.XToolkit.Connectivity.iOS.csproj @@ -1,9 +1,8 @@  - net6.0-ios12.0 + net8.0-ios17.0 12.0 - true false @@ -20,4 +19,8 @@ + + + + \ No newline at end of file diff --git a/Softeq.XToolkit.Connectivity/Softeq.XToolkit.Connectivity.csproj b/Softeq.XToolkit.Connectivity/Softeq.XToolkit.Connectivity.csproj index 800ef1918..51cb06f45 100644 --- a/Softeq.XToolkit.Connectivity/Softeq.XToolkit.Connectivity.csproj +++ b/Softeq.XToolkit.Connectivity/Softeq.XToolkit.Connectivity.csproj @@ -1,8 +1,7 @@ - net6.0 - true + net8.0 @@ -13,4 +12,8 @@ + + + + diff --git a/Softeq.XToolkit.Permissions.Droid/Permissions/Notifications.cs b/Softeq.XToolkit.Permissions.Droid/Permissions/Notifications.cs index 2e6ee63a6..f5d07b2e6 100644 --- a/Softeq.XToolkit.Permissions.Droid/Permissions/Notifications.cs +++ b/Softeq.XToolkit.Permissions.Droid/Permissions/Notifications.cs @@ -16,6 +16,7 @@ public override (string, bool)[] RequiredPermissions get { #if __ANDROID_33__ +#pragma warning disable CA1416 var isSupport = SdkVersion.IsBuildVersionAtLeast(BuildVersionCodes.Tiramisu) && SdkVersion.IsDeviceVersionAtLeast(BuildVersionCodes.Tiramisu) && @@ -24,10 +25,11 @@ public override (string, bool)[] RequiredPermissions return isSupport ? new (string, bool)[] { (Manifest.Permission.PostNotifications, true) } : System.Array.Empty<(string, bool)>(); +#pragma warning restore CA1416 #else return new (string, bool)[] { }; #endif } } } -} \ No newline at end of file +} diff --git a/Softeq.XToolkit.Permissions.Droid/Softeq.XToolkit.Permissions.Droid.csproj b/Softeq.XToolkit.Permissions.Droid/Softeq.XToolkit.Permissions.Droid.csproj index 07c4191b9..c12098125 100644 --- a/Softeq.XToolkit.Permissions.Droid/Softeq.XToolkit.Permissions.Droid.csproj +++ b/Softeq.XToolkit.Permissions.Droid/Softeq.XToolkit.Permissions.Droid.csproj @@ -1,9 +1,8 @@ - net6.0-android33.0 + net8.0-android34.0 21.0 - true @@ -24,4 +23,8 @@ + + + + \ No newline at end of file diff --git a/Softeq.XToolkit.Permissions.iOS/Softeq.XToolkit.Permissions.iOS.csproj b/Softeq.XToolkit.Permissions.iOS/Softeq.XToolkit.Permissions.iOS.csproj index 7e5b55bc6..a40594537 100644 --- a/Softeq.XToolkit.Permissions.iOS/Softeq.XToolkit.Permissions.iOS.csproj +++ b/Softeq.XToolkit.Permissions.iOS/Softeq.XToolkit.Permissions.iOS.csproj @@ -1,9 +1,8 @@ - net6.0-ios13.0 + net8.0-ios17.0 13.0 - true false @@ -22,5 +21,6 @@ + \ No newline at end of file diff --git a/Softeq.XToolkit.Permissions/Softeq.XToolkit.Permissions.csproj b/Softeq.XToolkit.Permissions/Softeq.XToolkit.Permissions.csproj index 44e9fe8b1..7ac8cbfd6 100644 --- a/Softeq.XToolkit.Permissions/Softeq.XToolkit.Permissions.csproj +++ b/Softeq.XToolkit.Permissions/Softeq.XToolkit.Permissions.csproj @@ -1,12 +1,12 @@ - net6.0 - true - - - + net8.0 True + + + + diff --git a/Softeq.XToolkit.PushNotifications.Droid/Softeq.XToolkit.PushNotifications.Droid.csproj b/Softeq.XToolkit.PushNotifications.Droid/Softeq.XToolkit.PushNotifications.Droid.csproj index 63dd6c269..c83c75a4b 100644 --- a/Softeq.XToolkit.PushNotifications.Droid/Softeq.XToolkit.PushNotifications.Droid.csproj +++ b/Softeq.XToolkit.PushNotifications.Droid/Softeq.XToolkit.PushNotifications.Droid.csproj @@ -1,7 +1,7 @@ - net6.0-android33.0 + net8.0-android34.0 26.0 @@ -27,7 +27,7 @@ - + \ No newline at end of file diff --git a/Softeq.XToolkit.PushNotifications.iOS/Softeq.XToolkit.PushNotifications.iOS.csproj b/Softeq.XToolkit.PushNotifications.iOS/Softeq.XToolkit.PushNotifications.iOS.csproj index 0394fd5a0..e486276b4 100644 --- a/Softeq.XToolkit.PushNotifications.iOS/Softeq.XToolkit.PushNotifications.iOS.csproj +++ b/Softeq.XToolkit.PushNotifications.iOS/Softeq.XToolkit.PushNotifications.iOS.csproj @@ -1,7 +1,7 @@  - net6.0-ios10.0 + net8.0-ios12.0 10.0 false diff --git a/Softeq.XToolkit.PushNotifications/Softeq.XToolkit.PushNotifications.csproj b/Softeq.XToolkit.PushNotifications/Softeq.XToolkit.PushNotifications.csproj index 7bdc3593f..dd8d7523e 100644 --- a/Softeq.XToolkit.PushNotifications/Softeq.XToolkit.PushNotifications.csproj +++ b/Softeq.XToolkit.PushNotifications/Softeq.XToolkit.PushNotifications.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 diff --git a/Softeq.XToolkit.Remote.Tests/Softeq.XToolkit.Remote.Tests.csproj b/Softeq.XToolkit.Remote.Tests/Softeq.XToolkit.Remote.Tests.csproj index 8c81a741c..bc17457b1 100644 --- a/Softeq.XToolkit.Remote.Tests/Softeq.XToolkit.Remote.Tests.csproj +++ b/Softeq.XToolkit.Remote.Tests/Softeq.XToolkit.Remote.Tests.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net8.0 diff --git a/Softeq.XToolkit.Remote/Softeq.XToolkit.Remote.csproj b/Softeq.XToolkit.Remote/Softeq.XToolkit.Remote.csproj index f7d89d821..247ae3790 100644 --- a/Softeq.XToolkit.Remote/Softeq.XToolkit.Remote.csproj +++ b/Softeq.XToolkit.Remote/Softeq.XToolkit.Remote.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 diff --git a/Softeq.XToolkit.WhiteLabel.Droid/Softeq.XToolkit.WhiteLabel.Droid.csproj b/Softeq.XToolkit.WhiteLabel.Droid/Softeq.XToolkit.WhiteLabel.Droid.csproj index fc258d188..cca1a38e8 100644 --- a/Softeq.XToolkit.WhiteLabel.Droid/Softeq.XToolkit.WhiteLabel.Droid.csproj +++ b/Softeq.XToolkit.WhiteLabel.Droid/Softeq.XToolkit.WhiteLabel.Droid.csproj @@ -1,9 +1,8 @@ - net6.0-android33.0 + net8.0-android34.0 24.0 - true @@ -30,6 +29,7 @@ + \ No newline at end of file diff --git a/Softeq.XToolkit.WhiteLabel.Essentials.Droid/ImagePicker/ImagePickerActivityResultHandler.cs b/Softeq.XToolkit.WhiteLabel.Essentials.Droid/ImagePicker/ImagePickerActivityResultHandler.cs index c0f183b95..8cee6ceee 100644 --- a/Softeq.XToolkit.WhiteLabel.Essentials.Droid/ImagePicker/ImagePickerActivityResultHandler.cs +++ b/Softeq.XToolkit.WhiteLabel.Essentials.Droid/ImagePicker/ImagePickerActivityResultHandler.cs @@ -48,8 +48,9 @@ public Task HandleCustomResultAsync(int requestCode, Result resultCode, Intent? { using (var stream = ImagePickerUtils.GetContentStream(context, fileUri)) { - bitmap = await ImagePickerUtils.FixRotation(bitmap, new ExifInterface(stream)) - .ConfigureAwait(false); +#pragma warning disable CA1416 + bitmap = await ImagePickerUtils.FixRotation(bitmap, new ExifInterface(stream)).ConfigureAwait(false); +#pragma warning restore CA1416 } } else diff --git a/Softeq.XToolkit.WhiteLabel.Essentials.Droid/ImagePicker/ImagePickerUtils.cs b/Softeq.XToolkit.WhiteLabel.Essentials.Droid/ImagePicker/ImagePickerUtils.cs index b65a16cb2..6595b147f 100644 --- a/Softeq.XToolkit.WhiteLabel.Essentials.Droid/ImagePicker/ImagePickerUtils.cs +++ b/Softeq.XToolkit.WhiteLabel.Essentials.Droid/ImagePicker/ImagePickerUtils.cs @@ -91,7 +91,9 @@ public static Bitmap GetBitmap(Context context, Uri uri) { if (Build.VERSION.SdkInt >= BuildVersionCodes.P) { +#pragma warning disable CA1416 return ImageDecoder.DecodeBitmap(ImageDecoder.CreateSource(context.ContentResolver!, uri)); +#pragma warning restore CA1416 } return MediaStore.Images.Media.GetBitmap(context.ContentResolver, uri); diff --git a/Softeq.XToolkit.WhiteLabel.Essentials.Droid/Softeq.XToolkit.WhiteLabel.Essentials.Droid.csproj b/Softeq.XToolkit.WhiteLabel.Essentials.Droid/Softeq.XToolkit.WhiteLabel.Essentials.Droid.csproj index 20ecd8b01..4dc6767b9 100644 --- a/Softeq.XToolkit.WhiteLabel.Essentials.Droid/Softeq.XToolkit.WhiteLabel.Essentials.Droid.csproj +++ b/Softeq.XToolkit.WhiteLabel.Essentials.Droid/Softeq.XToolkit.WhiteLabel.Essentials.Droid.csproj @@ -1,9 +1,8 @@  - net6.0-android33.0 + net8.0-android34.0 21.0 - true @@ -30,6 +29,7 @@ + \ No newline at end of file diff --git a/Softeq.XToolkit.WhiteLabel.Essentials.iOS/Softeq.XToolkit.WhiteLabel.Essentials.iOS.csproj b/Softeq.XToolkit.WhiteLabel.Essentials.iOS/Softeq.XToolkit.WhiteLabel.Essentials.iOS.csproj index b98d14c9d..aabec7e19 100644 --- a/Softeq.XToolkit.WhiteLabel.Essentials.iOS/Softeq.XToolkit.WhiteLabel.Essentials.iOS.csproj +++ b/Softeq.XToolkit.WhiteLabel.Essentials.iOS/Softeq.XToolkit.WhiteLabel.Essentials.iOS.csproj @@ -1,9 +1,8 @@  - net6.0-ios13.0 + net8.0-ios17.0 10.0 - true false @@ -27,4 +26,8 @@ + + + + \ No newline at end of file diff --git a/Softeq.XToolkit.WhiteLabel.Essentials/Softeq.XToolkit.WhiteLabel.Essentials.csproj b/Softeq.XToolkit.WhiteLabel.Essentials/Softeq.XToolkit.WhiteLabel.Essentials.csproj index f21d6a239..97d3352ad 100644 --- a/Softeq.XToolkit.WhiteLabel.Essentials/Softeq.XToolkit.WhiteLabel.Essentials.csproj +++ b/Softeq.XToolkit.WhiteLabel.Essentials/Softeq.XToolkit.WhiteLabel.Essentials.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 diff --git a/Softeq.XToolkit.WhiteLabel.Tests/Softeq.XToolkit.WhiteLabel.Tests.csproj b/Softeq.XToolkit.WhiteLabel.Tests/Softeq.XToolkit.WhiteLabel.Tests.csproj index 11171f4e7..366c523ae 100644 --- a/Softeq.XToolkit.WhiteLabel.Tests/Softeq.XToolkit.WhiteLabel.Tests.csproj +++ b/Softeq.XToolkit.WhiteLabel.Tests/Softeq.XToolkit.WhiteLabel.Tests.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 Exe disable diff --git a/Softeq.XToolkit.WhiteLabel.iOS/Softeq.XToolkit.WhiteLabel.iOS.csproj b/Softeq.XToolkit.WhiteLabel.iOS/Softeq.XToolkit.WhiteLabel.iOS.csproj index 1e8b40e7c..c7208258a 100644 --- a/Softeq.XToolkit.WhiteLabel.iOS/Softeq.XToolkit.WhiteLabel.iOS.csproj +++ b/Softeq.XToolkit.WhiteLabel.iOS/Softeq.XToolkit.WhiteLabel.iOS.csproj @@ -1,9 +1,8 @@  - net6.0-ios12.0 + net8.0-ios17.0 10.0 - true false @@ -24,4 +23,8 @@ + + + + \ No newline at end of file diff --git a/Softeq.XToolkit.WhiteLabel/Softeq.XToolkit.WhiteLabel.csproj b/Softeq.XToolkit.WhiteLabel/Softeq.XToolkit.WhiteLabel.csproj index 8e06b1132..3a42f470c 100644 --- a/Softeq.XToolkit.WhiteLabel/Softeq.XToolkit.WhiteLabel.csproj +++ b/Softeq.XToolkit.WhiteLabel/Softeq.XToolkit.WhiteLabel.csproj @@ -1,8 +1,7 @@  - net6.0 - true + net8.0 @@ -12,6 +11,7 @@ + diff --git a/azure-pipelines/templates/vars.yml b/azure-pipelines/templates/vars.yml index 4a2c9f8c3..024a69bc6 100644 --- a/azure-pipelines/templates/vars.yml +++ b/azure-pipelines/templates/vars.yml @@ -3,6 +3,6 @@ # https://github.com/actions/runner-images variables: - XCODE_VERSION: 14.3.1 - DOTNET_SDK_VERSION: 6.0.402 + XCODE_VERSION: 15.0.1 + DOTNET_SDK_VERSION: 8.0.100 MACOS_VM_IMAGE: 'macos-13' diff --git a/global.json b/global.json index 73a5d6411..72d38cd27 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "7.0.103", + "version": "8.0.100", "rollForward": "latestMajor", "allowPrerelease": false } diff --git a/samples/Playground/Playground.Droid/Playground.Droid.csproj b/samples/Playground/Playground.Droid/Playground.Droid.csproj index 335925531..c640e6181 100644 --- a/samples/Playground/Playground.Droid/Playground.Droid.csproj +++ b/samples/Playground/Playground.Droid/Playground.Droid.csproj @@ -6,10 +6,10 @@ --> - net7.0-android33.0 + net8.0-android34.0 Exe - true android-arm;android-arm64;android-x86;android-x64 + 26.0 @@ -42,6 +42,7 @@ + \ No newline at end of file diff --git a/samples/Playground/Playground.Droid/Properties/AndroidManifest.xml b/samples/Playground/Playground.Droid/Properties/AndroidManifest.xml index 0c8889f0d..91b5a0436 100644 --- a/samples/Playground/Playground.Droid/Properties/AndroidManifest.xml +++ b/samples/Playground/Playground.Droid/Properties/AndroidManifest.xml @@ -1,6 +1,6 @@  - + diff --git a/samples/Playground/Playground.iOS/Playground.iOS.csproj b/samples/Playground/Playground.iOS/Playground.iOS.csproj index 817447169..9bcfcf9b5 100644 --- a/samples/Playground/Playground.iOS/Playground.iOS.csproj +++ b/samples/Playground/Playground.iOS/Playground.iOS.csproj @@ -6,9 +6,8 @@ --> - net7.0-ios16.2 + net8.0-ios17.0 Exe - true iPhoneSimulator;iPhone;AnyCPU 14.0 false @@ -61,7 +60,7 @@ - + \ No newline at end of file diff --git a/samples/Playground/Playground/Playground.csproj b/samples/Playground/Playground/Playground.csproj index b5e9b99d8..aa3ab369e 100644 --- a/samples/Playground/Playground/Playground.csproj +++ b/samples/Playground/Playground/Playground.csproj @@ -1,8 +1,7 @@  - net7.0 - true + net8.0 @@ -10,4 +9,8 @@ + + + + From 4ea3f80571894ced207c7b07859c0e319f5f4beb Mon Sep 17 00:00:00 2001 From: Pavel Leonenko <1992leon@bk.ru> Date: Wed, 27 Mar 2024 16:06:46 +0100 Subject: [PATCH 22/26] Feature/minor fixes (#545) * Updated copyright * Fixed binding to ObservableRangeCollection.Count when calling RemoveRange (#544) * Removed excess AndroidResource tags * Restored mistakenly deleted PropertyGroup * Fixed copyright * rename methods --------- Co-authored-by: Pavel Leonenko Co-authored-by: s.leushunou --- Directory.Build.props | 2 +- LICENSE | 2 +- .../Softeq.XToolkit.Bindings.Droid.csproj | 4 ---- .../Softeq.XToolkit.Common.Droid.csproj | 4 ---- .../Collections/ObservableRangeCollection.cs | 2 ++ Softeq.XToolkit.Permissions.Droid/Permissions/Bluetooth.cs | 6 +++--- .../Permissions/Notifications.cs | 4 ++-- Softeq.XToolkit.Permissions.Droid/SdkVersion.cs | 6 +++--- .../Softeq.XToolkit.Permissions.Droid.csproj | 4 ---- .../Softeq.XToolkit.PushNotifications.Droid.csproj | 4 ---- .../Softeq.XToolkit.WhiteLabel.Droid.csproj | 4 ---- .../Softeq.XToolkit.WhiteLabel.Essentials.Droid.csproj | 4 ---- nuget/Softeq.XToolkit.Bindings.nuspec | 2 +- nuget/Softeq.XToolkit.Common.nuspec | 2 +- nuget/Softeq.XToolkit.Permissions.nuspec | 2 +- nuget/Softeq.XToolkit.PushNotifications.nuspec | 2 +- nuget/Softeq.XToolkit.Remote.nuspec | 2 +- nuget/Softeq.XToolkit.WhiteLabel.Essentials.nuspec | 2 +- nuget/Softeq.XToolkit.WhiteLabel.Forms.nuspec | 2 +- nuget/Softeq.XToolkit.WhiteLabel.nuspec | 2 +- .../Playground.Forms.Droid/Properties/AssemblyInfo.cs | 2 +- .../Playground.Forms.iOS/Properties/AssemblyInfo.cs | 2 +- samples/Playground/Playground.Droid/Playground.Droid.csproj | 1 - 23 files changed, 22 insertions(+), 45 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 7eba25610..3ab7eea7b 100755 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -12,7 +12,7 @@ XToolkit Softeq Development Corporation - Copyright © 2023 Softeq Development Corporation + Copyright © 2024 Softeq Development Corporation \ No newline at end of file diff --git a/LICENSE b/LICENSE index 25c1277cb..bfa391608 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2018 Softeq +Copyright (c) 2024 Softeq Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Softeq.XToolkit.Bindings.Droid/Softeq.XToolkit.Bindings.Droid.csproj b/Softeq.XToolkit.Bindings.Droid/Softeq.XToolkit.Bindings.Droid.csproj index 7f35df664..731b40980 100644 --- a/Softeq.XToolkit.Bindings.Droid/Softeq.XToolkit.Bindings.Droid.csproj +++ b/Softeq.XToolkit.Bindings.Droid/Softeq.XToolkit.Bindings.Droid.csproj @@ -14,10 +14,6 @@ SdkOnly - - - - diff --git a/Softeq.XToolkit.Common.Droid/Softeq.XToolkit.Common.Droid.csproj b/Softeq.XToolkit.Common.Droid/Softeq.XToolkit.Common.Droid.csproj index 835b75bb8..60be0cbf5 100644 --- a/Softeq.XToolkit.Common.Droid/Softeq.XToolkit.Common.Droid.csproj +++ b/Softeq.XToolkit.Common.Droid/Softeq.XToolkit.Common.Droid.csproj @@ -14,10 +14,6 @@ SdkOnly - - - - diff --git a/Softeq.XToolkit.Common/Collections/ObservableRangeCollection.cs b/Softeq.XToolkit.Common/Collections/ObservableRangeCollection.cs index 60112feec..1daaa7949 100644 --- a/Softeq.XToolkit.Common/Collections/ObservableRangeCollection.cs +++ b/Softeq.XToolkit.Common/Collections/ObservableRangeCollection.cs @@ -303,6 +303,8 @@ public void RemoveRange( Items.Remove(item); } + OnPropertyChanged(EventArgsCache.CountPropertyChanged); + OnPropertyChanged(EventArgsCache.IndexerPropertyChanged); OnCollectionChanged(EventArgsCache.ResetCollectionChanged); return; diff --git a/Softeq.XToolkit.Permissions.Droid/Permissions/Bluetooth.cs b/Softeq.XToolkit.Permissions.Droid/Permissions/Bluetooth.cs index c9a18e43f..8f5b0cff3 100644 --- a/Softeq.XToolkit.Permissions.Droid/Permissions/Bluetooth.cs +++ b/Softeq.XToolkit.Permissions.Droid/Permissions/Bluetooth.cs @@ -20,15 +20,15 @@ public override (string, bool)[] RequiredPermissions // When targeting Android 11 or lower, AccessFineLocation is required for Bluetooth. // For Android 12 and above, it is optional. - if (SdkVersion.IsBuildVersionLower(BuildVersionCodes.R) || + if (SdkVersion.IsTargetSdkLower(BuildVersionCodes.R) || EssentialsPermissions.IsDeclaredInManifest(Manifest.Permission.AccessFineLocation)) { permissions.Add((Manifest.Permission.AccessFineLocation, true)); } #if __ANDROID_31__ - if (SdkVersion.IsBuildVersionAtLeast(BuildVersionCodes.S) && - SdkVersion.IsDeviceVersionAtLeast(BuildVersionCodes.S)) + if (SdkVersion.IsTargetSdkAtLeast(BuildVersionCodes.S) && + SdkVersion.IsBuildSdkAtLeast(BuildVersionCodes.S)) { if (EssentialsPermissions.IsDeclaredInManifest(Manifest.Permission.BluetoothScan)) { diff --git a/Softeq.XToolkit.Permissions.Droid/Permissions/Notifications.cs b/Softeq.XToolkit.Permissions.Droid/Permissions/Notifications.cs index f5d07b2e6..7555a456f 100644 --- a/Softeq.XToolkit.Permissions.Droid/Permissions/Notifications.cs +++ b/Softeq.XToolkit.Permissions.Droid/Permissions/Notifications.cs @@ -18,8 +18,8 @@ public override (string, bool)[] RequiredPermissions #if __ANDROID_33__ #pragma warning disable CA1416 var isSupport = - SdkVersion.IsBuildVersionAtLeast(BuildVersionCodes.Tiramisu) && - SdkVersion.IsDeviceVersionAtLeast(BuildVersionCodes.Tiramisu) && + SdkVersion.IsTargetSdkAtLeast(BuildVersionCodes.Tiramisu) && + SdkVersion.IsBuildSdkAtLeast(BuildVersionCodes.Tiramisu) && EssentialsPermissions.IsDeclaredInManifest(Manifest.Permission.PostNotifications); return isSupport ? diff --git a/Softeq.XToolkit.Permissions.Droid/SdkVersion.cs b/Softeq.XToolkit.Permissions.Droid/SdkVersion.cs index eece15e64..602ab16f6 100644 --- a/Softeq.XToolkit.Permissions.Droid/SdkVersion.cs +++ b/Softeq.XToolkit.Permissions.Droid/SdkVersion.cs @@ -8,17 +8,17 @@ namespace Softeq.XToolkit.Permissions.Droid { internal static class SdkVersion { - public static bool IsBuildVersionLower(BuildVersionCodes versionCode) + public static bool IsTargetSdkLower(BuildVersionCodes versionCode) { return Platform.AppContext.ApplicationInfo?.TargetSdkVersion <= versionCode; } - public static bool IsBuildVersionAtLeast(BuildVersionCodes versionCode) + public static bool IsTargetSdkAtLeast(BuildVersionCodes versionCode) { return Platform.AppContext.ApplicationInfo?.TargetSdkVersion >= versionCode; } - public static bool IsDeviceVersionAtLeast(BuildVersionCodes versionCode) + public static bool IsBuildSdkAtLeast(BuildVersionCodes versionCode) { return Build.VERSION.SdkInt >= versionCode; } diff --git a/Softeq.XToolkit.Permissions.Droid/Softeq.XToolkit.Permissions.Droid.csproj b/Softeq.XToolkit.Permissions.Droid/Softeq.XToolkit.Permissions.Droid.csproj index c12098125..e44ebd58b 100644 --- a/Softeq.XToolkit.Permissions.Droid/Softeq.XToolkit.Permissions.Droid.csproj +++ b/Softeq.XToolkit.Permissions.Droid/Softeq.XToolkit.Permissions.Droid.csproj @@ -14,10 +14,6 @@ SdkOnly - - - - diff --git a/Softeq.XToolkit.PushNotifications.Droid/Softeq.XToolkit.PushNotifications.Droid.csproj b/Softeq.XToolkit.PushNotifications.Droid/Softeq.XToolkit.PushNotifications.Droid.csproj index c83c75a4b..ceb49c3a6 100644 --- a/Softeq.XToolkit.PushNotifications.Droid/Softeq.XToolkit.PushNotifications.Droid.csproj +++ b/Softeq.XToolkit.PushNotifications.Droid/Softeq.XToolkit.PushNotifications.Droid.csproj @@ -14,10 +14,6 @@ SdkOnly - - - - diff --git a/Softeq.XToolkit.WhiteLabel.Droid/Softeq.XToolkit.WhiteLabel.Droid.csproj b/Softeq.XToolkit.WhiteLabel.Droid/Softeq.XToolkit.WhiteLabel.Droid.csproj index cca1a38e8..384b8d28f 100644 --- a/Softeq.XToolkit.WhiteLabel.Droid/Softeq.XToolkit.WhiteLabel.Droid.csproj +++ b/Softeq.XToolkit.WhiteLabel.Droid/Softeq.XToolkit.WhiteLabel.Droid.csproj @@ -14,10 +14,6 @@ SdkOnly - - - - diff --git a/Softeq.XToolkit.WhiteLabel.Essentials.Droid/Softeq.XToolkit.WhiteLabel.Essentials.Droid.csproj b/Softeq.XToolkit.WhiteLabel.Essentials.Droid/Softeq.XToolkit.WhiteLabel.Essentials.Droid.csproj index 4dc6767b9..b6a7ad5d3 100644 --- a/Softeq.XToolkit.WhiteLabel.Essentials.Droid/Softeq.XToolkit.WhiteLabel.Essentials.Droid.csproj +++ b/Softeq.XToolkit.WhiteLabel.Essentials.Droid/Softeq.XToolkit.WhiteLabel.Essentials.Droid.csproj @@ -14,10 +14,6 @@ SdkOnly - - - - diff --git a/nuget/Softeq.XToolkit.Bindings.nuspec b/nuget/Softeq.XToolkit.Bindings.nuspec index 1bab00941..1ae0628bb 100644 --- a/nuget/Softeq.XToolkit.Bindings.nuspec +++ b/nuget/Softeq.XToolkit.Bindings.nuspec @@ -11,7 +11,7 @@ https://softeq.github.io/XToolkit.WhiteLabel/articles/xtoolkit/bindings.html Bindings implementation based on INotifyPropertyChanged interface. - Copyright 2023 Softeq Development Corp. + Copyright 2024 Softeq Development Corp. softeq, xtoolkit, xamarin, ios, android, mvvm, bindings Releases: https://github.com/Softeq/XToolkit.WhiteLabel/releases diff --git a/nuget/Softeq.XToolkit.Common.nuspec b/nuget/Softeq.XToolkit.Common.nuspec index c99771411..e7e9837da 100644 --- a/nuget/Softeq.XToolkit.Common.nuspec +++ b/nuget/Softeq.XToolkit.Common.nuspec @@ -11,7 +11,7 @@ https://softeq.github.io/XToolkit.WhiteLabel/articles/xtoolkit/common.html The most common components without dependencies that can be reused in any project. - Copyright 2023 Softeq Development Corp. + Copyright 2024 Softeq Development Corp. softeq, xtoolkit, xamarin, ios, android, mvvm, toolkit, kit Releases: https://github.com/Softeq/XToolkit.WhiteLabel/releases diff --git a/nuget/Softeq.XToolkit.Permissions.nuspec b/nuget/Softeq.XToolkit.Permissions.nuspec index 310aaa272..5bbeb4c79 100644 --- a/nuget/Softeq.XToolkit.Permissions.nuspec +++ b/nuget/Softeq.XToolkit.Permissions.nuspec @@ -11,7 +11,7 @@ https://softeq.github.io/XToolkit.WhiteLabel/articles/xtoolkit/permissions.html Simple cross platform plugin to request and check permissions for Android and iOS. - Copyright 2023 Softeq Development Corp. + Copyright 2024 Softeq Development Corp. softeq, xtoolkit, xamarin, ios, android, permissions diff --git a/nuget/Softeq.XToolkit.PushNotifications.nuspec b/nuget/Softeq.XToolkit.PushNotifications.nuspec index 569d55e25..ce91c5972 100644 --- a/nuget/Softeq.XToolkit.PushNotifications.nuspec +++ b/nuget/Softeq.XToolkit.PushNotifications.nuspec @@ -11,7 +11,7 @@ https://softeq.github.io/XToolkit.WhiteLabel/articles/xtoolkit/push-notifications.html Simple cross platform plugin to use push-notifications for Android and iOS. - Copyright 2023 Softeq Development Corp. + Copyright 2024 Softeq Development Corp. softeq, xtoolkit, xamarin, ios, android, push-notifications, firebase, apns, fcm Releases: https://github.com/Softeq/XToolkit.WhiteLabel/releases diff --git a/nuget/Softeq.XToolkit.Remote.nuspec b/nuget/Softeq.XToolkit.Remote.nuspec index c371da4c5..70b75fa05 100644 --- a/nuget/Softeq.XToolkit.Remote.nuspec +++ b/nuget/Softeq.XToolkit.Remote.nuspec @@ -11,7 +11,7 @@ https://softeq.github.io/XToolkit.WhiteLabel/articles/xtoolkit/remote.html Advanced HttpClient infrastructure for mobile applications. - Copyright 2023 Softeq Development Corp. + Copyright 2024 Softeq Development Corp. softeq, xtoolkit, xamarin, ios, android, mvvm, toolkit, remote, http, rest, api, auth Releases: https://github.com/Softeq/XToolkit.WhiteLabel/releases diff --git a/nuget/Softeq.XToolkit.WhiteLabel.Essentials.nuspec b/nuget/Softeq.XToolkit.WhiteLabel.Essentials.nuspec index b1b804652..331a55909 100644 --- a/nuget/Softeq.XToolkit.WhiteLabel.Essentials.nuspec +++ b/nuget/Softeq.XToolkit.WhiteLabel.Essentials.nuspec @@ -11,7 +11,7 @@ https://softeq.github.io/XToolkit.WhiteLabel/articles/xtoolkit/whitelabel/essentials.html Library over the Softeq.XToolkit.WhiteLabel that contains optional components for any application. - Copyright 2023 Softeq Development Corp. + Copyright 2024 Softeq Development Corp. softeq, xtoolkit, android, ios, whitelabel, xamarin, essentials, imagepicker, picker Releases: https://github.com/Softeq/XToolkit.WhiteLabel/releases diff --git a/nuget/Softeq.XToolkit.WhiteLabel.Forms.nuspec b/nuget/Softeq.XToolkit.WhiteLabel.Forms.nuspec index 6596141ca..38569221c 100644 --- a/nuget/Softeq.XToolkit.WhiteLabel.Forms.nuspec +++ b/nuget/Softeq.XToolkit.WhiteLabel.Forms.nuspec @@ -11,7 +11,7 @@ https://softeq.github.io/XToolkit.WhiteLabel/articles/xtoolkit/whitelabel/forms.html Integration library for using Softeq.XToolkit.WhiteLabel in Xamarin.Forms projects. - Copyright 2023 Softeq Development Corp. + Copyright 2024 Softeq Development Corp. softeq, xtoolkit, whitelabel, xamarin, forms, xamarinforms, xamarin.forms Releases: https://github.com/Softeq/XToolkit.WhiteLabel/releases diff --git a/nuget/Softeq.XToolkit.WhiteLabel.nuspec b/nuget/Softeq.XToolkit.WhiteLabel.nuspec index 4a51b69e3..6d546b57a 100644 --- a/nuget/Softeq.XToolkit.WhiteLabel.nuspec +++ b/nuget/Softeq.XToolkit.WhiteLabel.nuspec @@ -11,7 +11,7 @@ https://softeq.github.io/XToolkit.WhiteLabel/articles/xtoolkit/whitelabel.html XToolkit.WhiteLabel is a collection of "lego" components for fast create cross-platform mobile applications with Xamarin, based on XToolkit. - Copyright 2023 Softeq Development Corp. + Copyright 2024 Softeq Development Corp. softeq, xtoolkit, xamarin, ios, android, mvvm, toolkit, whitelabel Releases: https://github.com/Softeq/XToolkit.WhiteLabel/releases diff --git a/samples/Playground.Forms/Playground.Forms.Droid/Properties/AssemblyInfo.cs b/samples/Playground.Forms/Playground.Forms.Droid/Properties/AssemblyInfo.cs index 4a22c9fd0..073cfd49c 100644 --- a/samples/Playground.Forms/Playground.Forms.Droid/Properties/AssemblyInfo.cs +++ b/samples/Playground.Forms/Playground.Forms.Droid/Properties/AssemblyInfo.cs @@ -12,7 +12,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Softeq Development Corporation")] [assembly: AssemblyProduct("XToolkit")] -[assembly: AssemblyCopyright("Copyright © Softeq Development Corporation 2020")] +[assembly: AssemblyCopyright("Copyright © Softeq Development Corporation 2024")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/samples/Playground.Forms/Playground.Forms.iOS/Properties/AssemblyInfo.cs b/samples/Playground.Forms/Playground.Forms.iOS/Properties/AssemblyInfo.cs index 4c170dbeb..1caa182d7 100644 --- a/samples/Playground.Forms/Playground.Forms.iOS/Properties/AssemblyInfo.cs +++ b/samples/Playground.Forms/Playground.Forms.iOS/Properties/AssemblyInfo.cs @@ -12,7 +12,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Softeq Development Corporation")] [assembly: AssemblyProduct("XToolkit")] -[assembly: AssemblyCopyright("Copyright © Softeq Development Corporation 2020")] +[assembly: AssemblyCopyright("Copyright © Softeq Development Corporation 2024")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/samples/Playground/Playground.Droid/Playground.Droid.csproj b/samples/Playground/Playground.Droid/Playground.Droid.csproj index c640e6181..2c4bbdfd3 100644 --- a/samples/Playground/Playground.Droid/Playground.Droid.csproj +++ b/samples/Playground/Playground.Droid/Playground.Droid.csproj @@ -21,7 +21,6 @@ - From acd47d9ef9b5e121f3d4632b9b72bd9e0c6a435b Mon Sep 17 00:00:00 2001 From: Siarhei Leushunou Date: Wed, 27 Mar 2024 18:23:03 +0100 Subject: [PATCH 23/26] Update ph1 to net8 (#542) * Update documentation & CI (#501) * Simplify projects to SDK style (#498) * Migrate to .NET6+ (#513) * Migrate from Xamarin.Essentials to Microsoft.Maui.Essentials (#517) * Update documentation & rework nested namespaces (#514) * Resolve warnings (#515) * Implementation of 'Notification' and 'Bluetooth' permissions (#519) Co-authored-by: Yauheni Pakala * Add System.Text.Json serializer and tests (#521) * Show warning when ConfigureAwait should be used (#522) * Remove Chunkify method (#523) * Remove Newtonsoft.Json (#524) * Update CI environment (#530) * Migrate Common.iOS & Common.Droid Tests to .NET6 (#525) * Replace Connectivity plugin to MAUI Essentials (#531) * location eq added * comment fixed * Refactor permissions library (#535) * Add binding support for Count property (ObservableKeyGroupsCollection) (#538) * Update docfx (#534) * Improvements before migration to .NET8 (#539) * Add supported os versions * Bump year * Update target framework for nuspecs * Sync wl.essentials platform target frameworks * Add install missed android sdk for api 32 * Bump android target api * Revert "Add install missed android sdk for api 32" This reverts commit a89a7c424a11e61181a1f391d2e6c51dd1d1fb61. * Migrate to .NET8 (#536) * Bump dotnet version * Bump target framework to .net8 * Migrate Microsoft.Maui.Essentials to use as explicit NuGet packages * Remove obsoleted binary serialization for BiDictionary * Cleanup * Migrate Maui to use as explicit NuGet package * Sync target frameworks and min supported os * Use the same MAUI version in all projects * Bump .NET8 to stable * Update pipelines env * Bump iOS target framework for latest maui & essentials * Resolve warnings related to supported versions * Update nuget package * Sync versions * add extensions * make permission method virtual * remove unused property * add methods to icontainerbuilder * Feature/minor fixes (#545) * Updated copyright * Fixed binding to ObservableRangeCollection.Count when calling RemoveRange (#544) * Removed excess AndroidResource tags * Restored mistakenly deleted PropertyGroup * Fixed copyright * rename methods --------- Co-authored-by: Pavel Leonenko Co-authored-by: s.leushunou --------- Co-authored-by: Yauheni Pakala Co-authored-by: nkrisko <50995914+nkrisko@users.noreply.github.com> Co-authored-by: Kirill Akulich Co-authored-by: Pavel Leonenko <1992leon@bk.ru> Co-authored-by: Pavel Leonenko --- Directory.Build.props | 11 +- LICENSE | 2 +- README.md | 3 +- .../Properties/AssemblyInfo.cs | 31 -- .../Softeq.XToolkit.Bindings.Droid.csproj | 85 +--- .../Softeq.XToolkit.Bindings.Tests.csproj | 2 +- .../Properties/AssemblyInfo.cs | 34 -- .../Softeq.XToolkit.Bindings.iOS.csproj | 88 +--- .../Softeq.XToolkit.Bindings.csproj | 2 +- .../Assets/xunit.runner.json | 5 - .../Converters/VisibilityConverterTests.cs | 80 ---- .../DroidMainThreadExecutorTests.cs | 34 -- .../ContextExtensionsTests.cs | 34 -- .../EditTextExtensionsTests.cs | 94 ---- .../MockInputFilter.cs | 16 - .../StringExtensionsTests.cs | 230 ---------- Softeq.XToolkit.Common.Droid.Tests/Helpers.cs | 29 -- .../MainActivity.cs | 43 -- .../MauiProgram.cs | 31 ++ .../Platforms/Android/AndroidManifest.xml | 6 + .../Converters/VisibilityConverterTests.cs | 79 ++++ .../DroidMainThreadExecutorTests.cs | 33 ++ .../ContextExtensionsTests.cs | 33 ++ .../EditTextExtensionsTests.cs | 93 ++++ .../MockInputFilter.cs | 16 + .../StringExtensionsDataProvider.cs | 0 .../StringExtensionsTests.cs | 229 ++++++++++ .../Platforms/Android/Helpers.cs | 28 ++ .../Platforms/Android/MainActivity.cs | 24 + .../Platforms/Android/MainApplication.cs | 21 + .../Android/Resources/values/colors.xml | 6 + .../ForbiddenCharsInputFilterTests.cs | 101 +++++ .../Properties/AndroidManifest.xml | 5 - .../Properties/AssemblyInfo.cs | 31 -- .../Properties/launchSettings.json | 8 + .../Resources/AppIcon/appicon.svg | 4 + .../Resources/AppIcon/appiconfg.svg | 8 + .../Resources/Raw/AboutAssets.txt | 15 + .../Resources/Splash/splash.svg | 8 + .../Resources/mipmap-mdpi/ic_launcher.png | Bin 1362 -> 0 bytes .../Resources/mipmap-xhdpi/ic_launcher.png | Bin 2307 -> 0 bytes .../Resources/mipmap-xxhdpi/ic_launcher.png | Bin 3871 -> 0 bytes .../Resources/mipmap-xxxhdpi/ic_launcher.png | Bin 5016 -> 0 bytes .../Resources/values/strings.xml | 4 - .../Softeq.XToolkit.Common.Droid.Tests.csproj | 150 ++----- .../ForbiddenCharsInputFilterTests.cs | 102 ----- .../Extensions/ContextExtensions.cs | 6 +- .../Properties/AssemblyInfo.cs | 31 -- .../Softeq.XToolkit.Common.Droid.csproj | 63 +-- .../BiDictionaryTests/BiDictionaryTests.cs | 26 -- ...bleKeyGroupsCollectionTestsCountChanged.cs | 80 ++++ .../EnumExtensionsTests/TestEnum.cs | 1 - .../EnumerableExtensionsDataProvider.cs | 41 -- .../EnumerableExtensionsTests.cs | 44 +- .../MockFileSystemWrapper.cs | 5 + .../Softeq.XToolkit.Common.Tests.csproj | 4 +- .../AppDelegate.cs | 37 -- .../Entitlements.plist | 6 - .../NSDateExtensionsTests.cs | 78 ---- .../NSLocaleExtensionsTests.cs | 43 -- .../NSRangeExtensionsTests.cs | 48 -- .../NSStringExtensionsTests.cs | 193 -------- .../UIColorExtensionsTests.cs | 70 --- .../UITextViewExtensionsTests.cs | 106 ----- Softeq.XToolkit.Common.iOS.Tests/Helpers.cs | 30 -- Softeq.XToolkit.Common.iOS.Tests/Info.plist | 38 -- .../IosMainThreadExecutorTests.cs | 34 -- .../LaunchScreen.storyboard | 27 -- Softeq.XToolkit.Common.iOS.Tests/Main.cs | 17 - .../MauiProgram.cs | 31 ++ .../Platforms/iOS/AppDelegate.cs | 14 + .../NSDateExtensionsTests.cs | 78 ++++ .../NSLocaleExtensionsTests.cs | 42 ++ .../NSStringExtensionsTests.cs | 193 ++++++++ .../NSRangeExtensionsTests.cs | 47 ++ .../UIColorExtensionsTests.cs | 69 +++ .../UITextViewExtensionsTests.cs | 105 +++++ .../Platforms/iOS/Helpers.cs | 29 ++ .../Platforms/iOS/Info.plist | 32 ++ .../IosMainThreadExecutorTests.cs | 33 ++ .../Platforms/iOS/Program.cs | 17 + .../ForbiddenCharsFilterTests.cs | 91 ++++ .../GroupFilterTests/GroupFilterTests.cs | 65 +++ .../GroupFilterTests/MockTextFilter.cs | 23 + .../LengthFilterTests/LengthFilterTests.cs | 115 +++++ .../Properties/launchSettings.json | 8 + .../Resources/AppIcon/appicon.svg | 4 + .../Resources/AppIcon/appiconfg.svg | 8 + .../Resources/Raw/AboutAssets.txt | 15 + .../Resources/Splash/splash.svg | 8 + .../Softeq.XToolkit.Common.iOS.Tests.csproj | 180 ++------ .../ForbiddenCharsFilterTests.cs | 92 ---- .../GroupFilterTests/GroupFilterTests.cs | 66 --- .../GroupFilterTests/MockTextFilter.cs | 24 - .../LengthFilterTests/LengthFilterTests.cs | 116 ----- .../Extensions/UIViewExtensions.cs | 1 - .../Properties/AssemblyInfo.cs | 34 -- .../Softeq.XToolkit.Common.iOS.csproj | 67 +-- .../Collections/BiDictionary.cs | 55 +-- .../ObservableKeyGroupsCollection.cs | 38 +- .../Commands/AsyncCommandBase.cs | 2 +- .../Extensions/EnumerableExtensions.cs | 50 --- .../Files/BaseFileProvider.cs | 6 +- .../Interfaces/IJsonSerializer.cs | 11 +- .../Softeq.XToolkit.Common.csproj | 5 +- .../Threading/TaskDeferral.cs | 4 +- Softeq.XToolkit.Common/Timers/Timer.cs | 2 +- .../IosConnectivityService.cs | 68 +-- .../Properties/AssemblyInfo.cs | 34 -- .../Softeq.XToolkit.Connectivity.iOS.csproj | 59 +-- .../ConnectivityService.cs | 58 --- .../EssentialsConnectivityService.cs | 91 ++++ Softeq.XToolkit.Connectivity/Extensions.cs | 19 + .../IConnectivityService.cs | 22 +- .../Softeq.XToolkit.Connectivity.csproj | 6 +- .../NotificationsPlatformPermission.cs | 31 -- .../Permissions/Bluetooth.cs | 54 +++ .../Permissions/Notifications.cs | 35 ++ .../PermissionsManager.cs | 129 +++--- .../PermissionsService.cs | 72 --- .../Properties/AssemblyInfo.cs | 30 -- .../RequestResultHandler.cs | 2 +- .../SdkVersion.cs | 26 ++ .../Softeq.XToolkit.Permissions.Droid.csproj | 62 +-- .../Permissions/Bluetooth.cs | 87 ++++ .../Permissions/Notifications.cs | 50 +++ .../PermissionsManager.cs | 69 +-- .../PermissionsService.cs | 85 ---- .../Properties/AssemblyInfo.cs | 30 -- .../Softeq.XToolkit.Permissions.iOS.csproj | 80 ++-- .../DefaultPermissionsDialogService.cs | 2 +- .../IPermissionsDialogService.cs | 2 +- .../IPermissionsManager.cs | 2 +- .../IPermissionsService.cs | 2 +- .../NotificationsPermission.cs | 9 - .../Permissions/Bluetooth.cs | 9 + .../Permissions/Notifications.cs | 9 + .../PermissionsService.cs | 44 ++ .../PluginPermissionStatusExtensions.cs | 2 +- .../Softeq.XToolkit.Permissions.csproj | 7 +- .../INotificationsSettingsProvider.cs | 21 +- .../Properties/AssemblyInfo.cs | 31 -- ...eq.XToolkit.PushNotifications.Droid.csproj | 93 +--- .../Properties/AssemblyInfo.cs | 34 -- ...fteq.XToolkit.PushNotifications.iOS.csproj | 70 +-- .../Softeq.XToolkit.PushNotifications.csproj | 2 +- .../Softeq.XToolkit.Remote.Tests.csproj | 2 +- .../Api/IApiServiceFactory.cs | 6 + .../Api/RefitApiServiceFactory.cs | 5 + Softeq.XToolkit.Remote/Auth/AuthExtensions.cs | 3 + .../Client/ClientExtensions.cs | 3 + .../ExpiredRefreshTokenException.cs | 4 + Softeq.XToolkit.Remote/RemoteService.cs | 5 + .../RemoteServiceExtensions.cs | 3 + .../Softeq.XToolkit.Remote.csproj | 3 +- .../ActivityBase.cs | 2 +- .../Controls/BadgeView.cs | 13 +- .../Controls/BusyOverlayView.cs | 2 +- .../Controls/ColoredClickableSpan.cs | 2 +- .../Controls/DroidContextMenuComponent.cs | 2 +- .../Controls/NavigationBarView.cs | 22 +- .../Controls/NonConsumingTouchesToolbar.cs | 2 +- .../Controls/ObservableStackView.cs | 2 +- .../Controls/SwipeControlViewPager.cs | 4 +- .../Dialogs/DialogFragmentBase.cs | 8 +- .../DroidBootstrapperBase.cs | 3 + .../Extensions/ImageViewExtensions.cs | 38 -- .../Interfaces/IBundleService.cs | 46 ++ .../Interfaces/IDroidImageService.cs | 19 + .../Internal/ViewModelCache.cs | 2 +- .../MainApplicationBase.cs | 2 +- .../ActivityPageNavigationService.cs | 1 + .../Navigation/BundleService.cs | 99 ++--- .../Properties/AssemblyInfo.cs | 31 -- .../Providers/ContextProvider.cs | 21 +- ...on.axml => activity_bottom_navigation.xml} | 0 .../Services/DefaultDroidImageService.cs | 93 ++++ .../Services/DroidAppInfoService.cs | 17 - .../Services/DroidToastService.cs | 2 +- .../Services/KeyboardService.cs | 8 +- .../Softeq.XToolkit.WhiteLabel.Droid.csproj | 148 +------ .../BottomNavigationComponentBase.cs | 4 +- .../ViewComponents/ViewModelComponent.cs | 4 +- .../Views/BottomNavigationActivityBase.cs | 2 +- .../Views/BottomNavigationFragmentBase.cs | 4 +- .../Views/ToolbarActivityBase.cs | 2 +- .../Views/ToolbarFragmentBase.cs | 2 +- .../FullScreenImageDialogFragment.cs | 18 +- .../ImagePicker/DroidImagePickerService.cs | 6 +- .../ImagePickerActivityResultHandler.cs | 5 +- .../ImagePicker/ImagePickerUtils.cs | 4 +- .../Properties/AssemblyInfo.cs | 31 -- ...Toolkit.WhiteLabel.Essentials.Droid.csproj | 124 ++---- .../FullScreenImageViewController.cs | 16 +- .../ImagePicker/IosImagePickerService.cs | 6 +- .../Properties/AssemblyInfo.cs | 34 -- ....XToolkit.WhiteLabel.Essentials.iOS.csproj | 122 ++--- .../ImagePicker/IImagePickerService.cs | 8 +- ...fteq.XToolkit.WhiteLabel.Essentials.csproj | 2 +- .../DryIocContainerBuilderTests.cs | 2 - .../Extensions/ExpressionExtensionsTests.cs | 65 +++ .../GlobalSuppressions.cs | 5 + ...entNavigatorExtensionsTestsDataProvider.cs | 8 +- .../NavigationParameterModelDataProvider.cs | 6 +- .../NavigationParameterModelTests.cs | 2 +- ...ests.cs => NavigationPropertyInfoTests.cs} | 41 +- .../DefaultJsonSerializerTests.cs | 106 +++++ ...JsonSerializerTestsNavigationSerializer.cs | 48 ++ .../JsonDeserializationDataProvider.cs | 197 +++++++++ .../JsonSerializationDataProvider.cs | 183 ++++++++ .../JsonSerializationTestsStub.cs | 53 +++ .../Softeq.XToolkit.WhiteLabel.Tests.csproj | 4 +- .../AppDelegateBase.cs | 8 +- .../MvxCachedImageViewExtensions.cs | 63 --- .../Interfaces/IIosImageService.cs | 19 + .../Properties/AssemblyInfo.cs | 34 -- .../Services/DefaultIosImageService.cs | 34 ++ .../Services/IosAppInfoService.cs | 17 - .../Softeq.XToolkit.WhiteLabel.iOS.csproj | 119 +---- .../Abstract/IContainerBuilder.cs | 4 + .../Bootstrapper/BootstrapperBase.cs | 3 +- .../Containers/DryIocContainerBuilder.cs | 15 + Softeq.XToolkit.WhiteLabel/Dependencies.cs | 2 +- .../Extensions/ExpressionExtensions.cs | 3 + .../Interfaces/IAppInfoService.cs | 8 +- Softeq.XToolkit.WhiteLabel/Model/Platform.cs | 13 +- .../FluentNavigators/FluentNavigatorBase.cs | 2 +- .../Navigation/INavigationSerializer.cs | 46 ++ .../Navigation/NavigationExtensions.cs | 3 + .../Navigation/NavigationParameterModel.cs | 8 +- ...InfoModel.cs => NavigationPropertyInfo.cs} | 31 +- .../Services/DefaultJsonSerializer.cs | 132 ++++++ .../Services/EssentialsAppInfoService.cs | 67 ++- .../Services/EssentialsLauncherService.cs | 3 +- .../Services/InternalSettings.cs | 2 +- .../Services/NewtonsoftJsonSerializer.cs | 87 ---- .../Softeq.XToolkit.WhiteLabel.csproj | 8 +- XToolkit.ruleset | 202 +++++---- XToolkit.sln | 82 +--- _Tests.Platform.targets | 3 +- _Tests.targets | 10 +- azure-pipelines/Documentation.yml | 51 ++- azure-pipelines/XToolkit.yml | 32 +- azure-pipelines/templates/app-jobs.yml | 31 -- azure-pipelines/templates/setup-dotnet.yml | 24 + azure-pipelines/templates/setup-xcode.yml | 13 + azure-pipelines/templates/vars.yml | 7 +- documentation/Properties/AssemblyInfo.cs | 14 - .../Softeq.XToolkit.Documentation.csproj | 48 -- .../Softeq.XToolkit.Documentation.sln | 17 - documentation/articles/configure-android.md | 4 +- documentation/articles/create-activity.md | 2 +- .../create-storyboard-viewcontroller.md | 10 +- documentation/articles/getting-started.md | 6 +- documentation/articles/xtoolkit/overview.md | 2 +- .../articles/xtoolkit/push-notifications.md | 6 +- .../articles/xtoolkit/version-support.md | 70 ++- documentation/articles/xtoolkit/whitelabel.md | 2 +- .../articles/xtoolkit/whitelabel/forms.md | 3 + documentation/build.sh | 20 +- documentation/docfx.json | 39 +- documentation/filterConfig.yml | 4 + documentation/index.md | 7 +- documentation/nested-namespaces.ps1 | 176 -------- .../styles/main.css | 87 +++- .../templates/material-modern/public/main.css | 204 +++++++++ .../material/partials/head.tmpl.partial | 20 - generate_test_coverage_report.sh | 26 +- global.json | 7 + nuget/Softeq.XToolkit.Bindings.nuspec | 24 +- nuget/Softeq.XToolkit.Common.nuspec | 24 +- nuget/Softeq.XToolkit.Permissions.nuspec | 24 +- .../Softeq.XToolkit.PushNotifications.nuspec | 27 +- nuget/Softeq.XToolkit.Remote.nuspec | 6 +- ...fteq.XToolkit.WhiteLabel.Essentials.nuspec | 25 +- nuget/Softeq.XToolkit.WhiteLabel.Forms.nuspec | 8 +- nuget/Softeq.XToolkit.WhiteLabel.nuspec | 24 +- .../Playground.Forms.Droid.csproj | 2 - .../Properties/AssemblyInfo.cs | 2 +- .../Playground.Forms.iOS/IosBootstrapper.cs | 1 - .../Playground.Forms.iOS.csproj | 12 +- .../Properties/AssemblyInfo.cs | 2 +- .../Playground.Forms/CoreBootstrapper.cs | 4 +- .../Playground.Droid/Assets/AboutAssets.txt | 19 - .../CustomDroidBootstrapper.cs | 10 +- .../Extended/DroidChooseBetterDateDialog.cs | 2 +- .../Playground.Droid/GlobalSuppressions.cs | 9 + .../Playground.Droid/LinkerPleaseInclude.cs | 1 + .../Playground.Droid/MainApplication.cs | 4 +- .../Playground.Droid/Playground.Droid.csproj | 222 ++-------- .../Properties/AndroidManifest.xml | 13 +- .../Properties/AssemblyInfo.cs | 31 -- .../Resources/AboutResources.txt | 44 -- .../Resources/drawable/background_splash.xml | 2 +- .../Resources/layout/activity_permissions.xml | 17 +- .../Resources/mipmap-anydpi-v26/appicon.xml | 1 + .../mipmap-anydpi-v26/appicon_round.xml | 1 + .../mipmap-anydpi-v26/ic_launcher.xml | 5 - .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 - .../Resources/mipmap-hdpi/appicon.png | Bin 0 -> 1149 bytes .../mipmap-hdpi/appicon_background.png | Bin 0 -> 532 bytes .../mipmap-hdpi/appicon_foreground.png | Bin 0 -> 1511 bytes .../Resources/mipmap-hdpi/ic_launcher.png | Bin 1634 -> 0 bytes .../mipmap-hdpi/ic_launcher_foreground.png | Bin 1441 -> 0 bytes .../mipmap-hdpi/ic_launcher_round.png | Bin 3552 -> 0 bytes .../Resources/mipmap-mdpi/appicon.png | Bin 0 -> 868 bytes .../mipmap-mdpi/appicon_background.png | Bin 0 -> 352 bytes .../mipmap-mdpi/appicon_foreground.png | Bin 0 -> 1281 bytes .../Resources/mipmap-mdpi/ic_launcher.png | Bin 1362 -> 0 bytes .../mipmap-mdpi/ic_launcher_foreground.png | Bin 958 -> 0 bytes .../mipmap-mdpi/ic_launcher_round.png | Bin 2413 -> 0 bytes .../Resources/mipmap-xhdpi/appicon.png | Bin 0 -> 1473 bytes .../mipmap-xhdpi/appicon_background.png | Bin 0 -> 702 bytes .../mipmap-xhdpi/appicon_foreground.png | Bin 0 -> 2164 bytes .../Resources/mipmap-xhdpi/ic_launcher.png | Bin 2307 -> 0 bytes .../mipmap-xhdpi/ic_launcher_foreground.png | Bin 2056 -> 0 bytes .../mipmap-xhdpi/ic_launcher_round.png | Bin 4858 -> 0 bytes .../Resources/mipmap-xxhdpi/appicon.png | Bin 0 -> 1914 bytes .../mipmap-xxhdpi/appicon_background.png | Bin 0 -> 1040 bytes .../mipmap-xxhdpi/appicon_foreground.png | Bin 0 -> 2619 bytes .../Resources/mipmap-xxhdpi/ic_launcher.png | Bin 3871 -> 0 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 3403 -> 0 bytes .../mipmap-xxhdpi/ic_launcher_round.png | Bin 8001 -> 0 bytes .../Resources/mipmap-xxxhdpi/appicon.png | Bin 0 -> 2557 bytes .../mipmap-xxxhdpi/appicon_background.png | Bin 0 -> 1728 bytes .../mipmap-xxxhdpi/appicon_foreground.png | Bin 0 -> 4350 bytes .../Resources/mipmap-xxxhdpi/ic_launcher.png | Bin 5016 -> 0 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 4889 -> 0 bytes .../mipmap-xxxhdpi/ic_launcher_round.png | Bin 10893 -> 0 bytes .../Services/GlideImageService.cs | 24 + .../BottomTabs/BottomTabsPageActivity.cs | 2 +- .../Views/BottomTabs/First/RedFragment.cs | 6 +- .../Views/BottomTabs/First/YellowActivity.cs | 2 +- .../Views/BottomTabs/Second/BlueFragment.cs | 6 +- .../Views/BottomTabs/Second/GreenFragment.cs | 6 +- .../GroupedCollectionPageActivity.cs | 2 +- .../Collections/GroupedTablePageActivity.cs | 2 +- .../Collections/MovieCollectionViewHolder.cs | 7 +- .../Views/Collections/ProductViewHolder.cs | 15 +- .../Components/ConnectivityPageActivity.cs | 2 +- .../Components/PermissionsPageActivity.cs | 29 +- .../Views/Controls/PhotoBrowserActivity.cs | 4 +- .../Views/Dialogs/DialogsPageActivity.cs | 12 +- .../Views/Dialogs/SimpleDialogPageFragment.cs | 8 +- .../Views/EmptyPageActivity.cs | 2 +- .../Views/Frames/BlueFragment.cs | 6 +- .../Views/Frames/RedFragment.cs | 6 +- .../Views/Frames/SplitFrameFragment.cs | 2 +- .../Views/Frames/YellowFragment.cs | 6 +- .../Views/MainPageActivity.cs | 2 + .../AppIcon.appiconset/Contents.json | 5 +- .../AppIcon.appiconset/Icon1024.png | Bin 70429 -> 10753 bytes .../AppIcon.appiconset/Icon120.png | Bin 3773 -> 1155 bytes .../AppIcon.appiconset/Icon152.png | Bin 4750 -> 1381 bytes .../AppIcon.appiconset/Icon167.png | Bin 4692 -> 1503 bytes .../AppIcon.appiconset/Icon180.png | Bin 5192 -> 1558 bytes .../AppIcon.appiconset/Icon20.png | Bin 1313 -> 352 bytes .../AppIcon.appiconset/Icon29.png | Bin 845 -> 422 bytes .../AppIcon.appiconset/Icon40.png | Bin 1101 -> 537 bytes .../AppIcon.appiconset/Icon58.png | Bin 1761 -> 700 bytes .../AppIcon.appiconset/Icon60.png | Bin 2537 -> 700 bytes .../AppIcon.appiconset/Icon76.png | Bin 2332 -> 870 bytes .../AppIcon.appiconset/Icon80.png | Bin 2454 -> 888 bytes .../AppIcon.appiconset/Icon87.png | Bin 2758 -> 942 bytes .../Playground.iOS/CustomIosBootstrapper.cs | 7 +- .../Playground.iOS/GlobalSuppressions.cs | 9 + samples/Playground/Playground.iOS/Info.plist | 6 +- .../Playground.iOS/LinkerPleaseInclude.cs | 1 + samples/Playground/Playground.iOS/Main.cs | 2 +- .../Playground.iOS/Playground.iOS.csproj | 416 +++--------------- .../Playground.iOS/PlaygroundStyles.cs | 2 +- .../Playground.iOS/Properties/AssemblyInfo.cs | 35 -- .../Services/NukeImageService.cs | 30 ++ .../BottomTabs/First/RedViewController.cs | 6 +- .../BottomTabs/First/YellowViewController.cs | 6 +- .../BottomTabs/Second/BlueViewController.cs | 6 +- .../BottomTabs/Second/GreenViewController.cs | 6 +- .../CollectionPageViewController.cs | 4 +- .../AdaptiveSectionsViewController.cs | 2 +- .../NestedGroupsViewController.cs | 2 +- .../CompositionalLayoutPageViewController.cs | 4 +- .../GroupedCollectionPageViewController.cs | 3 +- ...GroupedCollectionViewDelegateFlowLayout.cs | 1 - .../GroupedListPageViewController.cs | 4 +- .../Collections/TablePageViewController.cs | 4 +- .../ConnectivityPageViewController.cs | 6 +- .../Components/GesturesPageViewController.cs | 4 +- .../PermissionsPageStoryboard.storyboard | 36 +- .../PermissionsPageViewController.cs | 8 +- .../PermissionsPageViewController.designer.cs | 111 +++-- .../Controls/PhotoBrowserViewController.cs | 3 +- .../Dialogs/CustomTransitioningDelegate.cs | 5 +- .../Dialogs/DialogsPageViewController.cs | 4 +- .../Dialogs/SimpleDialogPageViewController.cs | 3 +- .../EmptyPageViewController.cs | 4 +- .../Frames/FramesBlueViewController.cs | 4 +- .../Frames/FramesRedViewController.cs | 4 +- .../Frames/FramesViewController.cs | 4 +- .../Frames/FramesYellowViewController.cs | 4 +- .../Frames/SplitFrameViewController.cs | 4 +- .../ViewControllers/MainPageViewController.cs | 6 +- .../Pages/DetailsPageViewController.cs | 6 +- .../Views/Collections/DummyCell.cs | 4 +- .../Views/Collections/GroupedFooterView.cs | 3 +- .../Views/Collections/GroupedHeaderView.cs | 3 +- .../Views/Collections/ProductViewCell.cs | 8 +- .../Views/MainPageGroupHeaderViewCell.cs | 4 +- .../Views/MainPageItemViewCell.cs | 4 +- .../Views/MovieCollectionViewCell.cs | 9 +- .../Views/MovieTableViewCell.cs | 9 +- .../Views/Table/GroupedTableFooterView.cs | 3 +- .../Views/Table/GroupedTableHeaderView.cs | 3 +- .../Views/Table/ProductTableViewCell.cs | 9 +- .../Playground/CustomBootstrapper.cs | 6 + .../Playground/GlobalSuppressions.cs | 9 + .../Playground/Playground/Playground.csproj | 6 +- .../Products/ProductHeaderViewModel.cs | 9 +- .../Components/ConnectivityPageViewModel.cs | 21 +- .../Components/PermissionViewModel.cs | 4 +- .../Components/PermissionsPageViewModel.cs | 24 +- 420 files changed, 5449 insertions(+), 6089 deletions(-) delete mode 100644 Softeq.XToolkit.Bindings.Droid/Properties/AssemblyInfo.cs delete mode 100644 Softeq.XToolkit.Bindings.iOS/Properties/AssemblyInfo.cs delete mode 100644 Softeq.XToolkit.Common.Droid.Tests/Assets/xunit.runner.json delete mode 100644 Softeq.XToolkit.Common.Droid.Tests/Converters/VisibilityConverterTests.cs delete mode 100644 Softeq.XToolkit.Common.Droid.Tests/DroidMainThreadExecutorTests/DroidMainThreadExecutorTests.cs delete mode 100644 Softeq.XToolkit.Common.Droid.Tests/Extensions/ContextExtensionsTests/ContextExtensionsTests.cs delete mode 100644 Softeq.XToolkit.Common.Droid.Tests/Extensions/EditTextExtensionsTests/EditTextExtensionsTests.cs delete mode 100644 Softeq.XToolkit.Common.Droid.Tests/Extensions/EditTextExtensionsTests/MockInputFilter.cs delete mode 100644 Softeq.XToolkit.Common.Droid.Tests/Extensions/StringExtensionsTests/StringExtensionsTests.cs delete mode 100644 Softeq.XToolkit.Common.Droid.Tests/Helpers.cs delete mode 100644 Softeq.XToolkit.Common.Droid.Tests/MainActivity.cs create mode 100644 Softeq.XToolkit.Common.Droid.Tests/MauiProgram.cs create mode 100644 Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/AndroidManifest.xml create mode 100644 Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Converters/VisibilityConverterTests.cs create mode 100644 Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/DroidMainThreadExecutorTests/DroidMainThreadExecutorTests.cs create mode 100644 Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Extensions/ContextExtensionsTests/ContextExtensionsTests.cs create mode 100644 Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Extensions/EditTextExtensionsTests/EditTextExtensionsTests.cs create mode 100644 Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Extensions/EditTextExtensionsTests/MockInputFilter.cs rename Softeq.XToolkit.Common.Droid.Tests/{ => Platforms/Android}/Extensions/StringExtensionsTests/StringExtensionsDataProvider.cs (100%) create mode 100644 Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Extensions/StringExtensionsTests/StringExtensionsTests.cs create mode 100644 Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Helpers.cs create mode 100644 Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/MainActivity.cs create mode 100644 Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/MainApplication.cs create mode 100644 Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Resources/values/colors.xml create mode 100644 Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/TextFilters/ForbiddenCharsInputFilterTests.cs delete mode 100644 Softeq.XToolkit.Common.Droid.Tests/Properties/AndroidManifest.xml delete mode 100644 Softeq.XToolkit.Common.Droid.Tests/Properties/AssemblyInfo.cs create mode 100644 Softeq.XToolkit.Common.Droid.Tests/Properties/launchSettings.json create mode 100644 Softeq.XToolkit.Common.Droid.Tests/Resources/AppIcon/appicon.svg create mode 100644 Softeq.XToolkit.Common.Droid.Tests/Resources/AppIcon/appiconfg.svg create mode 100644 Softeq.XToolkit.Common.Droid.Tests/Resources/Raw/AboutAssets.txt create mode 100644 Softeq.XToolkit.Common.Droid.Tests/Resources/Splash/splash.svg delete mode 100644 Softeq.XToolkit.Common.Droid.Tests/Resources/mipmap-mdpi/ic_launcher.png delete mode 100644 Softeq.XToolkit.Common.Droid.Tests/Resources/mipmap-xhdpi/ic_launcher.png delete mode 100644 Softeq.XToolkit.Common.Droid.Tests/Resources/mipmap-xxhdpi/ic_launcher.png delete mode 100644 Softeq.XToolkit.Common.Droid.Tests/Resources/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 Softeq.XToolkit.Common.Droid.Tests/Resources/values/strings.xml delete mode 100644 Softeq.XToolkit.Common.Droid.Tests/TextFilters/ForbiddenCharsInputFilterTests.cs delete mode 100644 Softeq.XToolkit.Common.Droid/Properties/AssemblyInfo.cs create mode 100644 Softeq.XToolkit.Common.Tests/Collections/ObservableKeyGroupsCollectionTest/ObservableKeyGroupsCollectionTestsCountChanged.cs delete mode 100644 Softeq.XToolkit.Common.Tests/Extensions/EnumerableExtensionsTests/EnumerableExtensionsDataProvider.cs delete mode 100644 Softeq.XToolkit.Common.iOS.Tests/AppDelegate.cs delete mode 100644 Softeq.XToolkit.Common.iOS.Tests/Entitlements.plist delete mode 100644 Softeq.XToolkit.Common.iOS.Tests/Extensions/NSDateExtensionsTests/NSDateExtensionsTests.cs delete mode 100644 Softeq.XToolkit.Common.iOS.Tests/Extensions/NSLocaleExtensionsTests/NSLocaleExtensionsTests.cs delete mode 100644 Softeq.XToolkit.Common.iOS.Tests/Extensions/NSRangeExtensionsTests/NSRangeExtensionsTests.cs delete mode 100644 Softeq.XToolkit.Common.iOS.Tests/Extensions/NSStringExtensions/NSStringExtensionsTests.cs delete mode 100644 Softeq.XToolkit.Common.iOS.Tests/Extensions/UIColorExtensionsTests/UIColorExtensionsTests.cs delete mode 100644 Softeq.XToolkit.Common.iOS.Tests/Extensions/UITextViewExtensionsTests/UITextViewExtensionsTests.cs delete mode 100644 Softeq.XToolkit.Common.iOS.Tests/Helpers.cs delete mode 100644 Softeq.XToolkit.Common.iOS.Tests/Info.plist delete mode 100644 Softeq.XToolkit.Common.iOS.Tests/IosMainThreadExecutorTests/IosMainThreadExecutorTests.cs delete mode 100644 Softeq.XToolkit.Common.iOS.Tests/LaunchScreen.storyboard delete mode 100644 Softeq.XToolkit.Common.iOS.Tests/Main.cs create mode 100644 Softeq.XToolkit.Common.iOS.Tests/MauiProgram.cs create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/AppDelegate.cs create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/NSDateExtensionsTests/NSDateExtensionsTests.cs create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/NSLocaleExtensionsTests/NSLocaleExtensionsTests.cs create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/NSStringExtensions/NSStringExtensionsTests.cs create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/NsRangeExtensionsTests/NSRangeExtensionsTests.cs create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/UIColorExtensionsTests/UIColorExtensionsTests.cs create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/UITextViewExtensionsTests/UITextViewExtensionsTests.cs create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Helpers.cs create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Info.plist create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/IosMainThreadExecutorTests/IosMainThreadExecutorTests.cs create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Program.cs create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/TextFilters/ForbiddenCharsFilterTests/ForbiddenCharsFilterTests.cs create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/TextFilters/GroupFilterTests/GroupFilterTests.cs create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/TextFilters/GroupFilterTests/MockTextFilter.cs create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/TextFilters/LengthFilterTests/LengthFilterTests.cs create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Properties/launchSettings.json create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Resources/AppIcon/appicon.svg create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Resources/AppIcon/appiconfg.svg create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Resources/Raw/AboutAssets.txt create mode 100644 Softeq.XToolkit.Common.iOS.Tests/Resources/Splash/splash.svg delete mode 100644 Softeq.XToolkit.Common.iOS.Tests/TextFilters/ForbiddenCharsFilterTests/ForbiddenCharsFilterTests.cs delete mode 100644 Softeq.XToolkit.Common.iOS.Tests/TextFilters/GroupFilterTests/GroupFilterTests.cs delete mode 100644 Softeq.XToolkit.Common.iOS.Tests/TextFilters/GroupFilterTests/MockTextFilter.cs delete mode 100644 Softeq.XToolkit.Common.iOS.Tests/TextFilters/LengthFilterTests/LengthFilterTests.cs delete mode 100644 Softeq.XToolkit.Common.iOS/Properties/AssemblyInfo.cs delete mode 100644 Softeq.XToolkit.Connectivity.iOS/Properties/AssemblyInfo.cs delete mode 100644 Softeq.XToolkit.Connectivity/ConnectivityService.cs create mode 100644 Softeq.XToolkit.Connectivity/EssentialsConnectivityService.cs create mode 100644 Softeq.XToolkit.Connectivity/Extensions.cs delete mode 100644 Softeq.XToolkit.Permissions.Droid/NotificationsPlatformPermission.cs create mode 100644 Softeq.XToolkit.Permissions.Droid/Permissions/Bluetooth.cs create mode 100644 Softeq.XToolkit.Permissions.Droid/Permissions/Notifications.cs delete mode 100644 Softeq.XToolkit.Permissions.Droid/PermissionsService.cs delete mode 100644 Softeq.XToolkit.Permissions.Droid/Properties/AssemblyInfo.cs create mode 100644 Softeq.XToolkit.Permissions.Droid/SdkVersion.cs create mode 100644 Softeq.XToolkit.Permissions.iOS/Permissions/Bluetooth.cs create mode 100644 Softeq.XToolkit.Permissions.iOS/Permissions/Notifications.cs delete mode 100644 Softeq.XToolkit.Permissions.iOS/PermissionsService.cs delete mode 100644 Softeq.XToolkit.Permissions.iOS/Properties/AssemblyInfo.cs delete mode 100644 Softeq.XToolkit.Permissions/NotificationsPermission.cs create mode 100644 Softeq.XToolkit.Permissions/Permissions/Bluetooth.cs create mode 100644 Softeq.XToolkit.Permissions/Permissions/Notifications.cs create mode 100644 Softeq.XToolkit.Permissions/PermissionsService.cs delete mode 100644 Softeq.XToolkit.PushNotifications.Droid/Properties/AssemblyInfo.cs delete mode 100644 Softeq.XToolkit.PushNotifications.iOS/Properties/AssemblyInfo.cs delete mode 100644 Softeq.XToolkit.WhiteLabel.Droid/Extensions/ImageViewExtensions.cs create mode 100644 Softeq.XToolkit.WhiteLabel.Droid/Interfaces/IBundleService.cs create mode 100644 Softeq.XToolkit.WhiteLabel.Droid/Interfaces/IDroidImageService.cs delete mode 100644 Softeq.XToolkit.WhiteLabel.Droid/Properties/AssemblyInfo.cs rename Softeq.XToolkit.WhiteLabel.Droid/Resources/layout/{activity_bottom_navigation.axml => activity_bottom_navigation.xml} (100%) create mode 100644 Softeq.XToolkit.WhiteLabel.Droid/Services/DefaultDroidImageService.cs delete mode 100644 Softeq.XToolkit.WhiteLabel.Droid/Services/DroidAppInfoService.cs delete mode 100644 Softeq.XToolkit.WhiteLabel.Essentials.Droid/Properties/AssemblyInfo.cs delete mode 100644 Softeq.XToolkit.WhiteLabel.Essentials.iOS/Properties/AssemblyInfo.cs create mode 100644 Softeq.XToolkit.WhiteLabel.Tests/Extensions/ExpressionExtensionsTests.cs rename Softeq.XToolkit.WhiteLabel.Tests/Navigation/PropertyInfoModelTests/{PropertyInfoModelTests.cs => NavigationPropertyInfoTests.cs} (65%) create mode 100644 Softeq.XToolkit.WhiteLabel.Tests/Services/DefaultJsonSerializerTests/DefaultJsonSerializerTests.cs create mode 100644 Softeq.XToolkit.WhiteLabel.Tests/Services/DefaultJsonSerializerTests/DefaultJsonSerializerTestsNavigationSerializer.cs create mode 100644 Softeq.XToolkit.WhiteLabel.Tests/Services/JsonSerializers/JsonDeserializationDataProvider.cs create mode 100644 Softeq.XToolkit.WhiteLabel.Tests/Services/JsonSerializers/JsonSerializationDataProvider.cs create mode 100644 Softeq.XToolkit.WhiteLabel.Tests/Services/JsonSerializers/JsonSerializationTestsStub.cs delete mode 100644 Softeq.XToolkit.WhiteLabel.iOS/Extensions/MvxCachedImageViewExtensions.cs create mode 100644 Softeq.XToolkit.WhiteLabel.iOS/Interfaces/IIosImageService.cs delete mode 100644 Softeq.XToolkit.WhiteLabel.iOS/Properties/AssemblyInfo.cs create mode 100644 Softeq.XToolkit.WhiteLabel.iOS/Services/DefaultIosImageService.cs delete mode 100644 Softeq.XToolkit.WhiteLabel.iOS/Services/IosAppInfoService.cs create mode 100644 Softeq.XToolkit.WhiteLabel/Navigation/INavigationSerializer.cs rename Softeq.XToolkit.WhiteLabel/Navigation/{PropertyInfoModel.cs => NavigationPropertyInfo.cs} (82%) create mode 100644 Softeq.XToolkit.WhiteLabel/Services/DefaultJsonSerializer.cs delete mode 100644 Softeq.XToolkit.WhiteLabel/Services/NewtonsoftJsonSerializer.cs delete mode 100644 azure-pipelines/templates/app-jobs.yml create mode 100644 azure-pipelines/templates/setup-dotnet.yml create mode 100644 azure-pipelines/templates/setup-xcode.yml delete mode 100644 documentation/Properties/AssemblyInfo.cs delete mode 100644 documentation/Softeq.XToolkit.Documentation.csproj delete mode 100644 documentation/Softeq.XToolkit.Documentation.sln create mode 100644 documentation/filterConfig.yml delete mode 100644 documentation/nested-namespaces.ps1 rename documentation/templates/{material => material-classic}/styles/main.css (69%) create mode 100644 documentation/templates/material-modern/public/main.css delete mode 100644 documentation/templates/material/partials/head.tmpl.partial create mode 100644 global.json delete mode 100644 samples/Playground/Playground.Droid/Assets/AboutAssets.txt create mode 100644 samples/Playground/Playground.Droid/GlobalSuppressions.cs delete mode 100644 samples/Playground/Playground.Droid/Properties/AssemblyInfo.cs delete mode 100644 samples/Playground/Playground.Droid/Resources/AboutResources.txt create mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-anydpi-v26/appicon.xml create mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-anydpi-v26/appicon_round.xml delete mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-anydpi-v26/ic_launcher.xml delete mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-hdpi/appicon.png create mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-hdpi/appicon_background.png create mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-hdpi/appicon_foreground.png delete mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-hdpi/ic_launcher.png delete mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-hdpi/ic_launcher_foreground.png delete mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-hdpi/ic_launcher_round.png create mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-mdpi/appicon.png create mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-mdpi/appicon_background.png create mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-mdpi/appicon_foreground.png delete mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-mdpi/ic_launcher.png delete mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-mdpi/ic_launcher_foreground.png delete mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-mdpi/ic_launcher_round.png create mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-xhdpi/appicon.png create mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-xhdpi/appicon_background.png create mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-xhdpi/appicon_foreground.png delete mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-xhdpi/ic_launcher.png delete mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-xhdpi/ic_launcher_foreground.png delete mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-xhdpi/ic_launcher_round.png create mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-xxhdpi/appicon.png create mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-xxhdpi/appicon_background.png create mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-xxhdpi/appicon_foreground.png delete mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-xxhdpi/ic_launcher.png delete mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-xxhdpi/ic_launcher_foreground.png delete mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-xxxhdpi/appicon.png create mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-xxxhdpi/appicon_background.png create mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-xxxhdpi/appicon_foreground.png delete mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-xxxhdpi/ic_launcher_foreground.png delete mode 100644 samples/Playground/Playground.Droid/Resources/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 samples/Playground/Playground.Droid/Services/GlideImageService.cs create mode 100644 samples/Playground/Playground.iOS/GlobalSuppressions.cs delete mode 100644 samples/Playground/Playground.iOS/Properties/AssemblyInfo.cs create mode 100644 samples/Playground/Playground.iOS/Services/NukeImageService.cs create mode 100644 samples/Playground/Playground/GlobalSuppressions.cs diff --git a/Directory.Build.props b/Directory.Build.props index dbffb33ed..3ab7eea7b 100755 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,8 +2,17 @@ - 8.0 + 12.0 enable + portable + 8.0.3 + + + + + XToolkit + Softeq Development Corporation + Copyright © 2024 Softeq Development Corporation \ No newline at end of file diff --git a/LICENSE b/LICENSE index 25c1277cb..bfa391608 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2018 Softeq +Copyright (c) 2024 Softeq Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 40dd0175a..733d6f34b 100644 --- a/README.md +++ b/README.md @@ -17,10 +17,9 @@ Common | [![Softeq.XToolkit.Common](https://buildstats.info/nuget/Softeq.XToo Bindings | [![Softeq.XToolkit.Bindings](https://buildstats.info/nuget/Softeq.XToolkit.Bindings?includePreReleases=true)](https://www.nuget.org/packages/Softeq.XToolkit.Bindings) Permissions | [![Softeq.XToolkit.Permissions](https://buildstats.info/nuget/Softeq.XToolkit.Permissions?includePreReleases=true)](https://www.nuget.org/packages/Softeq.XToolkit.Permissions) PushNotifications | [![Softeq.XToolkit.PushNotifications](https://buildstats.info/nuget/Softeq.XToolkit.PushNotifications?includePreReleases=true)](https://www.nuget.org/packages/Softeq.XToolkit.PushNotifications) +Remote | [![Softeq.XToolkit.Remote](https://buildstats.info/nuget/Softeq.XToolkit.Remote?includePreReleases=true)](https://www.nuget.org/packages/Softeq.XToolkit.Remote) WhiteLabel | [![Softeq.XToolkit.WhiteLabel](https://buildstats.info/nuget/Softeq.XToolkit.WhiteLabel?includePreReleases=true)](https://www.nuget.org/packages/Softeq.XToolkit.WhiteLabel) WhiteLabel.Essentials | [![Softeq.XToolkit.WhiteLabel.Essentials](https://buildstats.info/nuget/Softeq.XToolkit.WhiteLabel.Essentials?includePreReleases=true)](https://www.nuget.org/packages/Softeq.XToolkit.WhiteLabel.Essentials) -WhiteLabel.Forms | [![Softeq.XToolkit.WhiteLabel.Forms](https://buildstats.info/nuget/Softeq.XToolkit.WhiteLabel.Forms?includePreReleases=true)](https://www.nuget.org/packages/Softeq.XToolkit.WhiteLabel.Forms) -Remote | [![Softeq.XToolkit.Remote](https://buildstats.info/nuget/Softeq.XToolkit.Remote?includePreReleases=true)](https://www.nuget.org/packages/Softeq.XToolkit.Remote) ## Documentation diff --git a/Softeq.XToolkit.Bindings.Droid/Properties/AssemblyInfo.cs b/Softeq.XToolkit.Bindings.Droid/Properties/AssemblyInfo.cs deleted file mode 100644 index 39b8e0f83..000000000 --- a/Softeq.XToolkit.Bindings.Droid/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Softeq.XToolkit.Bindings.Droid")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Softeq Development Corporation")] -[assembly: AssemblyProduct("XToolkit.Bindings")] -[assembly: AssemblyCopyright("Copyright © Softeq Development Corporation 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Softeq.XToolkit.Bindings.Droid/Softeq.XToolkit.Bindings.Droid.csproj b/Softeq.XToolkit.Bindings.Droid/Softeq.XToolkit.Bindings.Droid.csproj index 3b126c32d..0e094a945 100644 --- a/Softeq.XToolkit.Bindings.Droid/Softeq.XToolkit.Bindings.Droid.csproj +++ b/Softeq.XToolkit.Bindings.Droid/Softeq.XToolkit.Bindings.Droid.csproj @@ -1,84 +1,27 @@ - - + + - Debug - AnyCPU - 8.0.30703 - 2.0 - {2B4A678B-63B4-49DB-99B1-BFE7793110C4} - {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Library - Properties - Softeq.XToolkit.Bindings.Droid - Softeq.XToolkit.Bindings.Droid - 512 - Resources\Resource.Designer.cs - Off - v13.0 + net8.0-android34.0 + 21.0 + true - portable - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 None + - portable - true - bin\Release\ - TRACE - prompt - 4 SdkOnly + - - - - - - - - - - - - + + + + - - - - - - - - - - - - - - - - - - - {0F1F09A8-9CDB-4933-AA1B-898AB43D394C} - Softeq.XToolkit.Bindings - - - {18d3fdc1-b0a1-401e-87f2-1c43034e610c} - Softeq.XToolkit.Common.Droid - - - {24588814-B93D-4528-8917-9C2A3C4E85CA} - Softeq.XToolkit.Common - + - - \ No newline at end of file + + diff --git a/Softeq.XToolkit.Bindings.Tests/Softeq.XToolkit.Bindings.Tests.csproj b/Softeq.XToolkit.Bindings.Tests/Softeq.XToolkit.Bindings.Tests.csproj index 666e5710b..4bbaec837 100644 --- a/Softeq.XToolkit.Bindings.Tests/Softeq.XToolkit.Bindings.Tests.csproj +++ b/Softeq.XToolkit.Bindings.Tests/Softeq.XToolkit.Bindings.Tests.csproj @@ -1,8 +1,8 @@ + net8.0 Exe - net5.0 disable diff --git a/Softeq.XToolkit.Bindings.iOS/Properties/AssemblyInfo.cs b/Softeq.XToolkit.Bindings.iOS/Properties/AssemblyInfo.cs deleted file mode 100644 index 56877ab0c..000000000 --- a/Softeq.XToolkit.Bindings.iOS/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Softeq.XToolkit.Binding.iOS")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Softeq Development Corporation")] -[assembly: AssemblyProduct("XToolkit.Bindings")] -[assembly: AssemblyCopyright("Copyright © Softeq Development Corporation 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("2d399ba9-1878-43e2-af05-6873eae9151b")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Softeq.XToolkit.Bindings.iOS/Softeq.XToolkit.Bindings.iOS.csproj b/Softeq.XToolkit.Bindings.iOS/Softeq.XToolkit.Bindings.iOS.csproj index 67f3b4e21..4bff76dee 100644 --- a/Softeq.XToolkit.Bindings.iOS/Softeq.XToolkit.Bindings.iOS.csproj +++ b/Softeq.XToolkit.Bindings.iOS/Softeq.XToolkit.Bindings.iOS.csproj @@ -1,84 +1,24 @@ - - + + - Debug - AnyCPU - 8.0.30703 - 2.0 - {2D399BA9-1878-43E2-AF05-6873EAE9151B} - {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Library - Softeq.XToolkit.Bindings.iOS - Softeq.XToolkit.Bindings.iOS - Resources + net8.0-ios12.0 + 10.0 + false + true - portable - false - bin\Debug - DEBUG; - prompt - 4 - false - None + None + - portable - true - bin\Release - prompt - 4 - false - SdkOnly + SdkOnly + - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {0F1F09A8-9CDB-4933-AA1B-898AB43D394C} - Softeq.XToolkit.Bindings - - - {24588814-B93D-4528-8917-9C2A3C4E85CA} - Softeq.XToolkit.Common - - - {6BCB2009-2E46-458C-BCAA-AFC27A631924} - Softeq.XToolkit.Common.iOS - - - + \ No newline at end of file diff --git a/Softeq.XToolkit.Bindings/Softeq.XToolkit.Bindings.csproj b/Softeq.XToolkit.Bindings/Softeq.XToolkit.Bindings.csproj index b59267f4d..cd0e00201 100644 --- a/Softeq.XToolkit.Bindings/Softeq.XToolkit.Bindings.csproj +++ b/Softeq.XToolkit.Bindings/Softeq.XToolkit.Bindings.csproj @@ -1,7 +1,7 @@  - netstandard2.1 + net8.0 diff --git a/Softeq.XToolkit.Common.Droid.Tests/Assets/xunit.runner.json b/Softeq.XToolkit.Common.Droid.Tests/Assets/xunit.runner.json deleted file mode 100644 index 426a7cdfb..000000000 --- a/Softeq.XToolkit.Common.Droid.Tests/Assets/xunit.runner.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "diagnosticMessages": true, - "longRunningTestSeconds": 30, - "methodDisplay": "classAndMethod" -} \ No newline at end of file diff --git a/Softeq.XToolkit.Common.Droid.Tests/Converters/VisibilityConverterTests.cs b/Softeq.XToolkit.Common.Droid.Tests/Converters/VisibilityConverterTests.cs deleted file mode 100644 index 9f39b98b3..000000000 --- a/Softeq.XToolkit.Common.Droid.Tests/Converters/VisibilityConverterTests.cs +++ /dev/null @@ -1,80 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using Android.Views; -using Softeq.XToolkit.Common.Droid.Converters; -using Xunit; - -namespace Softeq.XToolkit.Common.Droid.Tests.Converters -{ - public class VisibilityConverterTests - { - [Fact] - public void Gone_ReturnsNotNull() - { - Assert.NotNull(VisibilityConverter.Gone); - } - - [Fact] - public void Invisible_ReturnsNotNull() - { - Assert.NotNull(VisibilityConverter.Invisible); - } - - [Fact] - public void Instance_ReturnsInvisible() - { - Assert.Same(VisibilityConverter.Invisible, VisibilityConverter.Instance); - } - - [Theory] - [InlineData(true, ViewStates.Visible)] - [InlineData(false, ViewStates.Gone)] - public void ConvertValue_Gone_ReturnsExpectedValue(bool value, ViewStates expectedResult) - { - var converter = VisibilityConverter.Gone; - - var result = converter.ConvertValue(value); - - Assert.Equal(expectedResult, result); - } - - [Theory] - [InlineData(true, ViewStates.Visible)] - [InlineData(false, ViewStates.Invisible)] - public void ConvertValue_Invisible_ReturnsExpectedValue(bool value, ViewStates expectedResult) - { - var converter = VisibilityConverter.Invisible; - - var result = converter.ConvertValue(value); - - Assert.Equal(expectedResult, result); - } - - [Theory] - [InlineData(ViewStates.Visible, true)] - [InlineData(ViewStates.Invisible, false)] - [InlineData(ViewStates.Gone, false)] - public void ConvertValueBack_Gone_ReturnsExpectedValue(ViewStates value, bool expectedResult) - { - var converter = VisibilityConverter.Gone; - - var result = converter.ConvertValueBack(value); - - Assert.Equal(expectedResult, result); - } - - [Theory] - [InlineData(ViewStates.Visible, true)] - [InlineData(ViewStates.Invisible, false)] - [InlineData(ViewStates.Gone, false)] - public void ConvertValueBack_Invisible_ReturnsExpectedValue(ViewStates value, bool expectedResult) - { - var converter = VisibilityConverter.Invisible; - - var result = converter.ConvertValueBack(value); - - Assert.Equal(expectedResult, result); - } - } -} diff --git a/Softeq.XToolkit.Common.Droid.Tests/DroidMainThreadExecutorTests/DroidMainThreadExecutorTests.cs b/Softeq.XToolkit.Common.Droid.Tests/DroidMainThreadExecutorTests/DroidMainThreadExecutorTests.cs deleted file mode 100644 index ff76de6f2..000000000 --- a/Softeq.XToolkit.Common.Droid.Tests/DroidMainThreadExecutorTests/DroidMainThreadExecutorTests.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Threading.Tasks; -using Xunit; - -namespace Softeq.XToolkit.Common.Droid.Tests.DroidMainThreadExecutorTests -{ - public class DroidMainThreadExecutorTests - { - private readonly DroidMainThreadExecutor _executor; - - public DroidMainThreadExecutorTests() - { - _executor = new DroidMainThreadExecutor(); - } - - [Fact] - public async Task IsMainThread_InMainThread_ReturnsTrue() - { - var result = await Helpers.RunOnUIThreadAsync(() => _executor.IsMainThread); - - Assert.True(result); - } - - [Fact] - public async Task IsMainThread_NotInMainThread_ReturnsFalse() - { - var result = await Task.Run(() => _executor.IsMainThread); - - Assert.False(result); - } - } -} diff --git a/Softeq.XToolkit.Common.Droid.Tests/Extensions/ContextExtensionsTests/ContextExtensionsTests.cs b/Softeq.XToolkit.Common.Droid.Tests/Extensions/ContextExtensionsTests/ContextExtensionsTests.cs deleted file mode 100644 index cb8c1fe28..000000000 --- a/Softeq.XToolkit.Common.Droid.Tests/Extensions/ContextExtensionsTests/ContextExtensionsTests.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using Android.Content; -using Softeq.XToolkit.Common.Droid.Extensions; -using Xunit; - -namespace Softeq.XToolkit.Common.Droid.Tests.Extensions.ContextExtensionsTests -{ - public class ContextExtensionsTests - { - private readonly Context _context = MainActivity.Current; - - [Theory] - [InlineData(-1)] - [InlineData(0)] - [InlineData(10)] - public void PxToDp_DpToPx_Equivalence(double pixels) - { - var resultDp = _context.PxToDp(pixels); - var resultPx = _context.DpToPx(resultDp); - - Assert.Equal(pixels, resultPx); - } - - [Fact] - public void GetStatusBarHeight_Default_PositiveValue() - { - var result = _context.GetStatusBarHeight(); - - Assert.True(result > 0); - } - } -} diff --git a/Softeq.XToolkit.Common.Droid.Tests/Extensions/EditTextExtensionsTests/EditTextExtensionsTests.cs b/Softeq.XToolkit.Common.Droid.Tests/Extensions/EditTextExtensionsTests/EditTextExtensionsTests.cs deleted file mode 100644 index bdd1ab4fc..000000000 --- a/Softeq.XToolkit.Common.Droid.Tests/Extensions/EditTextExtensionsTests/EditTextExtensionsTests.cs +++ /dev/null @@ -1,94 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using Android.Text; -using Android.Widget; -using Softeq.XToolkit.Common.Droid.Extensions; -using Xunit; - -namespace Softeq.XToolkit.Common.Droid.Tests.Extensions.EditTextExtensionsTests -{ - public class EditTextExtensionsTests - { - [Fact] - public void SetFilters_WhenCalledWithoutFilters_DoNothing() - { - EditText editText = new EditText(MainActivity.Current); - editText.SetFilters(); - - var appliedFilters = editText.GetFilters(); - if (appliedFilters != null) - { - Assert.Empty(appliedFilters); - } - } - - [Fact] - public void SetFilters_WhenCalledWithMultipleFilters_AppliesAllFilters() - { - EditText editText = new EditText(MainActivity.Current); - - var filter1 = new MockInputFilter(); - var filter2 = new MockInputFilter(); - var filter3 = new MockInputFilter(); - var filters = new IInputFilter[] { filter1, filter2, filter3 }; - - editText.SetFilters(filter1, filter2, filter3); - - var appliedFilters = editText.GetFilters(); - Assert.Equal(filters, appliedFilters); - } - - [Fact] - public void SetFilters_WhenCalledWithMultipleFiltersWithNullAndDuplicates_AppliesAllFilters() - { - EditText editText = new EditText(MainActivity.Current); - - var filter1 = new MockInputFilter(); - var filter2 = new MockInputFilter(); - var filter3 = new MockInputFilter(); - var nullFilter = null as IInputFilter; - var filters = new[] { filter1, filter2, nullFilter, filter3, filter2, nullFilter, filter1, filter2 }; - - editText.SetFilters(filter1, filter2, nullFilter!, filter3, filter2, nullFilter!, filter1, filter2); - - var appliedFilters = editText.GetFilters(); - Assert.Equal(filters, appliedFilters); - } - - [Theory] - [InlineData("")] - [InlineData("a")] - [InlineData("abcd")] - [InlineData("abcdefg")] - public void KeepFocusAtTheEndOfField_WhenTextChangedWithoutFocus_WhenFocused_MovesCursorToTheEndOfField(string str) - { - EditText editText = new EditText(MainActivity.Current); - editText.ClearFocus(); - - editText.Text = str; - editText.RequestFocus(); - - Assert.Equal(editText.SelectionEnd, editText.SelectionStart); - Assert.Equal(editText.SelectionStart, str.Length); - } - - [Theory] - [InlineData("")] - [InlineData("a")] - [InlineData("abcd")] - [InlineData("abcdefg")] - public void KeepFocusAtTheEndOfField_WhenTextChangedWithFocus_WhenRefocused_MovesCursorToTheEndOfField(string str) - { - EditText editText = new EditText(MainActivity.Current); - editText.RequestFocus(); - - editText.Text = str; - editText.ClearFocus(); - editText.RequestFocus(); - - Assert.Equal(editText.SelectionEnd, editText.SelectionStart); - Assert.Equal(editText.SelectionStart, str.Length); - } - } -} diff --git a/Softeq.XToolkit.Common.Droid.Tests/Extensions/EditTextExtensionsTests/MockInputFilter.cs b/Softeq.XToolkit.Common.Droid.Tests/Extensions/EditTextExtensionsTests/MockInputFilter.cs deleted file mode 100644 index eeba9693e..000000000 --- a/Softeq.XToolkit.Common.Droid.Tests/Extensions/EditTextExtensionsTests/MockInputFilter.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using Android.Text; -using Java.Lang; - -namespace Softeq.XToolkit.Common.Droid.Tests.Extensions.EditTextExtensionsTests -{ - public class MockInputFilter : Object, IInputFilter - { - public ICharSequence? FilterFormatted(ICharSequence? source, int start, int end, ISpanned? dest, int dstart, int dend) - { - return null; - } - } -} diff --git a/Softeq.XToolkit.Common.Droid.Tests/Extensions/StringExtensionsTests/StringExtensionsTests.cs b/Softeq.XToolkit.Common.Droid.Tests/Extensions/StringExtensionsTests/StringExtensionsTests.cs deleted file mode 100644 index c3f1e25c7..000000000 --- a/Softeq.XToolkit.Common.Droid.Tests/Extensions/StringExtensionsTests/StringExtensionsTests.cs +++ /dev/null @@ -1,230 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System; -using System.Linq; -using Android.Text; -using Android.Text.Style; -using Softeq.XToolkit.Common.Droid.Extensions; -using Softeq.XToolkit.Common.Helpers; -using Xunit; -using Object = Java.Lang.Object; - -namespace Softeq.XToolkit.Common.Droid.Tests.Extensions.StringExtensionsTests -{ - public class StringExtensionsTests - { - #region Null string - - [Theory] - [MemberData( - nameof(StringExtensionsDataProvider.FormatSpannableNullStringWithRangeAndSpansTestData), - MemberType = typeof(StringExtensionsDataProvider))] - public void FormatSpannable_WhenCalledOnNullString_WithAnyTextRange_WithSpans_ThrowsCorrectException( - TextRange textRange, Object[] spans) - { - var str = null as string; - Assert.Throws(() => str!.FormatSpannable(textRange, spans)); - } - - [Theory] - [MemberData( - nameof(StringExtensionsDataProvider.FormatSpannableNullStringWithoutSpansTestData), - MemberType = typeof(StringExtensionsDataProvider))] - public void FormatSpannable_WhenCalledOnNullString_WithAnyTextRange_WithoutSpans_ThrowsCorrectException( - TextRange textRange) - { - var str = null as string; - Assert.Throws(() => str!.FormatSpannable(textRange)); - } - - [Theory] - [MemberData( - nameof(StringExtensionsDataProvider.FormatSpannableNullStringWithSpansOnlyTestData), - MemberType = typeof(StringExtensionsDataProvider))] - public void FormatSpannable_WhenCalledOnNullString_WithoutTextRange_WithSpans_ThrowsCorrectException( - Object[] spans) - { - var str = null as string; - Assert.Throws(() => str!.FormatSpannable(spans)); - } - - [Fact] - public void FormatSpannable_WhenCalledOnNullString_WithoutTextRange_WithoutSpans_ThrowsCorrectException() - { - var str = null as string; - Assert.Throws(() => str!.FormatSpannable()); - } - - #endregion - - [Theory] - [MemberData( - nameof(StringExtensionsDataProvider.FormatSpannableStringsTestData), - MemberType = typeof(StringExtensionsDataProvider))] - public void FormatSpannable_WhenCalledOnCorrectString_WithNullTextRange_ThrowsCorrectException( - string str) - { - var spans = new Object[] { }; - Assert.Throws(() => str.FormatSpannable(StringExtensionsDataProvider.NullTextRange, spans)); - } - - [Theory] - [MemberData( - nameof(StringExtensionsDataProvider.FormatSpannableWithNonNullTextRangeWithoutSpansTestData), - MemberType = typeof(StringExtensionsDataProvider))] - public void FormatSpannable_WhenCalledOnCorrectString_WithNonNullTextRange_WithoutSpans_ThrowsCorrectException( - string str, TextRange textRange) - { - var spans = new Object[] { }; - Assert.Throws(() => str.FormatSpannable(textRange, spans)); - } - - [Theory] - [MemberData( - nameof(StringExtensionsDataProvider.FormatSpannableWithIncorrectTextRangeWithSpansTestData), - MemberType = typeof(StringExtensionsDataProvider))] - public void FormatSpannable_WhenCalledOnCorrectString_WithIncorrectTextRange_WithSpans_ThrowsCorrectException( - string str, TextRange textRange, Object[] spans) - { - Assert.Throws(() => str.FormatSpannable(textRange, spans)); - } - - [Theory] - [MemberData( - nameof(StringExtensionsDataProvider.FormatSpannableWithCorrectTextRangeWithStyleSpansTestData), - MemberType = typeof(StringExtensionsDataProvider))] - public void FormatSpannable_WhenCalledOnCorrectString_WithCorrectTextRange_WithStyleSpans_AppliesSpans( - string str, TextRange textRange, Object[] spans) - { - var spannable = str.FormatSpannable(textRange, spans); - var appliedSpans = GetSpans(spannable, textRange); - - AssertAppliedSpans(spans, appliedSpans); - AssertNoSpansOutsideSpecifiedIntervalApplied(spannable, textRange); - } - - [Theory] - [MemberData( - nameof(StringExtensionsDataProvider.FormatSpannableWithCorrectTextRangeWithForegroundColorSpansTestData), - MemberType = typeof(StringExtensionsDataProvider))] - public void FormatSpannable_WhenCalledOnCorrectString_WithCorrectTextRange_WithForegroundColorSpans_AppliesSpans( - string str, TextRange textRange, Object[] spans) - { - var spannable = str.FormatSpannable(textRange, spans); - var appliedSpans = GetSpans(spannable, textRange); - - AssertAppliedSpans(spans, appliedSpans); - AssertNoSpansOutsideSpecifiedIntervalApplied(spannable, textRange); - } - - [Theory] - [MemberData( - nameof(StringExtensionsDataProvider.FormatSpannableWithCorrectTextRangeWithDifferentSpansTestData), - MemberType = typeof(StringExtensionsDataProvider))] - public void FormatSpannable_WhenCalledOnCorrectString_WithCorrectTextRange_WithDifferentSpans_AppliesSpans( - string str, TextRange textRange, Object[] spans) - { - var spannable = str.FormatSpannable(textRange, spans); - var appliedStyleSpans = GetSpans(spannable, textRange); - var appliedForegroundColorSpans = GetSpans(spannable, textRange); - var appliedSpans = appliedStyleSpans.Concat(appliedForegroundColorSpans).ToArray(); - - AssertAppliedSpans(spans, appliedSpans); - AssertNoSpansOutsideSpecifiedIntervalApplied(spannable, textRange); - AssertNoSpansOutsideSpecifiedIntervalApplied(spannable, textRange); - } - - [Theory] - [MemberData( - nameof(StringExtensionsDataProvider.FormatSpannableWithoutTextRangeWithStyleSpansTestData), - MemberType = typeof(StringExtensionsDataProvider))] - public void FormatSpannable_WhenCalledOnCorrectString_WithoutTextRange_WithStyleSpans_AppliesSpans( - string str, Object[] spans) - { - var spannable = str.FormatSpannable(spans); - var appliedSpans = GetSpans(spannable, new TextRange(0, str.Length)); - - AssertAppliedSpans(spans, appliedSpans); - } - - [Theory] - [MemberData( - nameof(StringExtensionsDataProvider.FormatSpannableWithoutTextRangeWithForegroundColorSpansTestData), - MemberType = typeof(StringExtensionsDataProvider))] - public void FormatSpannable_WhenCalledOnCorrectString_WithoutTextRange_WithForegroundColorSpans_AppliesSpans( - string str, Object[] spans) - { - var spannable = str.FormatSpannable(spans); - var appliedSpans = GetSpans(spannable, new TextRange(0, str.Length)); - - AssertAppliedSpans(spans, appliedSpans); - } - - [Theory] - [MemberData( - nameof(StringExtensionsDataProvider.FormatSpannableWithoutTextRangeWithDifferentSpansTestData), - MemberType = typeof(StringExtensionsDataProvider))] - public void FormatSpannable_WhenCalledOnCorrectString_WithoutTextRange_WithDifferentSpans_AppliesSpans( - string str, Object[] spans) - { - var spannable = str.FormatSpannable(spans); - var appliedStyleSpans = GetSpans(spannable, new TextRange(0, str.Length)); - var appliedForegroundColorSpans = GetSpans(spannable, new TextRange(0, str.Length)); - var appliedSpans = appliedStyleSpans.Concat(appliedForegroundColorSpans).ToArray(); - - AssertAppliedSpans(spans, appliedSpans); - } - - [Theory] - [MemberData( - nameof(StringExtensionsDataProvider.FormatSpannableStringsTestData), - MemberType = typeof(StringExtensionsDataProvider))] - public void FormatSpannable_WhenCalledOnCorrectString_WithoutTextRange_WithoutSpans_ThrowsCorrectException( - string str) - { - Assert.Throws(() => str.FormatSpannable()); - } - - private void AssertAppliedSpans(Object[] spans, Object[] appliedSpans) - { - var nonNullSpans = spans.Where(t => t != null).Distinct(); - var nonNullAppliedSpans = appliedSpans.Where(t => t != null); - - Assert.Equal(nonNullSpans.Count(), nonNullAppliedSpans.Count()); - foreach (var span in nonNullSpans) - { - Assert.Contains(span, appliedSpans); - } - - foreach (var appliedSpan in nonNullAppliedSpans) - { - Assert.Contains(appliedSpan, spans); - } - } - - private void AssertNoSpansOutsideSpecifiedIntervalApplied(SpannableString? spannable, TextRange textRange) - { - int spannableLength = spannable?.Length() ?? 0; - if (textRange.Position > 0) - { - var spansBefore = GetSpans(spannable, new TextRange(0, textRange.Position - 1)); - Assert.Empty(spansBefore); - } - - if (textRange.Position + textRange.Length < spannableLength - 1) - { - var spansAfter = GetSpans(spannable, new TextRange(textRange.Position + textRange.Length + 1, spannableLength - 1)); - Assert.Empty(spansAfter); - } - } - - private Object[] GetSpans(SpannableString? spannable, TextRange textRange) - { - return spannable?.GetSpans( - textRange.Position, - textRange.Position + textRange.Length, - Java.Lang.Class.FromType(typeof(T))) ?? new Object[] { }; - } - } -} diff --git a/Softeq.XToolkit.Common.Droid.Tests/Helpers.cs b/Softeq.XToolkit.Common.Droid.Tests/Helpers.cs deleted file mode 100644 index b4c3f22c0..000000000 --- a/Softeq.XToolkit.Common.Droid.Tests/Helpers.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System; -using System.Threading.Tasks; - -namespace Softeq.XToolkit.Common.Droid.Tests -{ - public static class Helpers - { - public static Task RunOnUIThreadAsync(Func func) - { - var tcs = new TaskCompletionSource(); - MainActivity.Current.RunOnUiThread(() => - { - try - { - var result = func(); - tcs.SetResult(result); - } - catch (Exception e) - { - tcs.SetException(e); - } - }); - return tcs.Task; - } - } -} diff --git a/Softeq.XToolkit.Common.Droid.Tests/MainActivity.cs b/Softeq.XToolkit.Common.Droid.Tests/MainActivity.cs deleted file mode 100644 index be02fddde..000000000 --- a/Softeq.XToolkit.Common.Droid.Tests/MainActivity.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Reflection; -using Android.App; -using Android.OS; -using Xunit.Runners.UI; -using Xunit.Sdk; - -namespace Softeq.XToolkit.Common.Droid.Tests -{ - [Activity(MainLauncher = true, Theme = "@android:style/Theme.Material.Light")] - public class MainActivity : RunnerActivity - { - public static MainActivity Current { get; private set; } = null!; - - protected override void OnCreate(Bundle bundle) - { - Current = this; - - // tests can be inside the main assembly - AddTestAssembly(Assembly.GetExecutingAssembly()); - - AddExecutionAssembly(typeof(ExtensibilityPointFactory).Assembly); - - // or in any reference assemblies - // AddTestAssembly(typeof(PortableTests).Assembly); - // or in any assembly that you load (since JIT is available) - -#if false - // you can use the default or set your own custom writer (e.g. save to web site and tweet it ;-) - Writer = new TcpTextWriter ("10.0.1.2", 16384); - // start running the test suites as soon as the application is loaded - AutoStart = true; - // crash the application (to ensure it's ended) and return to springboard - TerminateAfterExecution = true; -#endif - - // you cannot add more assemblies once calling base - base.OnCreate(bundle); - } - } -} diff --git a/Softeq.XToolkit.Common.Droid.Tests/MauiProgram.cs b/Softeq.XToolkit.Common.Droid.Tests/MauiProgram.cs new file mode 100644 index 000000000..cedd72d84 --- /dev/null +++ b/Softeq.XToolkit.Common.Droid.Tests/MauiProgram.cs @@ -0,0 +1,31 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using Microsoft.Extensions.Logging; +using Microsoft.Maui.Hosting; +using Xunit.Runners.Maui; + +namespace Softeq.XToolkit.Common.Droid.Tests; + +public static class MauiProgram +{ + public static MauiApp CreateMauiApp() + { + var builder = MauiApp + .CreateBuilder() + .ConfigureTests(new TestOptions + { + Assemblies = + { + typeof(MauiProgram).Assembly + } + }) + .UseVisualRunner(); + +#if DEBUG + builder.Logging.AddDebug(); +#endif + + return builder.Build(); + } +} diff --git a/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/AndroidManifest.xml b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/AndroidManifest.xml new file mode 100644 index 000000000..e9937ad77 --- /dev/null +++ b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Converters/VisibilityConverterTests.cs b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Converters/VisibilityConverterTests.cs new file mode 100644 index 000000000..be423e820 --- /dev/null +++ b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Converters/VisibilityConverterTests.cs @@ -0,0 +1,79 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using Android.Views; +using Softeq.XToolkit.Common.Droid.Converters; +using Xunit; + +namespace Softeq.XToolkit.Common.Droid.Tests.Converters; + +public class VisibilityConverterTests +{ + [Fact] + public void Gone_ReturnsNotNull() + { + Assert.NotNull(VisibilityConverter.Gone); + } + + [Fact] + public void Invisible_ReturnsNotNull() + { + Assert.NotNull(VisibilityConverter.Invisible); + } + + [Fact] + public void Instance_ReturnsInvisible() + { + Assert.Same(VisibilityConverter.Invisible, VisibilityConverter.Instance); + } + + [Theory] + [InlineData(true, ViewStates.Visible)] + [InlineData(false, ViewStates.Gone)] + public void ConvertValue_Gone_ReturnsExpectedValue(bool value, ViewStates expectedResult) + { + var converter = VisibilityConverter.Gone; + + var result = converter.ConvertValue(value); + + Assert.Equal(expectedResult, result); + } + + [Theory] + [InlineData(true, ViewStates.Visible)] + [InlineData(false, ViewStates.Invisible)] + public void ConvertValue_Invisible_ReturnsExpectedValue(bool value, ViewStates expectedResult) + { + var converter = VisibilityConverter.Invisible; + + var result = converter.ConvertValue(value); + + Assert.Equal(expectedResult, result); + } + + [Theory] + [InlineData(ViewStates.Visible, true)] + [InlineData(ViewStates.Invisible, false)] + [InlineData(ViewStates.Gone, false)] + public void ConvertValueBack_Gone_ReturnsExpectedValue(ViewStates value, bool expectedResult) + { + var converter = VisibilityConverter.Gone; + + var result = converter.ConvertValueBack(value); + + Assert.Equal(expectedResult, result); + } + + [Theory] + [InlineData(ViewStates.Visible, true)] + [InlineData(ViewStates.Invisible, false)] + [InlineData(ViewStates.Gone, false)] + public void ConvertValueBack_Invisible_ReturnsExpectedValue(ViewStates value, bool expectedResult) + { + var converter = VisibilityConverter.Invisible; + + var result = converter.ConvertValueBack(value); + + Assert.Equal(expectedResult, result); + } +} diff --git a/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/DroidMainThreadExecutorTests/DroidMainThreadExecutorTests.cs b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/DroidMainThreadExecutorTests/DroidMainThreadExecutorTests.cs new file mode 100644 index 000000000..c2710f329 --- /dev/null +++ b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/DroidMainThreadExecutorTests/DroidMainThreadExecutorTests.cs @@ -0,0 +1,33 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System.Threading.Tasks; +using Xunit; + +namespace Softeq.XToolkit.Common.Droid.Tests.DroidMainThreadExecutorTests; + +public class DroidMainThreadExecutorTests +{ + private readonly DroidMainThreadExecutor _executor; + + public DroidMainThreadExecutorTests() + { + _executor = new DroidMainThreadExecutor(); + } + + [Fact] + public async Task IsMainThread_InMainThread_ReturnsTrue() + { + var result = await Helpers.RunOnUIThreadAsync(() => _executor.IsMainThread); + + Assert.True(result); + } + + [Fact] + public async Task IsMainThread_NotInMainThread_ReturnsFalse() + { + var result = await Task.Run(() => _executor.IsMainThread); + + Assert.False(result); + } +} diff --git a/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Extensions/ContextExtensionsTests/ContextExtensionsTests.cs b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Extensions/ContextExtensionsTests/ContextExtensionsTests.cs new file mode 100644 index 000000000..adfeb0d9c --- /dev/null +++ b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Extensions/ContextExtensionsTests/ContextExtensionsTests.cs @@ -0,0 +1,33 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using Android.Content; +using Softeq.XToolkit.Common.Droid.Extensions; +using Xunit; + +namespace Softeq.XToolkit.Common.Droid.Tests.Extensions.ContextExtensionsTests; + +public class ContextExtensionsTests +{ + private readonly Context _context = MainActivity.Current; + + [Theory] + [InlineData(-1)] + [InlineData(0)] + [InlineData(10)] + public void PxToDp_DpToPx_Equivalence(double pixels) + { + var resultDp = _context.PxToDp(pixels); + var resultPx = _context.DpToPx(resultDp); + + Assert.Equal(pixels, resultPx); + } + + [Fact] + public void GetStatusBarHeight_Default_PositiveValue() + { + var result = _context.GetStatusBarHeight(); + + Assert.True(result > 0); + } +} diff --git a/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Extensions/EditTextExtensionsTests/EditTextExtensionsTests.cs b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Extensions/EditTextExtensionsTests/EditTextExtensionsTests.cs new file mode 100644 index 000000000..cd5f9d1af --- /dev/null +++ b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Extensions/EditTextExtensionsTests/EditTextExtensionsTests.cs @@ -0,0 +1,93 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using Android.Text; +using Android.Widget; +using Softeq.XToolkit.Common.Droid.Extensions; +using Xunit; + +namespace Softeq.XToolkit.Common.Droid.Tests.Extensions.EditTextExtensionsTests; + +public class EditTextExtensionsTests +{ + [Fact] + public void SetFilters_WhenCalledWithoutFilters_DoNothing() + { + EditText editText = new EditText(MainActivity.Current); + editText.SetFilters(); + + var appliedFilters = editText.GetFilters(); + if (appliedFilters != null) + { + Assert.Empty(appliedFilters); + } + } + + [Fact] + public void SetFilters_WhenCalledWithMultipleFilters_AppliesAllFilters() + { + EditText editText = new EditText(MainActivity.Current); + + var filter1 = new MockInputFilter(); + var filter2 = new MockInputFilter(); + var filter3 = new MockInputFilter(); + var filters = new IInputFilter[] { filter1, filter2, filter3 }; + + editText.SetFilters(filter1, filter2, filter3); + + var appliedFilters = editText.GetFilters(); + Assert.Equal(filters, appliedFilters); + } + + [Fact] + public void SetFilters_WhenCalledWithMultipleFiltersWithNullAndDuplicates_AppliesAllFilters() + { + EditText editText = new EditText(MainActivity.Current); + + var filter1 = new MockInputFilter(); + var filter2 = new MockInputFilter(); + var filter3 = new MockInputFilter(); + var nullFilter = null as IInputFilter; + var filters = new[] { filter1, filter2, nullFilter, filter3, filter2, nullFilter, filter1, filter2 }; + + editText.SetFilters(filter1, filter2, nullFilter!, filter3, filter2, nullFilter!, filter1, filter2); + + var appliedFilters = editText.GetFilters(); + Assert.Equal(filters, appliedFilters); + } + + [Theory] + [InlineData("")] + [InlineData("a")] + [InlineData("abcd")] + [InlineData("abcdefg")] + public void KeepFocusAtTheEndOfField_WhenTextChangedWithoutFocus_WhenFocused_MovesCursorToTheEndOfField(string str) + { + EditText editText = new EditText(MainActivity.Current); + editText.ClearFocus(); + + editText.Text = str; + editText.RequestFocus(); + + Assert.Equal(editText.SelectionEnd, editText.SelectionStart); + Assert.Equal(editText.SelectionStart, str.Length); + } + + [Theory] + [InlineData("")] + [InlineData("a")] + [InlineData("abcd")] + [InlineData("abcdefg")] + public void KeepFocusAtTheEndOfField_WhenTextChangedWithFocus_WhenRefocused_MovesCursorToTheEndOfField(string str) + { + EditText editText = new EditText(MainActivity.Current); + editText.RequestFocus(); + + editText.Text = str; + editText.ClearFocus(); + editText.RequestFocus(); + + Assert.Equal(editText.SelectionEnd, editText.SelectionStart); + Assert.Equal(editText.SelectionStart, str.Length); + } +} diff --git a/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Extensions/EditTextExtensionsTests/MockInputFilter.cs b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Extensions/EditTextExtensionsTests/MockInputFilter.cs new file mode 100644 index 000000000..b2a5172f4 --- /dev/null +++ b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Extensions/EditTextExtensionsTests/MockInputFilter.cs @@ -0,0 +1,16 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using Android.Text; +using Java.Lang; +using JObject = Java.Lang.Object; + +namespace Softeq.XToolkit.Common.Droid.Tests.Extensions.EditTextExtensionsTests; + +public class MockInputFilter : JObject, IInputFilter +{ + public ICharSequence? FilterFormatted(ICharSequence? source, int start, int end, ISpanned? dest, int dstart, int dend) + { + return null; + } +} diff --git a/Softeq.XToolkit.Common.Droid.Tests/Extensions/StringExtensionsTests/StringExtensionsDataProvider.cs b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Extensions/StringExtensionsTests/StringExtensionsDataProvider.cs similarity index 100% rename from Softeq.XToolkit.Common.Droid.Tests/Extensions/StringExtensionsTests/StringExtensionsDataProvider.cs rename to Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Extensions/StringExtensionsTests/StringExtensionsDataProvider.cs diff --git a/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Extensions/StringExtensionsTests/StringExtensionsTests.cs b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Extensions/StringExtensionsTests/StringExtensionsTests.cs new file mode 100644 index 000000000..ffc163908 --- /dev/null +++ b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Extensions/StringExtensionsTests/StringExtensionsTests.cs @@ -0,0 +1,229 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using System.Linq; +using Android.Text; +using Android.Text.Style; +using Softeq.XToolkit.Common.Droid.Extensions; +using Softeq.XToolkit.Common.Helpers; +using Xunit; +using Object = Java.Lang.Object; + +namespace Softeq.XToolkit.Common.Droid.Tests.Extensions.StringExtensionsTests; + +public class StringExtensionsTests +{ + #region Null string + + [Theory] + [MemberData( + nameof(StringExtensionsDataProvider.FormatSpannableNullStringWithRangeAndSpansTestData), + MemberType = typeof(StringExtensionsDataProvider))] + public void FormatSpannable_WhenCalledOnNullString_WithAnyTextRange_WithSpans_ThrowsCorrectException( + TextRange textRange, Object[] spans) + { + var str = null as string; + Assert.Throws(() => str!.FormatSpannable(textRange, spans)); + } + + [Theory] + [MemberData( + nameof(StringExtensionsDataProvider.FormatSpannableNullStringWithoutSpansTestData), + MemberType = typeof(StringExtensionsDataProvider))] + public void FormatSpannable_WhenCalledOnNullString_WithAnyTextRange_WithoutSpans_ThrowsCorrectException( + TextRange textRange) + { + var str = null as string; + Assert.Throws(() => str!.FormatSpannable(textRange)); + } + + [Theory] + [MemberData( + nameof(StringExtensionsDataProvider.FormatSpannableNullStringWithSpansOnlyTestData), + MemberType = typeof(StringExtensionsDataProvider))] + public void FormatSpannable_WhenCalledOnNullString_WithoutTextRange_WithSpans_ThrowsCorrectException( + Object[] spans) + { + var str = null as string; + Assert.Throws(() => str!.FormatSpannable(spans)); + } + + [Fact] + public void FormatSpannable_WhenCalledOnNullString_WithoutTextRange_WithoutSpans_ThrowsCorrectException() + { + var str = null as string; + Assert.Throws(() => str!.FormatSpannable()); + } + + #endregion + + [Theory] + [MemberData( + nameof(StringExtensionsDataProvider.FormatSpannableStringsTestData), + MemberType = typeof(StringExtensionsDataProvider))] + public void FormatSpannable_WhenCalledOnCorrectString_WithNullTextRange_ThrowsCorrectException( + string str) + { + var spans = new Object[] { }; + Assert.Throws(() => str.FormatSpannable(StringExtensionsDataProvider.NullTextRange, spans)); + } + + [Theory] + [MemberData( + nameof(StringExtensionsDataProvider.FormatSpannableWithNonNullTextRangeWithoutSpansTestData), + MemberType = typeof(StringExtensionsDataProvider))] + public void FormatSpannable_WhenCalledOnCorrectString_WithNonNullTextRange_WithoutSpans_ThrowsCorrectException( + string str, TextRange textRange) + { + var spans = new Object[] { }; + Assert.Throws(() => str.FormatSpannable(textRange, spans)); + } + + [Theory] + [MemberData( + nameof(StringExtensionsDataProvider.FormatSpannableWithIncorrectTextRangeWithSpansTestData), + MemberType = typeof(StringExtensionsDataProvider))] + public void FormatSpannable_WhenCalledOnCorrectString_WithIncorrectTextRange_WithSpans_ThrowsCorrectException( + string str, TextRange textRange, Object[] spans) + { + Assert.Throws(() => str.FormatSpannable(textRange, spans)); + } + + [Theory] + [MemberData( + nameof(StringExtensionsDataProvider.FormatSpannableWithCorrectTextRangeWithStyleSpansTestData), + MemberType = typeof(StringExtensionsDataProvider))] + public void FormatSpannable_WhenCalledOnCorrectString_WithCorrectTextRange_WithStyleSpans_AppliesSpans( + string str, TextRange textRange, Object[] spans) + { + var spannable = str.FormatSpannable(textRange, spans); + var appliedSpans = GetSpans(spannable, textRange); + + AssertAppliedSpans(spans, appliedSpans); + AssertNoSpansOutsideSpecifiedIntervalApplied(spannable, textRange); + } + + [Theory] + [MemberData( + nameof(StringExtensionsDataProvider.FormatSpannableWithCorrectTextRangeWithForegroundColorSpansTestData), + MemberType = typeof(StringExtensionsDataProvider))] + public void FormatSpannable_WhenCalledOnCorrectString_WithCorrectTextRange_WithForegroundColorSpans_AppliesSpans( + string str, TextRange textRange, Object[] spans) + { + var spannable = str.FormatSpannable(textRange, spans); + var appliedSpans = GetSpans(spannable, textRange); + + AssertAppliedSpans(spans, appliedSpans); + AssertNoSpansOutsideSpecifiedIntervalApplied(spannable, textRange); + } + + [Theory] + [MemberData( + nameof(StringExtensionsDataProvider.FormatSpannableWithCorrectTextRangeWithDifferentSpansTestData), + MemberType = typeof(StringExtensionsDataProvider))] + public void FormatSpannable_WhenCalledOnCorrectString_WithCorrectTextRange_WithDifferentSpans_AppliesSpans( + string str, TextRange textRange, Object[] spans) + { + var spannable = str.FormatSpannable(textRange, spans); + var appliedStyleSpans = GetSpans(spannable, textRange); + var appliedForegroundColorSpans = GetSpans(spannable, textRange); + var appliedSpans = appliedStyleSpans.Concat(appliedForegroundColorSpans).ToArray(); + + AssertAppliedSpans(spans, appliedSpans); + AssertNoSpansOutsideSpecifiedIntervalApplied(spannable, textRange); + AssertNoSpansOutsideSpecifiedIntervalApplied(spannable, textRange); + } + + [Theory] + [MemberData( + nameof(StringExtensionsDataProvider.FormatSpannableWithoutTextRangeWithStyleSpansTestData), + MemberType = typeof(StringExtensionsDataProvider))] + public void FormatSpannable_WhenCalledOnCorrectString_WithoutTextRange_WithStyleSpans_AppliesSpans( + string str, Object[] spans) + { + var spannable = str.FormatSpannable(spans); + var appliedSpans = GetSpans(spannable, new TextRange(0, str.Length)); + + AssertAppliedSpans(spans, appliedSpans); + } + + [Theory] + [MemberData( + nameof(StringExtensionsDataProvider.FormatSpannableWithoutTextRangeWithForegroundColorSpansTestData), + MemberType = typeof(StringExtensionsDataProvider))] + public void FormatSpannable_WhenCalledOnCorrectString_WithoutTextRange_WithForegroundColorSpans_AppliesSpans( + string str, Object[] spans) + { + var spannable = str.FormatSpannable(spans); + var appliedSpans = GetSpans(spannable, new TextRange(0, str.Length)); + + AssertAppliedSpans(spans, appliedSpans); + } + + [Theory] + [MemberData( + nameof(StringExtensionsDataProvider.FormatSpannableWithoutTextRangeWithDifferentSpansTestData), + MemberType = typeof(StringExtensionsDataProvider))] + public void FormatSpannable_WhenCalledOnCorrectString_WithoutTextRange_WithDifferentSpans_AppliesSpans( + string str, Object[] spans) + { + var spannable = str.FormatSpannable(spans); + var appliedStyleSpans = GetSpans(spannable, new TextRange(0, str.Length)); + var appliedForegroundColorSpans = GetSpans(spannable, new TextRange(0, str.Length)); + var appliedSpans = appliedStyleSpans.Concat(appliedForegroundColorSpans).ToArray(); + + AssertAppliedSpans(spans, appliedSpans); + } + + [Theory] + [MemberData( + nameof(StringExtensionsDataProvider.FormatSpannableStringsTestData), + MemberType = typeof(StringExtensionsDataProvider))] + public void FormatSpannable_WhenCalledOnCorrectString_WithoutTextRange_WithoutSpans_ThrowsCorrectException( + string str) + { + Assert.Throws(() => str.FormatSpannable()); + } + + private void AssertAppliedSpans(Object[] spans, Object[] appliedSpans) + { + var nonNullSpans = spans.Where(t => t != null).Distinct(); + var nonNullAppliedSpans = appliedSpans.Where(t => t != null); + + Assert.Equal(nonNullSpans.Count(), nonNullAppliedSpans.Count()); + foreach (var span in nonNullSpans) + { + Assert.Contains(span, appliedSpans); + } + + foreach (var appliedSpan in nonNullAppliedSpans) + { + Assert.Contains(appliedSpan, spans); + } + } + + private void AssertNoSpansOutsideSpecifiedIntervalApplied(SpannableString? spannable, TextRange textRange) + { + int spannableLength = spannable?.Length() ?? 0; + if (textRange.Position > 0) + { + var spansBefore = GetSpans(spannable, new TextRange(0, textRange.Position - 1)); + Assert.Empty(spansBefore); + } + + if (textRange.Position + textRange.Length < spannableLength - 1) + { + var spansAfter = GetSpans(spannable, new TextRange(textRange.Position + textRange.Length + 1, spannableLength - 1)); + Assert.Empty(spansAfter); + } + } + + private Object[] GetSpans(SpannableString? spannable, TextRange textRange) + { + return spannable?.GetSpans( + textRange.Position, + textRange.Position + textRange.Length, + Java.Lang.Class.FromType(typeof(T))) ?? new Object[] { }; + } +} diff --git a/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Helpers.cs b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Helpers.cs new file mode 100644 index 000000000..71454ce38 --- /dev/null +++ b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Helpers.cs @@ -0,0 +1,28 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using System.Threading.Tasks; + +namespace Softeq.XToolkit.Common.Droid.Tests; + +public static class Helpers +{ + public static Task RunOnUIThreadAsync(Func func) + { + var tcs = new TaskCompletionSource(); + MainActivity.Current.RunOnUiThread(() => + { + try + { + var result = func(); + tcs.SetResult(result); + } + catch (Exception e) + { + tcs.SetException(e); + } + }); + return tcs.Task; + } +} diff --git a/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/MainActivity.cs b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/MainActivity.cs new file mode 100644 index 000000000..0e8b5aa69 --- /dev/null +++ b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/MainActivity.cs @@ -0,0 +1,24 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using Android.App; +using Android.Content.PM; +using Android.OS; +using Microsoft.Maui; + +namespace Softeq.XToolkit.Common.Droid.Tests; + +[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, + ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | + ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)] +public class MainActivity : MauiAppCompatActivity +{ + public static MainActivity Current { get; private set; } = null!; + + protected override void OnCreate(Bundle? savedInstanceState) + { + Current = this; + + base.OnCreate(savedInstanceState); + } +} diff --git a/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/MainApplication.cs b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/MainApplication.cs new file mode 100644 index 000000000..9cfc2866d --- /dev/null +++ b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/MainApplication.cs @@ -0,0 +1,21 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using Android.App; +using Android.Runtime; +using Microsoft.Maui; +using Microsoft.Maui.Hosting; + +namespace Softeq.XToolkit.Common.Droid.Tests; + +[Application] +public class MainApplication : MauiApplication +{ + public MainApplication(IntPtr handle, JniHandleOwnership ownership) + : base(handle, ownership) + { + } + + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); +} diff --git a/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Resources/values/colors.xml b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Resources/values/colors.xml new file mode 100644 index 000000000..c04d7492a --- /dev/null +++ b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/Resources/values/colors.xml @@ -0,0 +1,6 @@ + + + #512BD4 + #2B0B98 + #2B0B98 + \ No newline at end of file diff --git a/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/TextFilters/ForbiddenCharsInputFilterTests.cs b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/TextFilters/ForbiddenCharsInputFilterTests.cs new file mode 100644 index 000000000..66d30da35 --- /dev/null +++ b/Softeq.XToolkit.Common.Droid.Tests/Platforms/Android/TextFilters/ForbiddenCharsInputFilterTests.cs @@ -0,0 +1,101 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using Android.Text; +using Softeq.XToolkit.Common.Droid.TextFilters; +using Xunit; +using JString = Java.Lang.String; + +namespace Softeq.XToolkit.Common.Droid.Tests.TextFilters; + +public class ForbiddenCharsInputFilterTests +{ + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("a")] + [InlineData("aaa")] + [InlineData("aA%@2-")] + public void Ctor_WhenCalledWithAnyChars_CreatesFilter(string? chars) + { + var obj = new ForbiddenCharsInputFilter(chars?.ToCharArray() ?? null!); + + Assert.IsAssignableFrom(obj); + } + + [Theory] + [PairwiseData] + public void FilterFormatted_ForFilterWithNullChars_ReturnsNull( + [CombinatorialValues(null, "", "b", "bbc", "+3b^ gb^")] string sourceStr, + [CombinatorialValues(-1, 0, 1, 3)] int start, + [CombinatorialValues(-1, 0, 1, 3)] int end, + [CombinatorialValues(null, "", "a", "abc")] string? destStr, + [CombinatorialValues(-1, 0, 1, 3)] int dstart, + [CombinatorialValues(-1, 0, 1, 3)] int dend) + { + var source = new JString(sourceStr); + var dest = destStr == null ? null : new SpannableString(destStr); + var filter = new ForbiddenCharsInputFilter(null!); + + var result = filter.FilterFormatted(source, start, end, dest, dstart, dend); + + Assert.Null(result); + } + + [Theory] + [PairwiseData] + public void FilterFormatted_ForFilterWithNonNullChars_WhenCalledWithNullSource_ReturnsNull( + [CombinatorialValues("", "a", "aaA%@2-")] string chars, + [CombinatorialValues(-1, 0, 1, 3)] int start, + [CombinatorialValues(-1, 0, 1, 3)] int end, + [CombinatorialValues(null, "", "a", "abc")] string? destStr, + [CombinatorialValues(-1, 0, 1, 3)] int dstart, + [CombinatorialValues(-1, 0, 1, 3)] int dend) + { + var dest = destStr == null ? null : new SpannableString(destStr); + var filter = new ForbiddenCharsInputFilter(chars.ToCharArray()); + + var result = filter.FilterFormatted(null, start, end, dest, dstart, dend); + + Assert.Null(result); + } + + [Theory] + [PairwiseData] + public void FilterFormatted_ForFilterWithNonNullChars_WhenCalledWithNonNullSource_IfSourceDoesNotContainForbiddenChars_ReturnsNull( + [CombinatorialValues("", "a", "aaA%@2-")] string chars, + [CombinatorialValues("", "b", "bbc", "+3b^ gb^")] string sourceStr, + [CombinatorialValues(-1, 0, 1, 3)] int start, + [CombinatorialValues(-1, 0, 1, 3)] int end, + [CombinatorialValues(null, "", "a", "abc")] string? destStr, + [CombinatorialValues(-1, 0, 1, 3)] int dstart, + [CombinatorialValues(-1, 0, 1, 3)] int dend) + { + var source = new JString(sourceStr); + var dest = destStr == null ? null : new SpannableString(destStr); + var filter = new ForbiddenCharsInputFilter(chars.ToCharArray()); + + var result = filter.FilterFormatted(source, start, end, dest, dstart, dend); + + Assert.Null(result); + } + + [Theory] + [PairwiseData] + public void FilterFormatted_ForFilterWithNonEmptyChars_WhenCalledWithNonNullSource_IfSourceContainForbiddenChars_ReturnsEmptyString( + [CombinatorialValues("a", "aaabc", "%klp", "jd2ye")] string sourceStr, + [CombinatorialValues(-1, 0, 1, 3)] int start, + [CombinatorialValues(-1, 0, 1, 3)] int end, + [CombinatorialValues(null, "", "a", "abc")] string? destStr, + [CombinatorialValues(-1, 0, 1, 3)] int dstart, + [CombinatorialValues(-1, 0, 1, 3)] int dend) + { + var source = new JString(sourceStr); + var dest = destStr == null ? null : new SpannableString(destStr); + var filter = new ForbiddenCharsInputFilter("aaAA%@2@-".ToCharArray()); + + var result = filter.FilterFormatted(source, start, end, dest, dstart, dend); + + Assert.Empty(result!); + } +} diff --git a/Softeq.XToolkit.Common.Droid.Tests/Properties/AndroidManifest.xml b/Softeq.XToolkit.Common.Droid.Tests/Properties/AndroidManifest.xml deleted file mode 100644 index af6610136..000000000 --- a/Softeq.XToolkit.Common.Droid.Tests/Properties/AndroidManifest.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/Softeq.XToolkit.Common.Droid.Tests/Properties/AssemblyInfo.cs b/Softeq.XToolkit.Common.Droid.Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index a759c959e..000000000 --- a/Softeq.XToolkit.Common.Droid.Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Softeq.XToolkit.Common.Droid.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Softeq Development Corporation")] -[assembly: AssemblyProduct("XToolkit.Common")] -[assembly: AssemblyCopyright("Copyright © Softeq Development Corporation 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Softeq.XToolkit.Common.Droid.Tests/Properties/launchSettings.json b/Softeq.XToolkit.Common.Droid.Tests/Properties/launchSettings.json new file mode 100644 index 000000000..edf8aadcc --- /dev/null +++ b/Softeq.XToolkit.Common.Droid.Tests/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Windows Machine": { + "commandName": "MsixPackage", + "nativeDebugging": false + } + } +} \ No newline at end of file diff --git a/Softeq.XToolkit.Common.Droid.Tests/Resources/AppIcon/appicon.svg b/Softeq.XToolkit.Common.Droid.Tests/Resources/AppIcon/appicon.svg new file mode 100644 index 000000000..9d63b6513 --- /dev/null +++ b/Softeq.XToolkit.Common.Droid.Tests/Resources/AppIcon/appicon.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Softeq.XToolkit.Common.Droid.Tests/Resources/AppIcon/appiconfg.svg b/Softeq.XToolkit.Common.Droid.Tests/Resources/AppIcon/appiconfg.svg new file mode 100644 index 000000000..21dfb25f1 --- /dev/null +++ b/Softeq.XToolkit.Common.Droid.Tests/Resources/AppIcon/appiconfg.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Softeq.XToolkit.Common.Droid.Tests/Resources/Raw/AboutAssets.txt b/Softeq.XToolkit.Common.Droid.Tests/Resources/Raw/AboutAssets.txt new file mode 100644 index 000000000..15d624484 --- /dev/null +++ b/Softeq.XToolkit.Common.Droid.Tests/Resources/Raw/AboutAssets.txt @@ -0,0 +1,15 @@ +Any raw assets you want to be deployed with your application can be placed in +this directory (and child directories). Deployment of the asset to your application +is automatically handled by the following `MauiAsset` Build Action within your `.csproj`. + + + +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/Softeq.XToolkit.Common.Droid.Tests/Resources/Splash/splash.svg b/Softeq.XToolkit.Common.Droid.Tests/Resources/Splash/splash.svg new file mode 100644 index 000000000..21dfb25f1 --- /dev/null +++ b/Softeq.XToolkit.Common.Droid.Tests/Resources/Splash/splash.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Softeq.XToolkit.Common.Droid.Tests/Resources/mipmap-mdpi/ic_launcher.png b/Softeq.XToolkit.Common.Droid.Tests/Resources/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 795ea7c005880b08ffe2ce47e0c07045675ac13d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1362 zcmV-Y1+DstP)S5C*yUv?lc$Zn7uu(=Jd zBQr(wEwogv4g_{iFq~uA3k~Z|L@DvE#_JQ>CKxj(Q|L@;_pg7{hnT!9|ZQb+#ochnl1kg9D@G4hNk|1@c1c) z{PkOR|2qXG{Wo$7`M-9{ZVdTtdk+0Kb_u1e2S8@7a?0x`-IJ*AtKYskrENiB%2SAk%zG8F7zQf=Uw)BkpfBE_?MDjX& z@xO&fB(T^G|G)3ZNu2smpTF|o#wUh09?%1ZEU4JTml;2Q`T9S*q6Mrzuc{3gQ-A*d z{Q2vDYEeB{thm1G|F`eoaq0)fT1(#ya4b^Y1D+8X|DV5nO|V2c3(TM(uHGc5|Nf&V|J{K3i0U2yrD0-<#2-I@{x5Ip1M7*&D*x{joegF;bWbC? z(kra(q`n6-N}I4|UUdBS-G~1{3Hjh;&W{YUBz~nhg z|9eJe{4Z(f##+{cVkED+{l6Db&737`v6TNa;pIQg8*`u<_1?qB7^TPOFJHjLD9$4G z$4`iwAE;_BU%Le^B3KtGndh}^?w7N zp&3LI9GX_%Z^hMgm2i3hX^M$M&D3?3wyocP$TZWyV~|^v4II`1-Ns4G92qkYkC3*q zq5Vcp3$J%tR^A_hzW)HC>4{->YFc`|Q_{EF#LX=TN77zUSIfaoZb;&wz(gJIJV1RP*k1Px^d*-VVwqO{!7ld0vtp>=YBj^&nilC)BD ztE56JwKUW~0k;-+RFq}dp}+e-W^~>R$~@;W&dj_2IschCoVoAvzVF`u|L_0b_pX%{ z6)IGyP@zJF3Kc5mBnw)^$H%v%8s8GJFdFO+JEdZDTx2p?EA@AYB&D^dY(zH?X>2dg zpy5tJROa3Z28cyt81c?9etOFk&xr%&3*Cbh*+g#>Eg@R0`V^9??-?=3MobVJO{{ny z`J@v!_h3Z<=@1%JPW6EjJc8u~t^rZ*yv_tQn_~aS4&orid8VU4d9`~`bS>$)jw&j_ zg26-quF~NbT>1ryc$*0i2#`iEZUA3VLuSH%bi}i@0TY6aG#dK)M6BY8fQInO#bsz4 zaghA9%Iwrpz#pj$Hhujfb44PtttN&BjsCvA5l)1FyLfRosiK|&-MBVjqktFuhZgk^ z4|Fql7N{CqJA2C9$%V@(0s0Z(>i?p$dmkSk#EuUFTJ-Yp_n-uDngM0q`gr*wc6<=f z(n;*=MG4?G1G>6+`XP3d07?KQfD%9npahr&0UkvAg~UR?(B@O`kP(!C#xx@SRrq+@ zPB?KY7qb66*KB(Hk2CQ8M_V9hcrqnGtx-vn;8ac?)YsP=MeFM7;Kw7!Avijj63{<1 z4i01^r%G~9`BVaIzdamCre5&B9^=!dK@Qp|m76IFL z9blpnQy`$GrWTg1*&rMO5>sYEX{pjAz*lSGogxU9zhe0Wpu_w1_fsYXzFN2K+zVc^ z7|SML%A92+2Cp+o0!qu2kT79}4jaw7 z&h+Yna8M#SwsE=dIg!^#X6-p)7_l&Gu=VGW4DW6_u6n_M#71?J*O2 zIyYah_Giu(K;W>KEr$T_kXYEU=R3VeZ*@%#B)>VEb&X)f7{-L?)Bcy=vY~%i9IO5O zmFdiN_5B~-Pv4?52+Wp%LyptC8cFBX7XGe-*ffG zEl&MkBflS(^oIEpFfei?93~F%Nm9md&0EP7X*7X6dgAdR>{t5^v5GD@iq~!YoU;?J ztE-2M-3K`pa7>Z_w8d3b)lU=_=97p?+mWWsSODdZ$eyC3ju|sWr_gine(@9aUqsqz z&nB}XAaukyI9G7Vpu)*Y5;MF%Ho)2I8!^)S z2*9bIwrM*Pj~fEO)$2E5NaAa(YsZb7t~07H{rxY5$Bt+HZe+?#gKG`t6_qf1$!hZ> z0AqK)vYlHpc7wO?K$(pgc9&)`JJJbaXw{`1aXh9Eu4mnK7i7cm*T z4*bAdir{Y1eVr76jD)3ys&&QboIJ)svny>&p|XiZ7nf`)I&!liAZ|P{5yd6E=4tkm z#hGSokE4D0nvKlpe|_dcR{w*dMl)e7pZ(t~ybaQ*(dI$GjQOiLEqe4(WqCOh0crLl z35#b;k@k9FUTPZewFc}T)991{jeZ7%C&1Pn-%tXKVS@I4|C5dh!sH&Bph>e9Ynh-V zI3Z*cWDF-95;K;mVlhrQHy;ADoba1McEZgahT`|FJNB@`(8V9D*9t=uATvv#VW?&f z#?Xb>m1{R3GBHKR#1)s6vVM2@?<)`K+5C$Jr6N|W z-N@QLh^dGJnT@9+)^FXZlZwdLbRp~@7Sd`cIArM?wNG+)- z&uLpqnUXltsjRk&SEg{@mV$*K?VSzN-d(}$m=NT)6n!^l;kp4wARimE&J|o_T_<12 z8?zqd=}mrX;#-!#Irrz|f0!fzm|67-j8lFp%R1=GI_T?a=nI=D0rZt+lmJQq zC4dq@37`Z}0(g6QH?IWr6bE=y0=Uiq4}abWz{3c{f$}0sfSxnJZ^%7IXAgz@iewH3#qR$Z~3UKiWJKwHd$F7JS8ODa4BO{SW@Q^Zl7fI+xWEKE(Pz^oA zr;$T^qM1W{+y)JU9v*(5B4#S=toR_n*51K!K%aq;S4c+;33zl9PB}NJT;Pgk2aoi^ zff)_Xl8|f9cIbo-*iI}KKV!v%Sc^m=JQ1j?sEc!AZ=bMht^rXG4=L z9D5}pRt^phc8Hx7PtwZH&dvc(w6gEmDZIO@?{=5|A(#624lX7Rr@ZgLNF{y>N!9mE zK1&db?ydte>^nRkff(7^+TuZOyq+nEOtxv?zI_+$fT(A?c6Nh0IChJ5=+twhs7v=m zAu8TGVnDEvA|{B93ZpiBj()XZMAX*C#->x-wr!or_ufQZiMk0~5rf`{31Wj7sjzAm zK~~Wz+Yleqk#yLZFz$$~3sfBu1H_^M69yY=D{Of^a}6C@3!JA~i98FX7+NQ2pIx?Ufb^ z3VM>RrkZg8anp*{)c6w{ua@Q=_bH*Cuxq%LI*7AGBwto)H-4!zzcekaq&2morKG}n zDqW!T*L~Hk*w&fLWkN_%TRacHzZw}4ksU%uD{7=< z4l@F>pf_Cn{g0o4;i*1H;#1e1-8Sexy}Xv7sq#ll}DbR&61Jz5)YqB}ZOJOXIqaqfl-_k@P*k!*Y-1 zd(EHAJP_%kR{C}E1hMnU!7Nn5&Xc@ zOW#dX-a7S(bXQ1)GD`E2+dA)roFGLZ$YG!>vm17Q#~qSAB*6DaQd9MaCo|S}wqb6S9B=T`wCw7@qZA zHbS^wMo*b2CVh9inNqd!C^;{$*8EGWf1W{RE8+5O2vQgbd8Q|#Z&D)~7#LW|`W&2L z_SyasQE5fzr8$fM0Zn_(DI~(K;s=4IGw}=5`M4LXXw%?Zd&A4B^1?jOnMXtv(4tuj zATG@Fl~sFhQWT1;`B1D2SSa~}-c~CzLg>+!-;3#7J?rnfA!~pBo zKQ;tVz*}4Grw3mfA+SZK^Sp%H{@X6r2psg~wG{kKWi$fIuTaUYJFc+AxB^Hw2(({r z_$0>HdR@Wy8L4?wi;8`FQFPbpt2#h8fmG`&B8tlM5!2hu3~W9;Mqv1GU+Z^bFm_b1!BHQjAzk$7fP& z^+rYz zVHe?I`XfV!78$8wvEthV$qSmS@AMbm$$^&CjwO*XiO*z1y?$BvZ^Zy5u4Q%*GwkuJ zdFhfDJOt}_7~rgd?V5#_fpC@U$k32TWQE{Z8>ywyPzxH=>)UDGWYnmX(Fb+@_3Ou~ zQDTc)-$8tyLf$*#c|I%opcN|Iwpi0aok4zEm|`s&mJ65u`O9-E$2vwO(g>l&pPd{? zI9B0e|2d$nht>or~UhZeZIs-;+8ZZsPv$1!{ zYkPAaeuiW<{zM*KV2e#>&FcN2K4-DYi+?kum$EY&dVq(b3UTbt^ZQoV{Tc2LA1UkH zBDgQD|M3jlVG2yoaJX%Fc+A2)TcRrG(d02quX~s4`tA9wYJVi4r|&{VIdWAu+b+UA z#D3m-q-AvGK>23Q=g)azqn6sg=~2SRnnXB}qwnBEf5Uu;3xhb1FkS2>9B6<#$v z+I*^>7jCs&{@h8Xi&E&$>jvHrN8I$!dUD8y^dULVQL)&{Q)}2As z6ZABSIMYqKkCm6M88j7N7xMEnC=gP0B;)u<9N5J_^%K> z*Az(p>9S5q8>$rgQhLa55;4pZ@2)^uB#99mJgk77uj5uN@6N-r{5Kqr_FZfZn6e>E zMKrwhrfKE?wa}r(M@=2{P1P+!6EZHVN8En4Y$L|dv>Hq!)_bP6R<9P9Z+s)zWA1ZLM5a4U@vGOf?w{MXFOt75#wAKL`?v{8Z z2$CP5w&Nu%jIM|Y`!>T(^5aPpEoX`FS-)HwHbD2~koRV8oR{Pw_kcl$MO)6=mgjSH zJOy6jb(-j$fYY8!!fUd0a{B6GJg=I-%O55W&rE6;7-8tgVgNNM$J3gSXW1RDNrc`< z#EedInYups6;GLd*K%^%^(uFYd}~YO@Pn8*O${mw51{s)%zn$Xe8Tw$jrbimPq!j@ z*0hIk!_i#DC*e{3zI}+oXk5SK3{#2$i0fjXjyAD@XI7?hYbeL?%@JI|d{iPK+D;kU zAGrkYsTV4sy%%Fpsx5N3qUfu8zQb<=cHoraH_Wcb!Be`WTwXmH$d*nUW=?wA`7A*o z<$A_%p{1zExsocwhl5+^BZ7UC(?%+H-|=fBd84jpK2*0vZeZ@aHO+a=(5;8Fo1F*_ z7RSB%61GElZ1qOkvK)2fds zr|EHY#3AP!54Lr49m8x=u<$D_mjj);=htK~crq~|t5E*iV`o5kN?WK~+ZqF}?4J$H zv}QvA=s4<%i2K&VtXgZaO8Ms1*eS~zW+p=i7$u=S>f_zrw*1VNnSd%QD5Ld9GloR@ z!RGDZ;LYg)_qUoX6EbZ+bRpGHNO_Amy#j~eears);u62C)Pop$=F&pnhKuVt<9*Lb z?nVO)Ox`p6+Av1SIzi?lPB(g!XG2>cRqRKpF!pYXQbOkpo6~W zr&=N0>J^NPXAK2RFFNLfEK14=LkgiktE^_fHiodhKBaCS?pvH=RXEy7)7Ti}-?jEIQaxkB@s8-7H- zP;(ydFBF&_M6q_x@*Z^2#u{9pR5^)lPzX{gM$vuoWl3qjG#5OA%3@B`+&<>FRM^PC zWW9q9)v=x=jPRaaR^-m!qmI4WkhVcz@g9E%FIcZE>S&@yl_Km=!FC07xZifd9I{B-wJj#*1$wX$TWLs} zW>O+MrpYyMN_z+l7V6hGU1{?UzdbnDyiF1yiScCsbS&~iYSa2Dxvf%yF1Ht2_{bD)hkvE@C;YuC|PRtV+*rJ3zu@>WdieCbY z?L^FvNcnD!@PR3HUfFE^DlHs`fbA*K=ESgH0kVN(Z1z9DXjS&W6nWMJh5SO~{z05N z<{!_&82``b;~4+n|06yAf6#}v1q4#xD5R7rz%^dWXP=7mZKrFXMV3LOsc-r0Lk^B* z*yW56L{@?c^6?B*`jZ<~_QxMRW>kP5*-MV8m7gjrZoRXShrUmLUhI4a(VdYLK&55r zU17e^C&gz4hl7mom-*BpFI2V{+7D6eAZ|2Ia^Vg3{euGU;>50HzV8hj<1S`qAmbwK zgfaxem$ENrvVy=#$6Q$PJ?>joXo~5|7K;K?OOeXFuh!s`y~S?fuBg-`eZ<(kO5=j5+?q5CtBYHR53EePl$zzHN=tqL zAT0t%Q#&;$Lw9BKz-ifw&RNE#LZ zm*Y}tqURdR>_s30cr0Kmm)t7#DrItL=Pr-fY-&x>r8OIyN>b?!<#VU$BR9WtYus|C zlb3z7)3d0E&l3aF=W^2M+}x|R0NK52~QqMAdhKneJ)#) zT7732cAbz3<9Y0*qG%PU`g=RHJ)IFk*+PLD`Ld=IP?Njd>VtWBR4-Ck3Hv18U0WI zV2uq3g6f^x2G7c=p@RHqN*TgM%4|`s^UtkutYSaPk<{TxQ5pftG4D{HdAqOLZ#1v_ ze9M+5dsmQgQfV0(U&(S!!AFzvis49pCTa?3*#F3|c3c({E49|qiLo*tWAg7N2r?$H zceChvA3_;lB9B|DgITla;p_)_r>v>z1zcg0vl49vG;Ili>b(32*1hN??A7sM@$nr4 z8!M}P<^@Xi%U%oe11bF}T`A`>43CK-Qz^~WSp-#Hv2Q9-9^X94+}vz@Y^)g{BUOYV z_|+d(CAi?WUj6zyz~}lnkBZ=80;M3*LU zHGMlZ?()$(qVAfc|G0}(d&tSfx)|^Mu2H_=kb4o=Ap3@`Lp&B)cL!~H9PI7w*YctI zQdh5sK=8^5AG8P>#9Vyr+q9%EwH3HQk{XQFUw1_hfFE3734S2!^#qIgdS@@Q{Gn}V z&i9cg|N4u1hekL~)kUtMXQYP=0K1b;zvVq4 zRb1r#*7T38ib@M@JD6D*ec@F^uyytIxz!L&dH3FxrvZWb8BV**eALkmeW5?93@}@n z4gNan2F?-Ie_od^USuAI0%QWj1;%?cUgs$RzY?UxLayXoAPU~f29Th25OmAI z06!5@vgYvOQk6;7bal;{!x-3L@ZzNh{0cx{9p0)g1j+z7i}n8i$po2mA$9%`)fE!Czt%i%kp_d^qH20s4XnQst#a^y8a7?M5z z*L>NT7jYu?ICpgEQUYh_OrrtIc)wKx1p6)`I=;61<0)vR1JCOJwvBjC!)Mv`b#ol9Akg)gKB^lewze1bTfSn@{B`u_A zN)PUeMM_x{I^}mc;UI<%**ErSWv7bWZqZOYaL!Vhe~kgeP$S=_d##+rr~Y2Hh1>Lf zY=aYSLIB5kY+Q46%@wn%6eSeDTv`P&y|-w1o@Q>{3O~TqAV%Mfc7n9fmZEe)q(iKx^n9(NLb73Fz+c+s z!>K-8XvAo7Xl~E$nxjkY=8*HY3k8UR*tK@ktoRk(m_t4G*)CvnEHo5Mv^lI*I$~VT zuH0CQ&e0+^wcyj7d5)_2{MUw8@JEb14uhKmP;dz#w@0mHpB@zWPB$AE8802Ak?aBk z1M!fDJDr>(_(|mFqjVXEY-2j@TGY<*rK|h113ZR$)F9b)LOQJZhEwYNf%4CFbZX7r zL16#j)!2N6%HO@+Vja^$%=71~T?~9Gg$KI>#Wwff2WtS32+6IQEv;R6a?Q?f&t~sy z^?UKhaZ#>^yY+4h*)R!0Fyiwv!ursg*ef5>>?IAD*ns7x&BkByqWr2RWnuEC)*Vud z`9a0}20fROX5f7JsQ%t$N;zJM+&`J&In$Q}u+M=I{b7@g!`prSoyZpQ9TV;3(@D1e z%BI66KJyYBWhq#q@AQ!=m9Nvfnq z-SG?FyKF)enqlGZ8yZrUBOey84zNfN!yy;zjn1@HJvxz3-Fp z@Tz6QUll*eYHc^+v(f|F6?U8_{nr~jaIG0W?B=i6B3RcSto*bvBsbTM=A9BU-3Ah8 zNi`l$9?&GMo=FEwRv_xSgyGZtj9#@e-B5nrpw{?~zkgz73X_}cv)*W^Rr8w)YwNHc z*5Nn6f`7FA!KOwX(rWwMR7CG2XjL0w!d?(-NK_z;CDgW!? zm{={qDnSAQe=8Vg-umXT=L(@JFv-`qNgoa*CdglVGRag)CSpU(wYQsW`&k0q_mT*%_hS-?>#U4EO z2MC~jQ3U6aUEVZn`ZAr-q_#O-3f;~=QSZ=x?WSyg+?f9&^TYDzkb6XdslA>n+|$$Y z#wjomIx&A!XAHF_GVmq|e@koN>Yw2r^&$^Gl_#ddWR=6%jFpj99RV`jcPw{gQUrpP z&}y~JthsyUaj=yQDO|`!1pHEh$z()Rxx-4E66v=_sVbSZ*qEz&S3yM0K3<= zl(AIalVLR~ZN4IX$r$zP!ZB`rtk!neSg;~!`TZzT`@!UHZQV6$;7SKpBW2rrUV6x# zmbf#hIQ8SB>u=fyo$!2K@J^E%%R8%^DUW6^Ebq2+fLvKX@){F7?rY$=jVkSNr#m^S zUpAC=E)0=|)VsRj1l+j|KCG0J1K2@28(?-SzJW8yW`-j@8fz?sRj+*;$DojX-q@wYb}{2W8MP`wCr zpMJgOGt1}UL%B`+e1=bS5ru|!T&(Bpqim_)`YyB+;aZ#ewM>398;>NO39z+)EM@9I zzqa%gS5q)4Ws**y4RgHdAlxy?P#N69EqQ~}t7qX#A{`ZoNn=1A+!}QMkw>!0732x3 z`%S`@brK1YzOF-F&+{yjtW_BZrcDAx(tO-GN;yTY1tuOT<*hG12+Xe>ynLs0qchz{ z`%mg>lPr;0bC~$^CnR=xKR;P3OfpfJ$f|c)lUs?S0JW(^)lwEvC4)e}5}SI^v{!1$ zjqz@CVW6_>%7&F`sY3xz9P-J|lBlF}so2Y{lOpC+^`4$YhDLpp3!lSk@7KlW@%84X z*IvEA!*PC8@8D;8o1-I7vgw9B2}E<;Gq@mSZ&q9x(yG-(0CRJ;r zbr$E?ta2}89WD9k`z^Rc!N4GdALcn;R6#TJ15qv>piYcX@`jjXw~iJvrTm)BH$ zb%K;N2--lOR@QBD`&ZF+4es%d!air^&5bM>hfj5->g#UzXEdTl_hyn zIkQLs>{x-PlSZZM!^euTA~#MxCZTd_Kbjkq`Dn%=#g_vd*TXIuYU@v(d_{kZ;gK)u zziBr#l9lQ0LjnAl*orcD2VJ5{3NMwFco~orS-1~*AxKWOzTLAVmkWPoR%xPGNdu_q zz;1sj4r&=@sDnZO$2EB8H~guAjJd#c{W^O({#pLgMS7mAt2DrusXx<^*a&kdXI-_Y z_9j_9_oo7Ni?ojhH{T{3!6L3yVd(f2Q0Zr`E!UF-##p;v7n$b-e;v^A-o+ab? zlVwJ*Qt6gkF!g%V9M;PT-|U= znQZgx^I%KEj2c)s_Obx$c&fXdCv3`UHn5IUlIGXDmDJu$E7UeYpf5^wf`~WfT87s{$hui5G`USZ+r7zlb|e z{ZrEYyI`t?3$8$w!SQh-JJib09-`-O7ZU4W&ZGTrlS_{>=JI+%v?F3Tq4~1)esPKE zOiQEtW@?$T*;OTKv!Sl$WxW~6_9*!_N!^2IYUo+ypU1@6-e{dt%xSFE+(Fb`n{t+) z$HuFNv2x025j(+st&hXUa}gE1f(XrQ=B;Jhk8HVYcyj)MC0D)AaFV7l_3cKkrp89u z(05Bo#PXm6x=Pa_jB9=7rv$M%r5HsdnqMzLuKQArS-14ABcqZOrYyX~mfY?EWt(fm z(L+_F&V`mRF)}iS^LN5w6g}wbzz9&?o&7$8Y%p%*CHR^I$9f1*yUyH}zB4^i`c9)n z^IWRH4CDIwFT)hq3)>yRq6eP@ro(m*m$s4>KJU-QgKcLrPB2?_UE8C%l~~G<7O(TM zW$LTyd`im-CExf(S*NOi-sw_1p>6i4+&79YR+?)afxX5n4mIp$-P0wan9u#)Ul4SvZ5P^5 z*}dWjId8T<(NSMTCXWyZOnb$5cGAW?f`MWbibU$G>fOxR97aMitp0y - - XT.Common.Tests - \ No newline at end of file diff --git a/Softeq.XToolkit.Common.Droid.Tests/Softeq.XToolkit.Common.Droid.Tests.csproj b/Softeq.XToolkit.Common.Droid.Tests/Softeq.XToolkit.Common.Droid.Tests.csproj index fa8c01b82..8cc240cf0 100644 --- a/Softeq.XToolkit.Common.Droid.Tests/Softeq.XToolkit.Common.Droid.Tests.csproj +++ b/Softeq.XToolkit.Common.Droid.Tests/Softeq.XToolkit.Common.Droid.Tests.csproj @@ -1,104 +1,48 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {69A56CFD-E5A6-4A7D-9F41-25EB88975BF8} - {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - {122416d6-6b49-4ee2-a1e8-b825f31c79fe} - Library - Properties - Softeq.XToolkit.Common.Droid.Tests - Softeq.XToolkit.Common.Droid.Tests - 512 - True - True - Resources\Resource.designer.cs - Resource - Off - v13.0 - Properties\AndroidManifest.xml - Resources - Assets - true - true - - - True - portable - False - bin\Debug\ - DEBUG;TRACE - prompt - 4 - True - None - False - armeabi-v7a;x86;arm64-v8a;x86_64 - - - True - portable - True - bin\Release\ - TRACE - prompt - 4 - true - False - SdkOnly - True - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {18d3fdc1-b0a1-401e-87f2-1c43034e610c} - Softeq.XToolkit.Common.Droid - - - {24588814-B93D-4528-8917-9C2A3C4E85CA} - Softeq.XToolkit.Common - - - - + + + + net8.0-android34.0 + Exe + true + disable + + + Softeq.XToolkit.Common.Droid.Tests + + + com.softeq.xtoolkit.common.droid.tests + + + 1.0 + 1 + + 21.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Softeq.XToolkit.Common.Droid.Tests/TextFilters/ForbiddenCharsInputFilterTests.cs b/Softeq.XToolkit.Common.Droid.Tests/TextFilters/ForbiddenCharsInputFilterTests.cs deleted file mode 100644 index 34371d97e..000000000 --- a/Softeq.XToolkit.Common.Droid.Tests/TextFilters/ForbiddenCharsInputFilterTests.cs +++ /dev/null @@ -1,102 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using Android.Text; -using Java.Lang; -using Softeq.XToolkit.Common.Droid.TextFilters; -using Xunit; - -namespace Softeq.XToolkit.Common.Droid.Tests.TextFilters -{ - public class ForbiddenCharsInputFilterTests - { - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData("a")] - [InlineData("aaa")] - [InlineData("aA%@2-")] - public void Ctor_WhenCalledWithAnyChars_CreatesFilter(string? chars) - { - var obj = new ForbiddenCharsInputFilter(chars?.ToCharArray() ?? null!); - - Assert.IsAssignableFrom(obj); - } - - [Theory] - [PairwiseData] - public void FilterFormatted_ForFilterWithNullChars_ReturnsNull( - [CombinatorialValues(null, "", "b", "bbc", "+3b^ gb^")] string sourceStr, - [CombinatorialValues(-1, 0, 1, 3)] int start, - [CombinatorialValues(-1, 0, 1, 3)] int end, - [CombinatorialValues(null, "", "a", "abc")] string? destStr, - [CombinatorialValues(-1, 0, 1, 3)] int dstart, - [CombinatorialValues(-1, 0, 1, 3)] int dend) - { - var source = new String(sourceStr); - var dest = destStr == null ? null : new SpannableString(destStr); - var filter = new ForbiddenCharsInputFilter(null!); - - var result = filter.FilterFormatted(source, start, end, dest, dstart, dend); - - Assert.Null(result); - } - - [Theory] - [PairwiseData] - public void FilterFormatted_ForFilterWithNonNullChars_WhenCalledWithNullSource_ReturnsNull( - [CombinatorialValues("", "a", "aaA%@2-")] string chars, - [CombinatorialValues(-1, 0, 1, 3)] int start, - [CombinatorialValues(-1, 0, 1, 3)] int end, - [CombinatorialValues(null, "", "a", "abc")] string? destStr, - [CombinatorialValues(-1, 0, 1, 3)] int dstart, - [CombinatorialValues(-1, 0, 1, 3)] int dend) - { - var dest = destStr == null ? null : new SpannableString(destStr); - var filter = new ForbiddenCharsInputFilter(chars.ToCharArray()); - - var result = filter.FilterFormatted(null, start, end, dest, dstart, dend); - - Assert.Null(result); - } - - [Theory] - [PairwiseData] - public void FilterFormatted_ForFilterWithNonNullChars_WhenCalledWithNonNullSource_IfSourceDoesNotContainForbiddenChars_ReturnsNull( - [CombinatorialValues("", "a", "aaA%@2-")] string chars, - [CombinatorialValues("", "b", "bbc", "+3b^ gb^")] string sourceStr, - [CombinatorialValues(-1, 0, 1, 3)] int start, - [CombinatorialValues(-1, 0, 1, 3)] int end, - [CombinatorialValues(null, "", "a", "abc")] string? destStr, - [CombinatorialValues(-1, 0, 1, 3)] int dstart, - [CombinatorialValues(-1, 0, 1, 3)] int dend) - { - var source = new String(sourceStr); - var dest = destStr == null ? null : new SpannableString(destStr); - var filter = new ForbiddenCharsInputFilter(chars.ToCharArray()); - - var result = filter.FilterFormatted(source, start, end, dest, dstart, dend); - - Assert.Null(result); - } - - [Theory] - [PairwiseData] - public void FilterFormatted_ForFilterWithNonEmptyChars_WhenCalledWithNonNullSource_IfSourceContainForbiddenChars_ReturnsEmptyString( - [CombinatorialValues("a", "aaabc", "%klp", "jd2ye")] string sourceStr, - [CombinatorialValues(-1, 0, 1, 3)] int start, - [CombinatorialValues(-1, 0, 1, 3)] int end, - [CombinatorialValues(null, "", "a", "abc")] string? destStr, - [CombinatorialValues(-1, 0, 1, 3)] int dstart, - [CombinatorialValues(-1, 0, 1, 3)] int dend) - { - var source = new String(sourceStr); - var dest = destStr == null ? null : new SpannableString(destStr); - var filter = new ForbiddenCharsInputFilter("aaAA%@2@-".ToCharArray()); - - var result = filter.FilterFormatted(source, start, end, dest, dstart, dend); - - Assert.Empty(result!); - } - } -} diff --git a/Softeq.XToolkit.Common.Droid/Extensions/ContextExtensions.cs b/Softeq.XToolkit.Common.Droid/Extensions/ContextExtensions.cs index 91472d85e..499ce74e2 100644 --- a/Softeq.XToolkit.Common.Droid/Extensions/ContextExtensions.cs +++ b/Softeq.XToolkit.Common.Droid/Extensions/ContextExtensions.cs @@ -57,9 +57,9 @@ private static void SetupMetrics(Context context) return; } - using (var metrics = context.Resources.DisplayMetrics) + using (var metrics = context.Resources!.DisplayMetrics) { - _displayDensity = metrics.Density; + _displayDensity = metrics!.Density; } } @@ -121,7 +121,7 @@ public static bool IsInPowerSavingMode(this Context context) /// Intent that should be handled. public static bool TryStartActivity(this Context context, Intent intent) { - var canResolveActivity = intent.ResolveActivity(context.PackageManager) != null; + var canResolveActivity = intent.ResolveActivity(context.PackageManager!) != null; if (canResolveActivity) { context.StartActivity(intent); diff --git a/Softeq.XToolkit.Common.Droid/Properties/AssemblyInfo.cs b/Softeq.XToolkit.Common.Droid/Properties/AssemblyInfo.cs deleted file mode 100644 index 811f5b392..000000000 --- a/Softeq.XToolkit.Common.Droid/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Softeq.XToolkit.Common.Droid")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Softeq Development Corporation")] -[assembly: AssemblyProduct("XToolkit.Common")] -[assembly: AssemblyCopyright("Copyright © Softeq Development Corporation 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Softeq.XToolkit.Common.Droid/Softeq.XToolkit.Common.Droid.csproj b/Softeq.XToolkit.Common.Droid/Softeq.XToolkit.Common.Droid.csproj index 9f906228d..60be0cbf5 100644 --- a/Softeq.XToolkit.Common.Droid/Softeq.XToolkit.Common.Droid.csproj +++ b/Softeq.XToolkit.Common.Droid/Softeq.XToolkit.Common.Droid.csproj @@ -1,64 +1,21 @@ - - + + - Debug - AnyCPU - 8.0.30703 - 2.0 - {18D3FDC1-B0A1-401E-87F2-1C43034E610C} - {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Library - Properties - Softeq.XToolkit.Common.Droid - Softeq.XToolkit.Common.Droid - 512 - Resources\Resource.Designer.cs - Off - v13.0 + net8.0-android34.0 + 21.0 + true - portable - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 None + - portable - true - bin\Release\ - TRACE - prompt - 4 SdkOnly + - - - - + - - - - - - - - - - - - - - - - {24588814-B93D-4528-8917-9C2A3C4E85CA} - Softeq.XToolkit.Common - - - - + + \ No newline at end of file diff --git a/Softeq.XToolkit.Common.Tests/Collections/BiDictionaryTests/BiDictionaryTests.cs b/Softeq.XToolkit.Common.Tests/Collections/BiDictionaryTests/BiDictionaryTests.cs index 6f065437d..f9562725b 100644 --- a/Softeq.XToolkit.Common.Tests/Collections/BiDictionaryTests/BiDictionaryTests.cs +++ b/Softeq.XToolkit.Common.Tests/Collections/BiDictionaryTests/BiDictionaryTests.cs @@ -3,10 +3,7 @@ using System; using System.Collections.Generic; -using System.IO; -using System.Runtime.Serialization.Formatters.Binary; using System.Text; -using Softeq.XToolkit.Common.Collections; using Xunit; namespace Softeq.XToolkit.Common.Tests.Collections.BiDictionaryTests @@ -141,29 +138,6 @@ public void Remove_Negative() Assert.Equal("[0, 0],[1, 1]-[0, 0],[1, 1]", dictionary.GetResult()); } - [Fact] - public void Serialization() - { - var dictionary = BiDictionaryHelper.CreateWithTwoItems(); - - var sb = new StringBuilder(); - - using (var memoryStream = new MemoryStream()) - { - var binaryFormatter = new BinaryFormatter(); - binaryFormatter.Serialize(memoryStream, dictionary); - memoryStream.Position = 0; - - var newDict = (BiDictionary) binaryFormatter.Deserialize(memoryStream); - - sb.AppendJoin(",", newDict); - sb.Append("-"); - sb.AppendJoin(",", newDict.Reverse); - } - - Assert.Equal("[0, 0],[1, 1]-[0, 0],[1, 1]", sb.ToString()); - } - [Fact] public void Set() { diff --git a/Softeq.XToolkit.Common.Tests/Collections/ObservableKeyGroupsCollectionTest/ObservableKeyGroupsCollectionTestsCountChanged.cs b/Softeq.XToolkit.Common.Tests/Collections/ObservableKeyGroupsCollectionTest/ObservableKeyGroupsCollectionTestsCountChanged.cs new file mode 100644 index 000000000..3abf739ac --- /dev/null +++ b/Softeq.XToolkit.Common.Tests/Collections/ObservableKeyGroupsCollectionTest/ObservableKeyGroupsCollectionTestsCountChanged.cs @@ -0,0 +1,80 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using Xunit; +using CollectionHelper = Softeq.XToolkit.Common.Tests.Collections.ObservableKeyGroupsCollectionTest.ObservableKeyGroupsCollectionHelper; + +namespace Softeq.XToolkit.Common.Tests.Collections.ObservableKeyGroupsCollectionTest; + +public class ObservableKeyGroupsCollectionTestsCountChanged +{ + [Fact] + public void AddGroups_KeysOnlyForbidEmptyGroupCorrectKeys_RaisesPropertyChangedForCount() + { + var collection = CollectionHelper.CreateFilledGroupsWithoutEmpty(); + var pairs = CollectionHelper.PairNotContainedKeyWithItems; + + Assert.PropertyChanged(collection, nameof(collection.Count), () => + { + collection.AddGroups(pairs); + }); + } + + [Fact] + public void InsertGroups_KeysOnlyForbidEmptyGroupCorrectKeys_RaisesPropertyChangedForCount() + { + var collection = CollectionHelper.CreateFilledGroupsWithoutEmpty(); + var pairs = CollectionHelper.PairNotContainedKeyWithItems; + + Assert.PropertyChanged(collection, nameof(collection.Count), () => + { + collection.InsertGroups(0, pairs); + }); + } + + [Fact] + public void ReplaceAllGroups_KeysOnlyAllowEmptyGroupCorrectKeys_RaisesPropertyChangedForCount() + { + var collection = CollectionHelper.CreateFilledGroupsWithEmpty(); + var keys = CollectionHelper.KeysEmpty; + + Assert.PropertyChanged(collection, nameof(collection.Count), () => + { + collection.ReplaceAllGroups(keys); + }); + } + + [Fact] + public void ReplaceAllGroups_KeysOnlyAllowEmptyGroupEmptyCollection_RaisesPropertyChangedForCount() + { + var collection = CollectionHelper.CreateFilledGroupsWithEmpty(); + var pairs = CollectionHelper.PairsEmpty; + + Assert.PropertyChanged(collection, nameof(collection.Count), () => + { + collection.ReplaceAllGroups(pairs); + }); + } + + [Fact] + public void RemoveGroups_KeysOnlyForbidEmptyGroupContainsKey_RaisesPropertyChangedForCount() + { + var collection = CollectionHelper.CreateFilledGroupsWithoutEmpty(); + + Assert.PropertyChanged(collection, nameof(collection.Count), () => + { + collection.RemoveGroups(CollectionHelper.KeysOneFill); + }); + } + + [Fact] + public void Clear_FilledCollection_RaisesPropertyChangedForCount() + { + var collection = CollectionHelper.CreateFilledGroupsWithoutEmpty(); + + Assert.PropertyChanged(collection, nameof(collection.Count), () => + { + collection.Clear(); + }); + } +} diff --git a/Softeq.XToolkit.Common.Tests/Extensions/EnumExtensionsTests/TestEnum.cs b/Softeq.XToolkit.Common.Tests/Extensions/EnumExtensionsTests/TestEnum.cs index e2035719f..224f473f3 100644 --- a/Softeq.XToolkit.Common.Tests/Extensions/EnumExtensionsTests/TestEnum.cs +++ b/Softeq.XToolkit.Common.Tests/Extensions/EnumExtensionsTests/TestEnum.cs @@ -1,7 +1,6 @@ // Developed by Softeq Development Corporation // http://www.softeq.com -using System; using System.ComponentModel; namespace Softeq.XToolkit.Common.Tests.Extensions.EnumExtensionsTests diff --git a/Softeq.XToolkit.Common.Tests/Extensions/EnumerableExtensionsTests/EnumerableExtensionsDataProvider.cs b/Softeq.XToolkit.Common.Tests/Extensions/EnumerableExtensionsTests/EnumerableExtensionsDataProvider.cs deleted file mode 100644 index ec27d5d29..000000000 --- a/Softeq.XToolkit.Common.Tests/Extensions/EnumerableExtensionsTests/EnumerableExtensionsDataProvider.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Collections.Generic; - -namespace Softeq.XToolkit.Common.Tests.Extensions.EnumerableExtensionsTests -{ - public class EnumerableExtensionsDataProvider - { - public static IEnumerable ChunkifyData - { - get - { - yield return new object[] - { - new List() { 1, 2, 3, 4 }, - 1, - new List() { new int[] { 1 }, new int[] { 2 }, new int[] { 3 }, new int[] { 4 } } - }; - yield return new object[] - { - new List() { 1, 2, 3, 4 }, - 2, - new List() { new int[] { 1, 2 }, new int[] { 3, 4 } } - }; - yield return new object[] - { - new List() { 1, 2, 3, 4, 5, 6, 7, 8 }, - 3, - new List() { new int[] { 1, 2, 3 }, new int[] { 4, 5, 6 }, new int[] { 7, 8 } } - }; - yield return new object[] - { - new List() { 1, 2, 3, 4 }, - 10, - new List() { new int[] { 1, 2, 3, 4 } } - }; - } - } - } -} diff --git a/Softeq.XToolkit.Common.Tests/Extensions/EnumerableExtensionsTests/EnumerableExtensionsTests.cs b/Softeq.XToolkit.Common.Tests/Extensions/EnumerableExtensionsTests/EnumerableExtensionsTests.cs index 8aa1ad7f9..22af20c16 100644 --- a/Softeq.XToolkit.Common.Tests/Extensions/EnumerableExtensionsTests/EnumerableExtensionsTests.cs +++ b/Softeq.XToolkit.Common.Tests/Extensions/EnumerableExtensionsTests/EnumerableExtensionsTests.cs @@ -1,11 +1,10 @@ // Developed by Softeq Development Corporation // http://www.softeq.com -using System; +using System; using System.Collections.Generic; using System.Linq; using NSubstitute; -using NSubstitute.ReceivedExtensions; using Softeq.XToolkit.Common.Extensions; using Xunit; @@ -88,46 +87,5 @@ public void Apply_NotNullParams_ActionIsCalledForEveryElement() _testAction.ReceivedWithAnyArgs(_testEnumerable.Count()).Invoke(Arg.Any()); } - - [Theory] - [InlineData(-1)] - [InlineData(0)] - [InlineData(1)] - public void Chunkify_NullEnumerable_ThrowsArgumentNullException(int size) - { - IEnumerable enumerable = null; - - Assert.Throws(() => - { - enumerable.Chunkify(size).ToList(); - }); - } - - [Theory] - [InlineData(-1)] - [InlineData(0)] - public void Chunkify_IncorrectSize_ThrowsOutOfRangeException(int size) - { - Assert.Throws(() => - { - _testEnumerable.Chunkify(size).ToList(); - }); - } - - [Theory] - [MemberData(nameof(EnumerableExtensionsDataProvider.ChunkifyData), MemberType = typeof(EnumerableExtensionsDataProvider))] - public void Chunkify_CorrectSize_ReturnsExpectedChunks(IEnumerable data, int size, IEnumerable expectedChunks) - { - var result = data.Chunkify(size).ToList(); - - Assert.Equal(expectedChunks.Count(), result.Count()); - - var i = 0; - foreach (var expectedChunk in expectedChunks) - { - Assert.Equal(expectedChunk, result[i]); - i++; - } - } } } diff --git a/Softeq.XToolkit.Common.Tests/Files/BaseFileProviderTests/MockFileSystemWrapper.cs b/Softeq.XToolkit.Common.Tests/Files/BaseFileProviderTests/MockFileSystemWrapper.cs index dfe944839..0288c0255 100644 --- a/Softeq.XToolkit.Common.Tests/Files/BaseFileProviderTests/MockFileSystemWrapper.cs +++ b/Softeq.XToolkit.Common.Tests/Files/BaseFileProviderTests/MockFileSystemWrapper.cs @@ -2,6 +2,7 @@ // http://www.softeq.com using System.Collections.Generic; +using System.IO; using System.IO.Abstractions.TestingHelpers; using Softeq.XToolkit.Common.Files.Abstractions; using IDirectory = Softeq.XToolkit.Common.Files.Abstractions.IDirectory; @@ -28,6 +29,10 @@ public MockFileWrapper(IMockFileDataAccessor mockFileDataAccessor) : base(mockFileDataAccessor) { } + + Stream IFile.OpenWrite(string path) => OpenWrite(path); + + Stream IFile.OpenRead(string path) => OpenRead(path); } internal class MockDirectoryWrapper : MockDirectory, IDirectory diff --git a/Softeq.XToolkit.Common.Tests/Softeq.XToolkit.Common.Tests.csproj b/Softeq.XToolkit.Common.Tests/Softeq.XToolkit.Common.Tests.csproj index 991d5b12d..c0600b9f4 100644 --- a/Softeq.XToolkit.Common.Tests/Softeq.XToolkit.Common.Tests.csproj +++ b/Softeq.XToolkit.Common.Tests/Softeq.XToolkit.Common.Tests.csproj @@ -2,7 +2,7 @@ Exe - net5.0 + net8.0 disable @@ -11,7 +11,7 @@ - + diff --git a/Softeq.XToolkit.Common.iOS.Tests/AppDelegate.cs b/Softeq.XToolkit.Common.iOS.Tests/AppDelegate.cs deleted file mode 100644 index 3a5e97d2a..000000000 --- a/Softeq.XToolkit.Common.iOS.Tests/AppDelegate.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Reflection; -using Foundation; -using UIKit; -using Xunit.Runner; -using Xunit.Sdk; - -namespace Softeq.XToolkit.Common.iOS.Tests -{ - [Register("AppDelegate")] - public class AppDelegate : RunnerAppDelegate - { - public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions) - { - // We need this to ensure the execution assembly is part of the app bundle - AddExecutionAssembly(typeof(ExtensibilityPointFactory).Assembly); - - // tests can be inside the main assembly - AddTestAssembly(Assembly.GetExecutingAssembly()); - - // otherwise you need to ensure that the test assemblies will - // become part of the app bundle - // AddTestAssembly(typeof(PortableTests).Assembly); -#if false - // you can use the default or set your own custom writer (e.g. save to web site and tweet it ;-) - Writer = new TcpTextWriter ("10.0.1.2", 16384); - // start running the test suites as soon as the application is loaded - AutoStart = true; - // crash the application (to ensure it's ended) and return to springboard - TerminateAfterExecution = true; -#endif - return base.FinishedLaunching(application, launchOptions); - } - } -} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Entitlements.plist b/Softeq.XToolkit.Common.iOS.Tests/Entitlements.plist deleted file mode 100644 index 5ea1ec76e..000000000 --- a/Softeq.XToolkit.Common.iOS.Tests/Entitlements.plist +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/Softeq.XToolkit.Common.iOS.Tests/Extensions/NSDateExtensionsTests/NSDateExtensionsTests.cs b/Softeq.XToolkit.Common.iOS.Tests/Extensions/NSDateExtensionsTests/NSDateExtensionsTests.cs deleted file mode 100644 index b882404f4..000000000 --- a/Softeq.XToolkit.Common.iOS.Tests/Extensions/NSDateExtensionsTests/NSDateExtensionsTests.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System; -using Foundation; -using Softeq.XToolkit.Common.iOS.Extensions; -using Xunit; - -namespace Softeq.XToolkit.Common.iOS.Tests.Extensions.NSDateExtensionsTests -{ - public class NSDateExtensionsTests - { - [Theory] - [InlineData(-100000)] - [InlineData(0)] - [InlineData(100000)] - public void ToUtcDateTime_ForNsDate_ConvertsToUtcDateTime(double secondsSinceNow) - { - var nsDate = NSDate.FromTimeIntervalSinceNow(secondsSinceNow); - var calendar = NSCalendar.CurrentCalendar; - calendar.TimeZone = NSTimeZone.FromGMT(0); - var utcComponents = CreateComponents(nsDate, calendar); - - var dateTime = nsDate.ToUtcDateTime(); - - Assert.IsType(dateTime); - Assert.Equal(DateTimeKind.Utc, dateTime.Kind); - Assert.Equal(utcComponents.Year, dateTime.Year); - Assert.Equal(utcComponents.Month, dateTime.Month); - Assert.Equal(utcComponents.Day, dateTime.Day); - Assert.Equal(utcComponents.Hour, dateTime.Hour); - Assert.Equal(utcComponents.Minute, dateTime.Minute); - Assert.Equal(utcComponents.Second, dateTime.Second); - } - - [Theory] - [InlineData(-100000)] - [InlineData(0)] - [InlineData(100000)] - public void ToLocalDateTime_ForNsDate_ConvertsToLocalDateTime(double secondsSinceNow) - { - var nsDate = NSDate.FromTimeIntervalSinceNow(secondsSinceNow); - var localComponents = CreateComponents(nsDate, NSCalendar.CurrentCalendar); - - var dateTime = nsDate.ToLocalDateTime(); - - Assert.IsType(dateTime); - Assert.Equal(DateTimeKind.Local, dateTime.Kind); - Assert.Equal(localComponents.Year, dateTime.Year); - Assert.Equal(localComponents.Month, dateTime.Month); - Assert.Equal(localComponents.Day, dateTime.Day); - Assert.Equal(localComponents.Hour, dateTime.Hour); - Assert.Equal(localComponents.Minute, dateTime.Minute); - Assert.Equal(localComponents.Second, dateTime.Second); - } - - [Theory] - [InlineData(-100000)] - [InlineData(0)] - [InlineData(100000)] - public void ToLocalDateTimeAndBack_ForNsDate_ReturnsSameNsDate(double secondsSinceNow) - { - var nsDate = NSDate.FromTimeIntervalSinceNow(secondsSinceNow); - - var dateTime = nsDate.ToLocalDateTime(); - var newNsDate = dateTime.ToNsDate(); - - Assert.Equal(nsDate.SecondsSince1970, newNsDate.SecondsSince1970, 3); - } - - private NSDateComponents CreateComponents(NSDate date, NSCalendar calendar) - { - return calendar.Components( - NSCalendarUnit.Second | NSCalendarUnit.Minute | NSCalendarUnit.Hour | - NSCalendarUnit.Day | NSCalendarUnit.Month | NSCalendarUnit.Year, date); - } - } -} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Extensions/NSLocaleExtensionsTests/NSLocaleExtensionsTests.cs b/Softeq.XToolkit.Common.iOS.Tests/Extensions/NSLocaleExtensionsTests/NSLocaleExtensionsTests.cs deleted file mode 100644 index 346928753..000000000 --- a/Softeq.XToolkit.Common.iOS.Tests/Extensions/NSLocaleExtensionsTests/NSLocaleExtensionsTests.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System; -using System.Diagnostics.CodeAnalysis; -using Foundation; -using Softeq.XToolkit.Common.iOS.Extensions; -using Xunit; - -namespace Softeq.XToolkit.Common.iOS.Tests.Extensions.NSLocaleExtensionsTests -{ - [SuppressMessage("ReSharper", "InvokeAsExtensionMethod", Justification = "Need for tests")] - public class NSLocaleExtensionsTests - { - [Fact] - public void Is24HourFormat_Null_ThrowsArgumentNullException() - { - var locale = null as NSLocale; - - Assert.Throws(() => - { - NSLocaleExtensions.Is24HourFormat(locale!); - }); - } - - [Theory] - [InlineData("en_US", false)] - [InlineData("en_CA", false)] - [InlineData("fil_PH", false)] - [InlineData("ru_RU", true)] - [InlineData("en_BY", true)] - [InlineData("de_DE", true)] - [InlineData("it_IT", true)] - public void Is24HourFormat_24HourLocaleFormat_ReturnsTrue(string localeId, bool expectedResult) - { - var locale = NSLocale.FromLocaleIdentifier(localeId); - - var result = locale.Is24HourFormat(); - - Assert.Equal(expectedResult, result); - } - } -} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Extensions/NSRangeExtensionsTests/NSRangeExtensionsTests.cs b/Softeq.XToolkit.Common.iOS.Tests/Extensions/NSRangeExtensionsTests/NSRangeExtensionsTests.cs deleted file mode 100644 index 4268f9eec..000000000 --- a/Softeq.XToolkit.Common.iOS.Tests/Extensions/NSRangeExtensionsTests/NSRangeExtensionsTests.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using Foundation; -using Softeq.XToolkit.Common.Helpers; -using Softeq.XToolkit.Common.iOS.Extensions; -using Xunit; - -namespace Softeq.XToolkit.Common.iOS.Tests.Extensions.NSRangeExtensionsTests -{ - public class NSRangeExtensionsTests - { - [Theory] - [InlineData(0, 0)] - [InlineData(0, 10)] - [InlineData(5, 10)] - [InlineData(9, 10)] - [InlineData(15, 10)] - public void ToTextRange_ForNsRange_Converts(int position, int length) - { - var nsRange = new NSRange(position, length); - - var textRange = nsRange.ToTextRange(); - - Assert.NotNull(textRange); - Assert.IsType(textRange); - Assert.Equal(position, textRange.Position); - Assert.Equal(length, textRange.Length); - } - - [Theory] - [InlineData(0, 0)] - [InlineData(0, 10)] - [InlineData(5, 10)] - [InlineData(9, 10)] - [InlineData(15, 10)] - public void ToNsRange_ForTextRange_Converts(int position, int length) - { - var textRange = new TextRange(position, length); - - var nsRange = textRange.ToNSRange(); - - Assert.IsType(nsRange); - Assert.Equal(position, nsRange.Location); - Assert.Equal(length, nsRange.Length); - } - } -} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Extensions/NSStringExtensions/NSStringExtensionsTests.cs b/Softeq.XToolkit.Common.iOS.Tests/Extensions/NSStringExtensions/NSStringExtensionsTests.cs deleted file mode 100644 index d5c94bb6f..000000000 --- a/Softeq.XToolkit.Common.iOS.Tests/Extensions/NSStringExtensions/NSStringExtensionsTests.cs +++ /dev/null @@ -1,193 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System; -using System.Diagnostics.CodeAnalysis; -using Foundation; -using Softeq.XToolkit.Common.iOS.Extensions; -using UIKit; -using Xunit; - -namespace Softeq.XToolkit.Common.iOS.Tests.Extensions.NSStringExtensions -{ - [SuppressMessage("ReSharper", "InvokeAsExtensionMethod", Justification = "Need for tests")] - public class NSStringExtensionsTests - { - [Fact] - public void NewParagraphStyle_CreateNewInstance_ReturnsInstance() - { - var result = iOS.Extensions.NSStringExtensions.NewParagraphStyle; - - Assert.IsType(result); - } - - [Fact] - public void ToNSUrl_Null_ThrowsArgumentNullException() - { - var link = null as string; - - Assert.Throws(() => - { - iOS.Extensions.NSStringExtensions.ToNSUrl(link!); - }); - } - - [Theory] - [InlineData("")] - public void ToNSUrl_InvalidLink_ThrowsUriFormatException(string link) - { - Assert.Throws(() => - { - link.ToNSUrl(); - }); - } - - [Theory] - [InlineData("localhost", "http://localhost/")] - [InlineData("softeq.com", "http://softeq.com/")] - [InlineData("www.softeq.com", "http://www.softeq.com/")] - [InlineData("http://softeq.com", "http://softeq.com/")] - [InlineData("https://softeq.com", "https://softeq.com/")] - [InlineData("https://www.softeq.com/", "https://www.softeq.com/")] - [InlineData("https://softeq.com//", "https://softeq.com//")] - [InlineData("https://example.com/??a", "https://example.com/??a")] - [InlineData("https://example.com/?arg=1", "https://example.com/?arg=1")] - [InlineData("https://api.example.com:3000/", "https://api.example.com:3000/")] - [InlineData("https://example.com/?arg=1&asd=&q=тест+-+test", "https://example.com/?arg=1&asd=&q=%D1%82%D0%B5%D1%81%D1%82+-+test")] - [InlineData("https://com.dev.api.example.com/&a=1?b=2", "https://com.dev.api.example.com/&a=1?b=2")] - [InlineData("https://com.dev.api.example.com/?a=b=", "https://com.dev.api.example.com/?a=b=")] - [InlineData("http://localhost/../..", "http://localhost/")] - [InlineData("http://localhost/%2E%2E/%2E%2E", "http://localhost/")] - [InlineData("htTP://localhost/", "http://localhost/")] - [InlineData("http://localHOST/", "http://localhost/")] - [InlineData("https://localhost:3000", "https://localhost:3000/")] - [InlineData("https://127.0.0.1", "https://127.0.0.1/")] - [InlineData("https://127.0.0.1:8080", "https://127.0.0.1:8080/")] - [InlineData("https://user:password@www.example.com:80/Home/Index.htm?q1=v1&q2=v2#FragmentName", "https://user:password@www.example.com:80/Home/Index.htm?q1=v1&q2=v2#FragmentName")] - [InlineData("ftp://example.com", "ftp://example.com/")] - [InlineData("mailto://example.com", "mailto://example.com")] - [InlineData("mailto:test@example.com", "mailto:test@example.com")] - [InlineData("test://localhost/", "test://localhost/")] - [InlineData("localhost:3000", "localhost:3000")] - [InlineData(@"\\some\directory\name\", "file://some/directory/name/")] - public void ToNSUrl_ValidLink_ReturnsExpectedNSUrl(string link, string expectedLink) - { - var result = link.ToNSUrl(); - - Assert.IsType(result); - Assert.Equal(expectedLink, result.AbsoluteString); - } - - [Fact] - public void BuildAttributedString_Null_ThrowsArgumentNullException() - { - var input = null as string; - - Assert.Throws(() => - { - iOS.Extensions.NSStringExtensions.BuildAttributedString(input!); - }); - } - - [Theory] - [InlineData("")] - [InlineData("test string")] - public void BuildAttributedString_NotNull_ReturnsAttributedString(string input) - { - var result = input.BuildAttributedString(); - - Assert.IsType(result); - Assert.Equal(input, result.Value); - } - - [Fact] - public void BuildAttributedStringFromHtml_Null_ThrowsArgumentNullException() - { - var input = null as string; - - Assert.Throws(() => - { - iOS.Extensions.NSStringExtensions.BuildAttributedStringFromHtml(input!); - }); - } - - [Theory] - [InlineData("", "")] - [InlineData("test string", "test string")] - [InlineData("

test string

", "test string\n")] - [InlineData("test string", "test string")] - [InlineData("test string", "test string")] - [InlineData("

test - string

", "test - string\n")] - [InlineData("test string", "test string")] - [InlineData("test string", "test string")] - public void BuildAttributedStringFromHtml_NotNull_ReturnsAttributedString(string html, string expectedResult) - { - var result = html.BuildAttributedStringFromHtml(); - - Assert.IsType(result); - Assert.Equal(expectedResult, result.Value); - } - - [Theory] - [InlineData("", NSStringEncoding.Unicode, "")] - [InlineData("test строка \u2022", NSStringEncoding.UTF8, "test строка •")] - [InlineData("test строка 👍", NSStringEncoding.UTF8, "test строка 👍")] - [InlineData("“test” string", NSStringEncoding.UTF8, "“test” string")] - [InlineData("test \u203C string", NSStringEncoding.UTF8, "test ‼ string")] - public void BuildAttributedStringFromHtml_SpecificEncoding_ReturnsAttributedString( - string html, - NSStringEncoding encoding, - string expectedResult) - { - var result = html.BuildAttributedStringFromHtml(encoding); - - Assert.IsType(result); - Assert.Equal(expectedResult, result.Value); - } - - [Fact] - public void DetectLinks_NullAttributedString_ThrowsNullReferenceException() - { - var obj = null as NSMutableAttributedString; - - Assert.Throws(() => - { - iOS.Extensions.NSStringExtensions.DetectLinks( - obj!, - UIColor.Red, - NSUnderlineStyle.Single, - false, - out _); - }); - } - - [Fact] - public void DetectLinks_NullColor_ExecutesWithoutExceptions() - { - var obj = "test string".BuildAttributedString(); - var color = null as UIColor; - - var modifiedObj = obj.DetectLinks(color!, NSUnderlineStyle.Single, false, out _); - - Assert.IsType(modifiedObj); - } - - [Theory] - [InlineData("link: https://softeq.com", 1)] - [InlineData("link: https://softeq.com, google: google.com", 2)] - [InlineData("test string: https://www.softeq.com/featured_projects#mobile, example: http://example.com/test/page", 2)] - public void DetectLinks_TextWithLinks_ExecutesWithoutExceptions(string text, int linkCount) - { - var obj = text.BuildAttributedString(); - - var modifiedObj = obj.DetectLinks( - UIColor.Red, - NSUnderlineStyle.Single, - true, - out var tags); - - Assert.IsType(modifiedObj); - Assert.Equal(linkCount, tags.Length); - } - } -} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Extensions/UIColorExtensionsTests/UIColorExtensionsTests.cs b/Softeq.XToolkit.Common.iOS.Tests/Extensions/UIColorExtensionsTests/UIColorExtensionsTests.cs deleted file mode 100644 index 07d6586e9..000000000 --- a/Softeq.XToolkit.Common.iOS.Tests/Extensions/UIColorExtensionsTests/UIColorExtensionsTests.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System; -using Softeq.XToolkit.Common.iOS.Extensions; -using UIKit; -using Xunit; - -namespace Softeq.XToolkit.Common.iOS.Tests.Extensions.UIColorExtensionsTests -{ - public class UIColorExtensionsTests - { - [Theory] - [InlineData("#333333", "UIColor [A=255, R=51, G=51, B=51]")] - [InlineData("0000FF", "UIColor [A=255, R=0, G=0, B=255]")] - [InlineData("00FF00", "UIColor [A=255, R=0, G=255, B=0]")] - [InlineData("F00", "UIColor [A=255, R=255, G=0, B=0]")] - [InlineData("#008080", "UIColor [A=255, R=0, G=128, B=128]")] - [InlineData("FA8072", "UIColor [A=255, R=250, G=128, B=114]")] - [InlineData("350027", "UIColor [A=255, R=53, G=0, B=39]")] - public void UIColorFromHex_CorrectValueWithoutAlpha_ReturnsValidColor(string hexColor, string expected) - { - var result = hexColor.UIColorFromHex(); - - Assert.Equal(expected, result.ToString()); - } - - [Theory] - [InlineData(1, 1)] - [InlineData(2, 1)] - [InlineData(0, 0)] - [InlineData(-2, 0)] - [InlineData(0.5, 0.5)] - [InlineData(0.33, 0.33)] - public void UIColorFromHex_CorrectValueWithAlpha_ReturnsValidColor(float alpha, float expectedAlpha) - { - var result = "#FFF".UIColorFromHex(alpha); - - Assert.Equal(expectedAlpha, result.CGColor.Alpha); - } - - [Theory] - [InlineData("")] - [InlineData("#00")] - [InlineData("1122")] - [InlineData("#11223344")] - [InlineData("11223344")] - public void UIColorFromHex_IncorrectValue_ThrowsArgumentOutOfRangeException(string hexColor) - { - Assert.Throws(() => - { - hexColor.UIColorFromHex(); - }); - } - - [Theory] - [InlineData(255, 255, 255, "#FFFFFF")] - [InlineData(0, 0, 0, "#000000")] - [InlineData(6, 27, 250, "#061BFA")] - [InlineData(0, 52, -1, "#0034FF")] - public void ToHex_UIColor_ReturnsHexColor(int red, int green, int blue, string expectedHex) - { - var color = UIColor.FromRGB(red, green, blue); - - var result = color.ToHex(); - - Assert.Equal(expectedHex, result); - } - } -} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Extensions/UITextViewExtensionsTests/UITextViewExtensionsTests.cs b/Softeq.XToolkit.Common.iOS.Tests/Extensions/UITextViewExtensionsTests/UITextViewExtensionsTests.cs deleted file mode 100644 index 23d42da63..000000000 --- a/Softeq.XToolkit.Common.iOS.Tests/Extensions/UITextViewExtensionsTests/UITextViewExtensionsTests.cs +++ /dev/null @@ -1,106 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System; -using System.Diagnostics.CodeAnalysis; -using System.Threading.Tasks; -using Foundation; -using Softeq.XToolkit.Common.iOS.Extensions; -using Softeq.XToolkit.Common.iOS.Tests.TextFilters.GroupFilterTests; -using UIKit; -using Xunit; - -namespace Softeq.XToolkit.Common.iOS.Tests.Extensions.UITextViewExtensionsTests -{ - [SuppressMessage("ReSharper", "InvokeAsExtensionMethod", Justification = "Need for tests")] - public class UITextViewExtensionsTests - { - [Fact] - public void SetFilter_UITextFieldIsNull_ThrowsNullReferenceException() - { - var textField = (UITextField) null!; - - Assert.Throws(() => - { - UITextViewExtensions.SetFilter(textField, new MockTextFilter(true)); - }); - } - - [Fact] - public async Task SetFilter_UITextFieldWithNullFilter_ThrowsArgumentNullException() - { - var result = Helpers.RunOnUIThreadAsync(() => - { - var textField = new UITextField(); - - textField.SetFilter(null!); - - return false; - }); - - await Assert.ThrowsAsync(() => result); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task SetFilter_UITextFieldWithFilter_ReturnsExpected(bool expectedResult) - { - var result = await Helpers.RunOnUIThreadAsync(() => - { - var textField = new UITextField(); - var filter = new MockTextFilter(expectedResult); - - textField.SetFilter(filter); - - return textField.ShouldChangeCharacters(textField, new NSRange(1, 0), "new"); - }); - - Assert.Equal(expectedResult, result); - } - - [Fact] - public void SetFilter_UITextViewIsNull_ThrowsNullReferenceException() - { - var textView = (UITextView) null!; - - Assert.Throws(() => - { - UITextViewExtensions.SetFilter(textView, new MockTextFilter(true)); - }); - } - - [Fact] - public async Task SetFilter_UITextViewWithNullFilter_ThrowsArgumentNullException() - { - var result = Helpers.RunOnUIThreadAsync(() => - { - var textField = new UITextField(); - - textField.SetFilter(null!); - - return false; - }); - - await Assert.ThrowsAsync(() => result); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task SetFilter_UITextViewWithFilter_ReturnsExpected(bool expectedResult) - { - var result = await Helpers.RunOnUIThreadAsync(() => - { - var textView = new UITextView(); - var filter = new MockTextFilter(expectedResult); - - textView.SetFilter(filter); - - return textView.ShouldChangeText!(textView, new NSRange(1, 0), "new"); - }); - - Assert.Equal(expectedResult, result); - } - } -} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Helpers.cs b/Softeq.XToolkit.Common.iOS.Tests/Helpers.cs deleted file mode 100644 index c412eac6e..000000000 --- a/Softeq.XToolkit.Common.iOS.Tests/Helpers.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System; -using System.Threading.Tasks; -using UIKit; - -namespace Softeq.XToolkit.Common.iOS.Tests -{ - public static class Helpers - { - public static Task RunOnUIThreadAsync(Func func) - { - var tcs = new TaskCompletionSource(); - UIApplication.SharedApplication.BeginInvokeOnMainThread(() => - { - try - { - var result = func(); - tcs.SetResult(result); - } - catch (Exception e) - { - tcs.SetException(e); - } - }); - return tcs.Task; - } - } -} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Info.plist b/Softeq.XToolkit.Common.iOS.Tests/Info.plist deleted file mode 100644 index 176aea3ab..000000000 --- a/Softeq.XToolkit.Common.iOS.Tests/Info.plist +++ /dev/null @@ -1,38 +0,0 @@ - - - - - CFBundleName - XT.Common.Tests - CFBundleIdentifier - com.softeq.xtoolkit-common-ios-tests - CFBundleShortVersionString - 1.0 - CFBundleVersion - 1.0 - LSRequiresIPhoneOS - - MinimumOSVersion - 12.4 - UIDeviceFamily - - 1 - 2 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UILaunchStoryboardName - LaunchScreen - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - - UIUserInterfaceStyle - Light - - diff --git a/Softeq.XToolkit.Common.iOS.Tests/IosMainThreadExecutorTests/IosMainThreadExecutorTests.cs b/Softeq.XToolkit.Common.iOS.Tests/IosMainThreadExecutorTests/IosMainThreadExecutorTests.cs deleted file mode 100644 index 91d7091d5..000000000 --- a/Softeq.XToolkit.Common.iOS.Tests/IosMainThreadExecutorTests/IosMainThreadExecutorTests.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Threading.Tasks; -using Xunit; - -namespace Softeq.XToolkit.Common.iOS.Tests.IosMainThreadExecutorTests -{ - public class IosMainThreadExecutorTests - { - private readonly IosMainThreadExecutor _executor; - - public IosMainThreadExecutorTests() - { - _executor = new IosMainThreadExecutor(); - } - - [Fact] - public async Task IsMainThread_InMainThread_ReturnsTrue() - { - var result = await Helpers.RunOnUIThreadAsync(() => _executor.IsMainThread); - - Assert.True(result); - } - - [Fact] - public async Task IsMainThread_NotInMainThread_ReturnsFalse() - { - var result = await Task.Run(() => _executor.IsMainThread); - - Assert.False(result); - } - } -} diff --git a/Softeq.XToolkit.Common.iOS.Tests/LaunchScreen.storyboard b/Softeq.XToolkit.Common.iOS.Tests/LaunchScreen.storyboard deleted file mode 100644 index 535551fca..000000000 --- a/Softeq.XToolkit.Common.iOS.Tests/LaunchScreen.storyboard +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Softeq.XToolkit.Common.iOS.Tests/Main.cs b/Softeq.XToolkit.Common.iOS.Tests/Main.cs deleted file mode 100644 index 6023e350d..000000000 --- a/Softeq.XToolkit.Common.iOS.Tests/Main.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using UIKit; - -namespace Softeq.XToolkit.Common.iOS.Tests -{ -#pragma warning disable SA1649 - public class Application -#pragma warning restore SA1649 - { - private static void Main(string[] args) - { - UIApplication.Main(args, null, typeof(AppDelegate)); - } - } -} diff --git a/Softeq.XToolkit.Common.iOS.Tests/MauiProgram.cs b/Softeq.XToolkit.Common.iOS.Tests/MauiProgram.cs new file mode 100644 index 000000000..ba2105586 --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/MauiProgram.cs @@ -0,0 +1,31 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using Microsoft.Extensions.Logging; +using Microsoft.Maui.Hosting; +using Xunit.Runners.Maui; + +namespace Softeq.XToolkit.Common.iOS.Tests; + +public static class MauiProgram +{ + public static MauiApp CreateMauiApp() + { + var builder = MauiApp + .CreateBuilder() + .ConfigureTests(new TestOptions + { + Assemblies = + { + typeof(MauiProgram).Assembly + } + }) + .UseVisualRunner(); + +#if DEBUG + builder.Logging.AddDebug(); +#endif + + return builder.Build(); + } +} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/AppDelegate.cs b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/AppDelegate.cs new file mode 100644 index 000000000..fc53d8a47 --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/AppDelegate.cs @@ -0,0 +1,14 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using Foundation; +using Microsoft.Maui; +using Microsoft.Maui.Hosting; + +namespace Softeq.XToolkit.Common.iOS.Tests; + +[Register(nameof(AppDelegate))] +public class AppDelegate : MauiUIApplicationDelegate +{ + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); +} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/NSDateExtensionsTests/NSDateExtensionsTests.cs b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/NSDateExtensionsTests/NSDateExtensionsTests.cs new file mode 100644 index 000000000..be0137190 --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/NSDateExtensionsTests/NSDateExtensionsTests.cs @@ -0,0 +1,78 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using Foundation; +using Softeq.XToolkit.Common.iOS.Extensions; +using Xunit; + +namespace Softeq.XToolkit.Common.iOS.Tests.Extensions.NSDateExtensionsTests; + +public class NSDateExtensionsTests +{ + [Theory] + [InlineData(-100000)] + [InlineData(0)] + [InlineData(100000)] + public void ToUtcDateTime_ForNsDate_ConvertsToUtcDateTime(double secondsSinceNow) + { + var nsDate = NSDate.FromTimeIntervalSinceNow(secondsSinceNow); + var calendar = NSCalendar.CurrentCalendar; + calendar.TimeZone = NSTimeZone.FromGMT(0); + var utcComponents = CreateComponents(nsDate, calendar); + + var dateTime = nsDate.ToUtcDateTime(); + + Assert.IsType(dateTime); + Assert.Equal(DateTimeKind.Utc, dateTime.Kind); + Assert.Equal(utcComponents.Year, dateTime.Year); + Assert.Equal(utcComponents.Month, dateTime.Month); + Assert.Equal(utcComponents.Day, dateTime.Day); + Assert.Equal(utcComponents.Hour, dateTime.Hour); + Assert.Equal(utcComponents.Minute, dateTime.Minute); + Assert.Equal(utcComponents.Second, dateTime.Second); + } + + [Theory] + [InlineData(-100000)] + [InlineData(0)] + [InlineData(100000)] + public void ToLocalDateTime_ForNsDate_ConvertsToLocalDateTime(double secondsSinceNow) + { + var nsDate = NSDate.FromTimeIntervalSinceNow(secondsSinceNow); + var localComponents = CreateComponents(nsDate, NSCalendar.CurrentCalendar); + + var dateTime = nsDate.ToLocalDateTime(); + + Assert.IsType(dateTime); + Assert.Equal(DateTimeKind.Local, dateTime.Kind); + Assert.Equal(localComponents.Year, dateTime.Year); + Assert.Equal(localComponents.Month, dateTime.Month); + Assert.Equal(localComponents.Day, dateTime.Day); + Assert.Equal(localComponents.Hour, dateTime.Hour); + Assert.Equal(localComponents.Minute, dateTime.Minute); + Assert.Equal(localComponents.Second, dateTime.Second); + } + + [Theory] + [InlineData(-100000)] + [InlineData(0)] + [InlineData(100000)] + public void ToLocalDateTimeAndBack_ForNsDate_ReturnsSameNsDate(double secondsSinceNow) + { + var nsDate = NSDate.FromTimeIntervalSinceNow(secondsSinceNow); + + var dateTime = nsDate.ToLocalDateTime(); + var newNsDate = dateTime.ToNsDate(); + + Assert.Equal(nsDate.SecondsSince1970, newNsDate.SecondsSince1970, 3); + } + + private NSDateComponents CreateComponents(NSDate date, NSCalendar calendar) + { + return calendar.Components( + NSCalendarUnit.Second | NSCalendarUnit.Minute | NSCalendarUnit.Hour | + NSCalendarUnit.Day | NSCalendarUnit.Month | NSCalendarUnit.Year, + date); + } +} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/NSLocaleExtensionsTests/NSLocaleExtensionsTests.cs b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/NSLocaleExtensionsTests/NSLocaleExtensionsTests.cs new file mode 100644 index 000000000..cc6fe6df1 --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/NSLocaleExtensionsTests/NSLocaleExtensionsTests.cs @@ -0,0 +1,42 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using System.Diagnostics.CodeAnalysis; +using Foundation; +using Softeq.XToolkit.Common.iOS.Extensions; +using Xunit; + +namespace Softeq.XToolkit.Common.iOS.Tests.Extensions.NSLocaleExtensionsTests; + +[SuppressMessage("ReSharper", "InvokeAsExtensionMethod", Justification = "Need for tests")] +public class NSLocaleExtensionsTests +{ + [Fact] + public void Is24HourFormat_Null_ThrowsArgumentNullException() + { + var locale = null as NSLocale; + + Assert.Throws(() => + { + NSLocaleExtensions.Is24HourFormat(locale!); + }); + } + + [Theory] + [InlineData("en_US", false)] + [InlineData("en_CA", false)] + [InlineData("fil_PH", false)] + [InlineData("ru_RU", true)] + [InlineData("en_BY", true)] + [InlineData("de_DE", true)] + [InlineData("it_IT", true)] + public void Is24HourFormat_24HourLocaleFormat_ReturnsTrue(string localeId, bool expectedResult) + { + var locale = NSLocale.FromLocaleIdentifier(localeId); + + var result = locale.Is24HourFormat(); + + Assert.Equal(expectedResult, result); + } +} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/NSStringExtensions/NSStringExtensionsTests.cs b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/NSStringExtensions/NSStringExtensionsTests.cs new file mode 100644 index 000000000..0484191af --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/NSStringExtensions/NSStringExtensionsTests.cs @@ -0,0 +1,193 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using System.Diagnostics.CodeAnalysis; +using Foundation; +using Softeq.XToolkit.Common.iOS.Extensions; +using UIKit; +using Xunit; +using CommonIosExtensions = Softeq.XToolkit.Common.iOS.Extensions; + +namespace Softeq.XToolkit.Common.iOS.Tests.Extensions.NSStringExtensions; + +[SuppressMessage("ReSharper", "InvokeAsExtensionMethod", Justification = "Need for tests")] +public class NSStringExtensionsTests +{ + [Fact] + public void NewParagraphStyle_CreateNewInstance_ReturnsInstance() + { + var result = CommonIosExtensions.NSStringExtensions.NewParagraphStyle; + + Assert.IsType(result); + } + + [Fact] + public void ToNSUrl_Null_ThrowsArgumentNullException() + { + var link = null as string; + + Assert.Throws(() => + { + CommonIosExtensions.NSStringExtensions.ToNSUrl(link!); + }); + } + + [Theory] + [InlineData("")] + public void ToNSUrl_InvalidLink_ThrowsUriFormatException(string link) + { + Assert.Throws(() => + { + link.ToNSUrl(); + }); + } + + [Theory] + [InlineData("localhost", "http://localhost/")] + [InlineData("softeq.com", "http://softeq.com/")] + [InlineData("www.softeq.com", "http://www.softeq.com/")] + [InlineData("http://softeq.com", "http://softeq.com/")] + [InlineData("https://softeq.com", "https://softeq.com/")] + [InlineData("https://www.softeq.com/", "https://www.softeq.com/")] + [InlineData("https://softeq.com//", "https://softeq.com//")] + [InlineData("https://example.com/??a", "https://example.com/??a")] + [InlineData("https://example.com/?arg=1", "https://example.com/?arg=1")] + [InlineData("https://api.example.com:3000/", "https://api.example.com:3000/")] + [InlineData("https://example.com/?arg=1&asd=&q=тест+-+test", "https://example.com/?arg=1&asd=&q=%D1%82%D0%B5%D1%81%D1%82+-+test")] + [InlineData("https://com.dev.api.example.com/&a=1?b=2", "https://com.dev.api.example.com/&a=1?b=2")] + [InlineData("https://com.dev.api.example.com/?a=b=", "https://com.dev.api.example.com/?a=b=")] + [InlineData("http://localhost/../..", "http://localhost/")] + [InlineData("http://localhost/%2E%2E/%2E%2E", "http://localhost/")] + [InlineData("htTP://localhost/", "http://localhost/")] + [InlineData("http://localHOST/", "http://localhost/")] + [InlineData("https://localhost:3000", "https://localhost:3000/")] + [InlineData("https://127.0.0.1", "https://127.0.0.1/")] + [InlineData("https://127.0.0.1:8080", "https://127.0.0.1:8080/")] + [InlineData("https://user:password@www.example.com:80/Home/Index.htm?q1=v1&q2=v2#FragmentName", "https://user:password@www.example.com:80/Home/Index.htm?q1=v1&q2=v2#FragmentName")] + [InlineData("ftp://example.com", "ftp://example.com/")] + [InlineData("mailto://example.com", "mailto://example.com")] + [InlineData("mailto:test@example.com", "mailto:test@example.com")] + [InlineData("test://localhost/", "test://localhost/")] + [InlineData("localhost:3000", "localhost:3000")] + [InlineData(@"\\some\directory\name\", "file://some/directory/name/")] + public void ToNSUrl_ValidLink_ReturnsExpectedNSUrl(string link, string expectedLink) + { + var result = link.ToNSUrl(); + + Assert.IsType(result); + Assert.Equal(expectedLink, result.AbsoluteString); + } + + [Fact] + public void BuildAttributedString_Null_ThrowsArgumentNullException() + { + var input = null as string; + + Assert.Throws(() => + { + CommonIosExtensions.NSStringExtensions.BuildAttributedString(input!); + }); + } + + [Theory] + [InlineData("")] + [InlineData("test string")] + public void BuildAttributedString_NotNull_ReturnsAttributedString(string input) + { + var result = input.BuildAttributedString(); + + Assert.IsType(result); + Assert.Equal(input, result.Value); + } + + [Fact] + public void BuildAttributedStringFromHtml_Null_ThrowsArgumentNullException() + { + var input = null as string; + + Assert.Throws(() => + { + CommonIosExtensions.NSStringExtensions.BuildAttributedStringFromHtml(input!); + }); + } + + [Theory] + [InlineData("", "")] + [InlineData("test string", "test string")] + [InlineData("

test string

", "test string\n")] + [InlineData("test string", "test string")] + [InlineData("test string", "test string")] + [InlineData("

test - string

", "test - string\n")] + [InlineData("test string", "test string")] + [InlineData("test string", "test string")] + public void BuildAttributedStringFromHtml_NotNull_ReturnsAttributedString(string html, string expectedResult) + { + var result = html.BuildAttributedStringFromHtml(); + + Assert.IsType(result); + Assert.Equal(expectedResult, result.Value); + } + + [Theory] + [InlineData("", NSStringEncoding.Unicode, "")] + [InlineData("test строка \u2022", NSStringEncoding.UTF8, "test строка •")] + [InlineData("test строка 👍", NSStringEncoding.UTF8, "test строка 👍")] + [InlineData("“test” string", NSStringEncoding.UTF8, "“test” string")] + [InlineData("test \u203C string", NSStringEncoding.UTF8, "test ‼ string")] + public void BuildAttributedStringFromHtml_SpecificEncoding_ReturnsAttributedString( + string html, + NSStringEncoding encoding, + string expectedResult) + { + var result = html.BuildAttributedStringFromHtml(encoding); + + Assert.IsType(result); + Assert.Equal(expectedResult, result.Value); + } + + [Fact] + public void DetectLinks_NullAttributedString_ThrowsNullReferenceException() + { + var obj = null as NSMutableAttributedString; + + Assert.Throws(() => + { + CommonIosExtensions.NSStringExtensions.DetectLinks( + obj!, + UIColor.Red, + NSUnderlineStyle.Single, + false, + out _); + }); + } + + [Fact] + public void DetectLinks_NullColor_ExecutesWithoutExceptions() + { + var obj = "test string".BuildAttributedString(); + var color = null as UIColor; + + var modifiedObj = obj.DetectLinks(color!, NSUnderlineStyle.Single, false, out _); + + Assert.IsType(modifiedObj); + } + + [Theory] + [InlineData("link: https://softeq.com", 1)] + [InlineData("link: https://softeq.com, google: google.com", 2)] + [InlineData("test string: https://www.softeq.com/featured_projects#mobile, example: http://example.com/test/page", 2)] + public void DetectLinks_TextWithLinks_ExecutesWithoutExceptions(string text, int linkCount) + { + var obj = text.BuildAttributedString(); + + var modifiedObj = obj.DetectLinks( + UIColor.Red, + NSUnderlineStyle.Single, + true, + out var tags); + + Assert.IsType(modifiedObj); + Assert.Equal(linkCount, tags.Length); + } +} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/NsRangeExtensionsTests/NSRangeExtensionsTests.cs b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/NsRangeExtensionsTests/NSRangeExtensionsTests.cs new file mode 100644 index 000000000..6dc66e71a --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/NsRangeExtensionsTests/NSRangeExtensionsTests.cs @@ -0,0 +1,47 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using Foundation; +using Softeq.XToolkit.Common.Helpers; +using Softeq.XToolkit.Common.iOS.Extensions; +using Xunit; + +namespace Softeq.XToolkit.Common.iOS.Tests.Extensions.NsRangeExtensionsTests; + +public class NSRangeExtensionsTests +{ + [Theory] + [InlineData(0, 0)] + [InlineData(0, 10)] + [InlineData(5, 10)] + [InlineData(9, 10)] + [InlineData(15, 10)] + public void ToTextRange_ForNsRange_Converts(int position, int length) + { + var nsRange = new NSRange(position, length); + + var textRange = nsRange.ToTextRange(); + + Assert.NotNull(textRange); + Assert.IsType(textRange); + Assert.Equal(position, textRange.Position); + Assert.Equal(length, textRange.Length); + } + + [Theory] + [InlineData(0, 0)] + [InlineData(0, 10)] + [InlineData(5, 10)] + [InlineData(9, 10)] + [InlineData(15, 10)] + public void ToNsRange_ForTextRange_Converts(int position, int length) + { + var textRange = new TextRange(position, length); + + var nsRange = textRange.ToNSRange(); + + Assert.IsType(nsRange); + Assert.Equal(position, nsRange.Location); + Assert.Equal(length, nsRange.Length); + } +} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/UIColorExtensionsTests/UIColorExtensionsTests.cs b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/UIColorExtensionsTests/UIColorExtensionsTests.cs new file mode 100644 index 000000000..990fe478f --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/UIColorExtensionsTests/UIColorExtensionsTests.cs @@ -0,0 +1,69 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using Softeq.XToolkit.Common.iOS.Extensions; +using UIKit; +using Xunit; + +namespace Softeq.XToolkit.Common.iOS.Tests.Extensions.UIColorExtensionsTests; + +public class UIColorExtensionsTests +{ + [Theory] + [InlineData("#333333", "UIColor [A=255, R=51, G=51, B=51]")] + [InlineData("0000FF", "UIColor [A=255, R=0, G=0, B=255]")] + [InlineData("00FF00", "UIColor [A=255, R=0, G=255, B=0]")] + [InlineData("F00", "UIColor [A=255, R=255, G=0, B=0]")] + [InlineData("#008080", "UIColor [A=255, R=0, G=128, B=128]")] + [InlineData("FA8072", "UIColor [A=255, R=250, G=128, B=114]")] + [InlineData("350027", "UIColor [A=255, R=53, G=0, B=39]")] + public void UIColorFromHex_CorrectValueWithoutAlpha_ReturnsValidColor(string hexColor, string expected) + { + var result = hexColor.UIColorFromHex(); + + Assert.Equal(expected, result.ToString()); + } + + [Theory] + [InlineData(1, 1)] + [InlineData(2, 1)] + [InlineData(0, 0)] + [InlineData(-2, 0)] + [InlineData(0.5, 0.5)] + [InlineData(0.33, 0.33)] + public void UIColorFromHex_CorrectValueWithAlpha_ReturnsValidColor(float alpha, float expectedAlpha) + { + var result = "#FFF".UIColorFromHex(alpha); + + Assert.Equal(expectedAlpha, result.CGColor.Alpha); + } + + [Theory] + [InlineData("")] + [InlineData("#00")] + [InlineData("1122")] + [InlineData("#11223344")] + [InlineData("11223344")] + public void UIColorFromHex_IncorrectValue_ThrowsArgumentOutOfRangeException(string hexColor) + { + Assert.Throws(() => + { + hexColor.UIColorFromHex(); + }); + } + + [Theory] + [InlineData(255, 255, 255, "#FFFFFF")] + [InlineData(0, 0, 0, "#000000")] + [InlineData(6, 27, 250, "#061BFA")] + [InlineData(0, 52, -1, "#0034FF")] + public void ToHex_UIColor_ReturnsHexColor(int red, int green, int blue, string expectedHex) + { + var color = UIColor.FromRGB(red, green, blue); + + var result = color.ToHex(); + + Assert.Equal(expectedHex, result); + } +} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/UITextViewExtensionsTests/UITextViewExtensionsTests.cs b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/UITextViewExtensionsTests/UITextViewExtensionsTests.cs new file mode 100644 index 000000000..128f34c53 --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Extensions/UITextViewExtensionsTests/UITextViewExtensionsTests.cs @@ -0,0 +1,105 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using Foundation; +using Softeq.XToolkit.Common.iOS.Extensions; +using Softeq.XToolkit.Common.iOS.Tests.TextFilters.GroupFilterTests; +using UIKit; +using Xunit; + +namespace Softeq.XToolkit.Common.iOS.Tests.Extensions.UITextViewExtensionsTests; + +[SuppressMessage("ReSharper", "InvokeAsExtensionMethod", Justification = "Need for tests")] +public class UITextViewExtensionsTests +{ + [Fact] + public void SetFilter_UITextFieldIsNull_ThrowsNullReferenceException() + { + var textField = (UITextField) null!; + + Assert.Throws(() => + { + UITextViewExtensions.SetFilter(textField, new MockTextFilter(true)); + }); + } + + [Fact] + public async Task SetFilter_UITextFieldWithNullFilter_ThrowsArgumentNullException() + { + var result = Helpers.RunOnUIThreadAsync(() => + { + var textField = new UITextField(); + + textField.SetFilter(null!); + + return false; + }); + + await Assert.ThrowsAsync(() => result); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task SetFilter_UITextFieldWithFilter_ReturnsExpected(bool expectedResult) + { + var result = await Helpers.RunOnUIThreadAsync(() => + { + var textField = new UITextField(); + var filter = new MockTextFilter(expectedResult); + + textField.SetFilter(filter); + + return textField.ShouldChangeCharacters(textField, new NSRange(1, 0), "new"); + }); + + Assert.Equal(expectedResult, result); + } + + [Fact] + public void SetFilter_UITextViewIsNull_ThrowsNullReferenceException() + { + var textView = (UITextView) null!; + + Assert.Throws(() => + { + UITextViewExtensions.SetFilter(textView, new MockTextFilter(true)); + }); + } + + [Fact] + public async Task SetFilter_UITextViewWithNullFilter_ThrowsArgumentNullException() + { + var result = Helpers.RunOnUIThreadAsync(() => + { + var textField = new UITextField(); + + textField.SetFilter(null!); + + return false; + }); + + await Assert.ThrowsAsync(() => result); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task SetFilter_UITextViewWithFilter_ReturnsExpected(bool expectedResult) + { + var result = await Helpers.RunOnUIThreadAsync(() => + { + var textView = new UITextView(); + var filter = new MockTextFilter(expectedResult); + + textView.SetFilter(filter); + + return textView.ShouldChangeText!(textView, new NSRange(1, 0), "new"); + }); + + Assert.Equal(expectedResult, result); + } +} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Helpers.cs b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Helpers.cs new file mode 100644 index 000000000..873dbc476 --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Helpers.cs @@ -0,0 +1,29 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using System.Threading.Tasks; +using UIKit; + +namespace Softeq.XToolkit.Common.iOS.Tests; + +public static class Helpers +{ + public static Task RunOnUIThreadAsync(Func func) + { + var tcs = new TaskCompletionSource(); + UIApplication.SharedApplication.BeginInvokeOnMainThread(() => + { + try + { + var result = func(); + tcs.SetResult(result); + } + catch (Exception e) + { + tcs.SetException(e); + } + }); + return tcs.Task; + } +} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Info.plist b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Info.plist new file mode 100644 index 000000000..0004a4fde --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/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/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/IosMainThreadExecutorTests/IosMainThreadExecutorTests.cs b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/IosMainThreadExecutorTests/IosMainThreadExecutorTests.cs new file mode 100644 index 000000000..32e090758 --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/IosMainThreadExecutorTests/IosMainThreadExecutorTests.cs @@ -0,0 +1,33 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System.Threading.Tasks; +using Xunit; + +namespace Softeq.XToolkit.Common.iOS.Tests.IosMainThreadExecutorTests; + +public class IosMainThreadExecutorTests +{ + private readonly IosMainThreadExecutor _executor; + + public IosMainThreadExecutorTests() + { + _executor = new IosMainThreadExecutor(); + } + + [Fact] + public async Task IsMainThread_InMainThread_ReturnsTrue() + { + var result = await Helpers.RunOnUIThreadAsync(() => _executor.IsMainThread); + + Assert.True(result); + } + + [Fact] + public async Task IsMainThread_NotInMainThread_ReturnsFalse() + { + var result = await Task.Run(() => _executor.IsMainThread); + + Assert.False(result); + } +} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Program.cs b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Program.cs new file mode 100644 index 000000000..d12ce8e7d --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/Program.cs @@ -0,0 +1,17 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using UIKit; + +namespace Softeq.XToolkit.Common.iOS.Tests; + +public class Program +{ + // This is the main entry point of the application. + 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/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/TextFilters/ForbiddenCharsFilterTests/ForbiddenCharsFilterTests.cs b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/TextFilters/ForbiddenCharsFilterTests/ForbiddenCharsFilterTests.cs new file mode 100644 index 000000000..4d1494cd7 --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/TextFilters/ForbiddenCharsFilterTests/ForbiddenCharsFilterTests.cs @@ -0,0 +1,91 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using System.Threading.Tasks; +using Foundation; +using Softeq.XToolkit.Common.iOS.TextFilters; +using UIKit; +using Xunit; + +namespace Softeq.XToolkit.Common.iOS.Tests.TextFilters.ForbiddenCharsFilterTests; + +public class ForbiddenCharsFilterTests +{ + [Fact] + public void Ctor_Empty_ReturnsITextFilter() + { + var obj = new ForbiddenCharsFilter(); + + Assert.IsAssignableFrom(obj); + } + + [Fact] + public void Ctor_Null_ThrowsArgumentNullException() + { + Assert.Throws(() => new ForbiddenCharsFilter(null!)); + } + + [Fact] + public void Ctor_SingleChar_ReturnsITextFilter() + { + var obj = new ForbiddenCharsFilter('@'); + + Assert.IsAssignableFrom(obj); + } + + [Fact] + public void Ctor_Chars_ReturnsITextFilter() + { + var obj = new ForbiddenCharsFilter('@', '+', '#'); + + Assert.IsAssignableFrom(obj); + } + + [Theory] + [InlineData("", "", "", true)] + [InlineData("", "old", "new", true)] + [InlineData("#$", "old", "new#a", false)] + [InlineData("#$@", "new", "@", false)] + [InlineData("#$@", "old", "new", true)] + public async Task ShouldChangeText_UITextFieldForbiddenChars_ReturnsExpectedResult( + string forbiddenChars, + string oldText, + string newText, + bool expectedResult) + { + var result = await Helpers.RunOnUIThreadAsync(() => + { + var textField = new UITextField(); + var range = new NSRange(oldText.Length - 1, newText.Length); + var chars = forbiddenChars.ToCharArray(); + var filter = new ForbiddenCharsFilter(chars); + + return filter.ShouldChangeText(textField, oldText, range, newText); + }); + + Assert.Equal(expectedResult, result); + } + + [Theory] + [InlineData(null, "old")] + [InlineData(null, null)] + public void ShouldChangeText_NullResponderAndOldText_ReturnsExpectedResult(UIResponder responder, string oldText) + { + var filter = new ForbiddenCharsFilter('%'); + + var result = filter.ShouldChangeText(responder, oldText, new NSRange(1, 1), "new"); + + Assert.True(result); + } + + [Fact] + public void ShouldChangeText_DefaultRange_ReturnsExpectedResult() + { + var filter = new ForbiddenCharsFilter('%'); + + var result = filter.ShouldChangeText(null!, "old", default, "new"); + + Assert.True(result); + } +} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/TextFilters/GroupFilterTests/GroupFilterTests.cs b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/TextFilters/GroupFilterTests/GroupFilterTests.cs new file mode 100644 index 000000000..d766c5cd9 --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/TextFilters/GroupFilterTests/GroupFilterTests.cs @@ -0,0 +1,65 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using Softeq.XToolkit.Common.iOS.TextFilters; +using Xunit; + +namespace Softeq.XToolkit.Common.iOS.Tests.TextFilters.GroupFilterTests; + +public class GroupFilterTests +{ + [Fact] + public void Ctor_Empty_ReturnsITextFilter() + { + var obj = new GroupFilter(); + + Assert.IsAssignableFrom(obj); + } + + [Fact] + public void Ctor_Null_ThrowsArgumentNullException() + { + Assert.Throws(() => new GroupFilter(null!)); + } + + [Fact] + public void Ctor_SingleFilter_ReturnsITextFilter() + { + var filter = new MockTextFilter(true); + + var obj = new GroupFilter(filter); + + Assert.IsAssignableFrom(obj); + } + + [Fact] + public void Ctor_Filters_ReturnsITextFilter() + { + var filter1 = new MockTextFilter(true); + var filter2 = new MockTextFilter(true); + + var obj = new GroupFilter(filter1, filter2); + + Assert.IsAssignableFrom(obj); + } + + [Theory] + [InlineData(true, true, true)] + [InlineData(true, false, false)] + [InlineData(false, true, false)] + [InlineData(false, false, false)] + public void ShouldChangeText_FilterReturnsResult_ReturnsExpected( + bool filter1Result, + bool filter2Result, + bool expectedResult) + { + var filter1 = new MockTextFilter(filter1Result); + var filter2 = new MockTextFilter(filter2Result); + var groupFilter = new GroupFilter(filter1, filter2); + + var result = groupFilter.ShouldChangeText(null!, string.Empty, default, string.Empty); + + Assert.Equal(expectedResult, result); + } +} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/TextFilters/GroupFilterTests/MockTextFilter.cs b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/TextFilters/GroupFilterTests/MockTextFilter.cs new file mode 100644 index 000000000..b465a5b2d --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/TextFilters/GroupFilterTests/MockTextFilter.cs @@ -0,0 +1,23 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using Foundation; +using Softeq.XToolkit.Common.iOS.TextFilters; +using UIKit; + +namespace Softeq.XToolkit.Common.iOS.Tests.TextFilters.GroupFilterTests; + +internal class MockTextFilter : ITextFilter +{ + private readonly bool _result; + + public MockTextFilter(bool result) + { + _result = result; + } + + public bool ShouldChangeText(UIResponder responder, string? oldText, NSRange range, string replacementString) + { + return _result; + } +} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/TextFilters/LengthFilterTests/LengthFilterTests.cs b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/TextFilters/LengthFilterTests/LengthFilterTests.cs new file mode 100644 index 000000000..bb49fb98b --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Platforms/iOS/TextFilters/LengthFilterTests/LengthFilterTests.cs @@ -0,0 +1,115 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using System.Threading.Tasks; +using Foundation; +using Softeq.XToolkit.Common.iOS.TextFilters; +using UIKit; +using Xunit; + +namespace Softeq.XToolkit.Common.iOS.Tests.TextFilters.LengthFilterTests; + +public class LengthFilterTests +{ + [Theory] + [InlineData(0)] + [InlineData(1000)] + public void Ctor_Positive_ReturnsITextFilter(int maxLength) + { + var obj = new LengthFilter(maxLength); + + Assert.IsAssignableFrom(obj); + } + + [Fact] + public void Ctor_Negative_ThrowsArgumentException() + { + Assert.Throws(() => new LengthFilter(-1)); + } + + [Fact] + public void IsCharacterOverwritingEnabled_Default_ReturnsFalse() + { + var obj = new LengthFilter(0); + + var result = obj.IsCharacterOverwritingEnabled; + + Assert.False(result); + } + + [Theory] + [InlineData("old", 3, "new", 6, true)] + [InlineData("old", 3, "new", 3, false)] + [InlineData("old", 1, "new", 2, false)] + public async Task ShouldChangeText_UITextField_ReturnsExpected( + string oldText, + int insertPosition, + string newText, + int maxLength, + bool expectedResult) + { + var result = await Helpers.RunOnUIThreadAsync(() => + { + var textField = new UITextField(); + var range = new NSRange(insertPosition, 0); + var filter = new LengthFilter(maxLength); + + return filter.ShouldChangeText(textField, oldText, range, newText); + }); + + Assert.Equal(expectedResult, result); + } + + [Theory] + [InlineData("old", 0, "new", 3, "new")] + [InlineData("old-extra-text", 4, "new", 7, "old-new")] + public async Task ShouldChangeText_UITextFieldWithCharacterOverwritingEnabled_ReturnsExpected( + string oldText, + int insertPosition, + string newText, + int maxLength, + string expectedResult) + { + var result = await Helpers.RunOnUIThreadAsync(() => + { + var textField = new UITextField { Text = oldText }; + var range = new NSRange(insertPosition, 0); + var filter = new LengthFilter(maxLength); + + filter.IsCharacterOverwritingEnabled = true; + filter.ShouldChangeText(textField, oldText, range, newText); + + return textField.Text; + }); + + Assert.Equal(expectedResult, result); + } + + [Fact] + public void ShouldChangeText_NullResponderWithCharacterOverwritingEnabled_ReturnsExpected() + { + var range = new NSRange(2, 0); + var filter = new LengthFilter(0); + + filter.IsCharacterOverwritingEnabled = true; + var result = filter.ShouldChangeText(null!, "old", range, "new"); + + Assert.False(result); + } + + [Fact] + public async Task ShouldChangeText_InvalidRange_ThrowsArgumentOutOfRangeException() + { + var result = Helpers.RunOnUIThreadAsync(() => + { + var textField = new UITextField(); + var filter = new LengthFilter(0); + + var range = new NSRange(0, 1000); + return filter.ShouldChangeText(textField, "old", range, "new"); + }); + + await Assert.ThrowsAsync(() => result); + } +} diff --git a/Softeq.XToolkit.Common.iOS.Tests/Properties/launchSettings.json b/Softeq.XToolkit.Common.iOS.Tests/Properties/launchSettings.json new file mode 100644 index 000000000..edf8aadcc --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Windows Machine": { + "commandName": "MsixPackage", + "nativeDebugging": false + } + } +} \ No newline at end of file diff --git a/Softeq.XToolkit.Common.iOS.Tests/Resources/AppIcon/appicon.svg b/Softeq.XToolkit.Common.iOS.Tests/Resources/AppIcon/appicon.svg new file mode 100644 index 000000000..9d63b6513 --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Resources/AppIcon/appicon.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Softeq.XToolkit.Common.iOS.Tests/Resources/AppIcon/appiconfg.svg b/Softeq.XToolkit.Common.iOS.Tests/Resources/AppIcon/appiconfg.svg new file mode 100644 index 000000000..21dfb25f1 --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Resources/AppIcon/appiconfg.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Softeq.XToolkit.Common.iOS.Tests/Resources/Raw/AboutAssets.txt b/Softeq.XToolkit.Common.iOS.Tests/Resources/Raw/AboutAssets.txt new file mode 100644 index 000000000..15d624484 --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Resources/Raw/AboutAssets.txt @@ -0,0 +1,15 @@ +Any raw assets you want to be deployed with your application can be placed in +this directory (and child directories). Deployment of the asset to your application +is automatically handled by the following `MauiAsset` Build Action within your `.csproj`. + + + +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/Softeq.XToolkit.Common.iOS.Tests/Resources/Splash/splash.svg b/Softeq.XToolkit.Common.iOS.Tests/Resources/Splash/splash.svg new file mode 100644 index 000000000..21dfb25f1 --- /dev/null +++ b/Softeq.XToolkit.Common.iOS.Tests/Resources/Splash/splash.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Softeq.XToolkit.Common.iOS.Tests/Softeq.XToolkit.Common.iOS.Tests.csproj b/Softeq.XToolkit.Common.iOS.Tests/Softeq.XToolkit.Common.iOS.Tests.csproj index 93f442e21..5d391770b 100644 --- a/Softeq.XToolkit.Common.iOS.Tests/Softeq.XToolkit.Common.iOS.Tests.csproj +++ b/Softeq.XToolkit.Common.iOS.Tests/Softeq.XToolkit.Common.iOS.Tests.csproj @@ -1,132 +1,48 @@ - - - - Debug - iPhoneSimulator - {8F494CF3-61BE-4140-AA49-D9660C0271C6} - {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Exe - Softeq.XToolkit.Common.iOS.Tests - Softeq.XToolkit.Common.iOS.Tests - Resources - - - true - portable - false - bin\iPhoneSimulator\Debug - DEBUG; - prompt - 4 - iPhone Developer - true - true - true - 40309 - None - x86_64 - NSUrlSessionHandler - false - - Default - - - - portable - true - bin\iPhone\Release - - prompt - 4 - iPhone Developer - true - true - Entitlements.plist - SdkOnly - ARM64 - NSUrlSessionHandler - - Default - - - - portable - true - bin\iPhoneSimulator\Release - - prompt - 4 - iPhone Developer - true - None - x86_64 - NSUrlSessionHandler - - Default - - - - true - portable - false - bin\iPhone\Debug - DEBUG; - prompt - 4 - iPhone Developer - true - true - true - true - true - Entitlements.plist - 52119 - SdkOnly - ARM64 - NSUrlSessionHandler - - Default - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {24588814-B93D-4528-8917-9C2A3C4E85CA} - Softeq.XToolkit.Common - - - {6BCB2009-2E46-458C-BCAA-AFC27A631924} - Softeq.XToolkit.Common.iOS - - - - - \ No newline at end of file + + + + net8.0-ios17.0 + Exe + true + disable + + + Softeq.XToolkit.Common.iOS.Tests + + + com.softeq.xtoolkit.common.ios.tests + + + 1.0 + 1 + + 11.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Softeq.XToolkit.Common.iOS.Tests/TextFilters/ForbiddenCharsFilterTests/ForbiddenCharsFilterTests.cs b/Softeq.XToolkit.Common.iOS.Tests/TextFilters/ForbiddenCharsFilterTests/ForbiddenCharsFilterTests.cs deleted file mode 100644 index 71907d0f6..000000000 --- a/Softeq.XToolkit.Common.iOS.Tests/TextFilters/ForbiddenCharsFilterTests/ForbiddenCharsFilterTests.cs +++ /dev/null @@ -1,92 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System; -using System.Threading.Tasks; -using Foundation; -using Softeq.XToolkit.Common.iOS.TextFilters; -using UIKit; -using Xunit; - -namespace Softeq.XToolkit.Common.iOS.Tests.TextFilters.ForbiddenCharsFilterTests -{ - public class ForbiddenCharsFilterTests - { - [Fact] - public void Ctor_Empty_ReturnsITextFilter() - { - var obj = new ForbiddenCharsFilter(); - - Assert.IsAssignableFrom(obj); - } - - [Fact] - public void Ctor_Null_ThrowsArgumentNullException() - { - Assert.Throws(() => new ForbiddenCharsFilter(null!)); - } - - [Fact] - public void Ctor_SingleChar_ReturnsITextFilter() - { - var obj = new ForbiddenCharsFilter('@'); - - Assert.IsAssignableFrom(obj); - } - - [Fact] - public void Ctor_Chars_ReturnsITextFilter() - { - var obj = new ForbiddenCharsFilter('@', '+', '#'); - - Assert.IsAssignableFrom(obj); - } - - [Theory] - [InlineData("", "", "", true)] - [InlineData("", "old", "new", true)] - [InlineData("#$", "old", "new#a", false)] - [InlineData("#$@", "new", "@", false)] - [InlineData("#$@", "old", "new", true)] - public async Task ShouldChangeText_UITextFieldForbiddenChars_ReturnsExpectedResult( - string forbiddenChars, - string oldText, - string newText, - bool expectedResult) - { - var result = await Helpers.RunOnUIThreadAsync(() => - { - var textField = new UITextField(); - var range = new NSRange(oldText.Length - 1, newText.Length); - var chars = forbiddenChars.ToCharArray(); - var filter = new ForbiddenCharsFilter(chars); - - return filter.ShouldChangeText(textField, oldText, range, newText); - }); - - Assert.Equal(expectedResult, result); - } - - [Theory] - [InlineData(null, "old")] - [InlineData(null, null)] - public void ShouldChangeText_NullResponderAndOldText_ReturnsExpectedResult(UIResponder responder, string oldText) - { - var filter = new ForbiddenCharsFilter('%'); - - var result = filter.ShouldChangeText(responder, oldText, new NSRange(1, 1), "new"); - - Assert.True(result); - } - - [Fact] - public void ShouldChangeText_DefaultRange_ReturnsExpectedResult() - { - var filter = new ForbiddenCharsFilter('%'); - - var result = filter.ShouldChangeText(null!, "old", default, "new"); - - Assert.True(result); - } - } -} diff --git a/Softeq.XToolkit.Common.iOS.Tests/TextFilters/GroupFilterTests/GroupFilterTests.cs b/Softeq.XToolkit.Common.iOS.Tests/TextFilters/GroupFilterTests/GroupFilterTests.cs deleted file mode 100644 index 60c5426b2..000000000 --- a/Softeq.XToolkit.Common.iOS.Tests/TextFilters/GroupFilterTests/GroupFilterTests.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System; -using Softeq.XToolkit.Common.iOS.TextFilters; -using Xunit; - -namespace Softeq.XToolkit.Common.iOS.Tests.TextFilters.GroupFilterTests -{ - public class GroupFilterTests - { - [Fact] - public void Ctor_Empty_ReturnsITextFilter() - { - var obj = new GroupFilter(); - - Assert.IsAssignableFrom(obj); - } - - [Fact] - public void Ctor_Null_ThrowsArgumentNullException() - { - Assert.Throws(() => new GroupFilter(null!)); - } - - [Fact] - public void Ctor_SingleFilter_ReturnsITextFilter() - { - var filter = new MockTextFilter(true); - - var obj = new GroupFilter(filter); - - Assert.IsAssignableFrom(obj); - } - - [Fact] - public void Ctor_Filters_ReturnsITextFilter() - { - var filter1 = new MockTextFilter(true); - var filter2 = new MockTextFilter(true); - - var obj = new GroupFilter(filter1, filter2); - - Assert.IsAssignableFrom(obj); - } - - [Theory] - [InlineData(true, true, true)] - [InlineData(true, false, false)] - [InlineData(false, true, false)] - [InlineData(false, false, false)] - public void ShouldChangeText_FilterReturnsResult_ReturnsExpected( - bool filter1Result, - bool filter2Result, - bool expectedResult) - { - var filter1 = new MockTextFilter(filter1Result); - var filter2 = new MockTextFilter(filter2Result); - var groupFilter = new GroupFilter(filter1, filter2); - - var result = groupFilter.ShouldChangeText(null!, string.Empty, default, string.Empty); - - Assert.Equal(expectedResult, result); - } - } -} diff --git a/Softeq.XToolkit.Common.iOS.Tests/TextFilters/GroupFilterTests/MockTextFilter.cs b/Softeq.XToolkit.Common.iOS.Tests/TextFilters/GroupFilterTests/MockTextFilter.cs deleted file mode 100644 index 57550120c..000000000 --- a/Softeq.XToolkit.Common.iOS.Tests/TextFilters/GroupFilterTests/MockTextFilter.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using Foundation; -using Softeq.XToolkit.Common.iOS.TextFilters; -using UIKit; - -namespace Softeq.XToolkit.Common.iOS.Tests.TextFilters.GroupFilterTests -{ - internal class MockTextFilter : ITextFilter - { - private readonly bool _result; - - public MockTextFilter(bool result) - { - _result = result; - } - - public bool ShouldChangeText(UIResponder responder, string? oldText, NSRange range, string replacementString) - { - return _result; - } - } -} diff --git a/Softeq.XToolkit.Common.iOS.Tests/TextFilters/LengthFilterTests/LengthFilterTests.cs b/Softeq.XToolkit.Common.iOS.Tests/TextFilters/LengthFilterTests/LengthFilterTests.cs deleted file mode 100644 index 2b7888ba4..000000000 --- a/Softeq.XToolkit.Common.iOS.Tests/TextFilters/LengthFilterTests/LengthFilterTests.cs +++ /dev/null @@ -1,116 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System; -using System.Threading.Tasks; -using Foundation; -using Softeq.XToolkit.Common.iOS.TextFilters; -using UIKit; -using Xunit; - -namespace Softeq.XToolkit.Common.iOS.Tests.TextFilters.LengthFilterTests -{ - public class LengthFilterTests - { - [Theory] - [InlineData(0)] - [InlineData(1000)] - public void Ctor_Positive_ReturnsITextFilter(int maxLength) - { - var obj = new LengthFilter(maxLength); - - Assert.IsAssignableFrom(obj); - } - - [Fact] - public void Ctor_Negative_ThrowsArgumentException() - { - Assert.Throws(() => new LengthFilter(-1)); - } - - [Fact] - public void IsCharacterOverwritingEnabled_Default_ReturnsFalse() - { - var obj = new LengthFilter(0); - - var result = obj.IsCharacterOverwritingEnabled; - - Assert.False(result); - } - - [Theory] - [InlineData("old", 3, "new", 6, true)] - [InlineData("old", 3, "new", 3, false)] - [InlineData("old", 1, "new", 2, false)] - public async Task ShouldChangeText_UITextField_ReturnsExpected( - string oldText, - int insertPosition, - string newText, - int maxLength, - bool expectedResult) - { - var result = await Helpers.RunOnUIThreadAsync(() => - { - var textField = new UITextField(); - var range = new NSRange(insertPosition, 0); - var filter = new LengthFilter(maxLength); - - return filter.ShouldChangeText(textField, oldText, range, newText); - }); - - Assert.Equal(expectedResult, result); - } - - [Theory] - [InlineData("old", 0, "new", 3, "new")] - [InlineData("old-extra-text", 4, "new", 7, "old-new")] - public async Task ShouldChangeText_UITextFieldWithCharacterOverwritingEnabled_ReturnsExpected( - string oldText, - int insertPosition, - string newText, - int maxLength, - string expectedResult) - { - var result = await Helpers.RunOnUIThreadAsync(() => - { - var textField = new UITextField { Text = oldText }; - var range = new NSRange(insertPosition, 0); - var filter = new LengthFilter(maxLength); - - filter.IsCharacterOverwritingEnabled = true; - filter.ShouldChangeText(textField, oldText, range, newText); - - return textField.Text; - }); - - Assert.Equal(expectedResult, result); - } - - [Fact] - public void ShouldChangeText_NullResponderWithCharacterOverwritingEnabled_ReturnsExpected() - { - var range = new NSRange(2, 0); - var filter = new LengthFilter(0); - - filter.IsCharacterOverwritingEnabled = true; - var result = filter.ShouldChangeText(null!, "old", range, "new"); - - Assert.False(result); - } - - [Fact] - public async Task ShouldChangeText_InvalidRange_ThrowsArgumentOutOfRangeException() - { - var result = Helpers.RunOnUIThreadAsync(() => - { - var textField = new UITextField(); - var filter = new LengthFilter(0); - - var range = new NSRange(0, 1000); - return filter.ShouldChangeText(textField, "old", range, "new"); - }); - - await Assert.ThrowsAsync(() => result); - } - } -} diff --git a/Softeq.XToolkit.Common.iOS/Extensions/UIViewExtensions.cs b/Softeq.XToolkit.Common.iOS/Extensions/UIViewExtensions.cs index 9d9821637..0dff64888 100644 --- a/Softeq.XToolkit.Common.iOS/Extensions/UIViewExtensions.cs +++ b/Softeq.XToolkit.Common.iOS/Extensions/UIViewExtensions.cs @@ -1,7 +1,6 @@ // Developed by Softeq Development Corporation // http://www.softeq.com -using System; using CoreAnimation; using CoreGraphics; using UIKit; diff --git a/Softeq.XToolkit.Common.iOS/Properties/AssemblyInfo.cs b/Softeq.XToolkit.Common.iOS/Properties/AssemblyInfo.cs deleted file mode 100644 index 63261470a..000000000 --- a/Softeq.XToolkit.Common.iOS/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Softeq.XToolkit.Common.iOS")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Softeq Development Corporation")] -[assembly: AssemblyProduct("XToolkit.Common")] -[assembly: AssemblyCopyright("Copyright © Softeq Development Corporation 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("6bcb2009-2e46-458c-bcaa-afc27a631924")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Softeq.XToolkit.Common.iOS/Softeq.XToolkit.Common.iOS.csproj b/Softeq.XToolkit.Common.iOS/Softeq.XToolkit.Common.iOS.csproj index 52a74a129..1ef220853 100644 --- a/Softeq.XToolkit.Common.iOS/Softeq.XToolkit.Common.iOS.csproj +++ b/Softeq.XToolkit.Common.iOS/Softeq.XToolkit.Common.iOS.csproj @@ -1,65 +1,22 @@ - - + + - Debug - AnyCPU - 8.0.30703 - 2.0 - {6BCB2009-2E46-458C-BCAA-AFC27A631924} - {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Library - Softeq.XToolkit.Common.iOS - Resources - Softeq.XToolkit.Common.iOS + net8.0-ios12.0 + 10.0 + false + true - portable - false - bin\Debug - DEBUG; - prompt - 4 - false - None + None + - portable - true - bin\Release - prompt - 4 - false - SdkOnly + SdkOnly + - - - - + - - - - - - - - - - - - - - - - - - - - {24588814-B93D-4528-8917-9C2A3C4E85CA} - Softeq.XToolkit.Common - - - + \ No newline at end of file diff --git a/Softeq.XToolkit.Common/Collections/BiDictionary.cs b/Softeq.XToolkit.Common/Collections/BiDictionary.cs index 0739279bf..695f1f10f 100644 --- a/Softeq.XToolkit.Common/Collections/BiDictionary.cs +++ b/Softeq.XToolkit.Common/Collections/BiDictionary.cs @@ -4,20 +4,14 @@ using System; using System.Collections; using System.Collections.Generic; -using System.IO; -using System.Runtime.Serialization; -using System.Runtime.Serialization.Formatters.Binary; -using System.Security.Permissions; #pragma warning disable CS1591 namespace Softeq.XToolkit.Common.Collections { - [Serializable] public class BiDictionary : IDictionary, IReadOnlyDictionary, - IDictionary, - ISerializable + IDictionary { private readonly IDictionary _firstToSecond = new Dictionary(); @@ -33,24 +27,6 @@ public BiDictionary() _reverseDictionary = new ReverseDictionary(this); } - protected BiDictionary(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw new ArgumentNullException(nameof(info)); - } - - var data = (byte[]) info.GetValue("firstToSecond", typeof(byte[])); - using (var memoryStream = new MemoryStream(data)) - { - var binaryFormatter = new BinaryFormatter(); - _firstToSecond = (Dictionary) binaryFormatter.Deserialize(memoryStream); - } - - _secondToFirst = new Dictionary(); - _reverseDictionary = new ReverseDictionary(this); - } - public int Count => _firstToSecond.Count; public bool IsReadOnly => _firstToSecond.IsReadOnly || _secondToFirst.IsReadOnly; @@ -196,35 +172,6 @@ void ICollection>.CopyTo(KeyValuePair ReverseItem(KeyValuePair item) { return new KeyValuePair(item.Value, item.Key); diff --git a/Softeq.XToolkit.Common/Collections/ObservableKeyGroupsCollection.cs b/Softeq.XToolkit.Common/Collections/ObservableKeyGroupsCollection.cs index 9ab70ff45..4eab2c6fa 100644 --- a/Softeq.XToolkit.Common/Collections/ObservableKeyGroupsCollection.cs +++ b/Softeq.XToolkit.Common/Collections/ObservableKeyGroupsCollection.cs @@ -6,7 +6,9 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; +using System.ComponentModel; using System.Linq; +using System.Runtime.CompilerServices; using Softeq.XToolkit.Common.Collections.EventArgs; using Softeq.XToolkit.Common.Extensions; @@ -21,7 +23,8 @@ namespace Softeq.XToolkit.Common.Collections public sealed class ObservableKeyGroupsCollection : IObservableKeyGroupsCollection, INotifyKeyGroupCollectionChanged, - INotifyCollectionChanged + INotifyCollectionChanged, + INotifyPropertyChanged where TKey : notnull where TValue : notnull { @@ -42,6 +45,7 @@ public ObservableKeyGroupsCollection(bool allowEmptyGroups = true) public event NotifyCollectionChangedEventHandler? CollectionChanged; public event EventHandler>? ItemsChanged; + public event PropertyChangedEventHandler? PropertyChanged; public IList Keys => _groups.Select(item => item.Key).ToList(); @@ -161,14 +165,11 @@ public void ReplaceAllGroups(IEnumerable>> item _groups.Clear(); var insertedGroups = InsertGroupsWithoutNotify(0, items, _emptyGroupsDisabled); - if (insertedGroups == null) - { - return; - } + var insertedGroupKeys = insertedGroups?.Select(x => x.Key).ToList() ?? new List(); var newItems = new Collection<(int, IReadOnlyList)> { - (0, insertedGroups.Select(x => x.Key).ToList()) + (0, insertedGroupKeys) }; OnChanged( @@ -534,6 +535,8 @@ private void RaiseEvents(NotifyKeyGroupCollectionChangedEventArgs } ItemsChanged?.Invoke(this, args); + + NotifyCountIfNeeded(args); } private IEnumerable? InsertGroupsWithoutNotify( @@ -697,6 +700,29 @@ private void DecrementIndex(IList> indexes) } } + private void NotifyCountIfNeeded(NotifyKeyGroupCollectionChangedEventArgs args) + { + var isCountOfGroupsChanged = IsActionCanModifyGroup(args.Action); + if (isCountOfGroupsChanged) + { + OnPropertyChanged(nameof(Count)); + } + } + + private bool IsActionCanModifyGroup(NotifyCollectionChangedAction? action) + { + return action + is NotifyCollectionChangedAction.Add + or NotifyCollectionChangedAction.Remove + or NotifyCollectionChangedAction.Replace + or NotifyCollectionChangedAction.Reset; + } + + private void OnPropertyChanged([CallerMemberName] string? propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + private class Group : List, IGrouping { public Group(KeyValuePair> keyValuePair) diff --git a/Softeq.XToolkit.Common/Commands/AsyncCommandBase.cs b/Softeq.XToolkit.Common/Commands/AsyncCommandBase.cs index 18231677a..00deaba7d 100644 --- a/Softeq.XToolkit.Common/Commands/AsyncCommandBase.cs +++ b/Softeq.XToolkit.Common/Commands/AsyncCommandBase.cs @@ -47,7 +47,7 @@ protected async Task DoExecuteAsync(Func executionProvider) try { - await executionProvider.Invoke(); + await executionProvider.Invoke().ConfigureAwait(false); } finally { diff --git a/Softeq.XToolkit.Common/Extensions/EnumerableExtensions.cs b/Softeq.XToolkit.Common/Extensions/EnumerableExtensions.cs index 245288a0b..ce798a450 100644 --- a/Softeq.XToolkit.Common/Extensions/EnumerableExtensions.cs +++ b/Softeq.XToolkit.Common/Extensions/EnumerableExtensions.cs @@ -52,55 +52,5 @@ public static void Apply(this IEnumerable enumerable, Action action) action(item); } } - - /// - /// Splits the current into chunks of a specified size. - /// - /// instance. - /// Chunk size. - /// Item type. - /// - /// Collection of arrays. Each array is a chunk of the source collection and will have the specified size, - /// the last chunk might have size less than specified. - /// - /// - /// parameter cannot be . - /// - /// - /// must be greater than 0. - /// - public static IEnumerable Chunkify(this IEnumerable source, int size) - { - if (source == null) - { - throw new ArgumentNullException(nameof(source)); - } - - if (size < 1) - { - throw new ArgumentOutOfRangeException(nameof(size)); - } - - var chunkIndex = 0; - var lastChunkIndex = Math.Ceiling(source.Count() / (double) size) - 1; - var incompleteChunkSize = source.Count() % size; - var lastChunkSize = incompleteChunkSize == 0 ? size : incompleteChunkSize; - - using (var iter = source.GetEnumerator()) - { - while (iter.MoveNext()) - { - var chunk = new T[chunkIndex == lastChunkIndex ? lastChunkSize : size]; - chunk[0] = iter.Current; - for (var i = 1; i < size && iter.MoveNext(); i++) - { - chunk[i] = iter.Current; - } - - chunkIndex++; - yield return chunk; - } - } - } } } diff --git a/Softeq.XToolkit.Common/Files/BaseFileProvider.cs b/Softeq.XToolkit.Common/Files/BaseFileProvider.cs index 8e0ea5b56..62a2c3721 100644 --- a/Softeq.XToolkit.Common/Files/BaseFileProvider.cs +++ b/Softeq.XToolkit.Common/Files/BaseFileProvider.cs @@ -32,7 +32,7 @@ public Task ClearFolderAsync(string path) { var tasks = _fileSystem.Directory.GetFiles(path). Select(async x => await RemoveFileAsync(x).ConfigureAwait(false)); - await Task.WhenAll(tasks); + await Task.WhenAll(tasks).ConfigureAwait(false); }); } @@ -69,10 +69,10 @@ public Task CopyFileAsync(string sourcePath, string destinationPath, bool overwr public async Task WriteFileAsync(string path, Stream stream) { path = GetAbsolutePath(path); - using (var outputStream = await OpenFileForWriteAsync(path)) + using (var outputStream = await OpenFileForWriteAsync(path).ConfigureAwait(false)) { stream.Position = 0; - await stream.CopyToAsync(outputStream); + await stream.CopyToAsync(outputStream).ConfigureAwait(false); stream.Dispose(); } diff --git a/Softeq.XToolkit.Common/Interfaces/IJsonSerializer.cs b/Softeq.XToolkit.Common/Interfaces/IJsonSerializer.cs index 716e2a406..04b8a21d8 100644 --- a/Softeq.XToolkit.Common/Interfaces/IJsonSerializer.cs +++ b/Softeq.XToolkit.Common/Interfaces/IJsonSerializer.cs @@ -1,7 +1,6 @@ // Developed by Softeq Development Corporation // http://www.softeq.com -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Threading.Tasks; @@ -22,11 +21,10 @@ public interface IJsonSerializer /// /// Deserializes the JSON to a .NET object. /// - /// The Stream that contains the JSON structure to deserialize. - /// The deserialized object from the JSON string. + /// The string that contains the JSON structure to deserialize. + /// The deserialized object. /// Object type. - [return:MaybeNull] - TResult Deserialize(string value); + TResult? Deserialize(string value); /// /// Asynchronously serializes the specified object to a JSON string. @@ -48,7 +46,6 @@ public interface IJsonSerializer /// The value of the TResult parameter contains the deserialized object from the JSON string. /// /// Object type. - [return:MaybeNull] - Task DeserializeAsync(Stream stream); + Task DeserializeAsync(Stream stream); } } diff --git a/Softeq.XToolkit.Common/Softeq.XToolkit.Common.csproj b/Softeq.XToolkit.Common/Softeq.XToolkit.Common.csproj index b17e301c6..cf562f7bf 100644 --- a/Softeq.XToolkit.Common/Softeq.XToolkit.Common.csproj +++ b/Softeq.XToolkit.Common/Softeq.XToolkit.Common.csproj @@ -1,8 +1,7 @@ - - + - netstandard2.1 + net8.0 diff --git a/Softeq.XToolkit.Common/Threading/TaskDeferral.cs b/Softeq.XToolkit.Common/Threading/TaskDeferral.cs index 72eb68655..eaf2fab24 100644 --- a/Softeq.XToolkit.Common/Threading/TaskDeferral.cs +++ b/Softeq.XToolkit.Common/Threading/TaskDeferral.cs @@ -41,7 +41,7 @@ public async Task DoWorkAsync(Func> taskFactory) if (_queue.IsEmpty) { _semaphoreSlim.Release(); - return await tcs.Task; + return await tcs.Task.ConfigureAwait(false); } var result = await ExecuteAsync(taskFactory).ConfigureAwait(false); @@ -54,7 +54,7 @@ public async Task DoWorkAsync(Func> taskFactory) _semaphoreSlim.Release(); - return await tcs.Task; + return await tcs.Task.ConfigureAwait(false); } private static async Task ExecuteAsync(Func> taskFactory) diff --git a/Softeq.XToolkit.Common/Timers/Timer.cs b/Softeq.XToolkit.Common/Timers/Timer.cs index 49dc78b92..18d060fd9 100644 --- a/Softeq.XToolkit.Common/Timers/Timer.cs +++ b/Softeq.XToolkit.Common/Timers/Timer.cs @@ -79,7 +79,7 @@ private async Task DoWork() { do { - await Task.Delay(_interval); + await Task.Delay(_interval).ConfigureAwait(false); if (IsActive && _taskFactory != null) { await _taskFactory().ConfigureAwait(false); diff --git a/Softeq.XToolkit.Connectivity.iOS/IosConnectivityService.cs b/Softeq.XToolkit.Connectivity.iOS/IosConnectivityService.cs index a9b1d3482..a1ec7a17b 100644 --- a/Softeq.XToolkit.Connectivity.iOS/IosConnectivityService.cs +++ b/Softeq.XToolkit.Connectivity.iOS/IosConnectivityService.cs @@ -6,23 +6,28 @@ using System.Linq; using System.Threading; using CoreFoundation; +using Microsoft.Maui.Networking; using Network; -using Plugin.Connectivity.Abstractions; namespace Softeq.XToolkit.Connectivity.iOS { - public class IosConnectivityService : IConnectivityService + /// + /// iOS implementation of . + /// Uses iOS 12+ Network framework: https://developer.apple.com/documentation/network?language=objc + /// MAUI.Essentials issue: https://github.com/dotnet/maui/issues/2574 . + /// + public class IosConnectivityService : IConnectivityService, IDisposable { - private readonly ReaderWriterLockSlim _statusesLock = new ReaderWriterLockSlim(); - + private readonly ReaderWriterLockSlim _statusesLock; private readonly Dictionary _connectionStatuses; private readonly IList _monitors; - public event EventHandler? ConnectivityChanged; - public event EventHandler? ConnectivityTypeChanged; - + /// + /// Initializes a new instance of the class. + /// public IosConnectivityService() { + _statusesLock = new ReaderWriterLockSlim(); _connectionStatuses = new Dictionary(); _monitors = new List { @@ -39,6 +44,10 @@ public IosConnectivityService() Dispose(false); } + /// + public event EventHandler? ConnectivityChanged; + + /// public bool IsConnected { get @@ -55,9 +64,8 @@ public bool IsConnected } } - public bool IsSupported => true; - - public IEnumerable ConnectionTypes + /// + public IEnumerable ConnectionProfiles { get { @@ -74,12 +82,19 @@ public IEnumerable ConnectionTypes } } + /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } + /// + /// Releases the unmanaged and optionally the managed resources. + /// + /// true to dispose managed state. + /// + /// protected virtual void Dispose(bool disposing) { if (disposing) @@ -101,7 +116,7 @@ protected virtual bool CheckConnectivity(IReadOnlyDictionary x.Value); } - protected virtual IEnumerable FilterConnectionTypes(IList activeNetworkTypes) + protected virtual IEnumerable FilterConnectionTypes(IList activeNetworkTypes) { if (activeNetworkTypes.Contains(NWInterfaceType.Other) && activeNetworkTypes.Count > 1) { @@ -111,13 +126,15 @@ protected virtual IEnumerable FilterConnectionTypes(IList ConnectionType.Cellular, - NWInterfaceType.Wifi => ConnectionType.WiFi, - _ => ConnectionType.Other + NWInterfaceType.Cellular => ConnectionProfile.Cellular, + NWInterfaceType.Wifi => ConnectionProfile.WiFi, + NWInterfaceType.Wired => ConnectionProfile.Ethernet, + NWInterfaceType.Loopback => ConnectionProfile.Unknown, + _ => ConnectionProfile.Unknown }; } @@ -149,7 +166,7 @@ private NWPathMonitor CreateMonitor(NWInterfaceType type, Action action) private void HandleUpdateSnapshot(NWPath nWPath, NWInterfaceType type) { var isConnectedOld = IsConnected; - var connectionTypesOld = ConnectionTypes; + var connectionTypesOld = ConnectionProfiles; _statusesLock.EnterWriteLock(); try @@ -161,21 +178,14 @@ private void HandleUpdateSnapshot(NWPath nWPath, NWInterfaceType type) _statusesLock.ExitWriteLock(); } - if (isConnectedOld != IsConnected) - { - ConnectivityChanged?.Invoke(this, new ConnectivityChangedEventArgs - { - IsConnected = IsConnected - }); - } + var isConnectedNew = IsConnected; + var connectionTypesNew = ConnectionProfiles; - if (!ConnectionTypes.SequenceEqual(connectionTypesOld)) + if (isConnectedOld != isConnectedNew || + !connectionTypesNew.SequenceEqual(connectionTypesOld)) { - ConnectivityTypeChanged?.Invoke(this, new ConnectivityTypeChangedEventArgs - { - IsConnected = IsConnected, - ConnectionTypes = ConnectionTypes - }); + var access = isConnectedNew ? NetworkAccess.Internet : NetworkAccess.None; + ConnectivityChanged?.Invoke(this, new ConnectivityChangedEventArgs(access, ConnectionProfiles)); } } } diff --git a/Softeq.XToolkit.Connectivity.iOS/Properties/AssemblyInfo.cs b/Softeq.XToolkit.Connectivity.iOS/Properties/AssemblyInfo.cs deleted file mode 100644 index c8204f46a..000000000 --- a/Softeq.XToolkit.Connectivity.iOS/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Softeq.XToolkit.Connectivity.iOS")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Softeq Development Corporation")] -[assembly: AssemblyProduct("XToolkit.Connectivity")] -[assembly: AssemblyCopyright("Copyright © Softeq Development Corporation 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("50c7b8c9-e664-45af-b88e-0c9b8b9c1be1")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Softeq.XToolkit.Connectivity.iOS/Softeq.XToolkit.Connectivity.iOS.csproj b/Softeq.XToolkit.Connectivity.iOS/Softeq.XToolkit.Connectivity.iOS.csproj index f18d43df5..26d93a3c8 100644 --- a/Softeq.XToolkit.Connectivity.iOS/Softeq.XToolkit.Connectivity.iOS.csproj +++ b/Softeq.XToolkit.Connectivity.iOS/Softeq.XToolkit.Connectivity.iOS.csproj @@ -1,57 +1,26 @@ - - + + - Debug - AnyCPU - 8.0.30703 - 2.0 - {E3CCEEA3-C0B0-47B0-B414-2D6C18AC100A} - {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - {a52b8a63-bc84-4b47-910d-692533484892} - Library - Softeq.XToolkit.Connectivity.iOS - Resources - Softeq.XToolkit.Connectivity.iOS - PackageReference + net8.0-ios17.0 + 12.0 + false + true - portable - false - bin\Debug - DEBUG; - prompt - 4 - None + None + - portable - true - bin\Release - prompt - 4 - SdkOnly + SdkOnly + - - - - + + - - + - - - {044152C1-3B2A-45A6-B233-80A51C511129} - Softeq.XToolkit.Connectivity - - - - - 3.2.0 - - - + \ No newline at end of file diff --git a/Softeq.XToolkit.Connectivity/ConnectivityService.cs b/Softeq.XToolkit.Connectivity/ConnectivityService.cs deleted file mode 100644 index b9fdfbbd1..000000000 --- a/Softeq.XToolkit.Connectivity/ConnectivityService.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System; -using System.Collections.Generic; -using Plugin.Connectivity; -using Plugin.Connectivity.Abstractions; - -namespace Softeq.XToolkit.Connectivity -{ - public class ConnectivityService : IConnectivityService - { - public virtual event EventHandler? ConnectivityChanged; - public virtual event EventHandler? ConnectivityTypeChanged; - - public ConnectivityService() - { - CrossConnectivity.Current.ConnectivityChanged += CurrentConnectivityChanged; - CrossConnectivity.Current.ConnectivityTypeChanged += CurrentConnectivityTypeChanged; - } - - ~ConnectivityService() - { - Dispose(false); - } - - public virtual bool IsConnected => CrossConnectivity.Current.IsConnected; - - public bool IsSupported => CrossConnectivity.IsSupported; - - public IEnumerable ConnectionTypes => CrossConnectivity.Current.ConnectionTypes; - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected void Dispose(bool disposing) - { - if (disposing) - { - CrossConnectivity.Current.ConnectivityChanged -= CurrentConnectivityChanged; - CrossConnectivity.Current.ConnectivityTypeChanged -= CurrentConnectivityTypeChanged; - } - } - - private void CurrentConnectivityChanged(object sender, ConnectivityChangedEventArgs e) - { - ConnectivityChanged?.Invoke(sender, e); - } - - private void CurrentConnectivityTypeChanged(object sender, ConnectivityTypeChangedEventArgs e) - { - ConnectivityTypeChanged?.Invoke(sender, e); - } - } -} diff --git a/Softeq.XToolkit.Connectivity/EssentialsConnectivityService.cs b/Softeq.XToolkit.Connectivity/EssentialsConnectivityService.cs new file mode 100644 index 000000000..e14bde167 --- /dev/null +++ b/Softeq.XToolkit.Connectivity/EssentialsConnectivityService.cs @@ -0,0 +1,91 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Maui.Networking; + +namespace Softeq.XToolkit.Connectivity +{ + /// + /// MAUI.Essentials cross-platform implementation of . + /// + public class EssentialsConnectivityService : IConnectivityService, IDisposable + { + private readonly IConnectivity _connectivity; + + /// + /// Initializes a new instance of the class. + /// + /// + /// Custom instance of + /// or you can use static method. + /// + public EssentialsConnectivityService(IConnectivity connectivity) + { + _connectivity = connectivity; + + _connectivity.ConnectivityChanged += CurrentConnectivityChanged; + } + + ~EssentialsConnectivityService() + { + Dispose(false); + } + + /// + public event EventHandler? ConnectivityChanged; + + /// + public virtual bool IsConnected + { + get + { + var profiles = _connectivity.ConnectionProfiles; + var access = _connectivity.NetworkAccess; + + var hasAnyConnection = profiles.Any(); + var hasInternet = access == NetworkAccess.Internet; + + return hasAnyConnection && hasInternet; + } + } + + /// + public IEnumerable ConnectionProfiles => _connectivity.ConnectionProfiles; + + /// + /// Initializes a new instance of the class + /// with a default instance of . + /// + /// instance. + public static EssentialsConnectivityService Default() => new(Microsoft.Maui.Networking.Connectivity.Current); + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases the unmanaged and optionally the managed resources. + /// + /// true to dispose managed state. + /// + /// + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _connectivity.ConnectivityChanged -= CurrentConnectivityChanged; + } + } + + private void CurrentConnectivityChanged(object? sender, ConnectivityChangedEventArgs e) + { + ConnectivityChanged?.Invoke(sender, e); + } + } +} diff --git a/Softeq.XToolkit.Connectivity/Extensions.cs b/Softeq.XToolkit.Connectivity/Extensions.cs new file mode 100644 index 000000000..50ea64f43 --- /dev/null +++ b/Softeq.XToolkit.Connectivity/Extensions.cs @@ -0,0 +1,19 @@ +using System.Linq; +using Microsoft.Maui.Networking; + +namespace Softeq.XToolkit.Connectivity +{ + public static class Extensions + { + public static bool IsConnected(this ConnectivityChangedEventArgs args) + { + var profiles = args.ConnectionProfiles; + var access = args.NetworkAccess; + + var hasAnyConnection = profiles.Any(); + var hasInternet = access == NetworkAccess.Internet; + + return hasAnyConnection && hasInternet; + } + } +} \ No newline at end of file diff --git a/Softeq.XToolkit.Connectivity/IConnectivityService.cs b/Softeq.XToolkit.Connectivity/IConnectivityService.cs index df8c9fe01..01d5fecfa 100644 --- a/Softeq.XToolkit.Connectivity/IConnectivityService.cs +++ b/Softeq.XToolkit.Connectivity/IConnectivityService.cs @@ -3,20 +3,28 @@ using System; using System.Collections.Generic; -using Plugin.Connectivity.Abstractions; +using Microsoft.Maui.Networking; namespace Softeq.XToolkit.Connectivity { - public interface IConnectivityService : IDisposable + /// + /// Interface for Connectivity Service. + /// + public interface IConnectivityService { + /// + /// Event handler when connection state changes. + /// event EventHandler ConnectivityChanged; - event EventHandler ConnectivityTypeChanged; - + /// + /// Gets a value indicating whether there is an active internet connection. + /// bool IsConnected { get; } - bool IsSupported { get; } - - IEnumerable ConnectionTypes { get; } + /// + /// Gets the active connectivity types for the device. + /// + IEnumerable ConnectionProfiles { get; } } } diff --git a/Softeq.XToolkit.Connectivity/Softeq.XToolkit.Connectivity.csproj b/Softeq.XToolkit.Connectivity/Softeq.XToolkit.Connectivity.csproj index 89f60f546..51cb06f45 100644 --- a/Softeq.XToolkit.Connectivity/Softeq.XToolkit.Connectivity.csproj +++ b/Softeq.XToolkit.Connectivity/Softeq.XToolkit.Connectivity.csproj @@ -1,7 +1,7 @@ - netstandard2.1 + net8.0 @@ -9,11 +9,11 @@ - + - + diff --git a/Softeq.XToolkit.Permissions.Droid/NotificationsPlatformPermission.cs b/Softeq.XToolkit.Permissions.Droid/NotificationsPlatformPermission.cs deleted file mode 100644 index 6b20b64fe..000000000 --- a/Softeq.XToolkit.Permissions.Droid/NotificationsPlatformPermission.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -#if __ANDROID_33__ -using Android; -using Android.OS; -using Xamarin.Essentials; -#endif -using BasePlatformPermission = Xamarin.Essentials.Permissions.BasePlatformPermission; - -namespace Softeq.XToolkit.Permissions.Droid -{ - public class NotificationsPlatformPermission : BasePlatformPermission - { - public override (string androidPermission, bool isRuntime)[] RequiredPermissions - { - get - { - #if __ANDROID_33__ - if (Build.VERSION.SdkInt >= BuildVersionCodes.Tiramisu - && Platform.AppContext.ApplicationInfo?.TargetSdkVersion >= BuildVersionCodes.Tiramisu) - { - return new[] { (Manifest.Permission.PostNotifications, true) }; - } - #endif - - return new (string, bool)[] { }; - } - } - } -} diff --git a/Softeq.XToolkit.Permissions.Droid/Permissions/Bluetooth.cs b/Softeq.XToolkit.Permissions.Droid/Permissions/Bluetooth.cs new file mode 100644 index 000000000..8f5b0cff3 --- /dev/null +++ b/Softeq.XToolkit.Permissions.Droid/Permissions/Bluetooth.cs @@ -0,0 +1,54 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +#if __ANDROID_31__ +using System.Collections.Generic; +using Android; +using Android.OS; +#endif +using EssentialsPermissions = Microsoft.Maui.ApplicationModel.Permissions; + +namespace Softeq.XToolkit.Permissions.Droid.Permissions +{ + public class Bluetooth : EssentialsPermissions.BasePlatformPermission + { + public override (string, bool)[] RequiredPermissions + { + get + { + var permissions = new List<(string, bool)>(); + + // When targeting Android 11 or lower, AccessFineLocation is required for Bluetooth. + // For Android 12 and above, it is optional. + if (SdkVersion.IsTargetSdkLower(BuildVersionCodes.R) || + EssentialsPermissions.IsDeclaredInManifest(Manifest.Permission.AccessFineLocation)) + { + permissions.Add((Manifest.Permission.AccessFineLocation, true)); + } + +#if __ANDROID_31__ + if (SdkVersion.IsTargetSdkAtLeast(BuildVersionCodes.S) && + SdkVersion.IsBuildSdkAtLeast(BuildVersionCodes.S)) + { + if (EssentialsPermissions.IsDeclaredInManifest(Manifest.Permission.BluetoothScan)) + { + permissions.Add((Manifest.Permission.BluetoothScan, true)); + } + + if (EssentialsPermissions.IsDeclaredInManifest(Manifest.Permission.BluetoothConnect)) + { + permissions.Add((Manifest.Permission.BluetoothConnect, true)); + } + + if (EssentialsPermissions.IsDeclaredInManifest(Manifest.Permission.BluetoothAdvertise)) + { + permissions.Add((Manifest.Permission.BluetoothAdvertise, true)); + } + } +#endif + + return permissions.ToArray(); + } + } + } +} \ No newline at end of file diff --git a/Softeq.XToolkit.Permissions.Droid/Permissions/Notifications.cs b/Softeq.XToolkit.Permissions.Droid/Permissions/Notifications.cs new file mode 100644 index 000000000..7555a456f --- /dev/null +++ b/Softeq.XToolkit.Permissions.Droid/Permissions/Notifications.cs @@ -0,0 +1,35 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +#if __ANDROID_33__ +using Android; +using Android.OS; +#endif +using EssentialsPermissions = Microsoft.Maui.ApplicationModel.Permissions; + +namespace Softeq.XToolkit.Permissions.Droid.Permissions +{ + public class Notifications : EssentialsPermissions.BasePlatformPermission + { + public override (string, bool)[] RequiredPermissions + { + get + { +#if __ANDROID_33__ +#pragma warning disable CA1416 + var isSupport = + SdkVersion.IsTargetSdkAtLeast(BuildVersionCodes.Tiramisu) && + SdkVersion.IsBuildSdkAtLeast(BuildVersionCodes.Tiramisu) && + EssentialsPermissions.IsDeclaredInManifest(Manifest.Permission.PostNotifications); + + return isSupport ? + new (string, bool)[] { (Manifest.Permission.PostNotifications, true) } : + System.Array.Empty<(string, bool)>(); +#pragma warning restore CA1416 +#else + return new (string, bool)[] { }; +#endif + } + } + } +} diff --git a/Softeq.XToolkit.Permissions.Droid/PermissionsManager.cs b/Softeq.XToolkit.Permissions.Droid/PermissionsManager.cs index b9592c667..aa030f1d2 100644 --- a/Softeq.XToolkit.Permissions.Droid/PermissionsManager.cs +++ b/Softeq.XToolkit.Permissions.Droid/PermissionsManager.cs @@ -2,15 +2,19 @@ // http://www.softeq.com using System; +using System.Diagnostics; using System.Threading.Tasks; -using Xamarin.Essentials; -using BasePermission = Xamarin.Essentials.Permissions.BasePermission; +using Microsoft.Maui.Storage; +using BasePermission = Microsoft.Maui.ApplicationModel.Permissions.BasePermission; +using CustomPermissions = Softeq.XToolkit.Permissions.Permissions; +using PlatformCustomPermissions = Softeq.XToolkit.Permissions.Droid.Permissions; namespace Softeq.XToolkit.Permissions.Droid { /// public class PermissionsManager : IPermissionsManager { + private readonly TimeSpan _showPermissionDialogThreshold = TimeSpan.FromMilliseconds(200); private readonly IPermissionsService _permissionsService; private IPermissionsDialogService _permissionsDialogService; @@ -20,45 +24,79 @@ public PermissionsManager( { _permissionsService = permissionsService; _permissionsDialogService = new DefaultPermissionsDialogService(); - } - + } + /// public virtual Task CheckAsync() where T : BasePermission, new() { - return _permissionsService.CheckPermissionsAsync(); + var permissionType = typeof(T); + if (permissionType == typeof(CustomPermissions.Notifications)) + { + return _permissionsService.CheckPermissionsAsync(); + } + else if (permissionType == typeof(CustomPermissions.Bluetooth)) + { + return _permissionsService.CheckPermissionsAsync(); + } + else + { + return _permissionsService.CheckPermissionsAsync(); + } } /// - public Task CheckWithRequestAsync() + public virtual Task CheckWithRequestAsync() where T : BasePermission, new() { - return CommonCheckWithRequestAsync(); + var permissionType = typeof(T); + if (permissionType == typeof(CustomPermissions.Notifications)) + { + return CommonCheckWithRequestAsync(); + } + else if (permissionType == typeof(CustomPermissions.Bluetooth)) + { + return CommonCheckWithRequestAsync(); + } + else + { + return CommonCheckWithRequestAsync(); + } } /// public void SetPermissionDialogService(IPermissionsDialogService permissionsDialogService) { _permissionsDialogService = permissionsDialogService - ?? throw new ArgumentNullException(nameof(permissionsDialogService)); + ?? throw new ArgumentNullException(nameof(permissionsDialogService)); } - protected bool IsPermissionDeniedEver() - where T : BasePermission + protected virtual void RemoveOldKeys() + where T : BasePermission, new() { - return Preferences.Get(GetPermissionDeniedEverKey(), false); - } + var requestedKey = GetPermissionRequestedKey(); + if (Preferences.ContainsKey(requestedKey)) + { + Preferences.Remove(requestedKey); + } - private void SetPermissionDenied(bool value) - where T : BasePermission - { - Preferences.Set(GetPermissionDeniedEverKey(), value); - } + var deniedEverKey = GetPermissionDeniedEverKey(); + if (Preferences.ContainsKey(deniedEverKey)) + { + Preferences.Remove(deniedEverKey); + } - private string GetPermissionDeniedEverKey() - where T : BasePermission - { - return $"{nameof(PermissionsManager)}_IsPermissionDeniedEver_{typeof(T).Name}"; + string GetPermissionRequestedKey() + where TPermission : BasePermission + { + return $"{nameof(PermissionsManager)}_IsPermissionRequested_{typeof(T).Name}"; + } + + string GetPermissionDeniedEverKey() + where T : BasePermission + { + return $"{nameof(PermissionsManager)}_IsPermissionDeniedEver_{typeof(T).Name}"; + } } private void OpenSettings() @@ -66,32 +104,6 @@ private void OpenSettings() _permissionsService.OpenSettings(); } - private void ApplyKeysMigration(PermissionStatus permissionStatus) - where T : BasePermission, new() - { - var requestedKeyName = GetPermissionRequestedKey(); - if (!Preferences.ContainsKey(requestedKeyName)) - { - return; - } - - if (Preferences.Get(requestedKeyName, false)) - { - if (permissionStatus == PermissionStatus.Denied) - { - SetPermissionDenied(true); - } - - Preferences.Remove(requestedKeyName); - } - - string GetPermissionRequestedKey() - where TPermission : BasePermission - { - return $"{nameof(PermissionsManager)}_IsPermissionRequested_{typeof(T).Name}"; - } - } - private async Task CommonCheckWithRequestAsync() where T : BasePermission, new() { @@ -101,21 +113,24 @@ private async Task CommonCheckWithRequestAsync() return permissionStatus; } - ApplyKeysMigration(permissionStatus); + RemoveOldKeys(); - if (permissionStatus == PermissionStatus.Denied - && IsPermissionDeniedEver() - && !_permissionsService.ShouldShowRationale()) - { - await OpenSettingsWithConfirmationAsync().ConfigureAwait(false); - return PermissionStatus.Denied; - } + // Timer are used for confirm a fact of showing a request of permission access popup + // in another case user should see screen with settings for changing permission state + var timer = new Stopwatch(); + timer.Start(); var confirmationResult = await _permissionsDialogService.ConfirmPermissionAsync().ConfigureAwait(false); if (confirmationResult) { permissionStatus = await _permissionsService.RequestPermissionsAsync().ConfigureAwait(false); - SetPermissionDenied(permissionStatus == PermissionStatus.Denied); + } + + if (permissionStatus == PermissionStatus.Denied + && timer.Elapsed < _showPermissionDialogThreshold) + { + await OpenSettingsWithConfirmationAsync().ConfigureAwait(false); + return PermissionStatus.Denied; } return permissionStatus; @@ -125,7 +140,7 @@ private async Task OpenSettingsWithConfirmationAsync() where T : BasePermission { var openSettingsConfirmed = await _permissionsDialogService - .ConfirmOpenSettingsForPermissionAsync().ConfigureAwait(false); + .ConfirmOpenSettingsForPermissionAsync().ConfigureAwait(false); if (openSettingsConfirmed) { OpenSettings(); diff --git a/Softeq.XToolkit.Permissions.Droid/PermissionsService.cs b/Softeq.XToolkit.Permissions.Droid/PermissionsService.cs deleted file mode 100644 index 30eceb6af..000000000 --- a/Softeq.XToolkit.Permissions.Droid/PermissionsService.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Threading.Tasks; -using Xamarin.Essentials; -using BasePermission = Xamarin.Essentials.Permissions.BasePermission; -using EssentialsPermissions = Xamarin.Essentials.Permissions; - -namespace Softeq.XToolkit.Permissions.Droid -{ - /// - public class PermissionsService : IPermissionsService - { - /// - public async Task RequestPermissionsAsync() - where T : BasePermission, new() - { - return await MainThread.InvokeOnMainThreadAsync(async () => - { - Xamarin.Essentials.PermissionStatus result; - if (typeof(T) == typeof(NotificationsPermission)) - { - result = await EssentialsPermissions - .RequestAsync() - .ConfigureAwait(false); - } - else - { - result = await EssentialsPermissions - .RequestAsync() - .ConfigureAwait(false); - } - - return result.ToPermissionStatus(); - }); - } - - /// - public async Task CheckPermissionsAsync() - where T : BasePermission, new() - { - Xamarin.Essentials.PermissionStatus result; - if (typeof(T) == typeof(NotificationsPermission)) - { - result = await EssentialsPermissions - .CheckStatusAsync() - .ConfigureAwait(false); - } - else - { - result = await EssentialsPermissions - .CheckStatusAsync() - .ConfigureAwait(false); - } - - return result.ToPermissionStatus(); - } - - /// - public void OpenSettings() - { - MainThread.BeginInvokeOnMainThread(AppInfo.ShowSettingsUI); - } - - public bool ShouldShowRationale() where T : BasePermission, new() - { - return typeof(T) == typeof(NotificationsPermission) - ? EssentialsPermissions.ShouldShowRationale() - : EssentialsPermissions.ShouldShowRationale(); - } - } -} diff --git a/Softeq.XToolkit.Permissions.Droid/Properties/AssemblyInfo.cs b/Softeq.XToolkit.Permissions.Droid/Properties/AssemblyInfo.cs deleted file mode 100644 index 33242b045..000000000 --- a/Softeq.XToolkit.Permissions.Droid/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Reflection; -using System.Runtime.InteropServices; - -// Information about this assembly is defined by the following attributes. -// Change them to the values specific to your project. -[assembly: AssemblyTitle("Softeq.XToolkit.Permissions.Droid")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Softeq Development Corporation")] -[assembly: AssemblyProduct("WhiteLabel.Permissions")] -[assembly: AssemblyCopyright("Copyright © Softeq Development Corporation 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Softeq.XToolkit.Permissions.Droid/RequestResultHandler.cs b/Softeq.XToolkit.Permissions.Droid/RequestResultHandler.cs index 48f99e16a..33d71f9b0 100644 --- a/Softeq.XToolkit.Permissions.Droid/RequestResultHandler.cs +++ b/Softeq.XToolkit.Permissions.Droid/RequestResultHandler.cs @@ -16,7 +16,7 @@ public void Handle(int requestCode, string[] permissions, object grantResults) private void HandleImpl(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults) { - Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults); + Microsoft.Maui.ApplicationModel.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults); } } } diff --git a/Softeq.XToolkit.Permissions.Droid/SdkVersion.cs b/Softeq.XToolkit.Permissions.Droid/SdkVersion.cs new file mode 100644 index 000000000..602ab16f6 --- /dev/null +++ b/Softeq.XToolkit.Permissions.Droid/SdkVersion.cs @@ -0,0 +1,26 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using Android.OS; +using Microsoft.Maui.ApplicationModel; + +namespace Softeq.XToolkit.Permissions.Droid +{ + internal static class SdkVersion + { + public static bool IsTargetSdkLower(BuildVersionCodes versionCode) + { + return Platform.AppContext.ApplicationInfo?.TargetSdkVersion <= versionCode; + } + + public static bool IsTargetSdkAtLeast(BuildVersionCodes versionCode) + { + return Platform.AppContext.ApplicationInfo?.TargetSdkVersion >= versionCode; + } + + public static bool IsBuildSdkAtLeast(BuildVersionCodes versionCode) + { + return Build.VERSION.SdkInt >= versionCode; + } + } +} \ No newline at end of file diff --git a/Softeq.XToolkit.Permissions.Droid/Softeq.XToolkit.Permissions.Droid.csproj b/Softeq.XToolkit.Permissions.Droid/Softeq.XToolkit.Permissions.Droid.csproj index 87f70fdad..e44ebd58b 100644 --- a/Softeq.XToolkit.Permissions.Droid/Softeq.XToolkit.Permissions.Droid.csproj +++ b/Softeq.XToolkit.Permissions.Droid/Softeq.XToolkit.Permissions.Droid.csproj @@ -1,64 +1,26 @@ - - + + - Debug - AnyCPU - {955A4101-4989-4764-9134-E621FC4D9C86} - {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Library - Softeq.XToolkit.Permissions.Droid - Softeq.XToolkit.Permissions.Droid - v13.0 - Resources\Resource.designer.cs - Resource - Resources - Assets + net8.0-android34.0 + 21.0 + true - portable - false - bin\Debug - DEBUG; - prompt - 4 None - - + - portable - true - bin\Release - prompt - 4 SdkOnly + - - - - + + + - - - - - + - - - - - - {18d3fdc1-b0a1-401e-87f2-1c43034e610c} - Softeq.XToolkit.Common.Droid - - - {C8DA5EA8-703B-481F-9ED8-AB3AD29E5409} - Softeq.XToolkit.Permissions - - - + \ No newline at end of file diff --git a/Softeq.XToolkit.Permissions.iOS/Permissions/Bluetooth.cs b/Softeq.XToolkit.Permissions.iOS/Permissions/Bluetooth.cs new file mode 100644 index 000000000..1a2d66878 --- /dev/null +++ b/Softeq.XToolkit.Permissions.iOS/Permissions/Bluetooth.cs @@ -0,0 +1,87 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using CoreBluetooth; +using BasePlatformPermission = Microsoft.Maui.ApplicationModel.Permissions.BasePlatformPermission; +using EssentialsPermissionStatus = Microsoft.Maui.ApplicationModel.PermissionStatus; + +namespace Softeq.XToolkit.Permissions.iOS.Permissions +{ + public class Bluetooth : BasePlatformPermission + { + /// + protected override Func> RequiredInfoPlistKeys => + () => new string[] { "NSBluetoothAlwaysUsageDescription" }; + + /// + public override Task CheckStatusAsync() + { + EnsureDeclared(); + + return Task.FromResult(ParseAuthorization(CBManager.Authorization)); + } + + /// + public override async Task RequestAsync() + { + EnsureDeclared(); + + var status = await CheckStatusAsync().ConfigureAwait(false); + + if (status == EssentialsPermissionStatus.Granted) + { + return status; + } + + if (CBManager.Authorization == CBManagerAuthorization.NotDetermined) + { + var centralManagerDelegate = new CentralManagerDelegate(); + await centralManagerDelegate.RequestAccessAsync().ConfigureAwait(false); + } + + return ParseAuthorization(CBManager.Authorization); + } + + private static EssentialsPermissionStatus ParseAuthorization(CBManagerAuthorization managerAuthorization) + { + return managerAuthorization switch + { + CBManagerAuthorization.NotDetermined => EssentialsPermissionStatus.Unknown, + CBManagerAuthorization.AllowedAlways => EssentialsPermissionStatus.Granted, + CBManagerAuthorization.Restricted => EssentialsPermissionStatus.Granted, + CBManagerAuthorization.Denied => EssentialsPermissionStatus.Denied, + _ => EssentialsPermissionStatus.Unknown + }; + } + + private class CentralManagerDelegate : CBCentralManagerDelegate + { + private readonly CBCentralManager _centralManager; + + private readonly TaskCompletionSource _statusRequest = + new TaskCompletionSource(); + + public CentralManagerDelegate() + { + _centralManager = new CBCentralManager(this, null); + } + + public async Task RequestAccessAsync() + { + var state = await _statusRequest.Task.ConfigureAwait(false); + return state; + } + + public override void UpdatedState(CBCentralManager centralManager) + { + if (_centralManager.State != CBManagerState.Unknown) + { + _statusRequest.TrySetResult(_centralManager.State); + } + } + } + } +} diff --git a/Softeq.XToolkit.Permissions.iOS/Permissions/Notifications.cs b/Softeq.XToolkit.Permissions.iOS/Permissions/Notifications.cs new file mode 100644 index 000000000..99af89862 --- /dev/null +++ b/Softeq.XToolkit.Permissions.iOS/Permissions/Notifications.cs @@ -0,0 +1,50 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using System.Threading.Tasks; +using UserNotifications; +using BasePlatformPermission = Microsoft.Maui.ApplicationModel.Permissions.BasePlatformPermission; +using EssentialsPermissionStatus = Microsoft.Maui.ApplicationModel.PermissionStatus; + +namespace Softeq.XToolkit.Permissions.iOS.Permissions +{ + public class Notifications : BasePlatformPermission + { + public static UNAuthorizationOptions AuthorizationOptions = + UNAuthorizationOptions.Alert | UNAuthorizationOptions.Sound; + + /// + public override async Task CheckStatusAsync() + { + var notificationSettings = await UNUserNotificationCenter.Current + .GetNotificationSettingsAsync().ConfigureAwait(false); + + return ParseAuthorization(notificationSettings.AuthorizationStatus); + } + + /// + public override async Task RequestAsync() + { + var notificationCenter = UNUserNotificationCenter.Current; + var (isGranted, _) = await notificationCenter + .RequestAuthorizationAsync(AuthorizationOptions) + .ConfigureAwait(false); + return isGranted + ? EssentialsPermissionStatus.Granted + : EssentialsPermissionStatus.Denied; + } + + private static EssentialsPermissionStatus ParseAuthorization(UNAuthorizationStatus authorizationStatus) + { + return authorizationStatus switch + { + UNAuthorizationStatus.NotDetermined => EssentialsPermissionStatus.Unknown, + UNAuthorizationStatus.Authorized => EssentialsPermissionStatus.Granted, + UNAuthorizationStatus.Provisional => EssentialsPermissionStatus.Granted, + UNAuthorizationStatus.Denied => EssentialsPermissionStatus.Denied, + _ => EssentialsPermissionStatus.Unknown + }; + } + } +} diff --git a/Softeq.XToolkit.Permissions.iOS/PermissionsManager.cs b/Softeq.XToolkit.Permissions.iOS/PermissionsManager.cs index 950430a70..dea76e881 100644 --- a/Softeq.XToolkit.Permissions.iOS/PermissionsManager.cs +++ b/Softeq.XToolkit.Permissions.iOS/PermissionsManager.cs @@ -3,8 +3,10 @@ using System; using System.Threading.Tasks; -using Xamarin.Essentials; -using BasePermission = Xamarin.Essentials.Permissions.BasePermission; +using Microsoft.Maui.Storage; +using BasePermission = Microsoft.Maui.ApplicationModel.Permissions.BasePermission; +using CustomPermissions = Softeq.XToolkit.Permissions.Permissions; +using PlatformCustomPermissions = Softeq.XToolkit.Permissions.iOS.Permissions; namespace Softeq.XToolkit.Permissions.iOS { @@ -35,23 +37,47 @@ private bool IsNotificationsPermissionRequested public virtual Task CheckWithRequestAsync() where T : BasePermission, new() { - return typeof(T) == typeof(NotificationsPermission) - ? NotificationsCheckWithRequestAsync() - : CommonCheckWithRequestAsync(); + var permissionType = typeof(T); + + if (permissionType == typeof(CustomPermissions.Notifications)) + { + return CommonCheckWithRequestAsync(); + } + else if (permissionType == typeof(CustomPermissions.Bluetooth)) + { + return CommonCheckWithRequestAsync(); + } + else + { + return CommonCheckWithRequestAsync(); + } } /// public Task CheckAsync() where T : BasePermission, new() { - return _permissionsService.CheckPermissionsAsync(); + var permissionType = typeof(T); + + if (permissionType == typeof(CustomPermissions.Notifications)) + { + return _permissionsService.CheckPermissionsAsync(); + } + else if (permissionType == typeof(CustomPermissions.Bluetooth)) + { + return _permissionsService.CheckPermissionsAsync(); + } + else + { + return _permissionsService.CheckPermissionsAsync(); + } } /// public void SetPermissionDialogService(IPermissionsDialogService permissionsDialogService) { - _permissionsDialogService = permissionsDialogService - ?? throw new ArgumentNullException(nameof(permissionsDialogService)); + _permissionsDialogService = permissionsDialogService ?? + throw new ArgumentNullException(nameof(permissionsDialogService)); } private void OpenSettings() @@ -59,31 +85,6 @@ private void OpenSettings() _permissionsService.OpenSettings(); } - private async Task NotificationsCheckWithRequestAsync() - { - if (!IsNotificationsPermissionRequested) - { - var isConfirmed = await _permissionsDialogService.ConfirmPermissionAsync() - .ConfigureAwait(false); - if (!isConfirmed) - { - return PermissionStatus.Denied; - } - } - - var permissionStatus = - await _permissionsService.RequestPermissionsAsync().ConfigureAwait(false); - - if (IsNotificationsPermissionRequested && permissionStatus != PermissionStatus.Granted) - { - permissionStatus = await OpenSettingsWithConfirmationAsync().ConfigureAwait(false); - } - - IsNotificationsPermissionRequested = true; - - return permissionStatus; - } - private async Task CommonCheckWithRequestAsync() where T : BasePermission, new() { @@ -114,7 +115,7 @@ private async Task OpenSettingsWithConfirmationAsync() where T : BasePermission { var openSettingsConfirmed = await _permissionsDialogService - .ConfirmOpenSettingsForPermissionAsync().ConfigureAwait(false); + .ConfirmOpenSettingsForPermissionAsync().ConfigureAwait(false); if (openSettingsConfirmed) { OpenSettings(); diff --git a/Softeq.XToolkit.Permissions.iOS/PermissionsService.cs b/Softeq.XToolkit.Permissions.iOS/PermissionsService.cs deleted file mode 100644 index 1950884bc..000000000 --- a/Softeq.XToolkit.Permissions.iOS/PermissionsService.cs +++ /dev/null @@ -1,85 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System; -using System.Threading.Tasks; -using UserNotifications; -using Xamarin.Essentials; -using BasePermission = Xamarin.Essentials.Permissions.BasePermission; -using EssentialsPermissions = Xamarin.Essentials.Permissions; - -namespace Softeq.XToolkit.Permissions.iOS -{ - /// - public class PermissionsService : IPermissionsService - { - /// - public async Task RequestPermissionsAsync() - where T : BasePermission, new() - { - return await MainThread.InvokeOnMainThreadAsync(async () => - { - if (typeof(T) == typeof(NotificationsPermission)) - { - return await RequestNotificationPermissionAsync().ConfigureAwait(false); - } - - var result = await EssentialsPermissions.RequestAsync().ConfigureAwait(false); - - return result.ToPermissionStatus(); - }); - } - - /// - public async Task CheckPermissionsAsync() - where T : BasePermission, new() - { - if (typeof(T) == typeof(NotificationsPermission)) - { - return await CheckNotificationsPermissionAsync().ConfigureAwait(false); - } - - var result = await EssentialsPermissions.CheckStatusAsync().ConfigureAwait(false); - - return result.ToPermissionStatus(); - } - - /// - public void OpenSettings() - { - MainThread.BeginInvokeOnMainThread(AppInfo.ShowSettingsUI); - } - - // TODO YP: refactor to using partial NotificationsPermission for iOS. - private static async Task CheckNotificationsPermissionAsync() - { - var notificationCenter = UNUserNotificationCenter.Current; - var notificationSettings = await notificationCenter.GetNotificationSettingsAsync().ConfigureAwait(false); - if (notificationSettings.AuthorizationStatus == UNAuthorizationStatus.NotDetermined) - { - return PermissionStatus.Unknown; - } - - var notificationsSettingsEnabled = notificationSettings.SoundSetting == UNNotificationSetting.Enabled - && notificationSettings.AlertSetting == UNNotificationSetting.Enabled; - return notificationsSettingsEnabled - ? PermissionStatus.Granted - : PermissionStatus.Denied; - } - - private static async Task RequestNotificationPermissionAsync() - { - var notificationCenter = UNUserNotificationCenter.Current; - var (isGranted, _) = await notificationCenter.RequestAuthorizationAsync( - UNAuthorizationOptions.Alert | UNAuthorizationOptions.Sound); - return isGranted - ? PermissionStatus.Granted - : PermissionStatus.Denied; - } - - public bool ShouldShowRationale() where T : BasePermission, new() - { - return true; - } - } -} diff --git a/Softeq.XToolkit.Permissions.iOS/Properties/AssemblyInfo.cs b/Softeq.XToolkit.Permissions.iOS/Properties/AssemblyInfo.cs deleted file mode 100644 index 5c1f72717..000000000 --- a/Softeq.XToolkit.Permissions.iOS/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Reflection; -using System.Runtime.InteropServices; - -// Information about this assembly is defined by the following attributes. -// Change them to the values specific to your project. -[assembly: AssemblyTitle("Softeq.XToolkit.Permissions.iOS")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Softeq Development Corporation")] -[assembly: AssemblyProduct("WhiteLabel.Permissions")] -[assembly: AssemblyCopyright("Copyright © Softeq Development Corporation 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Softeq.XToolkit.Permissions.iOS/Softeq.XToolkit.Permissions.iOS.csproj b/Softeq.XToolkit.Permissions.iOS/Softeq.XToolkit.Permissions.iOS.csproj index c723c06a6..a40594537 100644 --- a/Softeq.XToolkit.Permissions.iOS/Softeq.XToolkit.Permissions.iOS.csproj +++ b/Softeq.XToolkit.Permissions.iOS/Softeq.XToolkit.Permissions.iOS.csproj @@ -1,56 +1,26 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {7EB2ADA9-C599-4644-AC6B-109F1AEED19F} - {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Library - Softeq.XToolkit.Permissions.iOS - Softeq.XToolkit.Permissions.iOS - Resources - - - true - portable - false - bin\Debug - DEBUG; - prompt - 4 - None - false - - - portable - true - bin\Release - prompt - 4 - false - SdkOnly - - - - - - - - - - - - - - - - - - {C8DA5EA8-703B-481F-9ED8-AB3AD29E5409} - Softeq.XToolkit.Permissions - - - + + + + net8.0-ios17.0 + 13.0 + false + + + + true + None + + + + SdkOnly + + + + + + + + + + \ No newline at end of file diff --git a/Softeq.XToolkit.Permissions/DefaultPermissionsDialogService.cs b/Softeq.XToolkit.Permissions/DefaultPermissionsDialogService.cs index 1703bd63a..7154736ac 100644 --- a/Softeq.XToolkit.Permissions/DefaultPermissionsDialogService.cs +++ b/Softeq.XToolkit.Permissions/DefaultPermissionsDialogService.cs @@ -2,7 +2,7 @@ // http://www.softeq.com using System.Threading.Tasks; -using BasePermission = Xamarin.Essentials.Permissions.BasePermission; +using BasePermission = Microsoft.Maui.ApplicationModel.Permissions.BasePermission; namespace Softeq.XToolkit.Permissions { diff --git a/Softeq.XToolkit.Permissions/IPermissionsDialogService.cs b/Softeq.XToolkit.Permissions/IPermissionsDialogService.cs index d2ef9d7d0..edb860e66 100644 --- a/Softeq.XToolkit.Permissions/IPermissionsDialogService.cs +++ b/Softeq.XToolkit.Permissions/IPermissionsDialogService.cs @@ -2,7 +2,7 @@ // http://www.softeq.com using System.Threading.Tasks; -using BasePermission = Xamarin.Essentials.Permissions.BasePermission; +using BasePermission = Microsoft.Maui.ApplicationModel.Permissions.BasePermission; namespace Softeq.XToolkit.Permissions { diff --git a/Softeq.XToolkit.Permissions/IPermissionsManager.cs b/Softeq.XToolkit.Permissions/IPermissionsManager.cs index fcdd04a60..01e91dc5f 100644 --- a/Softeq.XToolkit.Permissions/IPermissionsManager.cs +++ b/Softeq.XToolkit.Permissions/IPermissionsManager.cs @@ -2,7 +2,7 @@ // http://www.softeq.com using System.Threading.Tasks; -using BasePermission = Xamarin.Essentials.Permissions.BasePermission; +using BasePermission = Microsoft.Maui.ApplicationModel.Permissions.BasePermission; namespace Softeq.XToolkit.Permissions { diff --git a/Softeq.XToolkit.Permissions/IPermissionsService.cs b/Softeq.XToolkit.Permissions/IPermissionsService.cs index 9a9d9d91b..f7870a8f1 100644 --- a/Softeq.XToolkit.Permissions/IPermissionsService.cs +++ b/Softeq.XToolkit.Permissions/IPermissionsService.cs @@ -2,7 +2,7 @@ // http://www.softeq.com using System.Threading.Tasks; -using BasePermission = Xamarin.Essentials.Permissions.BasePermission; +using BasePermission = Microsoft.Maui.ApplicationModel.Permissions.BasePermission; namespace Softeq.XToolkit.Permissions { diff --git a/Softeq.XToolkit.Permissions/NotificationsPermission.cs b/Softeq.XToolkit.Permissions/NotificationsPermission.cs deleted file mode 100644 index 7fbf16bcf..000000000 --- a/Softeq.XToolkit.Permissions/NotificationsPermission.cs +++ /dev/null @@ -1,9 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -namespace Softeq.XToolkit.Permissions -{ - public class NotificationsPermission : Xamarin.Essentials.Permissions.BasePlatformPermission - { - } -} diff --git a/Softeq.XToolkit.Permissions/Permissions/Bluetooth.cs b/Softeq.XToolkit.Permissions/Permissions/Bluetooth.cs new file mode 100644 index 000000000..7a5b64c43 --- /dev/null +++ b/Softeq.XToolkit.Permissions/Permissions/Bluetooth.cs @@ -0,0 +1,9 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +namespace Softeq.XToolkit.Permissions.Permissions +{ + public class Bluetooth : Microsoft.Maui.ApplicationModel.Permissions.BasePlatformPermission + { + } +} \ No newline at end of file diff --git a/Softeq.XToolkit.Permissions/Permissions/Notifications.cs b/Softeq.XToolkit.Permissions/Permissions/Notifications.cs new file mode 100644 index 000000000..b9d31ccf5 --- /dev/null +++ b/Softeq.XToolkit.Permissions/Permissions/Notifications.cs @@ -0,0 +1,9 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +namespace Softeq.XToolkit.Permissions.Permissions +{ + public class Notifications : Microsoft.Maui.ApplicationModel.Permissions.BasePlatformPermission + { + } +} \ No newline at end of file diff --git a/Softeq.XToolkit.Permissions/PermissionsService.cs b/Softeq.XToolkit.Permissions/PermissionsService.cs new file mode 100644 index 000000000..cf17adc9d --- /dev/null +++ b/Softeq.XToolkit.Permissions/PermissionsService.cs @@ -0,0 +1,44 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System.Threading.Tasks; +using Microsoft.Maui.ApplicationModel; +using BasePermission = Microsoft.Maui.ApplicationModel.Permissions.BasePermission; +using EssentialsPermissions = Microsoft.Maui.ApplicationModel.Permissions; + +namespace Softeq.XToolkit.Permissions +{ + /// + public class PermissionsService : IPermissionsService + { + /// + public Task RequestPermissionsAsync() + where T : BasePermission, new() + { + return MainThread.InvokeOnMainThreadAsync(async () => + { + var result = await EssentialsPermissions.RequestAsync().ConfigureAwait(false); + return result.ToPermissionStatus(); + }); + } + + /// + public async Task CheckPermissionsAsync() + where T : BasePermission, new() + { + var result = await EssentialsPermissions.CheckStatusAsync().ConfigureAwait(false); + return result.ToPermissionStatus(); + } + + /// + public void OpenSettings() + { + MainThread.BeginInvokeOnMainThread(AppInfo.ShowSettingsUI); + } + + public bool ShouldShowRationale() where T : BasePermission, new() + { + return EssentialsPermissions.ShouldShowRationale(); + } + } +} diff --git a/Softeq.XToolkit.Permissions/PluginPermissionStatusExtensions.cs b/Softeq.XToolkit.Permissions/PluginPermissionStatusExtensions.cs index 33d0499db..e944f5a30 100644 --- a/Softeq.XToolkit.Permissions/PluginPermissionStatusExtensions.cs +++ b/Softeq.XToolkit.Permissions/PluginPermissionStatusExtensions.cs @@ -2,7 +2,7 @@ // http://www.softeq.com using System.ComponentModel; -using PluginPermissionStatus = Xamarin.Essentials.PermissionStatus; +using PluginPermissionStatus = Microsoft.Maui.ApplicationModel.PermissionStatus; namespace Softeq.XToolkit.Permissions { diff --git a/Softeq.XToolkit.Permissions/Softeq.XToolkit.Permissions.csproj b/Softeq.XToolkit.Permissions/Softeq.XToolkit.Permissions.csproj index ce1c13bb6..7ac8cbfd6 100644 --- a/Softeq.XToolkit.Permissions/Softeq.XToolkit.Permissions.csproj +++ b/Softeq.XToolkit.Permissions/Softeq.XToolkit.Permissions.csproj @@ -1,15 +1,12 @@ - netstandard2.1 - - - + net8.0 True - + diff --git a/Softeq.XToolkit.PushNotifications.Droid/Abstract/INotificationsSettingsProvider.cs b/Softeq.XToolkit.PushNotifications.Droid/Abstract/INotificationsSettingsProvider.cs index b94c11e06..981f9558d 100644 --- a/Softeq.XToolkit.PushNotifications.Droid/Abstract/INotificationsSettingsProvider.cs +++ b/Softeq.XToolkit.PushNotifications.Droid/Abstract/INotificationsSettingsProvider.cs @@ -16,10 +16,12 @@ public interface INotificationsSettingsProvider string DefaultChannelId { get; } /// - /// Obtains a dictionary of notification channles where the key is channel id and the value is channel name. - /// - /// Note: if you are using "notification" notifications, Firebase will use a separate channel for them when received in Background. + /// Obtains a dictionary of notification channels where the key is channel id and the value is channel name. /// + /// + /// if you are using "notification" notifications, + /// Firebase will use a separate channel for them when received in Background. + /// /// /// Context for obtaining channel names from resources. /// @@ -39,9 +41,8 @@ public interface INotificationsSettingsProvider /// /// You can add some custom configuration for a created Notification Channel (like description, sound, /// turn off badges, create and set group - to create a group, etc.). - /// - /// This method will only be called on API 26+. /// + /// This method will only be called on API 26+. /// Channel Id string. /// /// A NotificationChannel that will be registered in the system. Contains already set channelId, @@ -62,18 +63,22 @@ public interface INotificationsSettingsProvider /// want them to replace each other. /// /// Push notification data. - /// + /// Styles. PushNotificationStyles GetStylesForNotification(PushNotificationModel pushNotification); /// - /// You can customize how push notification will be shown (apart from ContentTitle, ContentText, channelid, + /// You can customize how push notification will be shown (apart from ContentTitle, ContentText, channelId, /// styles set in GetStylesForNotification and content intent). For instance, you can use SetNumber to change badge /// value increment on Android 26+; add Action buttons; add groups and create additional notifications - like group /// summary notification; add progress bar and later update for this notificationId; or simply save notificationId; etc. /// /// Already created notification builder that can be further customized. /// Push notification data. - void CustomizeNotificationBuilder(NotificationCompat.Builder notificationBuilder, PushNotificationModel pushNotification, int notificationId); + /// + void CustomizeNotificationBuilder( + NotificationCompat.Builder notificationBuilder, + PushNotificationModel pushNotification, + int notificationId); /// /// Provides info about the Activity which will be opened by tap on the given notification. diff --git a/Softeq.XToolkit.PushNotifications.Droid/Properties/AssemblyInfo.cs b/Softeq.XToolkit.PushNotifications.Droid/Properties/AssemblyInfo.cs deleted file mode 100644 index 8f7e2067d..000000000 --- a/Softeq.XToolkit.PushNotifications.Droid/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Softeq.XToolkit.PushNotifications.Droid")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Softeq Development Corporation")] -[assembly: AssemblyProduct("XToolkit.PushNotifications")] -[assembly: AssemblyCopyright("Copyright © Softeq Development Corporation 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Softeq.XToolkit.PushNotifications.Droid/Softeq.XToolkit.PushNotifications.Droid.csproj b/Softeq.XToolkit.PushNotifications.Droid/Softeq.XToolkit.PushNotifications.Droid.csproj index 87331c8f8..2d9284635 100644 --- a/Softeq.XToolkit.PushNotifications.Droid/Softeq.XToolkit.PushNotifications.Droid.csproj +++ b/Softeq.XToolkit.PushNotifications.Droid/Softeq.XToolkit.PushNotifications.Droid.csproj @@ -1,91 +1,30 @@ - - + + - Debug - AnyCPU - 8.0.30703 - 2.0 - {FACBCCE2-C5F5-4AA8-9E19-8D61B4386EEA} - {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - {9ef11e43-1701-4396-8835-8392d57abb70} - Library - Properties - Softeq.XToolkit.PushNotifications.Droid - Softeq.XToolkit.PushNotifications.Droid - 512 - Resources\Resource.designer.cs - Off - v13.0 + net8.0-android34.0 + 26.0 + true - portable - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 None + - portable - true - bin\Release\ - TRACE - prompt - 4 SdkOnly + - - - - - - - - + + + + - - - - - - - - - - - - - - - - - - - - + + + + - - - - - - - - {E0C9CDE2-4C35-410A-AE95-A7871BEE0180} - Softeq.XToolkit.PushNotifications - - - {24588814-B93D-4528-8917-9C2A3C4E85CA} - Softeq.XToolkit.Common - - - {98c3b9f4-4e4f-4c0b-baa6-c61685e0ac49} - Softeq.XToolkit.WhiteLabel - - - + \ No newline at end of file diff --git a/Softeq.XToolkit.PushNotifications.iOS/Properties/AssemblyInfo.cs b/Softeq.XToolkit.PushNotifications.iOS/Properties/AssemblyInfo.cs deleted file mode 100644 index c57929311..000000000 --- a/Softeq.XToolkit.PushNotifications.iOS/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Developed by Softeq Development Corporation -// http://www.softeq.com - -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Softeq.XToolkit.PushNotifications.iOS")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Softeq Development Corporation")] -[assembly: AssemblyProduct("XToolkit.PushNotifications")] -[assembly: AssemblyCopyright("Copyright © Softeq Development Corporation 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("50c7b8c9-e664-45af-b88e-0c9b8b9c1be1")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Softeq.XToolkit.PushNotifications.iOS/Softeq.XToolkit.PushNotifications.iOS.csproj b/Softeq.XToolkit.PushNotifications.iOS/Softeq.XToolkit.PushNotifications.iOS.csproj index aa79e34de..93a3e152b 100644 --- a/Softeq.XToolkit.PushNotifications.iOS/Softeq.XToolkit.PushNotifications.iOS.csproj +++ b/Softeq.XToolkit.PushNotifications.iOS/Softeq.XToolkit.PushNotifications.iOS.csproj @@ -1,68 +1,22 @@ - - + + - Debug - AnyCPU - 8.0.30703 - 2.0 - {0EAE7634-9497-4FB8-B62F-DDACF85985D2} - {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - {a52b8a63-bc84-4b47-910d-692533484892} - Library - Softeq.XToolkit.PushNotifications.iOS - Resources - Softeq.XToolkit.PushNotifications.iOS + net8.0-ios12.0 + 10.0 + false + true - portable - false - bin\Debug - DEBUG; - prompt - 4 - false - None + None + - portable - true - bin\Release - prompt - 4 - false - SdkOnly + SdkOnly + - - - - + + - - - - - - - - - - - - - - - - - - {E0C9CDE2-4C35-410A-AE95-A7871BEE0180} - Softeq.XToolkit.PushNotifications - - - {24588814-B93D-4528-8917-9C2A3C4E85CA} - Softeq.XToolkit.Common - - - \ No newline at end of file diff --git a/Softeq.XToolkit.PushNotifications/Softeq.XToolkit.PushNotifications.csproj b/Softeq.XToolkit.PushNotifications/Softeq.XToolkit.PushNotifications.csproj index 9e57e48f1..dd8d7523e 100644 --- a/Softeq.XToolkit.PushNotifications/Softeq.XToolkit.PushNotifications.csproj +++ b/Softeq.XToolkit.PushNotifications/Softeq.XToolkit.PushNotifications.csproj @@ -1,7 +1,7 @@ - netstandard2.1 + net8.0 diff --git a/Softeq.XToolkit.Remote.Tests/Softeq.XToolkit.Remote.Tests.csproj b/Softeq.XToolkit.Remote.Tests/Softeq.XToolkit.Remote.Tests.csproj index 6116a53ac..bc17457b1 100644 --- a/Softeq.XToolkit.Remote.Tests/Softeq.XToolkit.Remote.Tests.csproj +++ b/Softeq.XToolkit.Remote.Tests/Softeq.XToolkit.Remote.Tests.csproj @@ -2,7 +2,7 @@ Exe - net5.0 + net8.0 diff --git a/Softeq.XToolkit.Remote/Api/IApiServiceFactory.cs b/Softeq.XToolkit.Remote/Api/IApiServiceFactory.cs index e4a86fc5a..6256cd127 100644 --- a/Softeq.XToolkit.Remote/Api/IApiServiceFactory.cs +++ b/Softeq.XToolkit.Remote/Api/IApiServiceFactory.cs @@ -10,6 +10,12 @@ namespace Softeq.XToolkit.Remote.Api /// public interface IApiServiceFactory { + /// + /// Creates API service implementation. + /// + /// Configured instance of HttpClient. + /// Type of API service. + /// API service implementation. TApiService CreateService(HttpClient httpClient); } } diff --git a/Softeq.XToolkit.Remote/Api/RefitApiServiceFactory.cs b/Softeq.XToolkit.Remote/Api/RefitApiServiceFactory.cs index edee2417a..b18f0e7cf 100644 --- a/Softeq.XToolkit.Remote/Api/RefitApiServiceFactory.cs +++ b/Softeq.XToolkit.Remote/Api/RefitApiServiceFactory.cs @@ -14,11 +14,16 @@ public class RefitApiServiceFactory : IApiServiceFactory { private readonly RefitSettings? _settings; + /// + /// Initializes a new instance of the class. + /// + /// Refit settings. public RefitApiServiceFactory(RefitSettings? settings = null) { _settings = settings; } + /// public TApiService CreateService(HttpClient httpClient) { if (httpClient == null) diff --git a/Softeq.XToolkit.Remote/Auth/AuthExtensions.cs b/Softeq.XToolkit.Remote/Auth/AuthExtensions.cs index d9804ccf6..e498d133a 100644 --- a/Softeq.XToolkit.Remote/Auth/AuthExtensions.cs +++ b/Softeq.XToolkit.Remote/Auth/AuthExtensions.cs @@ -8,6 +8,9 @@ namespace Softeq.XToolkit.Remote.Auth { + /// + /// Contains extension methods for related to auth. + /// public static class AuthExtensions { /// diff --git a/Softeq.XToolkit.Remote/Client/ClientExtensions.cs b/Softeq.XToolkit.Remote/Client/ClientExtensions.cs index 39d5afa1b..b656165f5 100644 --- a/Softeq.XToolkit.Remote/Client/ClientExtensions.cs +++ b/Softeq.XToolkit.Remote/Client/ClientExtensions.cs @@ -8,6 +8,9 @@ namespace Softeq.XToolkit.Remote.Client { + /// + /// Contains extension methods for . + /// public static class ClientExtensions { /// diff --git a/Softeq.XToolkit.Remote/Exceptions/ExpiredRefreshTokenException.cs b/Softeq.XToolkit.Remote/Exceptions/ExpiredRefreshTokenException.cs index b04920cc1..348ebe469 100644 --- a/Softeq.XToolkit.Remote/Exceptions/ExpiredRefreshTokenException.cs +++ b/Softeq.XToolkit.Remote/Exceptions/ExpiredRefreshTokenException.cs @@ -10,6 +10,10 @@ namespace Softeq.XToolkit.Remote.Exceptions /// public class ExpiredRefreshTokenException : Exception { + /// + /// Initializes a new instance of the class. + /// + /// Inner exception. public ExpiredRefreshTokenException(Exception innerException) : base("Refresh Token was expired", innerException) { diff --git a/Softeq.XToolkit.Remote/RemoteService.cs b/Softeq.XToolkit.Remote/RemoteService.cs index 60af48c84..0c08a13c2 100644 --- a/Softeq.XToolkit.Remote/RemoteService.cs +++ b/Softeq.XToolkit.Remote/RemoteService.cs @@ -57,6 +57,11 @@ public virtual async Task MakeRequest( .ConfigureAwait(false); } + /// + /// Creates custom Polly policy. + /// + /// Request options. + /// Polly policy. protected virtual IAsyncPolicy CreatePolicy(RequestOptions options) { return _executorFactory diff --git a/Softeq.XToolkit.Remote/RemoteServiceExtensions.cs b/Softeq.XToolkit.Remote/RemoteServiceExtensions.cs index 949e7ddfe..a41e60cb9 100644 --- a/Softeq.XToolkit.Remote/RemoteServiceExtensions.cs +++ b/Softeq.XToolkit.Remote/RemoteServiceExtensions.cs @@ -9,6 +9,9 @@ namespace Softeq.XToolkit.Remote { + /// + /// Contains extension methods for . + /// public static class RemoteServiceExtensions { /// diff --git a/Softeq.XToolkit.Remote/Softeq.XToolkit.Remote.csproj b/Softeq.XToolkit.Remote/Softeq.XToolkit.Remote.csproj index 8be766aee..247ae3790 100644 --- a/Softeq.XToolkit.Remote/Softeq.XToolkit.Remote.csproj +++ b/Softeq.XToolkit.Remote/Softeq.XToolkit.Remote.csproj @@ -1,7 +1,7 @@ - netstandard2.1 + net8.0 @@ -11,7 +11,6 @@ - diff --git a/Softeq.XToolkit.WhiteLabel.Droid/ActivityBase.cs b/Softeq.XToolkit.WhiteLabel.Droid/ActivityBase.cs index 7d9a6d242..892d5b674 100644 --- a/Softeq.XToolkit.WhiteLabel.Droid/ActivityBase.cs +++ b/Softeq.XToolkit.WhiteLabel.Droid/ActivityBase.cs @@ -11,7 +11,7 @@ using Softeq.XToolkit.Bindings.Abstract; using Softeq.XToolkit.Bindings.Extensions; using Softeq.XToolkit.Common.Droid.Permissions; -using Softeq.XToolkit.WhiteLabel.Droid.Navigation; +using Softeq.XToolkit.WhiteLabel.Droid.Interfaces; using Softeq.XToolkit.WhiteLabel.Droid.ViewComponents; using Softeq.XToolkit.WhiteLabel.Mvvm; using Softeq.XToolkit.WhiteLabel.Navigation; diff --git a/Softeq.XToolkit.WhiteLabel.Droid/Controls/BadgeView.cs b/Softeq.XToolkit.WhiteLabel.Droid/Controls/BadgeView.cs index ca58c8639..3e832a6bc 100644 --- a/Softeq.XToolkit.WhiteLabel.Droid/Controls/BadgeView.cs +++ b/Softeq.XToolkit.WhiteLabel.Droid/Controls/BadgeView.cs @@ -41,11 +41,11 @@ public BadgeView(Context context, IAttributeSet attrs, int defStyle) public ColorStateList BackgroundColor { - get => ((GradientDrawable) _textView.Background).Color; - set => ((GradientDrawable) _textView.Background).SetColor(value); + get => ((GradientDrawable) _textView.Background!).Color!; + set => ((GradientDrawable) _textView.Background!).SetColor(value); } - public ColorStateList TextColor + public ColorStateList? TextColor { get => _textView.TextColors; set => _textView.SetTextColor(value); @@ -58,14 +58,15 @@ internal void SetViewModel(TabViewModel viewModel) _viewModelRef = new WeakReferenceEx>(viewModel); this.DetachBindings(); - this.Bind(() => _viewModelRef.Target.BadgeText, () => _textView.Text); - this.Bind(() => _viewModelRef.Target.IsBadgeVisible, () => Visibility, GoneConverter.Instance); + this.Bind(() => _viewModelRef.Target!.BadgeText, () => _textView.Text); + this.Bind(() => _viewModelRef.Target!.IsBadgeVisible, () => Visibility, VisibilityConverter.Gone); } private void Initialize(Context context) { Inflate(context, Resource.Layout.control_notification_badge, this); - _textView = FindViewById(Resource.Id.notification_badge_text_view); + + _textView = FindViewById(Resource.Id.notification_badge_text_view)!; } } } diff --git a/Softeq.XToolkit.WhiteLabel.Droid/Controls/BusyOverlayView.cs b/Softeq.XToolkit.WhiteLabel.Droid/Controls/BusyOverlayView.cs index 6b8dc7bc9..3c8310cc8 100644 --- a/Softeq.XToolkit.WhiteLabel.Droid/Controls/BusyOverlayView.cs +++ b/Softeq.XToolkit.WhiteLabel.Droid/Controls/BusyOverlayView.cs @@ -35,7 +35,7 @@ public BusyOverlayView(IntPtr handle, JniHandleOwnership owner) : base(handle, o public void SetOverlayBackgroundResource(int resourceId) { var view = FindViewById(Resource.Id.control_busy_overlay_container); - view.SetBackgroundResource(resourceId); + view!.SetBackgroundResource(resourceId); } private void Init(Context context) diff --git a/Softeq.XToolkit.WhiteLabel.Droid/Controls/ColoredClickableSpan.cs b/Softeq.XToolkit.WhiteLabel.Droid/Controls/ColoredClickableSpan.cs index deeb93bd1..0bb082bbf 100644 --- a/Softeq.XToolkit.WhiteLabel.Droid/Controls/ColoredClickableSpan.cs +++ b/Softeq.XToolkit.WhiteLabel.Droid/Controls/ColoredClickableSpan.cs @@ -30,7 +30,7 @@ public ColoredClickableSpan(Context context, Action clickAction, int? colorResou public override void OnClick(View widget) { - _clickAction?.Invoke(); + _clickAction.Invoke(); } public override void UpdateDrawState(TextPaint ds) diff --git a/Softeq.XToolkit.WhiteLabel.Droid/Controls/DroidContextMenuComponent.cs b/Softeq.XToolkit.WhiteLabel.Droid/Controls/DroidContextMenuComponent.cs index 11097df5f..9d030761a 100644 --- a/Softeq.XToolkit.WhiteLabel.Droid/Controls/DroidContextMenuComponent.cs +++ b/Softeq.XToolkit.WhiteLabel.Droid/Controls/DroidContextMenuComponent.cs @@ -32,7 +32,7 @@ public PopupMenu BuildMenu(Context context, View anchorView) return popup; } - private void Popup_MenuItemClick(object sender, PopupMenu.MenuItemClickEventArgs e) + private void Popup_MenuItemClick(object? sender, PopupMenu.MenuItemClickEventArgs e) { ExecuteCommand(e.Item.ItemId, null); } diff --git a/Softeq.XToolkit.WhiteLabel.Droid/Controls/NavigationBarView.cs b/Softeq.XToolkit.WhiteLabel.Droid/Controls/NavigationBarView.cs index 5d4690465..3c3b21080 100644 --- a/Softeq.XToolkit.WhiteLabel.Droid/Controls/NavigationBarView.cs +++ b/Softeq.XToolkit.WhiteLabel.Droid/Controls/NavigationBarView.cs @@ -49,7 +49,7 @@ public NavigationBarView(IntPtr handle, JniHandleOwnership owner) public ImageButton RightImageButton => _rightButton; - public void SetLeftButton(int resourceId, ICommand command, int? color = null) + public void SetLeftButton(int resourceId, ICommand? command, int? color = null) { _leftButton.SetImageResource(resourceId); _leftButton.Visibility = ViewStates.Visible; @@ -65,7 +65,7 @@ public void SetLeftButton(int resourceId, ICommand command, int? color = null) } } - public void SetRightButton(int resourceId, ICommand command) + public void SetRightButton(int resourceId, ICommand? command) { _rightButton.SetImageResource(resourceId); _rightButton.Visibility = ViewStates.Visible; @@ -76,7 +76,7 @@ public void SetRightButton(int resourceId, ICommand command) } } - public void SetRightButton(Drawable drawable, ICommand command) + public void SetRightButton(Drawable drawable, ICommand? command) { _rightButton.SetImageDrawable(drawable); _rightButton.Visibility = ViewStates.Visible; @@ -87,7 +87,7 @@ public void SetRightButton(Drawable drawable, ICommand command) } } - public void SetRightButton(string label, ICommand command) + public void SetRightButton(string label, ICommand? command) { RightTextButton.Text = label; RightTextButton.Visibility = ViewStates.Visible; @@ -98,7 +98,7 @@ public void SetRightButton(string label, ICommand command) } } - public void SetCenterImage(int resourceId, ICommand command) + public void SetCenterImage(int resourceId, ICommand? command) { _centerImageView.SetImageResource(resourceId); _centerImageView.Visibility = ViewStates.Visible; @@ -117,7 +117,7 @@ public void SetTitle(string text) public void SetBackground(int resourceId) { - var view = FindViewById(Resource.Id.control_navigation_bar_container); + var view = FindViewById(Resource.Id.control_navigation_bar_container)!; view.SetBackgroundResource(resourceId); } @@ -125,19 +125,19 @@ private void Init(Context context) { Inflate(context, Resource.Layout.control_navigation_bar, this); - _leftButton = FindViewById(Resource.Id.control_navigation_bar_left_button); + _leftButton = FindViewById(Resource.Id.control_navigation_bar_left_button)!; _leftButton.Visibility = ViewStates.Gone; - _rightButton = FindViewById(Resource.Id.control_navigation_bar_right_button); + _rightButton = FindViewById(Resource.Id.control_navigation_bar_right_button)!; _rightButton.Visibility = ViewStates.Gone; - RightTextButton = FindViewById