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 795ea7c00..000000000 Binary files a/Softeq.XToolkit.Common.Droid.Tests/Resources/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/Softeq.XToolkit.Common.Droid.Tests/Resources/mipmap-xhdpi/ic_launcher.png b/Softeq.XToolkit.Common.Droid.Tests/Resources/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 761cc91d9..000000000 Binary files a/Softeq.XToolkit.Common.Droid.Tests/Resources/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/Softeq.XToolkit.Common.Droid.Tests/Resources/mipmap-xxhdpi/ic_launcher.png b/Softeq.XToolkit.Common.Droid.Tests/Resources/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 9133e31b4..000000000 Binary files a/Softeq.XToolkit.Common.Droid.Tests/Resources/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/Softeq.XToolkit.Common.Droid.Tests/Resources/mipmap-xxxhdpi/ic_launcher.png b/Softeq.XToolkit.Common.Droid.Tests/Resources/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index d4fd714ee..000000000 Binary files a/Softeq.XToolkit.Common.Droid.Tests/Resources/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/Softeq.XToolkit.Common.Droid.Tests/Resources/values/strings.xml b/Softeq.XToolkit.Common.Droid.Tests/Resources/values/strings.xml deleted file mode 100644 index 661196a85..000000000 --- a/Softeq.XToolkit.Common.Droid.Tests/Resources/values/strings.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - 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