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
-
+
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 @@
-
-
-