diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 95cf5eb6b60..b95a16f1052 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -23,11 +23,13 @@ Follow the style used by the [.NET Foundation](https://github.com/dotnet/corefx/
- We do not use the `private` keyword, as it is the default accessibility level in C#.
- We use hard tabs over spaces.
-Read and follow our [Pull Request template](https://github.com/xamarin/Xamarin.Forms/blob/master/PULL_REQUEST_TEMPLATE.md).
+Read and follow our [Pull Request template](PULL_REQUEST_TEMPLATE.md).
### Pull Request Requirements
- We use red-green-refactor test driven development. If you're planning to work on a bug fix, please be sure to create a test case in the UI tests suite (or unit tests, if you're working on Core/XAML code) that proves that the behavior is broken and then proves that the behavior was resolved after your changes. If at all possible, the test should be automated. If the test cannot be automated, then it should include manual testing instructions on screen.
+We use red-green-refactor test driven development. If you're planning to work on a bug fix, please be sure to create a test case in the UI tests suite (or unit tests, if you're working on Core/XAML code) that proves that the behavior is broken and then proves that the behavior was resolved after your changes. If at all possible, the test should be automated. If the test cannot be automated, then it should include manual testing instructions on screen.
+
+Please check the "Allow edits from maintainers" checkbox on your pull request. This allows us to quickly make minor fixes and resolve conflicts for you.
## Proposals/Enhancements/Suggestions
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 459283efe54..5d9648e7786 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -41,3 +41,7 @@ assignees: ''
### Reproduction Link
+
+### Workaround
+
+
\ No newline at end of file
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index e86f3ba4311..9e5e02a2d96 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -2,7 +2,7 @@
- If this is an enhancement or contains API changes or breaking changes, target master.
- If the issue you're working on has a milestone, target the corresponding branch.
- If this is a bug fix, target the branch of the latest stable version (unless the bug is only in a prerelease or master, of course!).
- See [Contributing](https://github.com/xamarin/Xamarin.Forms/blob/master/CONTRIBUTING.md) for more tips!
+ See [Contributing](https://github.com/xamarin/Xamarin.Forms/blob/master/.github/CONTRIBUTING.md) for more tips!
PLEASE DELETE THE ALL THESE COMMENTS BEFORE SUBMITTING! THANKS!!!
-->
diff --git a/.nuspec/Xamarin.Forms.nuspec b/.nuspec/Xamarin.Forms.nuspec
index a8797123569..ccb1023b87e 100644
--- a/.nuspec/Xamarin.Forms.nuspec
+++ b/.nuspec/Xamarin.Forms.nuspec
@@ -272,6 +272,7 @@
+
@@ -292,6 +293,7 @@
+
diff --git a/.nuspec/Xamarin.Forms.props b/.nuspec/Xamarin.Forms.props
index 54c037276a4..1b0a2285d13 100644
--- a/.nuspec/Xamarin.Forms.props
+++ b/.nuspec/Xamarin.Forms.props
@@ -12,4 +12,8 @@
-->
+
+ <_IsHotRestartDefined>$([System.Text.RegularExpressions.Regex]::IsMatch('$(DefineConstants.Trim())', '(^|;)HOTRESTART($|;)'))
+ HOTRESTART;$(DefineConstants)
+
diff --git a/GitInfo.txt b/GitInfo.txt
index a84947d6ffe..6016e8addc4 100644
--- a/GitInfo.txt
+++ b/GitInfo.txt
@@ -1 +1 @@
-4.5.0
+4.6.0
diff --git a/README.md b/README.md
index 782e51d523a..fdb3954bea4 100644
--- a/README.md
+++ b/README.md
@@ -48,23 +48,23 @@ If you want to use the latest dev build then you should read [this blog post](ht
## Getting Started ##
### Windows ###
-##### Install Visual Studio 2017+ #####
+##### Install Visual Studio 2019+ #####
-VS 2017+ is required for developing Xamarin.Forms. If you do not already have it installed, you can download it [here](https://www.visualstudio.com/downloads/download-visual-studio-vs). VS 2017+ Community is completely free. If you are installing VS 2017+ for the first time, select the "Custom" installation type and select the following from the features list to install:
+VS 2019+ is required for developing Xamarin.Forms. If you do not already have it installed, you can download it [here](https://www.visualstudio.com/downloads/download-visual-studio-vs). VS 2019+ Community is completely free. If you are installing VS 2019+ for the first time, select the "Custom" installation type and select the following from the features list to install:
- .NET desktop development - In the `Summary > Optional select .NET Framework 4.7 SDK, .NET Framework 4.7 targeting pack`.
- Universal Windows Platform Development - In the `Summary > Optional select the Windows 10 Mobile Emulator`.
- Mobile Development with .NET - In the `Summary > Optional select Xamarin Remoted Simulator, Xamarin SDK Manager, Intel Hardware Accelerated Execution Manager (HAXM)`
-The Android 7.0 Nougat API 24 SDK is required for developing Xamarin.Forms. It can be installed by using the [Xamarin Android SDK Manager](https://docs.microsoft.com/xamarin/android/get-started/installation/android-sdk).
+The Android 10.0 API 29 SDK is required for developing Xamarin.Forms. It can be installed by using the [Xamarin Android SDK Manager](https://docs.microsoft.com/xamarin/android/get-started/installation/android-sdk).
We also recommend installing [Xamarin Android Device Manager](https://developer.xamarin.com/guides/android/getting_started/installation/android-emulator/xamarin-device-manager/) This will use the HAXM tools installed above and allow you to configure Android Virtual Devices (AVDs) that emulate Android devices.
-If you already have VS 2017+ installed, you can verify that these features are installed by modifying the VS 2017+ installation via the Visual Studio Installer.
+If you already have VS 2019+ installed, you can verify that these features are installed by modifying the VS 2019+ installation via the Visual Studio Installer.
### Mac ###
#### Install Visual Studio for Mac 2019 ####
-If you do not already have it installed, instructions to download and setup can be found [here](https://docs.microsoft.com/en-us/visualstudio/mac/installation?view=vsmac-2017).
+If you do not already have it installed, instructions to download and setup can be found [here](https://docs.microsoft.com/en-us/visualstudio/mac/installation?view=vsmac-2019).
Because of current Multi-Targeting limitations with Visual Studio for Mac you will need to manually build/restore some projects before you are able to work on the Xamarin Forms solution.
@@ -92,7 +92,7 @@ By default, the `Xamarin.Forms.Controls` project does not have a configuration f
UWPMapsAuthKey:
-You will have to obtain your own API keys for each of these services, inserted directly after the identifier (e.g. `UWPMapsAuthKey:abcdefghijklmnopqrstuvwxyz`). You can find out how to obtain each of these as follows:
+If you aren't working with maps, you can ignore this. If you want to work with maps, you will have to obtain your own API keys for each of these services, inserted directly after the identifier (e.g. `UWPMapsAuthKey:abcdefghijklmnopqrstuvwxyz`). You can find out how to obtain each of these as follows:
- `UWPMapsAuthKey` at https://microsoft.com/maps/create-a-bing-maps-key.aspx
@@ -134,7 +134,7 @@ You should now be able to run any of the UWP UI Tests.
We follow the style used by the [.NET Foundation](https://github.com/dotnet/corefx/blob/master/Documentation/coding-guidelines/coding-style.md), with a few exceptions:
- We do not use the `private` keyword as it is the default accessibility level in C#.
-- We use hard tabs over spaces. You can change this setting in VS 2015 via `Tools > Options` and navigating to `Text Editor > C#` and selecting the "Keep tabs" radio option. In Visual Studio for Mac it's set via preferences in `Source Code > Code Formatting > C# source code` and disabling the checkbox for `Convert tabs to spaces`.
+- We use hard tabs over spaces. You can change this setting in Visual Studio for Windows via `Tools > Options` and navigating to `Text Editor > C#` and selecting the "Keep tabs" radio option. In Visual Studio for Mac it's set via preferences in `Source Code > Code Formatting > C# source code` and disabling the checkbox for `Convert tabs to spaces`.
- Lines should be limited to a max of 120 characters (or as close as possible within reason). This may be set in Visual Studio for Mac via preferences in `Source Code > Code Formatting > C# source code` and changing the `Desired file width` to `120`.
## Contributing ##
diff --git a/Stubs/Xamarin.Forms.Platform.cs b/Stubs/Xamarin.Forms.Platform.cs
index e41c2937cf6..ef9b75db969 100644
--- a/Stubs/Xamarin.Forms.Platform.cs
+++ b/Stubs/Xamarin.Forms.Platform.cs
@@ -54,6 +54,13 @@ internal class _ButtonRenderer { }
[RenderWith(typeof(ImageButtonRenderer))]
internal class _ImageButtonRenderer { }
+#if __ANDROID__
+ [RenderWith(typeof(RadioButtonRenderer))]
+#elif !TIZEN4_0
+ [RenderWith(typeof(RadioButtonRenderer))]
+#endif
+ internal class _RadioButtonRenderer { }
+
[RenderWith (typeof (TableViewRenderer))]
internal class _TableViewRenderer { }
diff --git a/Xamarin.Forms.ControlGallery.Android/Activity1.cs b/Xamarin.Forms.ControlGallery.Android/Activity1.cs
index 24c3744a6b3..11b923db5a8 100644
--- a/Xamarin.Forms.ControlGallery.Android/Activity1.cs
+++ b/Xamarin.Forms.ControlGallery.Android/Activity1.cs
@@ -21,7 +21,6 @@
[assembly: Dependency (typeof (TestCloudService))]
[assembly: ExportRenderer (typeof (DisposePage), typeof (DisposePageRenderer))]
[assembly: ExportRenderer (typeof (DisposeLabel), typeof (DisposeLabelRenderer))]
-[assembly: ExportRenderer (typeof (CustomButton), typeof (CustomButtonRenderer))]
[assembly: ExportEffect (typeof (BorderEffect), "BorderEffect")]
namespace Xamarin.Forms.ControlGallery.Android
diff --git a/Xamarin.Forms.ControlGallery.Android/Assets/googlemap.html b/Xamarin.Forms.ControlGallery.Android/Assets/googlemap.html
new file mode 100644
index 00000000000..165e4e90c65
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.Android/Assets/googlemap.html
@@ -0,0 +1,49 @@
+
+
+
+
+
+ Google Maps
+
+
+
+
+map
+
+
diff --git a/Xamarin.Forms.ControlGallery.Android/Assets/googlemapsearch.html b/Xamarin.Forms.ControlGallery.Android/Assets/googlemapsearch.html
new file mode 100644
index 00000000000..a31af760957
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.Android/Assets/googlemapsearch.html
@@ -0,0 +1,84 @@
+
+
+
+ Place Searches
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Xamarin.Forms.ControlGallery.Android/ContentDescriptionEffectRenderer.cs b/Xamarin.Forms.ControlGallery.Android/ContentDescriptionEffectRenderer.cs
new file mode 100644
index 00000000000..18c1fe15592
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.Android/ContentDescriptionEffectRenderer.cs
@@ -0,0 +1,58 @@
+using System.ComponentModel;
+using AViews = Android.Views;
+using AWidget = Android.Widget;
+using Xamarin.Forms;
+using Xamarin.Forms.ControlGallery.Android;
+using Xamarin.Forms.Controls.Issues;
+using Xamarin.Forms.Platform.Android;
+using Android.Support.V4.View;
+using Android.Support.V4.View.Accessibility;
+using Android.AccessibilityServices;
+
+[assembly: ExportEffect(typeof(ContentDescriptionEffectRenderer), ContentDescriptionEffect.EffectName)]
+namespace Xamarin.Forms.ControlGallery.Android
+{
+ public class ContentDescriptionEffectRenderer : PlatformEffect
+ {
+ protected override void OnAttached()
+ {
+ }
+
+ protected override void OnDetached()
+ {
+ }
+
+ protected override void OnElementPropertyChanged(PropertyChangedEventArgs args)
+ {
+ System.Diagnostics.Debug.WriteLine("OnElementPropertyChanged" + args.PropertyName);
+
+ var viewGroup = Control as AViews.ViewGroup;
+ var nativeView = Control as AViews.View;
+
+ if (nativeView != null && viewGroup != null && viewGroup.ChildCount > 0)
+ {
+ nativeView = viewGroup.GetChildAt(0);
+ }
+
+ if (nativeView == null)
+ {
+ return;
+ }
+
+ var info = AccessibilityNodeInfoCompat.Obtain(nativeView);
+ ViewCompat.OnInitializeAccessibilityNodeInfo(nativeView, info);
+
+ System.Diagnostics.Debug.WriteLine(info.ContentDescription);
+ System.Diagnostics.Debug.WriteLine(nativeView.ContentDescription);
+
+ Element.SetValue(
+ ContentDescriptionEffectProperties.NameAndHelpTextProperty,
+ info.ContentDescription);
+
+ Element.SetValue(
+ ContentDescriptionEffectProperties.ContentDescriptionProperty,
+ nativeView.ContentDescription);
+ }
+
+ }
+}
diff --git a/Xamarin.Forms.ControlGallery.Android/CustomRenderers.cs b/Xamarin.Forms.ControlGallery.Android/CustomRenderers.cs
index e4679c8d6c2..a3192b36c11 100644
--- a/Xamarin.Forms.ControlGallery.Android/CustomRenderers.cs
+++ b/Xamarin.Forms.ControlGallery.Android/CustomRenderers.cs
@@ -57,6 +57,7 @@
[assembly: ExportRenderer(typeof(Issue7249Switch), typeof(Issue7249SwitchRenderer))]
[assembly: ExportRenderer(typeof(Issue9360.Issue9360NavigationPage), typeof(Issue9360NavigationPageRenderer))]
[assembly: ExportRenderer(typeof(Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries.HingeAngleLabel), typeof(HingeAngleLabelRenderer))]
+[assembly: ExportRenderer(typeof(Xamarin.Forms.Controls.Tests.TestClasses.CustomButton), typeof(CustomButtonRenderer))]
#if PRE_APPLICATION_CLASS
#elif FORMS_APPLICATION_ACTIVITY
diff --git a/Xamarin.Forms.ControlGallery.Android/Resources/drawable/rb_checked.png b/Xamarin.Forms.ControlGallery.Android/Resources/drawable/rb_checked.png
new file mode 100644
index 00000000000..b3438bae2fc
Binary files /dev/null and b/Xamarin.Forms.ControlGallery.Android/Resources/drawable/rb_checked.png differ
diff --git a/Xamarin.Forms.ControlGallery.Android/Resources/drawable/rb_unchecked.png b/Xamarin.Forms.ControlGallery.Android/Resources/drawable/rb_unchecked.png
new file mode 100644
index 00000000000..bc3abd78be7
Binary files /dev/null and b/Xamarin.Forms.ControlGallery.Android/Resources/drawable/rb_unchecked.png differ
diff --git a/Xamarin.Forms.ControlGallery.Android/Tests/AssertionExtensions.cs b/Xamarin.Forms.ControlGallery.Android/Tests/AssertionExtensions.cs
new file mode 100644
index 00000000000..31ec596eeca
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.Android/Tests/AssertionExtensions.cs
@@ -0,0 +1,122 @@
+using AView = Android.Views.View;
+using AColor = Android.Graphics.Color;
+using Android.Graphics;
+using System;
+using NUnit.Framework;
+using System.IO;
+
+#if __ANDROID_29__
+#else
+using Android.Support.V7.Widget;
+#endif
+
+namespace Xamarin.Forms.ControlGallery.Android.Tests
+{
+ internal static class AssertionExtensions
+ {
+ public static string CreateColorAtPointError(this Bitmap bitmap, AColor expectedColor, int x, int y)
+ {
+ using (var ms = new MemoryStream())
+ {
+ bitmap.Compress(Bitmap.CompressFormat.Png, 0, ms);
+ var imageAsString = Convert.ToBase64String(ms.ToArray());
+ return $"Expected {expectedColor} at point {x},{y} in renderered view. This is what it looked like: {imageAsString}";
+ }
+ }
+
+ public static AColor ColorAtPoint(this Bitmap bitmap, int x, int y)
+ {
+ int pixel = bitmap.GetPixel(x, y);
+
+ int red = AColor.GetRedComponent(pixel);
+ int blue = AColor.GetBlueComponent(pixel);
+ int green = AColor.GetGreenComponent(pixel);
+
+ return AColor.Rgb(red, green, blue);
+ }
+
+ public static Bitmap ToBitmap(this AView view)
+ {
+ var bitmap = Bitmap.CreateBitmap(view.Width, view.Height, Bitmap.Config.Argb8888);
+ var canvas = new Canvas(bitmap);
+ canvas.Save();
+ canvas.Translate(0, 0);
+ view.Draw(canvas);
+ canvas.Restore();
+
+ return bitmap;
+ }
+
+ public static Bitmap AssertColorAtPoint(this Bitmap bitmap, AColor expectedColor, int x, int y)
+ {
+ Assert.That(bitmap.ColorAtPoint(x, y), Is.EqualTo(expectedColor),
+ () => bitmap.CreateColorAtPointError(expectedColor, x, y));
+
+ return bitmap;
+ }
+
+ public static Bitmap AssertColorAtCenter(this Bitmap bitmap, AColor expectedColor)
+ {
+ return bitmap.AssertColorAtPoint(expectedColor, bitmap.Width / 2, bitmap.Height / 2);
+ }
+
+ public static Bitmap AssertColorAtBottomLeft(this Bitmap bitmap, AColor expectedColor)
+ {
+ return bitmap.AssertColorAtPoint(expectedColor, 0, 0);
+ }
+
+ public static Bitmap AssertColorAtBottomRight(this Bitmap bitmap, AColor expectedColor)
+ {
+ return bitmap.AssertColorAtPoint(expectedColor, bitmap.Width - 1, 0);
+ }
+
+ public static Bitmap AssertColorAtTopLeft(this Bitmap bitmap, AColor expectedColor)
+ {
+ return bitmap.AssertColorAtPoint(expectedColor, 0, bitmap.Height - 1);
+ }
+
+ public static Bitmap AssertColorAtTopRight(this Bitmap bitmap, AColor expectedColor)
+ {
+ return bitmap.AssertColorAtPoint(expectedColor, bitmap.Width - 1, bitmap.Height - 1);
+ }
+
+ public static Bitmap AssertColorAtPoint(this AView view, AColor expectedColor, int x, int y)
+ {
+ var bitmap = view.ToBitmap();
+ Assert.That(bitmap.ColorAtPoint(x, y), Is.EqualTo(expectedColor),
+ () => bitmap.CreateColorAtPointError(expectedColor, x, y));
+
+ return bitmap;
+ }
+
+ public static Bitmap AssertColorAtCenter(this AView view, AColor expectedColor)
+ {
+ var bitmap = view.ToBitmap();
+ return bitmap.AssertColorAtCenter(expectedColor);
+ }
+
+ public static Bitmap AssertColorAtBottomLeft(this AView view, AColor expectedColor)
+ {
+ var bitmap = view.ToBitmap();
+ return bitmap.AssertColorAtBottomLeft(expectedColor);
+ }
+
+ public static Bitmap AssertColorAtBottomRight(this AView view, AColor expectedColor)
+ {
+ var bitmap = view.ToBitmap();
+ return bitmap.AssertColorAtBottomRight(expectedColor);
+ }
+
+ public static Bitmap AssertColorAtTopLeft(this AView view, AColor expectedColor)
+ {
+ var bitmap = view.ToBitmap();
+ return bitmap.AssertColorAtTopLeft(expectedColor);
+ }
+
+ public static Bitmap AssertColorAtTopRight(this AView view, AColor expectedColor)
+ {
+ var bitmap = view.ToBitmap();
+ return bitmap.AssertColorAtTopRight(expectedColor);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.ControlGallery.Android/Tests/BackgroundColorTests.cs b/Xamarin.Forms.ControlGallery.Android/Tests/BackgroundColorTests.cs
new file mode 100644
index 00000000000..faa61e107ea
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.Android/Tests/BackgroundColorTests.cs
@@ -0,0 +1,104 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using Android.Graphics.Drawables;
+using NUnit.Framework;
+using NUnit.Framework.Internal;
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Platform.Android;
+
+namespace Xamarin.Forms.ControlGallery.Android.Tests
+{
+ [TestFixture]
+ public class BackgroundColorTests : PlatformTestFixture
+ {
+ static IEnumerable TestCases
+ {
+ get
+ {
+ foreach (var element in BasicElements
+ .Where(e => !(e is Button) && !(e is ImageButton) && !(e is Frame)))
+ {
+ element.BackgroundColor = Color.AliceBlue;
+ yield return new TestCaseData(element)
+ .SetCategory(element.GetType().Name);
+ }
+ }
+ }
+
+ [Test, Category("BackgroundColor"), Category("Button")]
+ [Description("Button background color should match renderer background color")]
+ public void ButtonBackgroundColorConsistent()
+ {
+ var button = new Button
+ {
+ Text = " ",
+ HeightRequest = 100, WidthRequest = 100,
+ BackgroundColor = Color.AliceBlue
+ };
+
+ using (var nativeButton = GetNativeControl(button))
+ {
+ var expectedColor = button.BackgroundColor.ToAndroid();
+ Layout(button, nativeButton);
+ nativeButton.AssertColorAtCenter(expectedColor);
+ }
+ }
+
+ [Test, Category("BackgroundColor"), Category("Button")]
+ [Description("ImageButton background color should match renderer background color")]
+ public void ImageButtonBackgroundColorConsistent()
+ {
+ var button = new ImageButton
+ {
+ HeightRequest = 100,
+ WidthRequest = 100,
+ BackgroundColor = Color.AliceBlue
+ };
+
+ using (var nativeButton = GetNativeControl(button))
+ {
+ var expectedColor = button.BackgroundColor.ToAndroid();
+ Layout(button, nativeButton);
+ nativeButton.AssertColorAtCenter(expectedColor);
+ }
+ }
+
+ [Test, Category("BackgroundColor")]
+ [Description("Frame background color should match renderer background color")]
+ public void FrameBackgroundColorConsistent()
+ {
+ var frame = new Frame
+ {
+ HeightRequest = 100,
+ WidthRequest = 100,
+ BackgroundColor = Color.AliceBlue
+ };
+
+ using (var renderer = GetRenderer(frame))
+ {
+ var expectedColor = frame.BackgroundColor.ToAndroid();
+ var view = renderer.View;
+ Layout(frame, view);
+ view.AssertColorAtCenter(expectedColor);
+ }
+ }
+
+ [Test, Category("BackgroundColor"), TestCaseSource(nameof(TestCases))]
+ [Description("VisualElement background color should match renderer background color")]
+ public void BackgroundColorConsistent(VisualElement element)
+ {
+ using (var renderer = GetRenderer(element))
+ {
+ var expectedColor = element.BackgroundColor.ToAndroid();
+ var view = renderer.View;
+ var colorDrawable = view.Background as ColorDrawable;
+ var nativeColor = colorDrawable.Color;
+ Assert.That(nativeColor, Is.EqualTo(expectedColor));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.ControlGallery.Android/Tests/CornerRadiusTests.cs b/Xamarin.Forms.ControlGallery.Android/Tests/CornerRadiusTests.cs
new file mode 100644
index 00000000000..65f02b21e1a
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.Android/Tests/CornerRadiusTests.cs
@@ -0,0 +1,96 @@
+using NUnit.Framework;
+using NUnit.Framework.Internal;
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Platform.Android;
+
+namespace Xamarin.Forms.ControlGallery.Android.Tests
+{
+ [TestFixture]
+ public class CornerRadiusTests : PlatformTestFixture
+ {
+ [Test, Category("CornerRadius"), Category("BoxView")]
+ public void BoxviewCornerRadius()
+ {
+ var boxView = new BoxView
+ {
+ HeightRequest = 100,
+ WidthRequest = 200,
+ CornerRadius = 15,
+ BackgroundColor = Color.Red
+ };
+
+ CheckCornerRadius(boxView);
+ }
+
+ [Test, Category("CornerRadius"), Category("Button")]
+ public void ButtonCornerRadius()
+ {
+ var backgroundColor = Color.Red;
+
+ var button = new Button
+ {
+ HeightRequest = 100,
+ WidthRequest = 200,
+ CornerRadius = 15,
+ BackgroundColor = backgroundColor
+ };
+
+ CheckCornerRadius(button);
+ }
+
+ [Test, Category("CornerRadius"), Category("Frame")]
+ public void FrameCornerRadius()
+ {
+ var backgroundColor = Color.Red;
+
+ var frame = new Frame
+ {
+ HeightRequest = 100,
+ WidthRequest = 200,
+ CornerRadius = 15,
+ BackgroundColor = backgroundColor
+ };
+
+ CheckCornerRadius(frame);
+ }
+
+ [Test, Category("CornerRadius"), Category("ImageButton")]
+ public void ImageButtonCornerRadius()
+ {
+ var backgroundColor = Color.Red;
+
+ var button = new ImageButton
+ {
+ HeightRequest = 100,
+ WidthRequest = 200,
+ CornerRadius = 15,
+ BackgroundColor = backgroundColor,
+ BorderColor = Color.Black,
+ BorderWidth = 2
+ };
+
+ CheckCornerRadius(button);
+ }
+
+ public void CheckCornerRadius(VisualElement visualElement)
+ {
+ using (var renderer = GetRenderer(visualElement))
+ {
+ var view = renderer.View;
+ Layout(visualElement, view);
+
+ // Need to parent the Frame for it to work on lower APIs (below Marshmallow)
+ ParentView(view);
+
+ // The corners should show the background color
+ view.AssertColorAtTopLeft(EmptyBackground)
+ .AssertColorAtTopRight(EmptyBackground)
+ .AssertColorAtBottomLeft(EmptyBackground)
+ .AssertColorAtBottomRight(EmptyBackground)
+ .AssertColorAtCenter(visualElement.BackgroundColor.ToAndroid());
+
+ UnparentView(view);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.ControlGallery.Android/Tests/IsEnabledTests.cs b/Xamarin.Forms.ControlGallery.Android/Tests/IsEnabledTests.cs
new file mode 100644
index 00000000000..83e303b2304
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.Android/Tests/IsEnabledTests.cs
@@ -0,0 +1,55 @@
+using System.Collections;
+using NUnit.Framework;
+using NUnit.Framework.Internal;
+using Xamarin.Forms.CustomAttributes;
+
+namespace Xamarin.Forms.ControlGallery.Android.Tests
+{
+ [TestFixture]
+ public class IsEnabledTests : PlatformTestFixture
+ {
+ static IEnumerable TestCases
+ {
+ get
+ {
+ // Generate IsEnabled = true cases
+ foreach (var element in BasicElements)
+ {
+ element.IsEnabled = true;
+ yield return CreateTestCase(element)
+ .SetName($"{element.GetType().Name}_IsEnabled_{element.IsEnabled}");
+ }
+
+ // Generate IsEnabled = false cases
+ foreach (var element in BasicElements)
+ {
+ element.IsEnabled = false;
+ yield return CreateTestCase(element)
+ .SetName($"{element.GetType().Name}_IsEnabled_{element.IsEnabled}");
+ }
+ }
+ }
+
+ [Test, Category("IsEnabled"), TestCaseSource(nameof(TestCases))]
+ [Description("VisualElement enabled should match renderer enabled")]
+ public void EnabledConsistent(VisualElement element)
+ {
+ using (var renderer = GetRenderer(element))
+ {
+ var expected = element.IsEnabled;
+ var nativeView = renderer.View;
+
+ ParentView(nativeView);
+
+ // Check the container control
+ Assert.That(renderer.View.Enabled, Is.EqualTo(expected));
+
+ // Check the actual control
+ var control = GetNativeControl(element);
+ Assert.That(control.Enabled, Is.EqualTo(expected));
+
+ UnparentView(nativeView);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.ControlGallery.Android/Tests/IsVisibleTests.cs b/Xamarin.Forms.ControlGallery.Android/Tests/IsVisibleTests.cs
new file mode 100644
index 00000000000..41f65a4384f
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.Android/Tests/IsVisibleTests.cs
@@ -0,0 +1,53 @@
+using System.Collections;
+using NUnit.Framework;
+using NUnit.Framework.Internal;
+using Xamarin.Forms.CustomAttributes;
+
+namespace Xamarin.Forms.ControlGallery.Android.Tests
+{
+ [TestFixture]
+ public class IsVisibleTests : PlatformTestFixture
+ {
+ static IEnumerable TestCases
+ {
+ get
+ {
+ // Generate IsVisible = true cases
+ foreach (var element in BasicElements)
+ {
+ element.IsVisible = true;
+ yield return CreateTestCase(element)
+ .SetName($"{element.GetType().Name}_IsVisible_True");
+ }
+
+ // Generate IsVisible = false cases
+ foreach (var element in BasicElements)
+ {
+ element.IsVisible = false;
+ yield return CreateTestCase(element)
+ .SetName($"{element.GetType().Name}_IsVisible_False");
+ }
+ }
+ }
+
+ [Test, Category("IsVisible"), TestCaseSource(nameof(TestCases))]
+ [Description("VisualElement visibility should match renderer visibility")]
+ public void VisibleConsistent(VisualElement element)
+ {
+ using (var renderer = GetRenderer(element))
+ {
+ var expected = element.IsVisible
+ ? global::Android.Views.ViewStates.Visible
+ : global::Android.Views.ViewStates.Invisible;
+
+ var nativeView = renderer.View;
+
+ ParentView(nativeView);
+
+ Assert.That(renderer.View.Visibility, Is.EqualTo(expected));
+
+ UnparentView(nativeView);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.ControlGallery.Android/Tests/Issues.cs b/Xamarin.Forms.ControlGallery.Android/Tests/Issues.cs
index f52de95ac98..2b5f062d384 100644
--- a/Xamarin.Forms.ControlGallery.Android/Tests/Issues.cs
+++ b/Xamarin.Forms.ControlGallery.Android/Tests/Issues.cs
@@ -10,7 +10,8 @@ namespace Xamarin.Forms.ControlGallery.Android.Tests
[TestFixture]
public class Issues : PlatformTestFixture
{
- [Test(Description = "The HorizontalAlignment of an Entry's renderer should match the Entry")]
+ [Test, Category("Entry")]
+ [Description("The HorizontalAlignment of an Entry's renderer should match the Entry")]
[Issue(IssueTracker.Github, 8137, "[Bug] XF 4.3 Entry HorizontalTextAlignment display wrong position")]
public void EntryHorizontalAlignmentCenterInRenderer()
{
diff --git a/Xamarin.Forms.ControlGallery.Android/Tests/OpacityTests.cs b/Xamarin.Forms.ControlGallery.Android/Tests/OpacityTests.cs
new file mode 100644
index 00000000000..f4f8c0df36b
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.Android/Tests/OpacityTests.cs
@@ -0,0 +1,40 @@
+using System.Collections;
+using NUnit.Framework;
+using NUnit.Framework.Internal;
+using Xamarin.Forms.CustomAttributes;
+
+namespace Xamarin.Forms.ControlGallery.Android.Tests
+{
+ [TestFixture]
+ public class OpacityTests : PlatformTestFixture
+ {
+ static IEnumerable TestCases
+ {
+ get
+ {
+ foreach (var element in BasicElements)
+ {
+ element.Opacity = 0.35;
+ yield return CreateTestCase(element);
+ }
+ }
+ }
+
+ [Test, Category("Opacity"), TestCaseSource(nameof(TestCases))]
+ [Description("VisualElement opacity should match renderer opacity")]
+ public void OpacityConsistent(VisualElement element)
+ {
+ using (var renderer = GetRenderer(element))
+ {
+ var expected = element.Opacity;
+ var nativeView = renderer.View;
+
+ ParentView(nativeView);
+
+ Assert.That((double)renderer.View.Alpha, Is.EqualTo(expected).Within(0.001d));
+
+ UnparentView(nativeView);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.ControlGallery.Android/Tests/PlatformTestFixture.cs b/Xamarin.Forms.ControlGallery.Android/Tests/PlatformTestFixture.cs
index 2d61e91efb2..febada0d9e5 100644
--- a/Xamarin.Forms.ControlGallery.Android/Tests/PlatformTestFixture.cs
+++ b/Xamarin.Forms.ControlGallery.Android/Tests/PlatformTestFixture.cs
@@ -2,6 +2,21 @@
using Android.Content.PM;
using Android.Widget;
using Xamarin.Forms.Platform.Android;
+using AView = Android.Views.View;
+using AColor = Android.Graphics.Color;
+using Android.Views;
+using System;
+using AProgressBar = Android.Widget.ProgressBar;
+using ASearchView = Android.Widget.SearchView;
+using System.Collections.Generic;
+using NUnit.Framework;
+
+#if __ANDROID_29__
+using AndroidX.AppCompat.Widget;
+using AndroidX.CardView.Widget;
+#else
+using Android.Support.V7.Widget;
+#endif
namespace Xamarin.Forms.ControlGallery.Android.Tests
{
@@ -9,6 +24,42 @@ public class PlatformTestFixture
{
Context _context;
+ protected static AColor EmptyBackground = new AColor(0, 0, 0, 255);
+
+ // Sequence for generating test cases
+ protected static IEnumerable BasicElements
+ {
+ get
+ {
+ yield return new BoxView { };
+ yield return new Button { };
+ yield return new CheckBox { };
+ yield return new DatePicker { };
+ yield return new Editor { };
+ yield return new Entry { };
+ yield return new Frame { };
+ yield return new Image { };
+ yield return new ImageButton { };
+ yield return new Label { };
+ yield return new Picker { };
+ yield return new ProgressBar { };
+ yield return new SearchBar { };
+ yield return new Slider { };
+ yield return new Stepper { };
+ yield return new Switch { };
+ yield return new TimePicker { };
+ }
+ }
+
+ protected static TestCaseData CreateTestCase(VisualElement element)
+ {
+ // We set the element type as a category on the test so that if you
+ // filter by category, say, "Button", you'll get any Button test
+ // generated from here.
+
+ return new TestCaseData(element).SetCategory(element.GetType().Name);
+ }
+
protected Context Context
{
get
@@ -31,21 +82,217 @@ protected static void ToggleRTLSupport(Context context, bool enabled)
protected IVisualElementRenderer GetRenderer(VisualElement element)
{
- return Platform.Android.Platform.CreateRendererWithContext(element, Context);
+ var renderer = element.GetRenderer();
+ if (renderer == null)
+ {
+ renderer = Platform.Android.Platform.CreateRendererWithContext(element, Context);
+ Platform.Android.Platform.SetRenderer(element, renderer);
+ }
+
+ return renderer;
+ }
+
+ protected AView GetNativeControl(VisualElement element)
+ {
+ switch (element)
+ {
+ case BoxView boxView:
+ return GetNativeControl(boxView);
+ case Button button:
+ return GetNativeControl(button);
+ case CheckBox checkBox:
+ return GetNativeControl(checkBox);
+ case DatePicker datePicker:
+ return GetNativeControl(datePicker);
+ case Editor editor:
+ return GetNativeControl(editor);
+ case Entry entry:
+ return GetNativeControl(entry);
+ case Image image:
+ return GetNativeControl(image);
+ case ImageButton imageButton:
+ return GetNativeControl(imageButton);
+ case Frame frame:
+ return GetNativeControl(frame);
+ case Label label:
+ return GetNativeControl(label);
+ case Picker picker:
+ return GetNativeControl(picker);
+ case ProgressBar progressBar:
+ return GetNativeControl(progressBar);
+ case SearchBar searchBar:
+ return GetNativeControl(searchBar);
+ case Slider slider:
+ return GetNativeControl(slider);
+ case Stepper stepper:
+ return GetNativeControl(stepper);
+ case Switch @switch:
+ return GetNativeControl(@switch);
+ case TimePicker timePicker:
+ return GetNativeControl(timePicker);
+ }
+
+ throw new NotImplementedException($"Don't know how to get the native control for {element}");
}
- protected TextView GetNativeControl(Label label)
+ protected BoxRenderer GetNativeControl(BoxView boxView)
{
- var renderer = GetRenderer(label);
- var viewRenderer = renderer.View as ViewRenderer;
+ var renderer = GetRenderer(boxView);
+ return renderer as BoxRenderer;
+ }
+
+ protected AppCompatButton GetNativeControl(Button button)
+ {
+ var renderer = GetRenderer(button);
+
+ if (renderer is AppCompatButton fastButton)
+ {
+ return fastButton;
+ }
+
+ var viewRenderer = renderer.View as Platform.Android.AppCompat.ButtonRenderer;
+ return viewRenderer.Control;
+ }
+
+ protected AppCompatCheckBox GetNativeControl(CheckBox checkbox)
+ {
+ var renderer = GetRenderer(checkbox);
+ return renderer as AppCompatCheckBox;
+ }
+
+ protected EditText GetNativeControl(DatePicker datePicker)
+ {
+ var renderer = GetRenderer(datePicker);
+ var viewRenderer = renderer.View as DatePickerRenderer;
+ return viewRenderer.Control;
+ }
+
+ protected FormsEditText GetNativeControl(Editor editor)
+ {
+ var renderer = GetRenderer(editor);
+ var viewRenderer = renderer.View as EditorRenderer;
return viewRenderer.Control;
}
protected FormsEditText GetNativeControl(Entry entry)
{
var renderer = GetRenderer(entry);
- var viewRenderer = renderer.View as ViewRenderer;
+ var viewRenderer = renderer.View as EntryRenderer;
return viewRenderer.Control;
}
+
+ protected ImageView GetNativeControl(Image image)
+ {
+ var renderer = GetRenderer(image);
+
+ if (renderer is ImageView fastImage)
+ {
+ return fastImage;
+ }
+
+ var viewRenderer = renderer.View as ImageRenderer;
+ return viewRenderer.Control;
+ }
+
+ protected AppCompatImageButton GetNativeControl(ImageButton imageButton)
+ {
+ return GetRenderer(imageButton) as AppCompatImageButton;
+ }
+
+ protected CardView GetNativeControl(Frame frame)
+ {
+ var renderer = GetRenderer(frame);
+ return renderer as CardView;
+ }
+
+ protected TextView GetNativeControl(Label label)
+ {
+ var renderer = GetRenderer(label);
+ var viewRenderer = renderer.View as LabelRenderer;
+
+ if (viewRenderer != null)
+ {
+ return viewRenderer.Control;
+ }
+
+ var fastRenderer = renderer.View as Platform.Android.FastRenderers.LabelRenderer;
+
+ return fastRenderer;
+ }
+
+ protected EditText GetNativeControl(Picker picker)
+ {
+ var renderer = GetRenderer(picker);
+ var viewRenderer = renderer.View as Platform.Android.AppCompat.PickerRenderer;
+ return viewRenderer.Control;
+ }
+
+ protected AProgressBar GetNativeControl(ProgressBar progressBar)
+ {
+ var renderer = GetRenderer(progressBar);
+ var viewRenderer = renderer.View as ProgressBarRenderer;
+ return viewRenderer.Control;
+ }
+
+ protected ASearchView GetNativeControl(SearchBar searchBar)
+ {
+ var renderer = GetRenderer(searchBar);
+ var viewRenderer = renderer.View as SearchBarRenderer;
+ return viewRenderer.Control;
+ }
+
+ protected SeekBar GetNativeControl(Slider slider)
+ {
+ var renderer = GetRenderer(slider);
+ var viewRenderer = renderer.View as SliderRenderer;
+ return viewRenderer.Control;
+ }
+
+ protected LinearLayout GetNativeControl(Stepper stepper)
+ {
+ var renderer = GetRenderer(stepper);
+ var viewRenderer = renderer.View as StepperRenderer;
+ return viewRenderer.Control;
+ }
+
+ protected SwitchCompat GetNativeControl(Switch @switch)
+ {
+ var renderer = GetRenderer(@switch);
+ var viewRenderer = renderer.View as Platform.Android.AppCompat.SwitchRenderer;
+ return viewRenderer.Control;
+ }
+
+ protected EditText GetNativeControl(TimePicker timePicker)
+ {
+ var renderer = GetRenderer(timePicker);
+ var viewRenderer = renderer.View as TimePickerRenderer;
+ return viewRenderer.Control;
+ }
+
+ protected void Layout(VisualElement element, AView nativeView)
+ {
+ var size = element.Measure(double.PositiveInfinity, double.PositiveInfinity, MeasureFlags.IncludeMargins);
+ var width = size.Request.Width;
+ var height = size.Request.Height;
+ element.Layout(new Rectangle(0, 0, width, height));
+
+ int widthSpec = AView.MeasureSpec.MakeMeasureSpec((int)width, MeasureSpecMode.Exactly);
+ int heightSpec = AView.MeasureSpec.MakeMeasureSpec((int)height, MeasureSpecMode.Exactly);
+ nativeView.Measure(widthSpec, heightSpec);
+ nativeView.Layout(0, 0, (int)width, (int)height);
+ }
+
+ // Some of the renderer properties aren't set until the renderer is actually
+ // attached to the view hierarchy; to test them, we need to add them, run the test,
+ // then remove them
+ protected void ParentView(AView view)
+ {
+ ((ViewGroup)Application.Current.MainPage.GetRenderer().View).AddView(view);
+ }
+
+ protected void UnparentView(AView view)
+ {
+ ((ViewGroup)Application.Current.MainPage.GetRenderer().View).RemoveView(view);
+ }
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.ControlGallery.Android/Tests/PlatformTestSettings.cs b/Xamarin.Forms.ControlGallery.Android/Tests/PlatformTestSettings.cs
index 0795131b48b..4faa6dc1bb6 100644
--- a/Xamarin.Forms.ControlGallery.Android/Tests/PlatformTestSettings.cs
+++ b/Xamarin.Forms.ControlGallery.Android/Tests/PlatformTestSettings.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Reflection;
+using NUnit;
using Xamarin.Forms;
using Xamarin.Forms.ControlGallery.Android.Tests;
using Xamarin.Forms.Controls.Tests;
@@ -9,7 +10,17 @@ namespace Xamarin.Forms.ControlGallery.Android.Tests
{
public class PlatformTestSettings : IPlatformTestSettings
{
+ public PlatformTestSettings()
+ {
+ TestRunSettings = new Dictionary
+ {
+ // Creating/modifying any renderers off the UI thread causes problems
+ // so we want to force the tests to run on main
+ { FrameworkPackageSettings.RunOnMainThread, true }
+ };
+ }
+
public Assembly Assembly { get => Assembly.GetExecutingAssembly(); }
- public Dictionary TestRunSettings { get => new Dictionary(); }
+ public Dictionary TestRunSettings { get; }
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.ControlGallery.Android/Tests/RendererTests.cs b/Xamarin.Forms.ControlGallery.Android/Tests/RendererTests.cs
index 19dbe8bfc2a..95482abdcf2 100644
--- a/Xamarin.Forms.ControlGallery.Android/Tests/RendererTests.cs
+++ b/Xamarin.Forms.ControlGallery.Android/Tests/RendererTests.cs
@@ -8,17 +8,8 @@ namespace Xamarin.Forms.ControlGallery.Android.Tests
[TestFixture]
public class RendererTests : PlatformTestFixture
{
- [Test(Description = "Basic sanity check that Label text matches renderer text")]
- public void LabelTextMatchesRendererText()
- {
- var label = new Label { Text = "foo" };
- using (var textView = GetNativeControl(label))
- {
- Assert.That(label.Text == textView.Text);
- }
- }
-
- [Test(Description = "Validate renderer vertical alignment for Entry with VerticalTextAlignment Center")]
+ [Test, Category("Entry")]
+ [Description("Validate renderer vertical alignment for Entry with VerticalTextAlignment Center")]
public void EntryVerticalAlignmentCenterInRenderer()
{
var entry1 = new Entry { Text = "foo", VerticalTextAlignment = TextAlignment.Center };
diff --git a/Xamarin.Forms.ControlGallery.Android/Tests/RotationTests.cs b/Xamarin.Forms.ControlGallery.Android/Tests/RotationTests.cs
new file mode 100644
index 00000000000..acc27045cab
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.Android/Tests/RotationTests.cs
@@ -0,0 +1,88 @@
+using System;
+using System.Collections;
+using NUnit.Framework;
+using NUnit.Framework.Internal;
+using Xamarin.Forms.CustomAttributes;
+using AView = Android.Views.View;
+
+namespace Xamarin.Forms.ControlGallery.Android.Tests
+{
+ [TestFixture]
+ public class RotationTests : PlatformTestFixture
+ {
+ static IEnumerable RotationXCases
+ {
+ get
+ {
+ foreach (var element in BasicElements)
+ {
+ element.RotationX = 33.0;
+ yield return CreateTestCase(element);
+ }
+ }
+ }
+
+ static IEnumerable RotationYCases
+ {
+ get
+ {
+ foreach (var element in BasicElements)
+ {
+ element.RotationY = 87.0;
+ yield return CreateTestCase(element);
+ }
+ }
+ }
+
+ static IEnumerable RotationCases
+ {
+ get
+ {
+ foreach (var element in BasicElements)
+ {
+ element.Rotation = 23.0;
+ yield return CreateTestCase(element);
+ }
+ }
+ }
+
+ void AssertRotationConsistent(VisualElement element, Func getRotation,
+ Func getNativeRotation)
+ {
+ using (var renderer = GetRenderer(element))
+ {
+ var expected = getRotation(element);
+ var nativeView = renderer.View;
+
+ ParentView(nativeView);
+
+ var actual = getNativeRotation(renderer.View);
+
+ Assert.That((double)actual, Is.EqualTo(expected).Within(0.0001d));
+
+ UnparentView(nativeView);
+ }
+ }
+
+ [Test, Category("RotationX"), TestCaseSource(nameof(RotationXCases))]
+ [Description("VisualElement X rotation should match renderer X rotation")]
+ public void RotationXConsistent(VisualElement element)
+ {
+ AssertRotationConsistent(element, e => e.RotationX, v => v.RotationX);
+ }
+
+ [Test, Category("RotationY"), TestCaseSource(nameof(RotationYCases))]
+ [Description("VisualElement Y rotation should match renderer Y rotation")]
+ public void RotationYConsistent(VisualElement element)
+ {
+ AssertRotationConsistent(element, e => e.RotationY, v => v.RotationY);
+ }
+
+ [Test, Category("Rotation"), TestCaseSource(nameof(RotationCases))]
+ [Description("VisualElement rotation should match renderer rotation")]
+ public void RotationConsistent(VisualElement element)
+ {
+ AssertRotationConsistent(element, e => e.Rotation, v => v.Rotation);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.ControlGallery.Android/Tests/ScaleTests.cs b/Xamarin.Forms.ControlGallery.Android/Tests/ScaleTests.cs
new file mode 100644
index 00000000000..823d77a22a5
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.Android/Tests/ScaleTests.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Collections;
+using NUnit.Framework;
+using AView = Android.Views.View;
+
+namespace Xamarin.Forms.ControlGallery.Android.Tests
+{
+ [TestFixture]
+ public class ScaleTests : PlatformTestFixture
+ {
+ static IEnumerable ScaleXCases
+ {
+ get
+ {
+ foreach (var element in BasicElements)
+ {
+ element.ScaleX = 0.45;
+ yield return CreateTestCase(element);
+ }
+ }
+ }
+
+ static IEnumerable ScaleYCases
+ {
+ get
+ {
+ foreach (var element in BasicElements)
+ {
+ element.ScaleY = 1.23;
+ yield return CreateTestCase(element);
+ }
+ }
+ }
+
+ void AssertScaleConsistent(View view, Func getScale,
+ Func getNativeScale)
+ {
+ using (var renderer = GetRenderer(view))
+ {
+ var expected = getScale(view);
+ var nativeView = renderer.View;
+
+ ParentView(nativeView);
+
+ Assert.That(getNativeScale(nativeView), Is.EqualTo(expected).Within(0.01));
+
+ UnparentView(nativeView);
+ }
+ }
+
+ [Test, Category("ScaleX"), TestCaseSource(nameof(ScaleXCases))]
+ [Description("View X scale should match renderer X scale")]
+ public void ScaleXConsistent(View view)
+ {
+ AssertScaleConsistent(view, e => e.ScaleX, v => v.ScaleX);
+ }
+
+ [Test, Category("ScaleY"), TestCaseSource(nameof(ScaleYCases))]
+ [Description("View Y scale should match renderer Y scale")]
+ public void ScaleYConsistent(View view)
+ {
+ AssertScaleConsistent(view, e => e.ScaleY, v => v.ScaleY);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.ControlGallery.Android/Tests/TestingPlatformService.cs b/Xamarin.Forms.ControlGallery.Android/Tests/TestingPlatformService.cs
new file mode 100644
index 00000000000..f18b0301e85
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.Android/Tests/TestingPlatformService.cs
@@ -0,0 +1,17 @@
+using Android.Content;
+using Xamarin.Forms;
+using Xamarin.Forms.ControlGallery.Android.Tests;
+using Xamarin.Forms.Controls.Tests;
+
+[assembly: Dependency(typeof(TestingPlatformService))]
+namespace Xamarin.Forms.ControlGallery.Android.Tests
+{
+ class TestingPlatformService : ITestingPlatformService
+ {
+ public void CreateRenderer(VisualElement visualElement)
+ {
+ Platform.Android.Platform.CreateRendererWithContext(visualElement,
+ DependencyService.Resolve());
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.ControlGallery.Android/Tests/TextTests.cs b/Xamarin.Forms.ControlGallery.Android/Tests/TextTests.cs
new file mode 100644
index 00000000000..5d5ba7239a2
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.Android/Tests/TextTests.cs
@@ -0,0 +1,54 @@
+using NUnit.Framework;
+using NUnit.Framework.Internal;
+using Xamarin.Forms.CustomAttributes;
+
+namespace Xamarin.Forms.ControlGallery.Android.Tests
+{
+ [TestFixture]
+ public class TextTests : PlatformTestFixture
+ {
+ [Test, Category("Text"), Category("Label")]
+ [Description("Label text should match renderer text")]
+ public void LabelTextMatchesRendererText()
+ {
+ var label = new Label { Text = "foo" };
+ using (var uiLabel = GetNativeControl(label))
+ {
+ Assert.That(label.Text, Is.EqualTo(uiLabel.Text));
+ }
+ }
+
+ [Test, Category("Text"), Category("Button")]
+ [Description("Button text should match renderer text")]
+ public void ButtonTextMatchesRendererText()
+ {
+ var button = new Button { Text = "foo" };
+ using (var nativeControl = GetNativeControl(button))
+ {
+ Assert.That(nativeControl.Text, Is.EqualTo(button.Text));
+ }
+ }
+
+ [Test, Category("Text"), Category("Entry")]
+ [Description("Entry text should match renderer text")]
+ public void EntryTextMatchesRendererText()
+ {
+ var entry = new Entry { Text = "foo" };
+ using (var nativeControl = GetNativeControl(entry))
+ {
+ Assert.That(nativeControl.Text, Is.EqualTo(entry.Text));
+ }
+ }
+
+ [Test, Category("Text"), Category("Editor")]
+ [Description("Editor text should match renderer text")]
+ public void EditorTextMatchesRendererText()
+ {
+ var editor = new Editor { Text = "foo" };
+ using (var nativeControl = GetNativeControl(editor))
+ {
+ Assert.That(nativeControl.Text, Is.EqualTo(editor.Text));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.ControlGallery.Android/Tests/TranslationTests.cs b/Xamarin.Forms.ControlGallery.Android/Tests/TranslationTests.cs
new file mode 100644
index 00000000000..5884312681a
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.Android/Tests/TranslationTests.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Collections;
+using NUnit.Framework;
+using AView = Android.Views.View;
+using Xamarin.Forms.Platform.Android;
+
+namespace Xamarin.Forms.ControlGallery.Android.Tests
+{
+ [TestFixture]
+ public class TranslationTests : PlatformTestFixture
+ {
+ static IEnumerable TranslationXCases
+ {
+ get
+ {
+ foreach (var element in BasicElements)
+ {
+ element.TranslationX = -100;
+ yield return CreateTestCase(element);
+ }
+ }
+ }
+
+ static IEnumerable TranslationYCases
+ {
+ get
+ {
+ foreach (var element in BasicElements)
+ {
+ element.TranslationY = -40;
+ yield return CreateTestCase(element);
+ }
+ }
+ }
+
+ void AssertTranslationConsistent(View view, Func getTranslation,
+ Func getNativeTranslation)
+ {
+ using (var renderer = GetRenderer(view))
+ {
+ var expected = Context.ToPixels(getTranslation(view));
+ var nativeView = renderer.View;
+
+ ParentView(nativeView);
+
+ Assert.That(getNativeTranslation(nativeView), Is.EqualTo(expected).Within(0.01));
+
+ UnparentView(nativeView);
+ }
+ }
+
+ [Test, Category("TranslateX"), TestCaseSource(nameof(TranslationXCases))]
+ [Description("View X translation should match renderer X translation")]
+ public void TranslationXConsistent(View view)
+ {
+ AssertTranslationConsistent(view, e => e.TranslationX, v => v.TranslationX);
+ }
+
+ [Test, Category("ScaleY"), TestCaseSource(nameof(TranslationYCases))]
+ [Description("View Y translation should match renderer Y translation")]
+ public void TranslationYConsistent(View view)
+ {
+ AssertTranslationConsistent(view, e => e.TranslationY, v => v.TranslationY);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj b/Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj
index ae7fe68333f..ee78d08929c 100644
--- a/Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj
+++ b/Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj
@@ -1,4 +1,4 @@
-
+
@@ -55,7 +55,6 @@
4
None
true
- false
armeabi-v7a;x86
Xamarin
True
@@ -108,7 +107,16 @@
+
+
+
+
+
+
+
+
+
@@ -122,6 +130,8 @@
+
+
@@ -132,6 +142,7 @@
+
GalleryPages\AdvancedOpenGLGallery.cs
@@ -395,6 +406,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
@@ -403,4 +426,4 @@
-
+
\ No newline at end of file
diff --git a/Xamarin.Forms.ControlGallery.WindowsUniversal/CustomRenderers.cs b/Xamarin.Forms.ControlGallery.WindowsUniversal/CustomRenderers.cs
index bbf6ec9b163..0cc317d10b8 100644
--- a/Xamarin.Forms.ControlGallery.WindowsUniversal/CustomRenderers.cs
+++ b/Xamarin.Forms.ControlGallery.WindowsUniversal/CustomRenderers.cs
@@ -119,7 +119,7 @@ public static void SetKeyboardFlags(this FormsTextBox Control, KeyboardFlags? fl
}
- public class TextBoxViewRenderer : BoxViewRenderer
+ public class TextBoxViewRenderer : BoxViewBorderRenderer
{
Canvas m_Canvas;
diff --git a/Xamarin.Forms.ControlGallery.WindowsUniversal/MainPage.xaml.cs b/Xamarin.Forms.ControlGallery.WindowsUniversal/MainPage.xaml.cs
index 8651729792b..63c56eb4f20 100644
--- a/Xamarin.Forms.ControlGallery.WindowsUniversal/MainPage.xaml.cs
+++ b/Xamarin.Forms.ControlGallery.WindowsUniversal/MainPage.xaml.cs
@@ -60,6 +60,10 @@ void OnKeyDown(CoreWindow coreWindow, KeyEventArgs args)
_app.Reset();
args.Handled = true;
}
+ else if (args.VirtualKey == VirtualKey.F1)
+ {
+ _app.PlatformTest();
+ }
}
void AddNativeControls(NestedNativeControlGalleryPage page)
diff --git a/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/BackgroundColorTests.cs b/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/BackgroundColorTests.cs
new file mode 100644
index 00000000000..9cda58458c6
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/BackgroundColorTests.cs
@@ -0,0 +1,100 @@
+using System.Collections;
+using System.Linq;
+using NUnit.Framework;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Media;
+using Windows.UI.Xaml.Shapes;
+using Xamarin.Forms.Platform.UWP;
+using WColor = Windows.UI.Color;
+
+namespace Xamarin.Forms.ControlGallery.WindowsUniversal.Tests
+{
+ [TestFixture]
+ public class BackgroundColorTests : PlatformTestFixture
+ {
+ static IEnumerable TestCases
+ {
+ get
+ {
+ // SearchBar is currently busted; when 8773 gets merged we can stop filtering it
+ foreach (var element in BasicViews
+ .Where(v => !(v is SearchBar))
+ .Where(v => !(v is Frame)))
+ {
+ element.BackgroundColor = Color.AliceBlue;
+ yield return CreateTestCase(element);
+ }
+ }
+ }
+
+ WColor GetBackgroundColor(Control control)
+ {
+ if (control is FormsButton button)
+ {
+ return (button.BackgroundColor as SolidColorBrush).Color;
+ }
+
+ if (control is StepperControl stepper)
+ {
+ return stepper.ButtonBackgroundColor.ToUwpColor();
+ }
+
+ return (control.Background as SolidColorBrush).Color;
+ }
+
+ WColor GetBackgroundColor(Panel panel)
+ {
+ return (panel.Background as SolidColorBrush).Color;
+ }
+
+ WColor GetBackgroundColor(Border border)
+ {
+ return (border.Background as SolidColorBrush).Color;
+ }
+
+ WColor GetNativeColor(View view)
+ {
+ var control = GetNativeControl(view);
+
+ if (control != null)
+ {
+ return GetBackgroundColor(control);
+ }
+
+ var border = GetBorder(view);
+
+ if (border != null)
+ {
+ return GetBackgroundColor(border);
+ }
+
+ var panel = GetPanel(view);
+ return GetBackgroundColor(panel);
+ }
+
+ [Test, TestCaseSource(nameof(TestCases))]
+ [Description("View background color should match renderer background color")]
+ public void BackgroundColorConsistent(View view)
+ {
+ var nativeColor = GetNativeColor(view);
+ var formsColor = view.BackgroundColor.ToUwpColor();
+ Assert.That(nativeColor, Is.EqualTo(formsColor));
+ }
+
+ [Test, Category("BackgroundColor"), Category("Frame")]
+ [Description("Frame background color should match renderer background color")]
+ public void FrameBackgroundColorConsistent()
+ {
+ var frame = new Frame() { BackgroundColor = Color.Orange };
+
+ var renderer = GetRenderer(frame);
+ var nativeElement = renderer.GetNativeElement() as Border;
+
+ var backgroundBrush = nativeElement.Background as SolidColorBrush;
+ var actualColor = backgroundBrush.Color;
+
+ var expectedColor = frame.BackgroundColor.ToUwpColor();
+ Assert.That(actualColor, Is.EqualTo(expectedColor));
+ }
+ }
+}
diff --git a/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/IsEnabledTests.cs b/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/IsEnabledTests.cs
new file mode 100644
index 00000000000..e100827fe84
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/IsEnabledTests.cs
@@ -0,0 +1,54 @@
+using System.Collections;
+using NUnit.Framework;
+
+namespace Xamarin.Forms.ControlGallery.WindowsUniversal.Tests
+{
+ [TestFixture]
+ public class IsEnabledTests : PlatformTestFixture
+ {
+ static IEnumerable TestCases
+ {
+ get
+ {
+ // Generate IsEnabled = true cases
+ foreach (var element in BasicViews)
+ {
+ element.IsEnabled = true;
+ yield return CreateTestCase(element)
+ .SetName($"{element.GetType().Name}_IsEnabled_{element.IsEnabled}");
+ }
+
+ // Generate IsEnabled = false cases
+ foreach (var element in BasicViews)
+ {
+ element.IsEnabled = false;
+ yield return CreateTestCase(element)
+ .SetName($"{element.GetType().Name}_IsEnabled_{element.IsEnabled}");
+ }
+ }
+ }
+
+ [Test, Category("IsEnabled"), TestCaseSource(nameof(TestCases))]
+ [Description("View enabled should match renderer enabled")]
+ public void EnabledConsistent(VisualElement element)
+ {
+ using (var renderer = GetRenderer(element))
+ {
+ var expected = element.IsEnabled;
+ var container = renderer.ContainerElement;
+
+ // Check the container control
+ Assert.That(container.IsHitTestVisible, Is.EqualTo(expected));
+
+ // Check the actual control (if there is one; for some renderers, like Frame and BoxView, the
+ // native control isn't a Windows.UI.Xaml.Controls.Control)
+ var control = GetNativeControl(element);
+
+ if (control != null)
+ {
+ Assert.That(control.IsEnabled, Is.EqualTo(expected));
+ }
+ }
+ }
+ }
+}
diff --git a/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/PlatformTestFixture.cs b/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/PlatformTestFixture.cs
new file mode 100644
index 00000000000..7a12b328c62
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/PlatformTestFixture.cs
@@ -0,0 +1,87 @@
+using System.Collections.Generic;
+using NUnit.Framework;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Shapes;
+using Xamarin.Forms.Platform.UWP;
+
+namespace Xamarin.Forms.ControlGallery.WindowsUniversal.Tests
+{
+ public class PlatformTestFixture
+ {
+ // Sequence for generating test cases
+ protected static IEnumerable BasicViews
+ {
+ get
+ {
+ yield return new BoxView { };
+ yield return new Button { };
+ yield return new CheckBox { };
+ yield return new DatePicker { };
+ yield return new Editor { };
+ yield return new Entry { };
+ yield return new Frame { };
+ yield return new Image { };
+ yield return new ImageButton { };
+ yield return new Label { };
+ yield return new Picker { };
+ yield return new ProgressBar { };
+ yield return new SearchBar { };
+ yield return new Slider { };
+ yield return new Stepper { };
+ yield return new Switch { };
+ yield return new TimePicker { };
+ }
+ }
+
+ protected static TestCaseData CreateTestCase(VisualElement element)
+ {
+ // We set the element type as a category on the test so that if you
+ // filter by category, say, "Button", you'll get any Button test
+ // generated from here.
+
+ return new TestCaseData(element).SetCategory(element.GetType().Name);
+ }
+
+ protected IVisualElementRenderer GetRenderer(VisualElement element)
+ {
+ return element.GetOrCreateRenderer();
+ }
+
+ protected Control GetNativeControl(VisualElement element)
+ {
+ return GetRenderer(element).GetNativeElement() as Control;
+ }
+
+ protected Panel GetPanel(VisualElement element)
+ {
+ return GetRenderer(element).ContainerElement as Panel;
+ }
+
+ protected Border GetBorder(VisualElement element)
+ {
+ var renderer = GetRenderer(element);
+ var nativeElement = renderer.GetNativeElement();
+ return nativeElement as Border;
+ }
+
+ protected TextBlock GetNativeControl(Label label)
+ {
+ return GetRenderer(label).GetNativeElement() as TextBlock;
+ }
+
+ protected FormsButton GetNativeControl(Button button)
+ {
+ return GetRenderer(button).GetNativeElement() as FormsButton;
+ }
+
+ protected FormsTextBox GetNativeControl(Entry entry)
+ {
+ return GetRenderer(entry).GetNativeElement() as FormsTextBox;
+ }
+
+ protected FormsTextBox GetNativeControl(Editor editor)
+ {
+ return GetRenderer(editor).GetNativeElement() as FormsTextBox;
+ }
+ }
+}
diff --git a/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/PlatformTestSettings.cs b/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/PlatformTestSettings.cs
new file mode 100644
index 00000000000..aa93e5bf024
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/PlatformTestSettings.cs
@@ -0,0 +1,26 @@
+using System.Collections.Generic;
+using System.Reflection;
+using NUnit;
+using Xamarin.Forms;
+using Xamarin.Forms.ControlGallery.WindowsUniversal.Tests;
+using Xamarin.Forms.Controls.Tests;
+
+[assembly: Dependency(typeof(PlatformTestSettings))]
+namespace Xamarin.Forms.ControlGallery.WindowsUniversal.Tests
+{
+ public class PlatformTestSettings : IPlatformTestSettings
+ {
+ public PlatformTestSettings()
+ {
+ TestRunSettings = new Dictionary
+ {
+ // Creating/modifying any renderers off the UI thread causes problems
+ // so we want to force the tests to run on main
+ { FrameworkPackageSettings.RunOnMainThread, true }
+ };
+ }
+
+ public Assembly Assembly { get => Assembly.GetExecutingAssembly(); }
+ public Dictionary TestRunSettings { get; }
+ }
+}
diff --git a/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/RendererTests.cs b/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/RendererTests.cs
new file mode 100644
index 00000000000..7f49594220f
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/RendererTests.cs
@@ -0,0 +1,17 @@
+using NUnit.Framework;
+
+namespace Xamarin.Forms.ControlGallery.WindowsUniversal.Tests
+{
+ [TestFixture]
+ public class RendererTests : PlatformTestFixture
+ {
+ [Test]
+ [Description("Basic sanity check that Label text matches renderer text")]
+ public void LabelTextMatchesRendererText()
+ {
+ var label = new Label { Text = "foo" };
+ var textBlock = GetNativeControl(label);
+ Assert.That(label.Text == textBlock.Text);
+ }
+ }
+}
diff --git a/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/RotationTests.cs b/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/RotationTests.cs
new file mode 100644
index 00000000000..dbaa4f47398
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/RotationTests.cs
@@ -0,0 +1,114 @@
+using System;
+using System.Collections;
+using NUnit.Framework;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Media;
+
+namespace Xamarin.Forms.ControlGallery.WindowsUniversal.Tests
+{
+ [TestFixture]
+ public class RotationTests : PlatformTestFixture
+ {
+ static IEnumerable RotationXCases
+ {
+ get
+ {
+ foreach (var element in BasicViews)
+ {
+ element.RotationX = 33.0;
+ yield return CreateTestCase(element);
+ }
+ }
+ }
+
+ static IEnumerable RotationYCases
+ {
+ get
+ {
+ foreach (var element in BasicViews)
+ {
+ element.RotationY = 86.0;
+ yield return CreateTestCase(element);
+ }
+ }
+ }
+
+ static IEnumerable RotationCases
+ {
+ get
+ {
+ foreach (var element in BasicViews)
+ {
+ element.Rotation = 23.0;
+ yield return CreateTestCase(element);
+ }
+ }
+ }
+
+ void AssertRotationConsistent(View view, Func getRotation,
+ Func getNativeRotation)
+ {
+ var frameworkElement = GetRenderer(view).ContainerElement;
+
+ var expected = getRotation(view);
+ var actual = getNativeRotation(frameworkElement);
+
+ Assert.That(actual, Is.EqualTo(expected));
+ }
+
+ [Test, Category("RotationX"), TestCaseSource(nameof(RotationXCases))]
+ [Description("View X rotation should match renderer X rotation")]
+ public void RotationXConsistent(View view)
+ {
+ AssertRotationConsistent(view,
+ e => e.RotationX, GetRotationX);
+ }
+
+ [Test, Category("RotationY"), TestCaseSource(nameof(RotationYCases))]
+ [Description("View Y rotation should match renderer Y rotation")]
+ public void RotationYConsistent(View view)
+ {
+ AssertRotationConsistent(view,
+ e => e.RotationY, GetRotationY);
+ }
+
+ [Test, Category("Rotation"), TestCaseSource(nameof(RotationCases))]
+ [Description("View rotation should match renderer rotation")]
+ public void RotationConsistent(View view)
+ {
+ AssertRotationConsistent(view,
+ e => e.Rotation, GetRotation);
+ }
+
+
+ double GetRotationX(FrameworkElement fe)
+ {
+ if (fe.Projection is PlaneProjection planeProjection)
+ {
+ return -planeProjection.RotationX;
+ }
+
+ throw new Exception("No rotation found");
+ }
+
+ double GetRotationY(FrameworkElement fe)
+ {
+ if (fe.Projection is PlaneProjection planeProjection)
+ {
+ return -planeProjection.RotationY;
+ }
+
+ throw new Exception("No rotation found");
+ }
+
+ double GetRotation(FrameworkElement fe)
+ {
+ if (fe.RenderTransform is CompositeTransform compositeTransform)
+ {
+ return compositeTransform.Rotation;
+ }
+
+ throw new Exception("No rotation found");
+ }
+ }
+}
diff --git a/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/ScaleTests.cs b/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/ScaleTests.cs
new file mode 100644
index 00000000000..55fc1765239
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/ScaleTests.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Collections;
+using NUnit.Framework;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Media;
+
+namespace Xamarin.Forms.ControlGallery.WindowsUniversal.Tests
+{
+ [TestFixture]
+ public class ScaleTests : PlatformTestFixture
+ {
+ static IEnumerable ScaleXCases
+ {
+ get
+ {
+ foreach (var element in BasicViews)
+ {
+ element.ScaleX = 0.45;
+ yield return CreateTestCase(element);
+ }
+ }
+ }
+
+ static IEnumerable ScaleYCases
+ {
+ get
+ {
+ foreach (var element in BasicViews)
+ {
+ element.ScaleY = 1.23;
+ yield return CreateTestCase(element);
+ }
+ }
+ }
+
+ static IEnumerable ScaleCases
+ {
+ get
+ {
+ foreach (var element in BasicViews)
+ {
+ element.Scale = 0.5;
+ yield return CreateTestCase(element);
+ }
+ }
+ }
+
+ void AssertScaleConsistent(View view, Func getScale,
+ Func getNativeScale)
+ {
+ var frameworkElement = GetRenderer(view).ContainerElement;
+
+ var expected = getScale(view);
+ var actual = getNativeScale(frameworkElement);
+
+ Assert.That(actual, Is.EqualTo(expected).Within(0.001d));
+ }
+
+ [Test, Category("ScaleX"), TestCaseSource(nameof(ScaleXCases))]
+ [Description("View X scale should match renderer X scale")]
+ public void ScaleXConsistent(View view)
+ {
+ AssertScaleConsistent(view, e => e.ScaleX, GetScaleX);
+ }
+
+ [Test, Category("ScaleY"), TestCaseSource(nameof(ScaleYCases))]
+ [Description("View Y scale should match renderer Y scale")]
+ public void ScaleYConsistent(View view)
+ {
+ AssertScaleConsistent(view, e => e.ScaleY, GetScaleY);
+ }
+
+ [Test, Category("Scale"), TestCaseSource(nameof(ScaleCases))]
+ [Description("View scale should match renderer scale")]
+ public void ScaleConsistent(View view)
+ {
+ AssertScaleConsistent(view, e => e.Scale, GetScaleX);
+ AssertScaleConsistent(view, e => e.Scale, GetScaleY);
+ }
+
+ double GetScaleX(FrameworkElement fe)
+ {
+ if (fe.RenderTransform is CompositeTransform compositeTransform)
+ {
+ return compositeTransform.ScaleX;
+ }
+
+ throw new Exception("Could not determine ScaleX");
+ }
+
+ double GetScaleY(FrameworkElement fe)
+ {
+ if (fe.RenderTransform is CompositeTransform compositeTransform)
+ {
+ return compositeTransform.ScaleY;
+ }
+
+ throw new Exception("Could not determine ScaleY");
+ }
+ }
+}
diff --git a/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/TestingPlatformService.cs b/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/TestingPlatformService.cs
new file mode 100644
index 00000000000..290382a2ce3
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/TestingPlatformService.cs
@@ -0,0 +1,15 @@
+using Xamarin.Forms;
+using Xamarin.Forms.ControlGallery.WindowsUniversal.Tests;
+using Xamarin.Forms.Controls.Tests;
+
+[assembly: Dependency(typeof(TestingPlatformService))]
+namespace Xamarin.Forms.ControlGallery.WindowsUniversal.Tests
+{
+ class TestingPlatformService : ITestingPlatformService
+ {
+ public void CreateRenderer(VisualElement visualElement)
+ {
+ Platform.UWP.Platform.CreateRenderer(visualElement);
+ }
+ }
+}
diff --git a/Xamarin.Forms.ControlGallery.WindowsUniversal/Xamarin.Forms.ControlGallery.WindowsUniversal.csproj b/Xamarin.Forms.ControlGallery.WindowsUniversal/Xamarin.Forms.ControlGallery.WindowsUniversal.csproj
index d0b625241ba..9eddad9111b 100644
--- a/Xamarin.Forms.ControlGallery.WindowsUniversal/Xamarin.Forms.ControlGallery.WindowsUniversal.csproj
+++ b/Xamarin.Forms.ControlGallery.WindowsUniversal/Xamarin.Forms.ControlGallery.WindowsUniversal.csproj
@@ -131,6 +131,14 @@
+
+
+
+
+
+
+
+
@@ -251,6 +259,9 @@
11.0.2
+
+ 3.12.0
+
3.0.2
diff --git a/Xamarin.Forms.ControlGallery.iOS/Tests/AssertionExtensions.cs b/Xamarin.Forms.ControlGallery.iOS/Tests/AssertionExtensions.cs
new file mode 100644
index 00000000000..d60f3d0a968
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.iOS/Tests/AssertionExtensions.cs
@@ -0,0 +1,173 @@
+using System;
+using System.Collections;
+using CoreGraphics;
+using NUnit.Framework;
+using NUnit.Framework.Constraints;
+using UIKit;
+
+namespace Xamarin.Forms.ControlGallery.iOS.Tests
+{
+ internal static class AssertionExtensions
+ {
+ public static string CreateColorAtPointError(this UIImage bitmap, UIColor expectedColor, int x, int y)
+ {
+ var data = bitmap.AsPNG();
+ var imageAsString = data.GetBase64EncodedString(Foundation.NSDataBase64EncodingOptions.None);
+ return $"Expected {expectedColor} at point {x},{y} in renderered view. This is what it looked like: {imageAsString}";
+ }
+
+ public static UIImage ToBitmap(this UIView view)
+ {
+ var imageRect = new CGRect(0, 0, view.Frame.Width, view.Frame.Height);
+
+ UIGraphics.BeginImageContext(imageRect.Size);
+
+ var context = UIGraphics.GetCurrentContext();
+ view.Layer.RenderInContext(context);
+ var image = UIGraphics.GetImageFromCurrentImageContext();
+
+ UIGraphics.EndImageContext();
+
+ return image;
+ }
+
+ public static UIColor ColorAtPoint(this UIImage bitmap, int x, int y)
+ {
+ var pixel = bitmap.GetPixel(x, y);
+
+ // Returned pixel data is B, G, R, A (ARGB little endian byte order)
+ var color = new UIColor(pixel[2] / 255.0f, pixel[1] / 255.0f, pixel[0] / 255.0f, pixel[3] / 255.0f);
+
+ return color;
+ }
+
+ public static byte[] GetPixel(this UIImage bitmap, int x, int y)
+ {
+ var cgImage = bitmap.CGImage.WithColorSpace(CGColorSpace.CreateDeviceRGB());
+
+ // Grab the raw image data
+ var nsData = cgImage.DataProvider.CopyData();
+
+ // Copy the data into a buffer
+ var dataBytes = new byte[nsData.Length];
+ System.Runtime.InteropServices.Marshal.Copy(nsData.Bytes, dataBytes, 0, (int)nsData.Length);
+
+ // Figure out where the pixel we care about is
+ var pixelLocation = (cgImage.BytesPerRow * y) + (4 * x);
+
+ var pixel = new byte[4]
+ {
+ dataBytes[pixelLocation],
+ dataBytes[pixelLocation + 1],
+ dataBytes[pixelLocation + 2],
+ dataBytes[pixelLocation + 3],
+ };
+
+ return pixel;
+ }
+
+ static bool ARGBEquivalent(UIColor color1, UIColor color2)
+ {
+ color1.GetRGBA(out nfloat red1, out nfloat green1, out nfloat blue1, out nfloat alpha1);
+ color2.GetRGBA(out nfloat red2, out nfloat green2, out nfloat blue2, out nfloat alpha2);
+
+ const double tolerance = 0.000001;
+
+ return Equal(red1, red2, tolerance)
+ && Equal(green1, green2, tolerance)
+ && Equal(blue1, blue2, tolerance)
+ && Equal(alpha1, alpha2, tolerance);
+ }
+
+ static bool Equal(nfloat v1, nfloat v2, double tolerance)
+ {
+ return Math.Abs(v1 - v2) <= tolerance;
+ }
+
+ public static UIImage AssertColorAtPoint(this UIImage bitmap, UIColor expectedColor, int x, int y)
+ {
+ try
+ {
+ var cap = bitmap.ColorAtPoint(x, y);
+
+ if (!ARGBEquivalent(cap, expectedColor))
+ {
+ System.Diagnostics.Debug.WriteLine("Here");
+ }
+
+ Assert.That(cap, Is.EqualTo(expectedColor).Using(ARGBEquivalent),
+ () => bitmap.CreateColorAtPointError(expectedColor, x, y));
+
+ return bitmap;
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine(ex);
+ }
+
+ return null;
+ }
+
+ public static UIImage AssertColorAtCenter(this UIImage bitmap, UIColor expectedColor)
+ {
+ AssertColorAtPoint(bitmap, expectedColor, (int)bitmap.Size.Width / 2, (int)bitmap.Size.Height / 2);
+ return bitmap;
+ }
+
+ public static UIImage AssertColorAtBottomLeft(this UIImage bitmap, UIColor expectedColor)
+ {
+ return bitmap.AssertColorAtPoint(expectedColor, 0, 0);
+ }
+
+ public static UIImage AssertColorAtBottomRight(this UIImage bitmap, UIColor expectedColor)
+ {
+ return bitmap.AssertColorAtPoint(expectedColor, (int)bitmap.Size.Width - 1, 0);
+ }
+
+ public static UIImage AssertColorAtTopLeft(this UIImage bitmap, UIColor expectedColor)
+ {
+ return bitmap.AssertColorAtPoint(expectedColor, 0, (int)bitmap.Size.Height - 1);
+ }
+
+ public static UIImage AssertColorAtTopRight(this UIImage bitmap, UIColor expectedColor)
+ {
+ return bitmap.AssertColorAtPoint(expectedColor, (int)bitmap.Size.Width - 1, (int)bitmap.Size.Height - 1);
+ }
+
+ public static UIImage AssertColorAtPoint(this UIView view, UIColor expectedColor, int x, int y)
+ {
+ var bitmap = view.ToBitmap();
+ return bitmap.AssertColorAtPoint(expectedColor, x, y);
+ }
+
+ public static UIImage AssertColorAtCenter(this UIView view, UIColor expectedColor)
+ {
+ var bitmap = view.ToBitmap();
+ return bitmap.AssertColorAtCenter(expectedColor);
+ }
+
+ public static UIImage AssertColorAtBottomLeft(this UIView view, UIColor expectedColor)
+ {
+ var bitmap = view.ToBitmap();
+ return bitmap.AssertColorAtBottomLeft(expectedColor);
+ }
+
+ public static UIImage AssertColorAtBottomRight(this UIView view, UIColor expectedColor)
+ {
+ var bitmap = view.ToBitmap();
+ return bitmap.AssertColorAtBottomRight(expectedColor);
+ }
+
+ public static UIImage AssertColorAtTopLeft(this UIView view, UIColor expectedColor)
+ {
+ var bitmap = view.ToBitmap();
+ return bitmap.AssertColorAtTopLeft(expectedColor);
+ }
+
+ public static UIImage AssertColorAtTopRight(this UIView view, UIColor expectedColor)
+ {
+ var bitmap = view.ToBitmap();
+ return bitmap.AssertColorAtTopRight(expectedColor);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.ControlGallery.iOS/Tests/BackgroundColorTests.cs b/Xamarin.Forms.ControlGallery.iOS/Tests/BackgroundColorTests.cs
new file mode 100644
index 00000000000..dbe9fa6ecd8
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.iOS/Tests/BackgroundColorTests.cs
@@ -0,0 +1,81 @@
+using System.Collections;
+using System.Linq;
+using NUnit.Framework;
+using UIKit;
+using Xamarin.Forms.Platform.iOS;
+
+namespace Xamarin.Forms.ControlGallery.iOS.Tests
+{
+ [TestFixture]
+ public class BackgroundColorTests : PlatformTestFixture
+ {
+ static IEnumerable TestCases
+ {
+ get
+ {
+ foreach (var element in BasicViews
+ .Where(e => !(e is Label) && !(e is BoxView) && !(e is Frame)))
+ {
+ element.BackgroundColor = Color.AliceBlue;
+ yield return new TestCaseData(element)
+ .SetCategory(element.GetType().Name);
+ }
+ }
+ }
+
+ [Test, Category("BackgroundColor"), TestCaseSource(nameof(TestCases))]
+ [Description("VisualElement background color should match renderer background color")]
+ public void BackgroundColorConsistent(VisualElement element)
+ {
+ using (var uiView = GetNativeControl(element))
+ {
+ var expectedColor = element.BackgroundColor.ToUIColor();
+ Assert.That(uiView.BackgroundColor, Is.EqualTo(expectedColor));
+ }
+ }
+
+ [Test, Category("BackgroundColor"), Category("Frame")]
+ [Description("Frame background color should match renderer background color")]
+ public void FrameBackgroundColorConsistent()
+ {
+ var frame = new Frame { BackgroundColor = Color.AliceBlue };
+ using (var renderer = GetRenderer(frame))
+ {
+ var expectedColor = frame.BackgroundColor.ToUIColor();
+ Assert.That(renderer.NativeView.BackgroundColor, Is.EqualTo(expectedColor));
+ }
+ }
+
+ [Test, Category("BackgroundColor"), Category("Label")]
+ [Description("Label background color should match renderer background color")]
+ public void LabelBackgroundColorConsistent()
+ {
+ var label = new Label { Text = "foo", BackgroundColor = Color.AliceBlue };
+ using (var renderer = GetRenderer(label))
+ {
+ var expectedColor = label.BackgroundColor.ToUIColor();
+ Assert.That(renderer.NativeView.BackgroundColor, Is.EqualTo(expectedColor));
+ }
+ }
+
+ [Test, Category("BackgroundColor"), Category("BoxView")]
+ [Description("BoxView background color should match renderer background color")]
+ public void BoxViewBackgroundColorConsistent()
+ {
+ var boxView = new BoxView { BackgroundColor = Color.AliceBlue };
+ var expectedColor = boxView.BackgroundColor.ToUIColor();
+
+ var page = new ContentPage() { Content = boxView };
+
+ using (var pageRenderer = GetRenderer(page))
+ {
+ using (var uiView = GetRenderer(boxView).NativeView)
+ {
+ page.Layout(new Rectangle(0, 0, 200, 200));
+
+ uiView.AssertColorAtCenter(expectedColor);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.ControlGallery.iOS/Tests/CornerRadiusTests.cs b/Xamarin.Forms.ControlGallery.iOS/Tests/CornerRadiusTests.cs
new file mode 100644
index 00000000000..c4aead97e11
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.iOS/Tests/CornerRadiusTests.cs
@@ -0,0 +1,96 @@
+using NUnit.Framework;
+using Xamarin.Forms.Platform.iOS;
+
+namespace Xamarin.Forms.ControlGallery.iOS.Tests
+{
+ [TestFixture]
+ public class CornerRadiusTests : PlatformTestFixture
+ {
+ [Test, Category("CornerRadius"), Category("BoxView")]
+ public void BoxviewCornerRadius()
+ {
+ var boxView = new BoxView
+ {
+ HeightRequest = 100,
+ WidthRequest = 200,
+ CornerRadius = 15,
+ BackgroundColor = Color.CadetBlue
+ };
+
+ CheckCornerRadius(boxView);
+ }
+
+ [Test, Category("CornerRadius"), Category("Button")]
+ public void ButtonCornerRadius()
+ {
+ var backgroundColor = Color.CadetBlue;
+
+ var button = new Button
+ {
+ HeightRequest = 100,
+ WidthRequest = 200,
+ CornerRadius = 15,
+ BackgroundColor = backgroundColor
+ };
+
+ CheckCornerRadius(button);
+ }
+
+ [Test, Category("CornerRadius"), Category("Frame")]
+ [Ignore("Will not pass until https://github.com/xamarin/Xamarin.Forms/issues/9265 is fixed")]
+ public void FrameCornerRadius()
+ {
+ var backgroundColor = Color.CadetBlue;
+
+ var frame = new Frame
+ {
+ HeightRequest = 100,
+ WidthRequest = 200,
+ CornerRadius = 15,
+ BackgroundColor = backgroundColor,
+ BorderColor = Color.Brown,
+ Content = new Label { Text = "Hey" }
+ };
+
+ CheckCornerRadius(frame);
+ }
+
+ [Test, Category("CornerRadius"), Category("ImageButton")]
+ public void ImageButtonCornerRadius()
+ {
+ var backgroundColor = Color.CadetBlue;
+
+ var button = new ImageButton
+ {
+ HeightRequest = 100,
+ WidthRequest = 200,
+ CornerRadius = 15,
+ BackgroundColor = backgroundColor
+ };
+
+ CheckCornerRadius(button);
+ }
+
+ public void CheckCornerRadius(View view)
+ {
+ var page = new ContentPage() { Content = view };
+
+ using (var pageRenderer = GetRenderer(page))
+ {
+ using (var uiView = GetRenderer(view).NativeView)
+ {
+ page.Layout(new Rectangle(0, 0, view.WidthRequest, view.HeightRequest));
+
+
+ uiView
+ .AssertColorAtCenter(view.BackgroundColor.ToUIColor())
+ .AssertColorAtBottomLeft(EmptyBackground)
+ .AssertColorAtBottomRight(EmptyBackground)
+ .AssertColorAtTopLeft(EmptyBackground)
+ .AssertColorAtTopRight(EmptyBackground);
+
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.ControlGallery.iOS/Tests/IsEnabledTests.cs b/Xamarin.Forms.ControlGallery.iOS/Tests/IsEnabledTests.cs
new file mode 100644
index 00000000000..e1fb56c9795
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.iOS/Tests/IsEnabledTests.cs
@@ -0,0 +1,53 @@
+using System.Collections;
+using NUnit.Framework;
+using UIKit;
+
+namespace Xamarin.Forms.ControlGallery.iOS.Tests
+{
+ public class IsEnabledTests : PlatformTestFixture
+ {
+ static IEnumerable TestCases
+ {
+ get
+ {
+ // Generate IsEnabled = true cases
+ foreach (var element in BasicViews)
+ {
+ element.IsEnabled = true;
+ yield return CreateTestCase(element)
+ .SetName($"{element.GetType().Name}_IsEnabled_{element.IsEnabled}");
+ }
+
+ // Generate IsEnabled = false cases
+ foreach (var element in BasicViews)
+ {
+ element.IsEnabled = false;
+ yield return CreateTestCase(element)
+ .SetName($"{element.GetType().Name}_IsEnabled_{element.IsEnabled}");
+ }
+ }
+ }
+
+ [Test, Category("IsEnabled"), TestCaseSource(nameof(TestCases))]
+ [Description("VisualElement enabled should match renderer enabled")]
+ public void EnabledConsistent(View view)
+ {
+ using (var renderer = GetRenderer(view))
+ {
+ var expected = view.IsEnabled;
+ var nativeView = renderer.NativeView;
+
+ // Check the container
+ Assert.That(renderer.NativeView.UserInteractionEnabled, Is.EqualTo(expected));
+
+ // Check the actual control
+ var control = GetNativeControl(view);
+
+ if (control is UIControl uiControl)
+ {
+ Assert.That(uiControl.Enabled, Is.EqualTo(expected));
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.ControlGallery.iOS/Tests/IsVisibleTests.cs b/Xamarin.Forms.ControlGallery.iOS/Tests/IsVisibleTests.cs
new file mode 100644
index 00000000000..8f6597ad6a1
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.iOS/Tests/IsVisibleTests.cs
@@ -0,0 +1,50 @@
+using System.Collections;
+using NUnit.Framework;
+
+namespace Xamarin.Forms.ControlGallery.iOS.Tests
+{
+ public class IsVisibleTests : PlatformTestFixture
+ {
+ static IEnumerable TestCases
+ {
+ get
+ {
+ // Generate IsVisible = true cases
+ foreach (var element in BasicViews)
+ {
+ element.IsVisible = true;
+ yield return CreateTestCase(element)
+ .SetName($"{element.GetType().Name}_IsVisible_{element.IsVisible}");
+ }
+
+ // Generate IsEnabled = false cases
+ foreach (var element in BasicViews)
+ {
+ element.IsVisible = false;
+ yield return CreateTestCase(element)
+ .SetName($"{element.GetType().Name}_IsVisible_{element.IsVisible}");
+ }
+ }
+ }
+
+ [Test, Category("IsVisible"), TestCaseSource(nameof(TestCases))]
+ [Description("VisualElement visibility should match renderer visibility")]
+ public void VisibleConsistent(View view)
+ {
+ var page = new ContentPage() { Content = view };
+
+ using (var pageRenderer = GetRenderer(page))
+ {
+ using (var uiView = GetRenderer(view).NativeView)
+ {
+ page.Layout(new Rectangle(0, 0, 200, 200));
+
+ var expectedHidden = !view.IsVisible;
+ var actualHidden = uiView.Layer.Hidden;
+
+ Assert.That(actualHidden, Is.EqualTo(expectedHidden));
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.ControlGallery.iOS/Tests/OpacityTests.cs b/Xamarin.Forms.ControlGallery.iOS/Tests/OpacityTests.cs
new file mode 100644
index 00000000000..842c80aabaa
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.iOS/Tests/OpacityTests.cs
@@ -0,0 +1,64 @@
+using System.Collections;
+using System.Linq;
+using NUnit.Framework;
+
+namespace Xamarin.Forms.ControlGallery.iOS.Tests
+{
+ [TestFixture]
+ public class OpacityTests : PlatformTestFixture
+ {
+ static readonly double TestOpacity = 0.4;
+
+ static IEnumerable TestCases
+ {
+ get
+ {
+ foreach (var element in BasicViews.Where(e => !(e is Label)))
+ {
+ element.Opacity = TestOpacity;
+ yield return CreateTestCase(element);
+ }
+ }
+ }
+
+ [Test, Category("Opacity"), TestCaseSource(nameof(TestCases))]
+ [Description("VisualElement opacity should match renderer opacity")]
+ public void OpacityConsistent(View view)
+ {
+ var page = new ContentPage() { Content = view };
+
+ using (var pageRenderer = GetRenderer(page))
+ {
+ using (var uiView = GetRenderer(view).NativeView)
+ {
+ page.Layout(new Rectangle(0, 0, 200, 200));
+
+ var expected = view.Opacity;
+
+ // Deliberately casting this to double because Within doesn't seem to grasp nfloat
+ // If you write this the other way around (casting expected to an nfloat), it fails
+ Assert.That((double)uiView.Alpha, Is.EqualTo(expected).Within(0.001d));
+ }
+ }
+ }
+
+ [Test, Category("Opacity"), Category("Label")]
+ [Description("Label opacity should match renderer opacity")]
+ public void LabelOpacityConsistent()
+ {
+ var label = new Label { Text = "foo", Opacity = TestOpacity };
+
+ var page = new ContentPage() { Content = label };
+
+ using (var pageRenderer = GetRenderer(page))
+ {
+ using (var renderer = GetRenderer(label))
+ {
+ page.Layout(new Rectangle(0, 0, 200, 200));
+ var expected = label.Opacity;
+ Assert.That((double)renderer.NativeView.Alpha, Is.EqualTo(expected).Within(0.001d));
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.ControlGallery.iOS/Tests/PlatformTestFixture.cs b/Xamarin.Forms.ControlGallery.iOS/Tests/PlatformTestFixture.cs
index 6d0802f06c9..956d3454644 100644
--- a/Xamarin.Forms.ControlGallery.iOS/Tests/PlatformTestFixture.cs
+++ b/Xamarin.Forms.ControlGallery.iOS/Tests/PlatformTestFixture.cs
@@ -1,4 +1,6 @@
-using UIKit;
+using System.Collections.Generic;
+using NUnit.Framework;
+using UIKit;
using Xamarin.Forms.Platform.iOS;
namespace Xamarin.Forms.ControlGallery.iOS.Tests
@@ -6,15 +8,79 @@ namespace Xamarin.Forms.ControlGallery.iOS.Tests
[Internals.Preserve(AllMembers = true)]
public class PlatformTestFixture
{
+ protected static UIColor EmptyBackground = new UIColor(0f,0f,0f,0f);
+
+ // Sequence for generating test cases
+ protected static IEnumerable BasicViews
+ {
+ get
+ {
+ yield return new BoxView { };
+ yield return new Button { };
+ yield return new CheckBox { };
+ yield return new DatePicker { };
+ yield return new Editor { };
+ yield return new Entry { };
+ yield return new Frame { };
+ yield return new Image { };
+ yield return new ImageButton { };
+ yield return new Label { };
+ yield return new Picker { };
+ yield return new ProgressBar { };
+ yield return new SearchBar { };
+ yield return new Slider { };
+ yield return new Stepper { };
+ yield return new Switch { };
+ yield return new TimePicker { };
+ }
+ }
+
+ protected static TestCaseData CreateTestCase(VisualElement element)
+ {
+ // We set the element type as a category on the test so that if you
+ // filter by category, say, "Button", you'll get any Button test
+ // generated from here.
+
+ return new TestCaseData(element).SetCategory(element.GetType().Name);
+ }
+
protected IVisualElementRenderer GetRenderer(VisualElement element)
{
return Platform.iOS.Platform.CreateRenderer(element);
}
+ protected UIView GetNativeControl(VisualElement visualElement)
+ {
+ var renderer = GetRenderer(visualElement);
+ var viewRenderer = renderer as IVisualNativeElementRenderer;
+ return viewRenderer?.Control;
+ }
+
protected UILabel GetNativeControl(Label label)
{
var renderer = GetRenderer(label);
- var viewRenderer = renderer.NativeView as ViewRenderer;
+ var viewRenderer = renderer.NativeView as LabelRenderer;
+ return viewRenderer.Control;
+ }
+
+ protected UITextField GetNativeControl(Entry entry)
+ {
+ var renderer = GetRenderer(entry);
+ var viewRenderer = renderer.NativeView as EntryRenderer;
+ return viewRenderer.Control;
+ }
+
+ protected UITextView GetNativeControl(Editor editor)
+ {
+ var renderer = GetRenderer(editor);
+ var viewRenderer = renderer.NativeView as EditorRenderer;
+ return viewRenderer.Control;
+ }
+
+ protected UIButton GetNativeControl(Button button)
+ {
+ var renderer = GetRenderer(button);
+ var viewRenderer = renderer.NativeView as ButtonRenderer;
return viewRenderer.Control;
}
}
diff --git a/Xamarin.Forms.ControlGallery.iOS/Tests/PlatformTestSettings.cs b/Xamarin.Forms.ControlGallery.iOS/Tests/PlatformTestSettings.cs
index ce718130f89..6989d2aaa6e 100644
--- a/Xamarin.Forms.ControlGallery.iOS/Tests/PlatformTestSettings.cs
+++ b/Xamarin.Forms.ControlGallery.iOS/Tests/PlatformTestSettings.cs
@@ -16,8 +16,8 @@ public PlatformTestSettings()
{
TestRunSettings = new Dictionary
{
- // Creating/modifying any renderers on iOS off the UI thread causes problems
- // so at least for iOS, we want to force the tests to run on main
+ // Creating/modifying any renderers off the UI thread causes problems
+ // so we want to force the tests to run on main
{ FrameworkPackageSettings.RunOnMainThread, true }
};
}
diff --git a/Xamarin.Forms.ControlGallery.iOS/Tests/RendererTests.cs b/Xamarin.Forms.ControlGallery.iOS/Tests/RendererTests.cs
deleted file mode 100644
index 299e36dd530..00000000000
--- a/Xamarin.Forms.ControlGallery.iOS/Tests/RendererTests.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using NUnit.Framework;
-
-namespace Xamarin.Forms.ControlGallery.iOS.Tests
-{
- [TestFixture]
- [Internals.Preserve(AllMembers = true)]
- public class RendererTests : PlatformTestFixture
- {
- [Test(Description = "Basic sanity check that Label text matches renderer text")]
- public void LabelTextMatchesRendererText()
- {
- var label = new Label { Text = "foo" };
- using (var uiLabel = GetNativeControl(label))
- {
- Assert.That(label.Text == uiLabel.Text);
- }
- }
- }
-}
\ No newline at end of file
diff --git a/Xamarin.Forms.ControlGallery.iOS/Tests/RotationTests.cs b/Xamarin.Forms.ControlGallery.iOS/Tests/RotationTests.cs
new file mode 100644
index 00000000000..35ef5be0f79
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.iOS/Tests/RotationTests.cs
@@ -0,0 +1,95 @@
+using System;
+using System.Collections;
+using NUnit.Framework;
+using UIKit;
+using static Xamarin.Forms.Core.UITests.NumericExtensions;
+using static Xamarin.Forms.Core.UITests.ParsingUtils;
+
+namespace Xamarin.Forms.ControlGallery.iOS.Tests
+{
+ [TestFixture]
+ public class RotationTests : PlatformTestFixture
+ {
+ static IEnumerable RotationXCases
+ {
+ get
+ {
+ foreach (var element in BasicViews)
+ {
+ element.RotationX = 33.0;
+ yield return CreateTestCase(element);
+ }
+ }
+ }
+
+ static IEnumerable RotationYCases
+ {
+ get
+ {
+ foreach (var element in BasicViews)
+ {
+ element.RotationY = 87.0;
+ yield return CreateTestCase(element);
+ }
+ }
+ }
+
+ static IEnumerable RotationCases
+ {
+ get
+ {
+ foreach (var element in BasicViews)
+ {
+ element.Rotation = 23.0;
+ yield return CreateTestCase(element);
+ }
+ }
+ }
+
+ void AssertRotationConsistent(View view, Func getRotation,
+ Func getNativeRotation)
+ {
+ var page = new ContentPage() { Content = view };
+
+ using (var pageRenderer = GetRenderer(page))
+ {
+ using (var uiView = GetRenderer(view).NativeView)
+ {
+ page.Layout(new Rectangle(0, 0, 200, 200));
+
+ var expected = getRotation(view);
+ var actual = getNativeRotation(uiView);
+
+ Assert.That(actual, Is.EqualTo(expected));
+ }
+ }
+ }
+
+ [Test, Category("RotationX"), TestCaseSource(nameof(RotationXCases))]
+ [Description("VisualElement X rotation should match renderer X rotation")]
+ public void RotationXConsistent(View view)
+ {
+ AssertRotationConsistent(view,
+ e => CalculateRotationMatrixForDegrees((float)e.RotationX, Core.UITests.Axis.X),
+ v => ParseCATransform3D(v.Layer.Transform.ToString()));
+ }
+
+ [Test, Category("RotationY"), TestCaseSource(nameof(RotationYCases))]
+ [Description("VisualElement Y rotation should match renderer Y rotation")]
+ public void RotationYConsistent(View view)
+ {
+ AssertRotationConsistent(view,
+ e => CalculateRotationMatrixForDegrees((float)e.RotationY, Core.UITests.Axis.Y),
+ v => ParseCATransform3D(v.Layer.Transform.ToString()));
+ }
+
+ [Test, Category("Rotation"), TestCaseSource(nameof(RotationCases))]
+ [Description("VisualElement rotation should match renderer rotation")]
+ public void RotationConsistent(View view)
+ {
+ AssertRotationConsistent(view,
+ e => CalculateRotationMatrixForDegrees((float)e.Rotation, Core.UITests.Axis.Z),
+ v => ParseCATransform3D(v.Layer.Transform.ToString()));
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.ControlGallery.iOS/Tests/ScaleTests.cs b/Xamarin.Forms.ControlGallery.iOS/Tests/ScaleTests.cs
new file mode 100644
index 00000000000..e307ab01e97
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.iOS/Tests/ScaleTests.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Collections;
+using NUnit.Framework;
+using UIKit;
+using static Xamarin.Forms.Core.UITests.NumericExtensions;
+using static Xamarin.Forms.Core.UITests.ParsingUtils;
+
+namespace Xamarin.Forms.ControlGallery.iOS.Tests
+{
+ [TestFixture]
+ public class ScaleTests : PlatformTestFixture
+ {
+ static IEnumerable ScaleCases
+ {
+ get
+ {
+ foreach (var element in BasicViews)
+ {
+ element.Scale = 2.0;
+ yield return CreateTestCase(element);
+ }
+ }
+ }
+
+ void AssertScaleConsistent(View view, Func getScale,
+ Func getNativeScale)
+ {
+ var page = new ContentPage() { Content = view };
+
+ using (var pageRenderer = GetRenderer(page))
+ {
+ using (var uiView = GetRenderer(view).NativeView)
+ {
+ page.Layout(new Rectangle(0, 0, 200, 200));
+
+ var expected = getScale(view);
+ var actual = getNativeScale(uiView);
+
+ Assert.That(actual, Is.EqualTo(expected));
+ }
+ }
+ }
+
+ [Test, Category("Scale"), TestCaseSource(nameof(ScaleCases))]
+ [Description("View scale should match renderer scale")]
+ public void ScaleConsistent(View view)
+ {
+ AssertScaleConsistent(view,
+ e => BuildScaleMatrix((float)e.Scale),
+ v => ParseCATransform3D(v.Layer.Transform.ToString()));
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.ControlGallery.iOS/Tests/TestingPlatformService.cs b/Xamarin.Forms.ControlGallery.iOS/Tests/TestingPlatformService.cs
new file mode 100644
index 00000000000..b7ee6b070b2
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.iOS/Tests/TestingPlatformService.cs
@@ -0,0 +1,15 @@
+using Xamarin.Forms;
+using Xamarin.Forms.ControlGallery.iOS.Tests;
+using Xamarin.Forms.Controls.Tests;
+
+[assembly: Dependency(typeof(TestingPlatformService))]
+namespace Xamarin.Forms.ControlGallery.iOS.Tests
+{
+ class TestingPlatformService : ITestingPlatformService
+ {
+ public void CreateRenderer(VisualElement visualElement)
+ {
+ Platform.iOS.Platform.CreateRenderer(visualElement);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.ControlGallery.iOS/Tests/TextTests.cs b/Xamarin.Forms.ControlGallery.iOS/Tests/TextTests.cs
new file mode 100644
index 00000000000..2b006692539
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.iOS/Tests/TextTests.cs
@@ -0,0 +1,52 @@
+using NUnit.Framework;
+
+namespace Xamarin.Forms.ControlGallery.iOS.Tests
+{
+ [TestFixture]
+ public class TextTests : PlatformTestFixture
+ {
+ [Test, Category("Text"), Category("Label")]
+ [Description("Label text should match renderer text")]
+ public void LabelTextMatchesRendererText()
+ {
+ var label = new Label { Text = "foo" };
+ using (var uiLabel = GetNativeControl(label))
+ {
+ Assert.That(label.Text, Is.EqualTo(uiLabel.Text));
+ }
+ }
+
+ [Test, Category("Text"), Category("Button")]
+ [Description("Button text should match renderer text")]
+ public void ButtonTextMatchesRendererText()
+ {
+ var button = new Button { Text = "foo" };
+ using (var nativeControl = GetNativeControl(button))
+ {
+ Assert.That(nativeControl.TitleLabel.Text, Is.EqualTo(button.Text));
+ }
+ }
+
+ [Test, Category("Text"), Category("Entry")]
+ [Description("Entry text should match renderer text")]
+ public void EntryTextMatchesRendererText()
+ {
+ var entry = new Entry { Text = "foo" };
+ using (var nativeControl = GetNativeControl(entry))
+ {
+ Assert.That(nativeControl.Text, Is.EqualTo(entry.Text));
+ }
+ }
+
+ [Test, Category("Text"), Category("Editor")]
+ [Description("Editor text should match renderer text")]
+ public void EditorTextMatchesRendererText()
+ {
+ var editor = new Editor { Text = "foo" };
+ using (var nativeControl = GetNativeControl(editor))
+ {
+ Assert.That(nativeControl.Text, Is.EqualTo(editor.Text));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.ControlGallery.iOS/Xamarin.Forms.ControlGallery.iOS.csproj b/Xamarin.Forms.ControlGallery.iOS/Xamarin.Forms.ControlGallery.iOS.csproj
index be1523157a3..4ebf2389154 100644
--- a/Xamarin.Forms.ControlGallery.iOS/Xamarin.Forms.ControlGallery.iOS.csproj
+++ b/Xamarin.Forms.ControlGallery.iOS/Xamarin.Forms.ControlGallery.iOS.csproj
@@ -105,6 +105,12 @@
+
+ Tests\NumericExtensions.cs
+
+
+ Tests\ParsingUtils.cs
+
@@ -117,9 +123,18 @@
+
+
+
+
+
+
-
+
+
+
+
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla29128.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla29128.cs
index 954af170622..ddf545b62f8 100644
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla29128.cs
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla29128.cs
@@ -2,7 +2,9 @@
using Xamarin.Forms.CustomAttributes;
using Xamarin.Forms.Internals;
+
#if UITEST
+using Xamarin.Forms.Core.UITests;
using Xamarin.UITest;
using NUnit.Framework;
#endif
@@ -25,6 +27,7 @@ protected override void Init ()
#if UITEST
[Test]
+ [Category(UITestCategories.ManualReview)]
public void Bugzilla29128Test ()
{
RunningApp.WaitForElement (q => q.Marked ("SliderId"));
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla35738.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla35738.cs
deleted file mode 100644
index 5b8132fd322..00000000000
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla35738.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-using System;
-using System.Diagnostics;
-using Xamarin.Forms.CustomAttributes;
-using Xamarin.Forms.Internals;
-
-#if UITEST
-using Xamarin.UITest;
-using NUnit.Framework;
-#endif
-
-namespace Xamarin.Forms.Controls.Issues
-{
- [Preserve (AllMembers = true)]
- public class CustomButton : Button
- {
- // In the Android project, there's a custom renderer set up for this type
- }
-
- [Preserve (AllMembers = true)]
- [Issue (IssueTracker.Bugzilla, 35738, "ButtonRenderer UpdateTextColor function crash", PlatformAffected.Android)]
- public class Bugzilla35738 : TestContentPage
- {
- protected override void Init ()
- {
- var label = new Label () { Text = "If you can see the button, this test has passed" };
- var customButton = new CustomButton () { Text = "This is a custom button", TextColor = Color.Fuchsia };
-
- Content = new StackLayout () {
- Children = { label, customButton }
- };
- }
-
-#if UITEST
- [Test]
- [UiTest (typeof(TestContentPage))]
- public void CallingOnElementChangedOnCustomButtonShouldNotCrash ()
- {
- RunningApp.WaitForElement (q => q.Marked ("This is a custom button"));
- }
-#endif
- }
-}
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla51173.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla51173.cs
index 1e4c5e5bad0..3940eb73590 100644
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla51173.cs
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla51173.cs
@@ -128,7 +128,7 @@ protected override void Init()
var instructions = new Label
{
Text =
- "Pressing the 'Valid Image' button should display an image of a coffee cup. Every other button should cause the messager 'Error logged' to appear at the top of the page."
+ "Pressing the 'Valid Image' button should display an image of a coffee cup. Every other button should cause the message 'Error logged' to appear at the top of the page."
};
_image = new Image { BackgroundColor = Color.Bisque, HeightRequest = 20 };
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue2674.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue2674.cs
new file mode 100644
index 00000000000..fa42b75b855
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue2674.cs
@@ -0,0 +1,46 @@
+using System.Collections.Generic;
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+
+#if UITEST
+using Xamarin.Forms.Core.UITests;
+using Xamarin.UITest;
+using NUnit.Framework;
+#endif
+
+namespace Xamarin.Forms.Controls.Issues
+{
+#if UITEST
+ [Category(UITestCategories.Picker)]
+#endif
+ [Preserve(AllMembers = true)]
+ [Issue(IssueTracker.Github, 2674, "Exception occurs when giving null values in picker itemsource collection", PlatformAffected.All)]
+ public class Issue2674 : TestContentPage
+ {
+ protected override void Init()
+ {
+ var _picker = new Picker()
+ {
+ ItemsSource = new List { "cat", null, "rabbit" },
+ AutomationId = "picker",
+ };
+
+ Content = new StackLayout()
+ {
+ Children =
+ {
+ _picker
+ }
+ };
+ }
+
+#if UITEST
+ [Test]
+ public void Issue2674Test()
+ {
+ RunningApp.Screenshot("I am at Issue2674");
+ RunningApp.WaitForElement("picker");
+ }
+#endif
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue4459.xaml b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue4459.xaml
new file mode 100644
index 00000000000..8003110e77d
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue4459.xaml
@@ -0,0 +1,40 @@
+
+
+
+
+ Enter a corner radius for each corner and the box view below should update.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue4459.xaml.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue4459.xaml.cs
new file mode 100644
index 00000000000..348b60b55b5
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue4459.xaml.cs
@@ -0,0 +1,29 @@
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+using Xamarin.Forms.Xaml;
+
+namespace Xamarin.Forms.Controls.Issues
+{
+ [Preserve(AllMembers = true)]
+ [Issue(IssueTracker.Github, 4459, "[UWP] BoxView CornerRadius doesn't work", PlatformAffected.UWP)]
+#if APP
+ [XamlCompilation(XamlCompilationOptions.Compile)]
+#endif
+ public partial class Issue4459 : ContentPage
+ {
+ public Issue4459()
+ {
+#if APP
+ InitializeComponent();
+#endif
+ }
+
+ void InputView_OnTextChanged(object sender, TextChangedEventArgs e)
+ {
+#if APP
+ BoxView.CornerRadius = new CornerRadius(double.Parse(TopLeft.Text), double.Parse(TopRight.Text),
+ double.Parse(BottomLeft.Text), double.Parse(BottomRight.Text));
+#endif
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue5150.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue5150.cs
new file mode 100644
index 00000000000..2d9ec61729e
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue5150.cs
@@ -0,0 +1,148 @@
+using System.ComponentModel;
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+
+#if UITEST
+using CategoryAttribute = NUnit.Framework.CategoryAttribute;
+using Xamarin.Forms.Core.UITests;
+using Xamarin.UITest;
+using NUnit.Framework;
+#endif
+
+namespace Xamarin.Forms.Controls.Issues
+{
+
+ public static class ContentDescriptionEffectProperties
+ {
+ public static readonly BindableProperty ContentDescriptionProperty = BindableProperty.CreateAttached(
+ "ContentDescription",
+ typeof(string),
+ typeof(ContentDescriptionEffectProperties),
+ null);
+
+ public static readonly BindableProperty NameAndHelpTextProperty = BindableProperty.CreateAttached(
+ "NameAndHelpText",
+ typeof(string),
+ typeof(ContentDescriptionEffectProperties),
+ null);
+
+ public static string GetContentDescription(BindableObject view)
+ {
+ return (string)view.GetValue(ContentDescriptionProperty);
+ }
+
+ public static string GetNameAndHelpText(BindableObject view)
+ {
+ return (string)view.GetValue(NameAndHelpTextProperty);
+ }
+ }
+
+ public class ContentDescriptionEffect : RoutingEffect
+ {
+ public const string EffectName = "ContentDescriptionEffect";
+
+ public ContentDescriptionEffect() : base($"{Effects.ResolutionGroupName}.{EffectName}")
+ {
+ }
+ }
+
+ [Preserve(AllMembers = true)]
+ [Issue(IssueTracker.Github, 5150, "AutomationProperties.Name, AutomationProperties.HelpText on Button not read by Android TalkBack", PlatformAffected.Android)]
+ public class Issue5150 : TestContentPage // or TestMasterDetailPage, etc ...
+ {
+ void AddView(StackLayout layout, View view, string labelPrefix, string automationId, string buttonName = null, string buttonHelp = null)
+ {
+ var automationIdLabel = new Label();
+ automationIdLabel.Text = $"AutomationId = {automationId}";
+ automationIdLabel.AutomationId = $"{labelPrefix}-automationIdLabel";
+
+ var contentDescriptionLabel = new Label();
+ contentDescriptionLabel.AutomationId = $"{labelPrefix}-contentDescriptionLabel";
+
+ var nameAndHelpTextLabel = new Label();
+ nameAndHelpTextLabel.AutomationId = $"{labelPrefix}-nameAndHelpTextLabel";
+
+ view.AutomationId = automationId;
+ view.Effects.Add(new ContentDescriptionEffect());
+ view.PropertyChanged += (object sender, PropertyChangedEventArgs e) => {
+ if (e.PropertyName == ContentDescriptionEffectProperties.ContentDescriptionProperty.PropertyName)
+ {
+ contentDescriptionLabel.Text = $"ContentDescription = {ContentDescriptionEffectProperties.GetContentDescription(view)}";
+ }
+
+ if (e.PropertyName == ContentDescriptionEffectProperties.NameAndHelpTextProperty.PropertyName)
+ {
+ nameAndHelpTextLabel.Text = $"Name + HelpText = {ContentDescriptionEffectProperties.GetNameAndHelpText(view)}";
+ }
+ };
+ layout.Children.Add(view);
+ layout.Children.Add(automationIdLabel);
+ layout.Children.Add(contentDescriptionLabel);
+ layout.Children.Add(nameAndHelpTextLabel);
+
+ AutomationProperties.SetIsInAccessibleTree(view, true);
+ AutomationProperties.SetName(view, buttonName);
+ AutomationProperties.SetHelpText(view, buttonHelp);
+ }
+
+ void AddButton(StackLayout layout, string labelPrefix, string automationId, string buttonText, string buttonName = null, string buttonHelp = null)
+ {
+ var button = new Button();
+ button.Text = buttonText;
+ AddView(layout, button, labelPrefix, automationId, buttonName, buttonHelp);
+ }
+
+ protected override void Init()
+ {
+ var scrollView = new ScrollView();
+ var layout = new StackLayout();
+ scrollView.Content = layout;
+
+ layout.Children.Add(new Label
+ {
+ Text = "On the Android platform, the 'Name + Help Text' " +
+ "labels below each button should match the text read by " +
+ "TalkBack without interferring with the AutomationId and " +
+ "ContentDescription."
+ });
+
+ AddButton(layout, "button1prop", "button1", "Button 1", "Name 1");
+ AddButton(layout, "button2prop", "button2", "Button 2", buttonHelp: "Help 2.");
+ AddButton(layout, "button3prop", "button3", "Button 3", "Name 3", "Help 3.");
+ AddButton(layout, "button4prop", "button4", null , buttonHelp: "Help 4.");
+
+ AddView(layout, new Switch(), "switch1prop", "switch1", "Switch 1 Name", "Switch Help 1.");
+
+ var image = new Image();
+ image.Source = ImageSource.FromFile("coffee.png");
+ AddView(layout, image, "image1prop", "image1", "Coffee", "Get some coffee!");
+
+ Content = scrollView;
+ }
+
+#if UITEST
+ void Verify(string labelPrefix, string automationId, string expectedNameAndHelpText)
+ {
+ RunningApp.ScrollTo(automationId);
+ RunningApp.WaitForElement(q => q.Marked(automationId));
+ RunningApp.ScrollTo($"{labelPrefix}-nameAndHelpTextLabel");
+ RunningApp.WaitForElement(q => q.Text($"Name + HelpText = {expectedNameAndHelpText}"));
+ }
+
+ [Test]
+ [Category(UITestCategories.Button)]
+#if !__ANDROID__
+ [Ignore("This test verifies ContentDescription is set on the Android platform.")]
+#endif
+ public void Issue5150Test()
+ {
+ Verify("button1prop", "button1", "Name 1");
+ Verify("button2prop", "button2", "Button 2. Help 2.");
+ Verify("button3prop", "button3", "Name 3. Help 3.");
+ Verify("button4prop", "button4", "Help 4.");
+ Verify("switch1prop", "switch1", "Switch 1 Name. Switch Help 1.");
+ Verify("image1prop", "image1", "Coffee. Get some coffee!");
+ }
+#endif
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6932.xaml b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6932.xaml
new file mode 100644
index 00000000000..e4ab6d3367f
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6932.xaml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6932.xaml.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6932.xaml.cs
new file mode 100644
index 00000000000..90419eda8e9
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6932.xaml.cs
@@ -0,0 +1,118 @@
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Windows.Input;
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+
+#if UITEST
+using Xamarin.UITest;
+using NUnit.Framework;
+using Xamarin.Forms.Core.UITests;
+#endif
+
+namespace Xamarin.Forms.Controls.Issues
+{
+#if UITEST
+ [Category(UITestCategories.Layout)]
+#endif
+ [Preserve(AllMembers = true)]
+ [Issue(IssueTracker.Github, 6932, "EmptyView for BindableLayout (view)", PlatformAffected.All)]
+ public partial class Issue6932 : TestContentPage
+ {
+ readonly Page6932ViewModel _viewModel = new Page6932ViewModel();
+
+ public Issue6932()
+ {
+#if APP
+ InitializeComponent();
+ BindingContext = _viewModel;
+#endif
+ }
+
+ protected override void Init()
+ {
+
+ }
+
+#if UITEST
+ [Test]
+ public void EmptyViewBecomesVisibleWhenItemsSourceIsCleared()
+ {
+ RunningApp.Screenshot("Screen opens, items are shown");
+
+ RunningApp.WaitForElement(_viewModel.LayoutAutomationId);
+ RunningApp.Tap(_viewModel.ClearAutomationId);
+ RunningApp.WaitForElement(_viewModel.EmptyViewAutomationId);
+
+ RunningApp.Screenshot("Empty view is visible");
+ }
+
+ [Test]
+ public void EmptyViewBecomesVisibleWhenItemsSourceIsEmptiedOneByOne()
+ {
+ RunningApp.Screenshot("Screen opens, items are shown");
+
+ RunningApp.WaitForElement(_viewModel.LayoutAutomationId);
+
+ for (var i = 0; i < _viewModel.ItemsSource.Count; i++)
+ RunningApp.Tap(_viewModel.RemoveAutomationId);
+
+ RunningApp.WaitForElement(_viewModel.EmptyViewAutomationId);
+
+ RunningApp.Screenshot("Empty view is visible");
+ }
+
+ [Test]
+ public void EmptyViewHidesWhenItemsSourceIsFilled()
+ {
+ RunningApp.Screenshot("Screen opens, items are shown");
+
+ RunningApp.WaitForElement(_viewModel.LayoutAutomationId);
+ RunningApp.Tap(_viewModel.ClearAutomationId);
+ RunningApp.WaitForElement(_viewModel.EmptyViewAutomationId);
+
+ RunningApp.Screenshot("Items are cleared, empty view visible");
+
+ RunningApp.Tap(_viewModel.AddAutomationId);
+ RunningApp.WaitForNoElement(_viewModel.EmptyViewAutomationId);
+
+ RunningApp.Screenshot("Item is added, empty view is not visible");
+ }
+#endif
+ }
+
+ [Preserve(AllMembers = true)]
+ public class Page6932ViewModel
+ {
+ public string LayoutAutomationId { get => "StackLayoutThing"; }
+ public string AddAutomationId { get => "AddButton"; }
+ public string RemoveAutomationId { get => "RemoveButton"; }
+ public string ClearAutomationId { get => "ClearButton"; }
+ public string EmptyViewAutomationId { get => "EmptyViewId"; }
+ public string EmptyTemplateAutomationId { get => "EmptyTemplateId"; }
+ public string EmptyViewStringDescription { get => "Nothing to see here"; }
+
+ public ObservableCollection ItemsSource { get; set; }
+ public ICommand AddItemCommand { get; }
+ public ICommand RemoveItemCommand { get; }
+ public ICommand ClearCommand { get; }
+
+ public Page6932ViewModel()
+ {
+ ItemsSource = new ObservableCollection(Enumerable.Range(0, 10).Cast().ToList());
+
+ int i = ItemsSource.Count;
+ AddItemCommand = new Command(() => ItemsSource.Add(i++));
+ RemoveItemCommand = new Command(() =>
+ {
+ if (ItemsSource.Count > 0)
+ ItemsSource.RemoveAt(0);
+ });
+
+ ClearCommand = new Command(() =>
+ {
+ ItemsSource.Clear();
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6932_emptyviewstring.xaml b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6932_emptyviewstring.xaml
new file mode 100644
index 00000000000..27fdc84a7d2
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6932_emptyviewstring.xaml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6932_emptyviewstring.xaml.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6932_emptyviewstring.xaml.cs
new file mode 100644
index 00000000000..37ac3ca164f
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6932_emptyviewstring.xaml.cs
@@ -0,0 +1,82 @@
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+
+#if UITEST
+using Xamarin.UITest;
+using NUnit.Framework;
+using Xamarin.Forms.Core.UITests;
+#endif
+
+namespace Xamarin.Forms.Controls.Issues
+{
+#if UITEST
+ [Category(UITestCategories.Layout)]
+#endif
+ [Preserve(AllMembers = true)]
+ [Issue(IssueTracker.Github, 6932, "EmptyView for BindableLayout (string)", PlatformAffected.All, issueTestNumber: 2)]
+ public partial class Issue6932_emptyviewstring : TestContentPage
+ {
+ readonly Page6932ViewModel _viewModel = new Page6932ViewModel();
+
+ public Issue6932_emptyviewstring()
+ {
+#if APP
+ InitializeComponent();
+ BindingContext = _viewModel;
+
+ BindableLayout.SetEmptyView(TheStack, _viewModel.EmptyViewStringDescription);
+#endif
+ }
+
+ protected override void Init()
+ {
+
+ }
+
+#if UITEST
+ [Test]
+ public void EmptyViewStringBecomesVisibleWhenItemsSourceIsCleared()
+ {
+ RunningApp.Screenshot("Screen opens, items are shown");
+
+ RunningApp.WaitForElement(_viewModel.LayoutAutomationId);
+ RunningApp.Tap(_viewModel.ClearAutomationId);
+ RunningApp.WaitForElement(_viewModel.EmptyViewStringDescription);
+
+ RunningApp.Screenshot("Empty view is visible");
+ }
+
+ [Test]
+ public void EmptyViewStringBecomesVisibleWhenItemsSourceIsEmptiedOneByOne()
+ {
+ RunningApp.Screenshot("Screen opens, items are shown");
+
+ RunningApp.WaitForElement(_viewModel.LayoutAutomationId);
+
+ for (var i = 0; i < _viewModel.ItemsSource.Count; i++)
+ RunningApp.Tap(_viewModel.RemoveAutomationId);
+
+ RunningApp.WaitForElement(_viewModel.EmptyViewStringDescription);
+
+ RunningApp.Screenshot("Empty view is visible");
+ }
+
+ [Test]
+ public void EmptyViewStringHidesWhenItemsSourceIsFilled()
+ {
+ RunningApp.Screenshot("Screen opens, items are shown");
+
+ RunningApp.WaitForElement(_viewModel.LayoutAutomationId);
+ RunningApp.Tap(_viewModel.ClearAutomationId);
+ RunningApp.WaitForElement(_viewModel.EmptyViewStringDescription);
+
+ RunningApp.Screenshot("Items are cleared, empty view visible");
+
+ RunningApp.Tap(_viewModel.AddAutomationId);
+ RunningApp.WaitForNoElement(_viewModel.EmptyViewStringDescription);
+
+ RunningApp.Screenshot("Item is added, empty view is not visible");
+ }
+#endif
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6932_emptyviewtemplate.xaml b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6932_emptyviewtemplate.xaml
new file mode 100644
index 00000000000..237df9c9c5e
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6932_emptyviewtemplate.xaml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6932_emptyviewtemplate.xaml.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6932_emptyviewtemplate.xaml.cs
new file mode 100644
index 00000000000..e3a01e790ef
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6932_emptyviewtemplate.xaml.cs
@@ -0,0 +1,81 @@
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+
+#if UITEST
+using Xamarin.UITest;
+using NUnit.Framework;
+using Xamarin.Forms.Core.UITests;
+#endif
+
+namespace Xamarin.Forms.Controls.Issues
+{
+#if UITEST
+ [Category(UITestCategories.Layout)]
+#endif
+ [Preserve(AllMembers = true)]
+ [Issue(IssueTracker.Github, 6932, "EmptyView for BindableLayout (template)", PlatformAffected.All, issueTestNumber: 1)]
+ public partial class Issue6932_emptyviewtemplate : TestContentPage
+ {
+ readonly Page6932ViewModel _viewModel = new Page6932ViewModel();
+
+ public Issue6932_emptyviewtemplate()
+ {
+#if APP
+
+ InitializeComponent();
+ BindingContext = _viewModel;
+#endif
+ }
+
+ protected override void Init()
+ {
+
+ }
+
+#if UITEST
+ [Test]
+ public void EmptyViewTemplateBecomesVisibleWhenItemsSourceIsCleared()
+ {
+ RunningApp.Screenshot("Screen opens, items are shown");
+
+ RunningApp.WaitForElement(_viewModel.LayoutAutomationId);
+ RunningApp.Tap(_viewModel.ClearAutomationId);
+ RunningApp.WaitForElement(_viewModel.EmptyTemplateAutomationId);
+
+ RunningApp.Screenshot("Empty view is visible");
+ }
+
+ [Test]
+ public void EmptyViewTemplateBecomesVisibleWhenItemsSourceIsEmptiedOneByOne()
+ {
+ RunningApp.Screenshot("Screen opens, items are shown");
+
+ RunningApp.WaitForElement(_viewModel.LayoutAutomationId);
+
+ for (var i = 0; i < _viewModel.ItemsSource.Count; i++)
+ RunningApp.Tap(_viewModel.RemoveAutomationId);
+
+ RunningApp.WaitForElement(_viewModel.EmptyTemplateAutomationId);
+
+ RunningApp.Screenshot("Empty view is visible");
+ }
+
+ [Test]
+ public void EmptyViewTemplateHidesWhenItemsSourceIsFilled()
+ {
+ RunningApp.Screenshot("Screen opens, items are shown");
+
+ RunningApp.WaitForElement(_viewModel.LayoutAutomationId);
+ RunningApp.Tap(_viewModel.ClearAutomationId);
+ RunningApp.WaitForElement(_viewModel.EmptyTemplateAutomationId);
+
+ RunningApp.Screenshot("Items are cleared, empty view visible");
+
+ RunningApp.Tap(_viewModel.AddAutomationId);
+ RunningApp.WaitForNoElement(_viewModel.EmptyTemplateAutomationId);
+
+ RunningApp.Screenshot("Item is added, empty view is not visible");
+ }
+#endif
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8004.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8004.cs
index 862ec4a7284..9681827ddd9 100644
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8004.cs
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8004.cs
@@ -13,6 +13,7 @@ namespace Xamarin.Forms.Controls.Issues
{
#if UITEST
[Category(UITestCategories.Animation)]
+ [Category(UITestCategories.ManualReview)]
#endif
[Preserve(AllMembers = true)]
[Issue(IssueTracker.Github, 8004, "Add a ScaleXTo and ScaleYTo animation extension method", PlatformAffected.All)]
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8269.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8269.cs
deleted file mode 100644
index 00788ce230c..00000000000
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8269.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using Xamarin.Forms.CustomAttributes;
-using Xamarin.Forms.Internals;
-
-#if UITEST
-using Xamarin.Forms.Core.UITests;
-using Xamarin.UITest;
-using NUnit.Framework;
-#endif
-
-namespace Xamarin.Forms.Controls.Issues
-{
-#if UITEST
- [Category(UITestCategories.CollectionView)]
-#endif
- [Preserve(AllMembers = true)]
- [Issue(IssueTracker.Github, 8269, "[Bug] CollectionView exception when IsGrouped=true and null ItemSource",
- PlatformAffected.Android)]
- public class Issue8269 : TestContentPage
- {
- const string Success = "Success";
-
- protected override void Init()
- {
- var layout = new StackLayout();
-
- var instructions = new Label { Text = "If this page has not crashed, the test is sucessful." };
- var success = new Label { AutomationId = Success, Text = Success };
-
- var cv = new CollectionView { ItemsSource = null, IsGrouped = true };
-
- layout.Children.Add(success);
- layout.Children.Add(instructions);
- layout.Children.Add(cv);
-
- Content = layout;
- }
-
-#if UITEST
- [Test]
- public void IsGroupedWithNullItemsSourceShouldNotCrash()
- {
- RunningApp.WaitForElement(Success);
- }
-#endif
- }
-}
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8682.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8682.cs
deleted file mode 100644
index 50039feae34..00000000000
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8682.cs
+++ /dev/null
@@ -1,81 +0,0 @@
-using System;
-using System.Threading.Tasks;
-using Xamarin.Forms.CustomAttributes;
-using Xamarin.Forms.Internals;
-
-#if UITEST
-using Xamarin.Forms.Core.UITests;
-using Xamarin.UITest;
-using NUnit.Framework;
-#endif
-
-namespace Xamarin.Forms.Controls.Issues
-{
-#if UITEST
- [Category(UITestCategories.ManualReview)]
-#endif
- [Preserve(AllMembers = true)]
- [Issue(IssueTracker.Github, 8682, "[Bug] [UWP] NullReferenceException when call SavePropertiesAsync method off the main thread", PlatformAffected.UWP)]
- public class Issue8682 : TestContentPage
- {
- const string Run = "Save properties";
- const string Success = "Success";
-
- Label _result;
-
- protected override void Init()
- {
- var instructions = new Label
- {
- Text = $"Tap the button marked {Run}. If the Label below reads {Success} then the test has passed."
- };
-
- _result = new Label();
-
- var testButton = new Button
- {
- Text = Run
- };
- testButton.Clicked += OnTestButtonClicked;
-
- var layout = new StackLayout();
- layout.Children.Add(instructions);
- layout.Children.Add(_result);
- layout.Children.Add(testButton);
-
- Content = layout;
- }
-
- async void OnTestButtonClicked(object sender, EventArgs e)
- {
- _result.Text = await SavePropertiesAsyncOffMainThread();
- }
-
- async Task SavePropertiesAsyncOffMainThread()
- {
- return await Task.Run(async () =>
- {
- try
- {
- await Application.Current.SavePropertiesAsync();
-
- return Success;
- }
- catch (Exception e)
- {
- return $"Test failed: {e.Message}.";
- }
- });
- }
-
-#if UITEST
- [Test]
- public void SavePropertiesAsyncOffMainThreadDoesNotCrash()
- {
- RunningApp.WaitForElement(Run);
- RunningApp.Tap(Run);
- RunningApp.WaitForElement(Success);
- }
-#endif
- }
-}
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue9143.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue9143.cs
new file mode 100644
index 00000000000..418ca4f869b
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue9143.cs
@@ -0,0 +1,84 @@
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+using Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
+
+#if UITEST
+using Xamarin.Forms.Core.UITests;
+using Xamarin.UITest;
+using NUnit.Framework;
+#endif
+
+namespace Xamarin.Forms.Controls.Issues
+{
+#if UITEST
+ [Category(UITestCategories.MasterDetailPage)]
+#endif
+ [Preserve(AllMembers = true)]
+ [Issue(IssueTracker.Github, 9143, "[Android] Fix BottomNavigationItemView issue with MasterDetailPage", PlatformAffected.Android)]
+ public class Issue9143 : TestMasterDetailPage
+ {
+ protected override void Init()
+ {
+ Title = "Issue 9143";
+
+ DoNavigation();
+ }
+
+ void DoNavigation()
+ {
+ var tab1 = GetNavigationPage("One");
+ var tab2 = GetNavigationPage("Two");
+ var tab3 = GetNavigationPage("Three");
+ var tab4 = GetNavigationPage("Four");
+ var tab5 = GetNavigationPage("Five");
+
+ var menuNavigationPage = GetNavigationPage("Menu");
+ CreateAndPushPageForNavigationPage(menuNavigationPage);
+
+ var tabbedPage = new TabbedPage();
+ tabbedPage.Children.Add(tab1);
+ tabbedPage.Children.Add(tab2);
+ tabbedPage.Children.Add(tab3);
+ tabbedPage.Children.Add(tab4);
+ tabbedPage.Children.Add(tab5);
+
+ tabbedPage.Title = "tabbed";
+
+ tabbedPage.On().SetToolbarPlacement(ToolbarPlacement.Bottom);
+ tabbedPage.On().SetIsSwipePagingEnabled(false);
+ tabbedPage.On().SetIsSmoothScrollEnabled(false);
+ tabbedPage.On().SetOffscreenPageLimit(4);
+
+ CreateAndPushPageForNavigationPage(tab1);
+ CreateAndPushPageForNavigationPage(tab2);
+ CreateAndPushPageForNavigationPage(tab3);
+ CreateAndPushPageForNavigationPage(tab4);
+ CreateAndPushPageForNavigationPage(tab5);
+
+ Master = menuNavigationPage;
+ Detail = tabbedPage;
+ }
+
+ NavigationPage GetNavigationPage(string title)
+ {
+ var navigationPage = new NavigationPage
+ {
+ Title = title
+ };
+ return navigationPage;
+ }
+
+ void CreateAndPushPageForNavigationPage(NavigationPage navigationPage)
+ {
+ var aPage = new ContentPage
+ {
+ Title = navigationPage.Title,
+ Content = new Label
+ {
+ Text = navigationPage.Title
+ }
+ };
+ navigationPage.PushAsync(aPage).Wait();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue968.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue968.cs
index 5a78900815e..fb3a92cbbe2 100644
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue968.cs
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue968.cs
@@ -3,12 +3,16 @@
using Xamarin.Forms.Internals;
#if UITEST
+using Xamarin.Forms.Core.UITests;
using NUnit.Framework;
using Xamarin.UITest;
#endif
namespace Xamarin.Forms.Controls.Issues
{
+#if UITEST
+ [Category(UITestCategories.ManualReview)]
+#endif
[Preserve (AllMembers = true)]
[Issue (IssueTracker.Github, 968, "StackLayout does not relayout on device rotation", PlatformAffected.iOS, NavigationBehavior.PushModalAsync)]
public class Issue968 : TestContentPage
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems
index cea9c408687..338f763a1a6 100644
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems
@@ -10,10 +10,12 @@
+
Issue3228.xaml
Code
+
@@ -26,6 +28,10 @@
+
+ Issue4459.xaml
+ Code
+
Issue1455.xaml
@@ -155,7 +161,6 @@
-
@@ -205,7 +210,6 @@
Github5623.xaml
Code
-
@@ -350,7 +354,6 @@
-
@@ -1176,6 +1179,7 @@
+
@@ -1236,6 +1240,18 @@
+
+ Code
+ Issue6932.xaml
+
+
+ Code
+ Issue6932_emptyviewtemplate.xaml
+
+
+ Code
+ Issue6932_emptyviewstring.xaml
+
@@ -1258,7 +1274,8 @@
-
+
+
@@ -1399,6 +1416,9 @@
MSBuild:UpdateDesignTimeXaml
+
+
+
@@ -1719,6 +1739,12 @@
MSBuild:UpdateDesignTimeXaml
+
+
+ Designer
+ MSBuild:UpdateDesignTimeXaml
+
+
Designer
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/issue3262.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/issue3262.cs
new file mode 100644
index 00000000000..c78cf9b9c3e
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/issue3262.cs
@@ -0,0 +1,103 @@
+using System;
+using System.Net;
+using Xamarin.Forms.Internals;
+using Xamarin.Forms.CustomAttributes;
+
+#if UITEST
+using Xamarin.Forms.Core.UITests;
+using Xamarin.UITest;
+using NUnit.Framework;
+#endif
+
+namespace Xamarin.Forms.Controls.Issues
+{
+#if UITEST
+ [Category(UITestCategories.ManualReview)]
+#endif
+ [Preserve(AllMembers = true)]
+ [Issue(IssueTracker.Github, 3262, "Adding Cookies ability to a WebView...")]
+ public class Issue3262 : TestContentPage // or TestMasterDetailPage, etc ...
+ {
+ protected override void Init()
+ {
+ Label header = new Label
+ {
+ Text = "Check that a WebView can use Cookies...",
+ FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
+ HorizontalOptions = LayoutOptions.Center
+ };
+
+ try
+ {
+ CookieContainer cookieContainer = new CookieContainer();
+ string url = "https://dotnet.microsoft.com/apps/xamarin";
+ Uri uri = new Uri(url, UriKind.RelativeOrAbsolute);
+
+ Cookie cookie = new Cookie
+ {
+ Name = "TestCookie",
+ Expires = DateTime.Now.AddDays(1),
+ Value = "My Test Cookie...",
+ Domain = uri.Host,
+ Path = "/"
+ };
+
+ cookieContainer.Add(uri, cookie);
+
+ WebView webView = new WebView
+ {
+ Source = url,
+ HorizontalOptions = LayoutOptions.FillAndExpand,
+ VerticalOptions = LayoutOptions.FillAndExpand,
+ Cookies = cookieContainer
+ };
+
+
+
+ Content = new StackLayout
+ {
+ Padding = new Thickness(20),
+ Children =
+ {
+ header,
+ webView,
+ new Button()
+ {
+ Text = "Display all Cookies. You should see a cookie called 'TestCookie'",
+ AutomationId = "DisplayAllCookies",
+ Command = new Command(async () =>
+ {
+ await webView.EvaluateJavaScriptAsync("alert(document.cookie);");
+ })
+ },
+ new Button()
+ {
+ Text = "Load page without cookies and app shouldn't crash",
+ AutomationId = "PageWithoutCookies",
+ Command = new Command(() =>
+ {
+ webView.Cookies = null;
+ webView.Source = "file:///android_asset/googlemapsearch.html";
+ })
+ }
+ }
+ };
+ }
+ catch (Exception ex)
+ {
+ _ = ex.Message;
+ throw;
+ }
+ }
+
+#if UITEST
+ [Test]
+ public void LoadingPageWithoutCookiesSpecifiedDoesntCrash()
+ {
+ RunningApp.Tap("PageWithoutCookies");
+ RunningApp.WaitForElement("PageWithoutCookies");
+ }
+#endif
+
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls/App.cs b/Xamarin.Forms.Controls/App.cs
index fd833b80748..83daadcb6b7 100644
--- a/Xamarin.Forms.Controls/App.cs
+++ b/Xamarin.Forms.Controls/App.cs
@@ -246,5 +246,10 @@ public void Reset()
{
SetMainPage(CreateDefaultMainPage());
}
+
+ public void PlatformTest()
+ {
+ SetMainPage(new GalleryPages.PlatformTestsGallery.PlatformTestsConsole());
+ }
}
}
diff --git a/Xamarin.Forms.Controls/ControlGalleryPages/ToolbarItems.cs b/Xamarin.Forms.Controls/ControlGalleryPages/ToolbarItems.cs
index 317265e732d..fae47bf0ee8 100644
--- a/Xamarin.Forms.Controls/ControlGalleryPages/ToolbarItems.cs
+++ b/Xamarin.Forms.Controls/ControlGalleryPages/ToolbarItems.cs
@@ -23,10 +23,18 @@ public ToolbarItems()
tb1.IsEnabled = _isEnable;
tb1.AutomationId = "toolbaritem_primary";
- var tb2 = new ToolbarItem("tb2", null, () =>
+ var fis = new FontImageSource()
+ {
+ FontFamily = GetFontFamily(),
+ Glyph = '\uf101'.ToString(),
+ Color = Color.Red
+ };
+
+ var tb2 = new ToolbarItem("tb2 font", null, () =>
{
label.Text = "tb2";
}, ToolbarItemOrder.Primary);
+ tb2.IconImageSource = fis;
tb2.AutomationId = "toolbaritem_primary2";
var tb6 = new ToolbarItem("tb6 long long text", null, () =>
{
@@ -72,6 +80,31 @@ public ToolbarItems()
}
};
}
+
+ static string GetFontFamily()
+ {
+ var fontFamily = "";
+ switch (Device.RuntimePlatform)
+ {
+ case Device.macOS:
+ case Device.iOS:
+ fontFamily = "Ionicons";
+ break;
+ case Device.UWP:
+ fontFamily = "Assets/Fonts/ionicons.ttf#ionicons";
+ break;
+ case Device.WPF:
+ case Device.GTK:
+ fontFamily = "Assets/ionicons.ttf#ionicons";
+ break;
+ case Device.Android:
+ default:
+ fontFamily = "fonts/ionicons.ttf#";
+ break;
+ }
+
+ return fontFamily;
+ }
}
}
diff --git a/Xamarin.Forms.Controls/CoreGallery.cs b/Xamarin.Forms.Controls/CoreGallery.cs
index 3d22d424ca2..1cba1b42c85 100644
--- a/Xamarin.Forms.Controls/CoreGallery.cs
+++ b/Xamarin.Forms.Controls/CoreGallery.cs
@@ -218,6 +218,7 @@ public class CoreRootView : ListView
public CoreRootView()
{
var roots = new[] {
+ new CoreViewContainer ("SwapRoot - Tests", typeof(PlatformTestsConsole)),
new CoreViewContainer ("SwapRoot - CarouselPage", typeof(CoreCarouselPage)),
new CoreViewContainer ("SwapRoot - ContentPage", typeof(CoreContentPage)),
new CoreViewContainer ("SwapRoot - MasterDetailPage", typeof(CoreMasterDetailPage)),
@@ -291,6 +292,8 @@ public override string ToString()
new GalleryPageFactory(() => new EmbeddedFonts(), "Embedded Fonts"),
new GalleryPageFactory(() => new MemoryLeakGallery(), "Memory Leak"),
new GalleryPageFactory(() => new Issues.A11yTabIndex(), "Accessibility TabIndex"),
+ new GalleryPageFactory(() => new RadioButtonGroupGalleryPage(), "RadioButton group Gallery - Legacy"),
+ new GalleryPageFactory(() => new RadioButtonCoreGalleryPage(), "RadioButton Gallery"),
new GalleryPageFactory(() => new FontImageSourceGallery(), "Font ImageSource"),
new GalleryPageFactory(() => new IndicatorsSample(), "Indicator Gallery"),
new GalleryPageFactory(() => new CarouselViewGallery(), "CarouselView Gallery"),
diff --git a/Xamarin.Forms.Controls/CoreGalleryPages/RadioButtonCoreGalleryPage.cs b/Xamarin.Forms.Controls/CoreGalleryPages/RadioButtonCoreGalleryPage.cs
new file mode 100644
index 00000000000..3841effea19
--- /dev/null
+++ b/Xamarin.Forms.Controls/CoreGalleryPages/RadioButtonCoreGalleryPage.cs
@@ -0,0 +1,140 @@
+using Xamarin.Forms.CustomAttributes;
+
+namespace Xamarin.Forms.Controls
+{
+ class RadioButtonCoreGalleryPage : CoreGalleryPage
+ {
+ protected override bool SupportsFocus => false;
+ protected override bool SupportsTapGestureRecognizer => true;
+ protected override void InitializeElement(RadioButton element)
+ {
+ element.Text = "RadioButton";
+ }
+
+ protected override void Build(StackLayout stackLayout)
+ {
+ base.Build(stackLayout);
+
+ IsEnabledStateViewContainer.View.Clicked += (sender, args) => IsEnabledStateViewContainer.TitleLabel.Text += " (Tapped)";
+
+ var borderButtonContainer = new ViewContainer(Test.Button.BorderColor,
+ new RadioButton
+ {
+ Text = "BorderColor",
+ BackgroundColor = Color.Transparent,
+ BorderColor = Color.Red,
+ BorderWidth = 1,
+ }
+ );
+
+ var borderRadiusContainer = new ViewContainer(Test.Button.BorderRadius,
+ new RadioButton
+ {
+ Text = "BorderRadius",
+ BackgroundColor = Color.Transparent,
+ BorderColor = Color.Red,
+ BorderWidth = 1,
+ }
+ );
+
+ var borderWidthContainer = new ViewContainer(Test.Button.BorderWidth,
+ new RadioButton
+ {
+ Text = "BorderWidth",
+ BackgroundColor = Color.Transparent,
+ BorderColor = Color.Red,
+ BorderWidth = 15,
+ }
+ );
+
+ var clickedContainer = new EventViewContainer(Test.Button.Clicked,
+ new RadioButton
+ {
+ Text = "Clicked"
+ }
+ );
+ clickedContainer.View.Clicked += (sender, args) => clickedContainer.EventFired();
+
+ var pressedContainer = new EventViewContainer(Test.Button.Pressed,
+ new RadioButton
+ {
+ Text = "Pressed"
+ }
+ );
+ pressedContainer.View.Pressed += (sender, args) => pressedContainer.EventFired();
+
+ var commandContainer = new ViewContainer(Test.Button.Command,
+ new RadioButton
+ {
+ Text = "Command",
+ Command = new Command(() => DisplayActionSheet("Hello Command", "Cancel", "Destroy"))
+ }
+ );
+
+ var fontContainer = new ViewContainer(Test.Button.Font,
+ new RadioButton
+ {
+ Text = "Font",
+ Font = Font.SystemFontOfSize(NamedSize.Large, FontAttributes.Bold)
+ }
+ );
+
+ var textContainer = new ViewContainer(Test.Button.Text,
+ new RadioButton
+ {
+ Text = "Text"
+ }
+ );
+
+ var textColorContainer = new ViewContainer(Test.Button.TextColor,
+ new RadioButton
+ {
+ Text = "TextColor",
+ TextColor = Color.Pink
+ }
+ );
+
+ var paddingContainer = new ViewContainer(Test.Button.Padding,
+ new RadioButton
+ {
+ Text = "Padding",
+ BackgroundColor = Color.Red,
+ Padding = new Thickness(20, 30, 60, 15)
+ }
+ );
+
+ var isCheckedContainer = new ValueViewContainer(Test.RadioButton.IsChecked, new RadioButton() { IsChecked = true, HorizontalOptions = LayoutOptions.Start }, "IsChecked", value => value.ToString());
+
+ var checkedVisualState = new VisualState { Name = "IsChecked" };
+ checkedVisualState.Setters.Add(new Setter { Property = RadioButton.ButtonSourceProperty, Value = "rb_checked" });
+
+ var group = new VisualStateGroup();
+ group.States.Add(checkedVisualState);
+
+ var normalVisualState = new VisualState{ Name = "Normal" };
+ normalVisualState.Setters.Add(new Setter { Property = RadioButton.ButtonSourceProperty, Value = "rb_unchecked" });
+ group.States.Add(normalVisualState);
+
+ var groupList = new VisualStateGroupList();
+ groupList.Add(group);
+
+ var rbStateManaged = new RadioButton() { HorizontalOptions = LayoutOptions.Start };
+ VisualStateManager.SetVisualStateGroups(rbStateManaged, groupList);
+
+ var stateManagedContainer = new ValueViewContainer(Test.RadioButton.ButtonSource, rbStateManaged, "IsChecked", value => value.ToString());
+
+ Add(borderButtonContainer);
+ Add(borderRadiusContainer);
+ Add(borderWidthContainer);
+ Add(clickedContainer);
+ Add(pressedContainer);
+ Add(commandContainer);
+ Add(fontContainer);
+ Add(textContainer);
+ Add(textColorContainer);
+ Add(paddingContainer);
+ Add(isCheckedContainer);
+ Add(stateManagedContainer);
+ }
+ }
+}
diff --git a/Xamarin.Forms.Controls/GalleryPages/BindableLayoutGalleryPage.xaml b/Xamarin.Forms.Controls/GalleryPages/BindableLayoutGalleryPage.xaml
index 81a2c487062..972c38aca90 100644
--- a/Xamarin.Forms.Controls/GalleryPages/BindableLayoutGalleryPage.xaml
+++ b/Xamarin.Forms.Controls/GalleryPages/BindableLayoutGalleryPage.xaml
@@ -6,18 +6,24 @@
-
-
+
+
+
+
+
@@ -31,8 +37,15 @@
HorizontalScrollBarVisibility="Always">
+ Padding="10, 5">
+
+
+
+
+
+
Polyline
Polygon
+ Circle
().LastOrDefault();
+
+ if (_circle == null)
+ Map.MapElements.Add(_circle = new Circle());
+
break;
}
}
@@ -129,6 +161,9 @@ void ChangeColorClicked(object sender, EventArgs e)
case SelectedElementType.Polygon:
_polygon.StrokeColor = newColor;
break;
+ case SelectedElementType.Circle:
+ _circle.StrokeColor = newColor;
+ break;
}
}
@@ -143,6 +178,9 @@ void ChangeWidthClicked(object sender, EventArgs e)
case SelectedElementType.Polygon:
_polygon.StrokeWidth = newWidth;
break;
+ case SelectedElementType.Circle:
+ _circle.StrokeWidth = newWidth;
+ break;
}
}
@@ -154,6 +192,9 @@ void ChangeFillClicked(object sender, EventArgs e)
case SelectedElementType.Polygon:
_polygon.FillColor = newColor;
break;
+ case SelectedElementType.Circle:
+ _circle.FillColor = newColor;
+ break;
}
}
}
diff --git a/Xamarin.Forms.Controls/GalleryPages/PlatformSpecificsGalleries/MasterDetailPageiOS.cs b/Xamarin.Forms.Controls/GalleryPages/PlatformSpecificsGalleries/MasterDetailPageiOS.cs
index 9e6b140d0de..015367ed1bf 100644
--- a/Xamarin.Forms.Controls/GalleryPages/PlatformSpecificsGalleries/MasterDetailPageiOS.cs
+++ b/Xamarin.Forms.Controls/GalleryPages/PlatformSpecificsGalleries/MasterDetailPageiOS.cs
@@ -18,7 +18,13 @@ public MasterDetailPageiOS(ICommand restore)
Title = "This is the detail page's Title",
Padding = new Thickness(0,20,0,0)
};
-
+
+ void ToggleApplyShadow()
+ {
+ On().SetApplyShadow(!On().GetApplyShadow());
+ IsPresented = false;
+ }
+
var navItems = new List
{
new NavItem("Display Alert", "\uE171", new Command(() => DisplayAlert("Alert", "This is an alert", "OK"))),
@@ -27,6 +33,7 @@ public MasterDetailPageiOS(ICommand restore)
new NavItem("Audio", "\uE189", new Command(() => DisplayAlert("Audio", "Never gonna give you up...", "OK"))),
new NavItem("Set Detail to Navigation Page", "\uE16F", new Command(() => Detail = CreateNavigationPage())),
new NavItem("Set Detail to Content Page", "\uE160", new Command(() => Detail = detail)),
+ new NavItem("Toggle Apply Shadow", "\u2728", new Command(ToggleApplyShadow))
};
var navList = new NavList(navItems);
diff --git a/Xamarin.Forms.Controls/GalleryPages/PlatformSpecificsGalleries/SearchBariOS.cs b/Xamarin.Forms.Controls/GalleryPages/PlatformSpecificsGalleries/SearchBariOS.cs
new file mode 100644
index 00000000000..09569f648fd
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/PlatformSpecificsGalleries/SearchBariOS.cs
@@ -0,0 +1,41 @@
+using Xamarin.Forms.PlatformConfiguration;
+using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
+
+namespace Xamarin.Forms.Controls.GalleryPages.PlatformSpecificsGalleries
+{
+ public class SearchBariOS : ContentPage
+ {
+ public SearchBariOS()
+ {
+ var prominent = new SearchBar { Placeholder = "Prominent" };
+ prominent.On().SetSearchBarStyle(UISearchBarStyle.Prominent);
+
+ var minimal = new SearchBar { Placeholder = "Minimal" };
+ minimal.On().SetSearchBarStyle(UISearchBarStyle.Minimal);
+
+ var prominentBackground = new SearchBar { Placeholder = "Prominent on colored background" };
+ prominentBackground.On().SetSearchBarStyle(UISearchBarStyle.Prominent);
+
+ var minimalBackground = new SearchBar { Placeholder = "Minimal on colored background" };
+ minimalBackground.On().SetSearchBarStyle(UISearchBarStyle.Minimal);
+
+ Content = new StackLayout
+ {
+ Children =
+ {
+ prominent,
+ minimal,
+ new StackLayout()
+ {
+ BackgroundColor = Color.Red,
+ Children =
+ {
+ prominentBackground,
+ minimalBackground
+ }
+ }
+ }
+ };
+ }
+ }
+}
diff --git a/Xamarin.Forms.Controls/GalleryPages/PlatformSpecificsGallery.cs b/Xamarin.Forms.Controls/GalleryPages/PlatformSpecificsGallery.cs
index 84a2b66fb22..d096b2f94f8 100644
--- a/Xamarin.Forms.Controls/GalleryPages/PlatformSpecificsGallery.cs
+++ b/Xamarin.Forms.Controls/GalleryPages/PlatformSpecificsGallery.cs
@@ -28,6 +28,7 @@ public PlatformSpecificsGallery()
var modalformsheetiOSButton = new Button() { Text = "Modal FormSheet (iOS)" };
var homeIndicatoriOSButton = new Button() { Text = "Home indicator (iOS)" };
var refreshWindowsButton = new Button { Text = "RefreshView (Windows)" };
+ var searchBariOSButton = new Button { Text = "SearchBar (iOS)" };
mdpiOSButton.Clicked += (sender, args) => { SetRoot(new MasterDetailPageiOS(new Command(RestoreOriginal))); };
mdpWindowsButton.Clicked += (sender, args) => { SetRoot(new MasterDetailPageWindows(new Command(RestoreOriginal))); };
@@ -45,6 +46,7 @@ public PlatformSpecificsGallery()
modalformsheetiOSButton.Clicked += async (sender, args) => { await Navigation.PushModalAsync(new ModalFormSheetPageiOS()); };
homeIndicatoriOSButton.Clicked += (sender, args) => { Navigation.PushAsync(new HomeIndicatorPageiOS(new Command(RestoreOriginal))); };
refreshWindowsButton.Clicked += (sender, args) => { Navigation.PushAsync(new RefreshViewWindows()); };
+ searchBariOSButton.Clicked += (sender, args) => { Navigation.PushAsync(new SearchBariOS()); };
Content = new ScrollView
{
@@ -52,7 +54,7 @@ public PlatformSpecificsGallery()
{
Children = { mdpiOSButton, mdpWindowsButton, npWindowsButton, tbiOSButton, tbWindowsButton, viselemiOSButton,
appAndroidButton, tbAndroidButton, entryiOSButton, entryAndroidButton, largeTitlesiOSButton, safeareaiOSButton,
- modalformsheetiOSButton, homeIndicatoriOSButton, refreshWindowsButton }
+ modalformsheetiOSButton, homeIndicatoriOSButton, refreshWindowsButton, searchBariOSButton }
}
};
}
diff --git a/Xamarin.Forms.Controls/GalleryPages/PlatformTestsGallery/CategoryFilter.cs b/Xamarin.Forms.Controls/GalleryPages/PlatformTestsGallery/CategoryFilter.cs
new file mode 100644
index 00000000000..5cba66bbcc4
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/PlatformTestsGallery/CategoryFilter.cs
@@ -0,0 +1,25 @@
+using NUnit.Framework.Interfaces;
+using NUnit.Framework.Internal;
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms.Controls.GalleryPages.PlatformTestsGallery
+{
+ [Preserve(AllMembers = true)]
+ public class CategoryFilter : TestFilter
+ {
+ string _category;
+
+ public CategoryFilter(string category) => _category = category;
+
+ public override TNode AddToXml(TNode parentNode, bool recursive)
+ {
+ TNode result = parentNode.AddElement("category", _category);
+ return result;
+ }
+
+ public override bool Match(ITest test)
+ {
+ return test.Properties[PropertyNames.Category].Contains(_category);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls/GalleryPages/PlatformTestsGallery/PlatformTestsConsole.xaml b/Xamarin.Forms.Controls/GalleryPages/PlatformTestsGallery/PlatformTestsConsole.xaml
index 055351ccb14..85d452757bb 100644
--- a/Xamarin.Forms.Controls/GalleryPages/PlatformTestsGallery/PlatformTestsConsole.xaml
+++ b/Xamarin.Forms.Controls/GalleryPages/PlatformTestsGallery/PlatformTestsConsole.xaml
@@ -3,11 +3,24 @@
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- mc:Ignorable="d"
+ mc:Ignorable="d"
+ Title="Platform Test Console"
+ xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
+ ios:Page.UseSafeArea="true"
x:Class="Xamarin.Forms.Controls.GalleryPages.PlatformTestsGallery.PlatformTestsConsole">
-
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Xamarin.Forms.Controls/GalleryPages/PlatformTestsGallery/PlatformTestsConsole.xaml.cs b/Xamarin.Forms.Controls/GalleryPages/PlatformTestsGallery/PlatformTestsConsole.xaml.cs
index ed233018cc5..8f406d2b91a 100644
--- a/Xamarin.Forms.Controls/GalleryPages/PlatformTestsGallery/PlatformTestsConsole.xaml.cs
+++ b/Xamarin.Forms.Controls/GalleryPages/PlatformTestsGallery/PlatformTestsConsole.xaml.cs
@@ -6,6 +6,8 @@
using Xamarin.Forms.Internals;
using System;
using System.Threading.Tasks;
+using System.IO;
+using System.Reflection;
namespace Xamarin.Forms.Controls.GalleryPages.PlatformTestsGallery
{
@@ -23,6 +25,9 @@ public partial class PlatformTestsConsole : ContentPage
readonly Color _inconclusiveColor = Color.Goldenrod;
int _finishedAssemblyCount = 0;
+ int _testsRunCount = 0;
+
+ readonly PlatformTestRunner _runner = new PlatformTestRunner();
public PlatformTestsConsole()
{
@@ -33,17 +38,40 @@ public PlatformTestsConsole()
MessagingCenter.Subscribe(this, "TestFinished", TestFinished);
MessagingCenter.Subscribe(this, "TestRunnerError", OutputTestRunnerError);
+
+ Rerun.Clicked += RerunClicked;
+ }
+
+ async void RerunClicked(object sender, EventArgs e)
+ {
+ await Device.InvokeOnMainThreadAsync(() => {
+ Status.Text = "Running...";
+ RunCount.Text = "";
+ Results.Children.Clear();
+ Rerun.IsEnabled = false;
+ });
+
+ await Task.Delay(50);
+
+ await Run().ConfigureAwait(false);
}
protected override async void OnAppearing()
{
base.OnAppearing();
+ await Run().ConfigureAwait(false);
+ }
+
+ async Task Run()
+ {
+ _finishedAssemblyCount = 0;
+ _testsRunCount = 0;
+
+ // Only want to run a subset of tests? Create a filter and pass it into _runner.Run()
+ // e.g. var filter = new TestNameContainsFilter("Bugzilla");
+ // or var filter = new CategoryFilter("Picker");
- // Only want to run a subset of tests? Create a filter and pass it into tests.Run()
- //var filter = new TestNameContainsFilter("Bugzilla");
-
- var tests = new PlatformTestRunner();
- await Task.Run(() => tests.Run()).ConfigureAwait(false);
+ await Task.Run(() => _runner.Run()).ConfigureAwait(false);
}
void DisplayOverallResult()
@@ -64,6 +92,10 @@ void DisplayOverallResult()
Status.Text = SuccessText;
Status.TextColor = _successColor;
}
+
+ RunCount.Text = $"{_testsRunCount} tests run";
+
+ Rerun.IsEnabled = true;
});
}
@@ -77,6 +109,8 @@ void DisplayFailResult(string failText = null)
void AssemblyFinished(ITestResult assembly)
{
+ _testsRunCount += (assembly.PassCount + assembly.FailCount + assembly.InconclusiveCount);
+
_finishedAssemblyCount += 1;
if (_finishedAssemblyCount == 2)
{
@@ -100,7 +134,8 @@ void OutputFixtureStarted(TestFixture testFixture)
{
var name = testFixture.Name;
- var label = new Label { Text = $"{name} Started", LineBreakMode = LineBreakMode.HeadTruncation };
+ var label = new Label { Text = $"{name} Started", LineBreakMode = LineBreakMode.HeadTruncation,
+ FontAttributes = FontAttributes.Bold };
Device.BeginInvokeOnMainThread(() =>
{
@@ -125,7 +160,7 @@ void TestFinished(ITestResult result)
void OutputTestCaseResult(TestCaseResult result)
{
- var name = result.Test.Name; // ShortenTestName(result.FullName);
+ var name = result.Test.Name;
var outcome = "Fail";
@@ -164,13 +199,14 @@ void OutputTestCaseResult(TestCaseResult result)
{
if (assertionResult.Status != AssertionStatus.Passed)
{
- toAdd.Add(new Label { Text = assertionResult.Message });
+ ExtractErrorMessage(toAdd, assertionResult.Message);
toAdd.Add(new Editor { Text = assertionResult.StackTrace, IsReadOnly = true });
}
}
if (!string.IsNullOrEmpty(result.Output))
{
+ var output = result.Output;
toAdd.Add(new Label { Text = result.Output, Margin = margin });
}
@@ -243,5 +279,38 @@ void OutputTestRunnerError(Exception ex)
DisplayFailResult(ex.Message);
});
}
+
+ static void ExtractErrorMessage(List views, string message)
+ {
+ const string openTag = " ";
+ const string closeTag = "";
+ var openTagIndex = message.IndexOf(" ");
+ var closeTagIndex = message.IndexOf("");
+
+ if (openTagIndex >= 0 && closeTagIndex > openTagIndex)
+ {
+ var imgString = message.Substring(openTagIndex + openTag.Length, closeTagIndex - openTagIndex - openTag.Length);
+ var messageBefore = message.Substring(0, openTagIndex);
+ var messageAfter = message.Substring(closeTagIndex + closeTag.Length);
+ var imgBytes = Convert.FromBase64String(imgString);
+ var stream = new MemoryStream(imgBytes);
+
+ if (!string.IsNullOrEmpty(messageBefore))
+ {
+ views.Add(new Label { Text = messageBefore });
+ }
+
+ views.Add(new Image { Source = ImageSource.FromStream(() => stream) });
+
+ if (!string.IsNullOrEmpty(messageAfter))
+ {
+ views.Add(new Label { Text = messageAfter });
+ }
+ }
+ else
+ {
+ views.Add(new Label { Text = message });
+ }
+ }
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls/GalleryPages/RadioButtonGroupGalleryPage.xaml b/Xamarin.Forms.Controls/GalleryPages/RadioButtonGroupGalleryPage.xaml
new file mode 100644
index 00000000000..0d6aef75983
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/RadioButtonGroupGalleryPage.xaml
@@ -0,0 +1,151 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ mono
+ monodroid
+ monotouch
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ mono
+ monodroid
+ monotouch
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls/GalleryPages/RadioButtonGroupGalleryPage.xaml.cs b/Xamarin.Forms.Controls/GalleryPages/RadioButtonGroupGalleryPage.xaml.cs
new file mode 100644
index 00000000000..8694cdfbc2c
--- /dev/null
+++ b/Xamarin.Forms.Controls/GalleryPages/RadioButtonGroupGalleryPage.xaml.cs
@@ -0,0 +1,13 @@
+using Xamarin.Forms.Xaml;
+
+namespace Xamarin.Forms.Controls.GalleryPages
+{
+ [XamlCompilation(XamlCompilationOptions.Compile)]
+ public partial class RadioButtonGroupGalleryPage : TabbedPage
+ {
+ public RadioButtonGroupGalleryPage()
+ {
+ InitializeComponent();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls/Tests/ControlGalleryTestListener.cs b/Xamarin.Forms.Controls/Tests/ControlGalleryTestListener.cs
index 3709332c1ef..638185b9c03 100644
--- a/Xamarin.Forms.Controls/Tests/ControlGalleryTestListener.cs
+++ b/Xamarin.Forms.Controls/Tests/ControlGalleryTestListener.cs
@@ -8,7 +8,6 @@ public class ControlGalleryTestListener : ITestListener
{
public void SendMessage(TestMessage message)
{
- Debug.WriteLine(message);
}
public void TestFinished(ITestResult result)
@@ -16,12 +15,10 @@ public void TestFinished(ITestResult result)
var test = result.Test;
if (test is TestAssembly testAssembly)
{
- Debug.WriteLine($"Assembly finished {testAssembly.Assembly.FullName}");
MessagingCenter.Send(result, "AssemblyFinished");
}
else
{
- Debug.WriteLine($"{result.Name} finished");
MessagingCenter.Send(result, "TestFinished");
}
}
@@ -33,7 +30,6 @@ public void TestOutput(TestOutput output)
public void TestStarted(ITest test)
{
- Debug.WriteLine($"{test.Name} started");
MessagingCenter.Send(test, "TestStarted");
}
}
diff --git a/Xamarin.Forms.Controls/Tests/CrossPlatformTests.cs b/Xamarin.Forms.Controls/Tests/CrossPlatformTests.cs
index 234aed220b6..03a2cba80b3 100644
--- a/Xamarin.Forms.Controls/Tests/CrossPlatformTests.cs
+++ b/Xamarin.Forms.Controls/Tests/CrossPlatformTests.cs
@@ -1,17 +1,31 @@
-using NUnit.Framework;
+using System;
+using System.Threading.Tasks;
+using NUnit.Framework;
namespace Xamarin.Forms.Controls.Tests
{
[TestFixture]
public class CrossPlatformTests
{
- [Test(Description = "Always Passes")]
+ ITestingPlatformService _testingPlatformService;
+ ITestingPlatformService TestingPlatform
+ {
+ get
+ {
+ return _testingPlatformService = _testingPlatformService
+ ?? DependencyService.Resolve();
+ }
+ }
+
+ [Test]
+ [Description("Always Passes")]
public void PassingCrossPlatformTest()
{
Assert.Pass();
}
- [Test(Description = "Setting ListView Header to null should not crash")]
+ [Test]
+ [Description("Setting ListView Header to null should not crash")]
public void Bugzilla28575()
{
string header = "Hello I am Header!!!!";
@@ -29,7 +43,8 @@ public void Bugzilla28575()
listview.Header = null;
}
- [Test(Description = "isPresentedChanged raises multiple times")]
+ [Test]
+ [Description("isPresentedChanged raises multiple times")]
public void Bugzilla32230()
{
var mdp = new MasterDetailPage();
@@ -43,5 +58,33 @@ public void Bugzilla32230()
mdp.IsPresented = true;
Assert.That(count, Is.EqualTo(3));
}
+
+ [Test]
+ [Description("ButtonRenderer UpdateTextColor function crash")]
+ public void Bugzilla35738()
+ {
+ var customButton = new TestClasses.CustomButton() { Text = "This is a custom button", TextColor = Color.Fuchsia };
+ TestingPlatform.CreateRenderer(customButton);
+ }
+
+ [Test]
+ [Description("[Bug] CollectionView exception when IsGrouped=true and null ItemSource")]
+ public void GitHub8269()
+ {
+ var collectionView = new CollectionView { ItemsSource = null, IsGrouped = true };
+ TestingPlatform.CreateRenderer(collectionView);
+ }
+
+ [Test]
+ [Description("[Bug] [UWP] NullReferenceException when call SavePropertiesAsync method off the main thread")]
+ public void GitHub8682()
+ {
+ Task.Run(async () =>
+ {
+ await Application.Current.SavePropertiesAsync();
+ }).Wait();
+
+ Assert.True(true);
+ }
}
}
diff --git a/Xamarin.Forms.Controls/Tests/ITestingPlatformService.cs b/Xamarin.Forms.Controls/Tests/ITestingPlatformService.cs
new file mode 100644
index 00000000000..04d578ffbc5
--- /dev/null
+++ b/Xamarin.Forms.Controls/Tests/ITestingPlatformService.cs
@@ -0,0 +1,7 @@
+namespace Xamarin.Forms.Controls.Tests
+{
+ public interface ITestingPlatformService
+ {
+ void CreateRenderer(VisualElement visualElement);
+ }
+}
diff --git a/Xamarin.Forms.Controls/Tests/TestClasses/CustomButton.cs b/Xamarin.Forms.Controls/Tests/TestClasses/CustomButton.cs
new file mode 100644
index 00000000000..cd228e036b8
--- /dev/null
+++ b/Xamarin.Forms.Controls/Tests/TestClasses/CustomButton.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Xamarin.Forms.Controls.Tests.TestClasses
+{
+ public class CustomButton : Button
+ {
+ // In the Android project, there's a custom renderer set up for this type
+ }
+}
diff --git a/Xamarin.Forms.Controls/Xamarin.Forms.Controls.csproj b/Xamarin.Forms.Controls/Xamarin.Forms.Controls.csproj
index 9c5d027f823..455f0b21c5f 100644
--- a/Xamarin.Forms.Controls/Xamarin.Forms.Controls.csproj
+++ b/Xamarin.Forms.Controls/Xamarin.Forms.Controls.csproj
@@ -60,10 +60,28 @@
+
+
+ MSBuild:UpdateDesignTimeXaml
+
+
+
+
+
+ MSBuild:UpdateDesignTimeXaml
+
+
+ MSBuild:UpdateDesignTimeXaml
+
+
+ MSBuild:UpdateDesignTimeXaml
+
+
+
diff --git a/Xamarin.Forms.Core.UITests.Shared/Tests/BoxViewUITests.cs b/Xamarin.Forms.Core.UITests.Shared/Tests/BoxViewUITests.cs
index a99363dcffd..40c68d0240f 100644
--- a/Xamarin.Forms.Core.UITests.Shared/Tests/BoxViewUITests.cs
+++ b/Xamarin.Forms.Core.UITests.Shared/Tests/BoxViewUITests.cs
@@ -49,9 +49,43 @@ protected override void FixtureTeardown()
base.FixtureTeardown();
}
- [UiTestExempt(ExemptReason.CannotTest, "Invalid interaction")]
- public override void _IsEnabled()
- {
- }
+#if __ANDROID__ || __IOS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Opacity() { }
+#endif
+
+#if __ANDROID__ || __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _IsEnabled() { }
+#endif
+
+#if __ANDROID__ || __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Rotation() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _RotationX() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _RotationY() { }
+#endif
+
+#if __ANDROID__
+ [Ignore("This is covered by the platform tests")]
+ public override void _TranslationX() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _TranslationY() { }
+#endif
+
+#if __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Scale() { }
+#endif
+
+#if __ANDROID__ || __IOS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _IsVisible() { }
+#endif
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core.UITests.Shared/Tests/ButtonUITests.cs b/Xamarin.Forms.Core.UITests.Shared/Tests/ButtonUITests.cs
index a6d737e6b60..0296218fd10 100644
--- a/Xamarin.Forms.Core.UITests.Shared/Tests/ButtonUITests.cs
+++ b/Xamarin.Forms.Core.UITests.Shared/Tests/ButtonUITests.cs
@@ -139,6 +139,7 @@ public void Image()
remote.GoTo();
}
+#if !__ANDROID__ && !__IOS__
[Test]
[UiTest(typeof(Button), "Text")]
[Category(UITestCategories.UwpIgnore)]
@@ -150,7 +151,7 @@ public void Text()
var buttonText = remote.GetProperty(Button.TextProperty);
Assert.AreEqual("Text", buttonText);
}
-
+#endif
//TODO iOS
#if __ANDROID__
@@ -171,5 +172,44 @@ protected override void FixtureTeardown()
App.NavigateBack();
base.FixtureTeardown();
}
+
+#if __ANDROID__ || __IOS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Opacity() { }
+#endif
+
+#if __ANDROID__ || __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _IsEnabled() { }
+#endif
+
+#if __ANDROID__ || __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Rotation() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _RotationX() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _RotationY() { }
+#endif
+
+#if __ANDROID__
+ [Ignore("This is covered by the platform tests")]
+ public override void _TranslationX() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _TranslationY() { }
+#endif
+
+#if __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Scale() { }
+#endif
+
+#if __ANDROID__ || __IOS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _IsVisible() { }
+#endif
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core.UITests.Shared/Tests/CheckBoxUITests.cs b/Xamarin.Forms.Core.UITests.Shared/Tests/CheckBoxUITests.cs
index babc09cc28c..2c35cc8805c 100644
--- a/Xamarin.Forms.Core.UITests.Shared/Tests/CheckBoxUITests.cs
+++ b/Xamarin.Forms.Core.UITests.Shared/Tests/CheckBoxUITests.cs
@@ -44,5 +44,44 @@ protected override void FixtureTeardown()
App.NavigateBack();
base.FixtureTeardown();
}
+
+#if __ANDROID__ || __IOS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Opacity() { }
+#endif
+
+#if __ANDROID__ || __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _IsEnabled() { }
+#endif
+
+#if __ANDROID__ || __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Rotation() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _RotationX() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _RotationY() { }
+#endif
+
+#if __ANDROID__
+ [Ignore("This is covered by the platform tests")]
+ public override void _TranslationX() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _TranslationY() { }
+#endif
+
+#if __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Scale() { }
+#endif
+
+#if __ANDROID__ || __IOS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _IsVisible() { }
+#endif
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core.UITests.Shared/Tests/DatePickerUITests.cs b/Xamarin.Forms.Core.UITests.Shared/Tests/DatePickerUITests.cs
index 4e176df21a5..587c63f9e39 100644
--- a/Xamarin.Forms.Core.UITests.Shared/Tests/DatePickerUITests.cs
+++ b/Xamarin.Forms.Core.UITests.Shared/Tests/DatePickerUITests.cs
@@ -46,5 +46,44 @@ protected override void FixtureTeardown()
App.NavigateBack();
base.FixtureTeardown();
}
+
+#if __ANDROID__ || __IOS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Opacity() { }
+#endif
+
+#if __ANDROID__ || __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _IsEnabled() { }
+#endif
+
+#if __ANDROID__ || __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Rotation() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _RotationX() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _RotationY() { }
+#endif
+
+#if __ANDROID__
+ [Ignore("This is covered by the platform tests")]
+ public override void _TranslationX() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _TranslationY() { }
+#endif
+
+#if __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Scale() { }
+#endif
+
+#if __ANDROID__ || __IOS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _IsVisible() { }
+#endif
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core.UITests.Shared/Tests/EditorUITests.cs b/Xamarin.Forms.Core.UITests.Shared/Tests/EditorUITests.cs
index b2739b5c08e..1ab6143cc80 100644
--- a/Xamarin.Forms.Core.UITests.Shared/Tests/EditorUITests.cs
+++ b/Xamarin.Forms.Core.UITests.Shared/Tests/EditorUITests.cs
@@ -46,5 +46,44 @@ protected override void FixtureTeardown()
App.NavigateBack();
base.FixtureTeardown();
}
+
+#if __ANDROID__ || __IOS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Opacity() { }
+#endif
+
+#if __ANDROID__ || __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _IsEnabled() { }
+#endif
+
+#if __ANDROID__ || __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Rotation() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _RotationX() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _RotationY() { }
+#endif
+
+#if __ANDROID__
+ [Ignore("This is covered by the platform tests")]
+ public override void _TranslationX() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _TranslationY() { }
+#endif
+
+#if __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Scale() { }
+#endif
+
+#if __ANDROID__ || __IOS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _IsVisible() { }
+#endif
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core.UITests.Shared/Tests/EntryUITests.cs b/Xamarin.Forms.Core.UITests.Shared/Tests/EntryUITests.cs
index d9c5d1e7307..5815f186ab0 100644
--- a/Xamarin.Forms.Core.UITests.Shared/Tests/EntryUITests.cs
+++ b/Xamarin.Forms.Core.UITests.Shared/Tests/EntryUITests.cs
@@ -96,5 +96,44 @@ protected override void FixtureTeardown()
App.NavigateBack();
base.FixtureTeardown();
}
+
+#if __ANDROID__ || __IOS__
+ [Ignore("This is covered by the platform opacity tests")]
+ public override void _Opacity() { }
+#endif
+
+#if __ANDROID__ || __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _IsEnabled() { }
+#endif
+
+#if __ANDROID__ || __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Rotation() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _RotationX() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _RotationY() { }
+#endif
+
+#if __ANDROID__
+ [Ignore("This is covered by the platform tests")]
+ public override void _TranslationX() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _TranslationY() { }
+#endif
+
+#if __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Scale() { }
+#endif
+
+#if __ANDROID__ || __IOS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _IsVisible() { }
+#endif
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core.UITests.Shared/Tests/FrameUITests.cs b/Xamarin.Forms.Core.UITests.Shared/Tests/FrameUITests.cs
index 469c8a5ad1f..a0d88e4e891 100644
--- a/Xamarin.Forms.Core.UITests.Shared/Tests/FrameUITests.cs
+++ b/Xamarin.Forms.Core.UITests.Shared/Tests/FrameUITests.cs
@@ -49,9 +49,43 @@ protected override void FixtureTeardown()
base.FixtureTeardown();
}
- [UiTestExempt(ExemptReason.CannotTest, "Invalid interaction")]
- public override void _IsEnabled()
- {
- }
+#if __ANDROID__ || __IOS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Opacity() { }
+#endif
+
+#if __ANDROID__ || __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _IsEnabled() { }
+#endif
+
+#if __ANDROID__ || __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Rotation() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _RotationX() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _RotationY() { }
+#endif
+
+#if __ANDROID__
+ [Ignore("This is covered by the platform tests")]
+ public override void _TranslationX() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _TranslationY() { }
+#endif
+
+#if __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Scale() { }
+#endif
+
+#if __ANDROID__ || __IOS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _IsVisible() { }
+#endif
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core.UITests.Shared/Tests/ImageButtonUITests.cs b/Xamarin.Forms.Core.UITests.Shared/Tests/ImageButtonUITests.cs
index 9e8e51bbd06..0489022c9ba 100644
--- a/Xamarin.Forms.Core.UITests.Shared/Tests/ImageButtonUITests.cs
+++ b/Xamarin.Forms.Core.UITests.Shared/Tests/ImageButtonUITests.cs
@@ -152,5 +152,44 @@ public void Image()
var remote = new ViewContainerRemote(App, Test.ImageButton.Image, PlatformViewType);
remote.GoTo();
}
+
+#if __ANDROID__ || __IOS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Opacity() { }
+#endif
+
+#if __ANDROID__ || __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _IsEnabled() { }
+#endif
+
+#if __ANDROID__ || __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Rotation() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _RotationX() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _RotationY() { }
+#endif
+
+#if __ANDROID__
+ [Ignore("This is covered by the platform tests")]
+ public override void _TranslationX() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _TranslationY() { }
+#endif
+
+#if __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Scale() { }
+#endif
+
+#if __ANDROID__ || __IOS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _IsVisible() { }
+#endif
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core.UITests.Shared/Tests/ImageUITests.cs b/Xamarin.Forms.Core.UITests.Shared/Tests/ImageUITests.cs
index 0b2aa849e15..06467cc6e26 100644
--- a/Xamarin.Forms.Core.UITests.Shared/Tests/ImageUITests.cs
+++ b/Xamarin.Forms.Core.UITests.Shared/Tests/ImageUITests.cs
@@ -31,11 +31,6 @@ public override void _GestureRecognizers()
{
}
- [UiTestExempt(ExemptReason.CannotTest, "Invalid interaction")]
- public override void _IsEnabled()
- {
- }
-
[UiTestExempt(ExemptReason.CannotTest, "Invalid interaction")]
public override void _IsFocused()
{
@@ -57,5 +52,44 @@ protected override void FixtureTeardown()
App.NavigateBack();
base.FixtureTeardown();
}
+
+#if __ANDROID__ || __IOS__
+ [Ignore("This is covered by the platform opacity tests")]
+ public override void _Opacity() { }
+#endif
+
+#if __ANDROID__ || __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _IsEnabled() { }
+#endif
+
+#if __ANDROID__ || __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Rotation() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _RotationX() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _RotationY() { }
+#endif
+
+#if __ANDROID__
+ [Ignore("This is covered by the platform tests")]
+ public override void _TranslationX() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _TranslationY() { }
+#endif
+
+#if __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Scale() { }
+#endif
+
+#if __ANDROID__ || __IOS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _IsVisible() { }
+#endif
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core.UITests.Shared/Tests/LabelUITests.cs b/Xamarin.Forms.Core.UITests.Shared/Tests/LabelUITests.cs
index 8b0dd3117be..9565dec4d7d 100644
--- a/Xamarin.Forms.Core.UITests.Shared/Tests/LabelUITests.cs
+++ b/Xamarin.Forms.Core.UITests.Shared/Tests/LabelUITests.cs
@@ -27,11 +27,6 @@ public override void _GestureRecognizers()
{
}
- [UiTestExempt(ExemptReason.CannotTest, "Invalid interaction")]
- public override void _IsEnabled()
- {
- }
-
[UiTestExempt(ExemptReason.CannotTest, "Invalid interaction")]
public override void _IsFocused()
{
@@ -50,5 +45,44 @@ protected override void FixtureTeardown()
App.NavigateBack();
base.FixtureTeardown();
}
+
+#if __ANDROID__ || __IOS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Opacity() { }
+#endif
+
+#if __ANDROID__ || __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _IsEnabled() { }
+#endif
+
+#if __ANDROID__ || __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Rotation() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _RotationX() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _RotationY() { }
+#endif
+
+#if __ANDROID__
+ [Ignore("This is covered by the platform tests")]
+ public override void _TranslationX() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _TranslationY() { }
+#endif
+
+#if __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Scale() { }
+#endif
+
+#if __ANDROID__ || __IOS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _IsVisible() { }
+#endif
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core.UITests.Shared/Tests/PickerUITests.cs b/Xamarin.Forms.Core.UITests.Shared/Tests/PickerUITests.cs
index 9628c4a0867..34a267849d2 100644
--- a/Xamarin.Forms.Core.UITests.Shared/Tests/PickerUITests.cs
+++ b/Xamarin.Forms.Core.UITests.Shared/Tests/PickerUITests.cs
@@ -38,5 +38,44 @@ protected override void FixtureTeardown ()
App.NavigateBack ();
base.FixtureTeardown ();
}
+
+#if __ANDROID__ || __IOS__
+ [Ignore("This is covered by the platform opacity tests")]
+ public override void _Opacity() { }
+#endif
+
+#if __ANDROID__ || __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _IsEnabled() { }
+#endif
+
+#if __ANDROID__ || __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Rotation() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _RotationX() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _RotationY() { }
+#endif
+
+#if __ANDROID__
+ [Ignore("This is covered by the platform tests")]
+ public override void _TranslationX() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _TranslationY() { }
+#endif
+
+#if __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Scale() { }
+#endif
+
+#if __ANDROID__ || __IOS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _IsVisible() { }
+#endif
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core.UITests.Shared/Tests/ProgressBarUITests.cs b/Xamarin.Forms.Core.UITests.Shared/Tests/ProgressBarUITests.cs
index 63eceb60b53..c17b2465423 100644
--- a/Xamarin.Forms.Core.UITests.Shared/Tests/ProgressBarUITests.cs
+++ b/Xamarin.Forms.Core.UITests.Shared/Tests/ProgressBarUITests.cs
@@ -46,9 +46,43 @@ protected override void FixtureTeardown()
base.FixtureTeardown();
}
- [UiTestExempt(ExemptReason.CannotTest, "Invalid interaction")]
- public override void _IsEnabled()
- {
- }
+#if __ANDROID__ || __IOS__
+ [Ignore("This is covered by the platform opacity tests")]
+ public override void _Opacity() { }
+#endif
+
+#if __ANDROID__ || __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _IsEnabled() { }
+#endif
+
+#if __ANDROID__ || __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Rotation() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _RotationX() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _RotationY() { }
+#endif
+
+#if __ANDROID__
+ [Ignore("This is covered by the platform tests")]
+ public override void _TranslationX() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _TranslationY() { }
+#endif
+
+#if __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Scale() { }
+#endif
+
+#if __ANDROID__ || __IOS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _IsVisible() { }
+#endif
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core.UITests.Shared/Tests/SearchBarUITests.cs b/Xamarin.Forms.Core.UITests.Shared/Tests/SearchBarUITests.cs
index 2b25d275b63..ec689c84ce9 100644
--- a/Xamarin.Forms.Core.UITests.Shared/Tests/SearchBarUITests.cs
+++ b/Xamarin.Forms.Core.UITests.Shared/Tests/SearchBarUITests.cs
@@ -36,5 +36,44 @@ protected override void FixtureTeardown ()
App.NavigateBack ();
base.FixtureTeardown ();
}
+
+#if __ANDROID__ || __IOS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Opacity() { }
+#endif
+
+#if __ANDROID__ || __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _IsEnabled() { }
+#endif
+
+#if __ANDROID__ || __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Rotation() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _RotationX() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _RotationY() { }
+#endif
+
+#if __ANDROID__
+ [Ignore("This is covered by the platform tests")]
+ public override void _TranslationX() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _TranslationY() { }
+#endif
+
+#if __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Scale() { }
+#endif
+
+#if __ANDROID__ || __IOS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _IsVisible() { }
+#endif
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core.UITests.Shared/Tests/SliderUITests.cs b/Xamarin.Forms.Core.UITests.Shared/Tests/SliderUITests.cs
index eceed80b29f..7506eac7ad3 100644
--- a/Xamarin.Forms.Core.UITests.Shared/Tests/SliderUITests.cs
+++ b/Xamarin.Forms.Core.UITests.Shared/Tests/SliderUITests.cs
@@ -44,5 +44,44 @@ protected override void FixtureTeardown()
App.NavigateBack();
base.FixtureTeardown();
}
+
+#if __ANDROID__ || __IOS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Opacity() { }
+#endif
+
+#if __ANDROID__ || __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _IsEnabled() { }
+#endif
+
+#if __ANDROID__ || __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Rotation() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _RotationX() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _RotationY() { }
+#endif
+
+#if __ANDROID__
+ [Ignore("This is covered by the platform tests")]
+ public override void _TranslationX() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _TranslationY() { }
+#endif
+
+#if __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Scale() { }
+#endif
+
+#if __ANDROID__ || __IOS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _IsVisible() { }
+#endif
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core.UITests.Shared/Tests/StepperUITests.cs b/Xamarin.Forms.Core.UITests.Shared/Tests/StepperUITests.cs
index ccf6e72cc98..a6b2fadc2e7 100644
--- a/Xamarin.Forms.Core.UITests.Shared/Tests/StepperUITests.cs
+++ b/Xamarin.Forms.Core.UITests.Shared/Tests/StepperUITests.cs
@@ -37,18 +37,26 @@ public override void _UnFocus()
{
}
+#if __ANDROID__ || __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+#endif
[Category(UITestCategories.UwpIgnore)]
public override void _IsEnabled()
{
base._IsEnabled();
- }
+ }
+
-
+#if __ANDROID__ || __IOS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _IsVisible() { }
+#else
[Category(UITestCategories.UwpIgnore)]
public override void _IsVisible()
{
base._IsVisible();
}
+#endif
// TODO
// Implement control specific ui tests
@@ -57,5 +65,36 @@ protected override void FixtureTeardown()
App.NavigateBack();
base.FixtureTeardown();
}
+
+#if __ANDROID__ || __IOS__
+ [Ignore("This is covered by the platform opacity tests")]
+ public override void _Opacity() { }
+#endif
+
+#if __ANDROID__ || __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Rotation() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _RotationX() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _RotationY() { }
+#endif
+
+#if __ANDROID__
+ [Ignore("This is covered by the platform tests")]
+ public override void _TranslationX() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _TranslationY() { }
+#endif
+
+#if __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Scale() { }
+#endif
+
+
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core.UITests.Shared/Tests/SwitchUITests.cs b/Xamarin.Forms.Core.UITests.Shared/Tests/SwitchUITests.cs
index f2e1fe256f9..b1f973eab40 100644
--- a/Xamarin.Forms.Core.UITests.Shared/Tests/SwitchUITests.cs
+++ b/Xamarin.Forms.Core.UITests.Shared/Tests/SwitchUITests.cs
@@ -44,5 +44,44 @@ protected override void FixtureTeardown()
App.NavigateBack();
base.FixtureTeardown();
}
+
+#if __ANDROID__ || __IOS__
+ [Ignore("This is covered by the platform opacity tests")]
+ public override void _Opacity() { }
+#endif
+
+#if __ANDROID__ || __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _IsEnabled() { }
+#endif
+
+#if __ANDROID__ || __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Rotation() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _RotationX() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _RotationY() { }
+#endif
+
+#if __ANDROID__
+ [Ignore("This is covered by the platform tests")]
+ public override void _TranslationX() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _TranslationY() { }
+#endif
+
+#if __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Scale() { }
+#endif
+
+#if __ANDROID__ || __IOS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _IsVisible() { }
+#endif
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core.UITests.Shared/Tests/TimePickerUITests.cs b/Xamarin.Forms.Core.UITests.Shared/Tests/TimePickerUITests.cs
index db093dc87e3..c31483b239a 100644
--- a/Xamarin.Forms.Core.UITests.Shared/Tests/TimePickerUITests.cs
+++ b/Xamarin.Forms.Core.UITests.Shared/Tests/TimePickerUITests.cs
@@ -47,5 +47,44 @@ protected override void FixtureTeardown()
App.NavigateBack();
base.FixtureTeardown();
}
+
+#if __ANDROID__ || __IOS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Opacity() { }
+#endif
+
+#if __ANDROID__ || __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _IsEnabled() { }
+#endif
+
+#if __ANDROID__ || __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Rotation() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _RotationX() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _RotationY() { }
+#endif
+
+#if __ANDROID__
+ [Ignore("This is covered by the platform tests")]
+ public override void _TranslationX() { }
+
+ [Ignore("This is covered by the platform tests")]
+ public override void _TranslationY() { }
+#endif
+
+#if __IOS__ || __WINDOWS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _Scale() { }
+#endif
+
+#if __ANDROID__ || __IOS__
+ [Ignore("This is covered by the platform tests")]
+ public override void _IsVisible() { }
+#endif
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core.UITests.Shared/Tests/ViewUITests.cs b/Xamarin.Forms.Core.UITests.Shared/Tests/ViewUITests.cs
index 0011c40f06b..d9590ff3d69 100644
--- a/Xamarin.Forms.Core.UITests.Shared/Tests/ViewUITests.cs
+++ b/Xamarin.Forms.Core.UITests.Shared/Tests/ViewUITests.cs
@@ -28,21 +28,6 @@ public virtual void _AnchorY()
}
- // [Test]
- // [UiTest (Test.VisualElement.BackgroundColor)]
- // [UiTestBroken (BrokenReason.UITestBug, "UITest Issue #107")]
- public virtual void _BackgroundColor()
- {
- //TODO: this was failing and is changing in next version of calabash (UI-Test-pre nuget) to a json rgb
- // var remote = RemoteFactory.CreateRemote (App, "BackgroundColor", PlatformViewType);
- // remote.GoTo ();
- // if (App is iOSApp) {
- // var backgroundColor = remote.GetProperty (View.BackgroundColorProperty);
- // Assert.AreEqual (Color.Blue, backgroundColor);
- // }
-
- }
-
[Test]
[UiTest(typeof(VisualElement), "Focus")]
public abstract void _Focus();
@@ -230,7 +215,6 @@ public virtual void _Scale()
Matrix generatedMatrix = NumericExtensions.BuildScaleMatrix(0.5f);
Assert.AreEqual(generatedMatrix, scaleMatrix);
#endif
-
}
[Test]
diff --git a/Xamarin.Forms.Core.UnitTests/DistanceTests.cs b/Xamarin.Forms.Core.UnitTests/DistanceTests.cs
index 829bf957c1e..a2fde04b773 100644
--- a/Xamarin.Forms.Core.UnitTests/DistanceTests.cs
+++ b/Xamarin.Forms.Core.UnitTests/DistanceTests.cs
@@ -58,6 +58,21 @@ public void ConstructFromMiles()
Assert.True(Math.Abs(distance.Kilometers - 6378.09999805) < EPSILON_FOR_LARGE_MILES_TO_KM);
}
+ [Test]
+ public void ConstructFromPositions()
+ {
+ const double EPSILON = 0.001;
+
+ Position position1 = new Position(37.403992, -122.034988);
+ Position position2 = new Position(37.776691, -122.416534);
+
+ Distance distance = Distance.BetweenPositions(position1, position2);
+
+ Assert.True(Math.Abs(distance.Meters - 53363.08) < EPSILON);
+ Assert.True(Math.Abs(distance.Kilometers - 53.36308) < EPSILON);
+ Assert.True(Math.Abs(distance.Miles - 33.15828) < EPSILON);
+ }
+
[Test]
public void EqualityOp([Range(5, 9)] double x, [Range(5, 9)] double y)
{
diff --git a/Xamarin.Forms.Core.UnitTests/WebViewUnitTests.cs b/Xamarin.Forms.Core.UnitTests/WebViewUnitTests.cs
index 5565ec31018..605bd446001 100644
--- a/Xamarin.Forms.Core.UnitTests/WebViewUnitTests.cs
+++ b/Xamarin.Forms.Core.UnitTests/WebViewUnitTests.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Net;
using NUnit.Framework;
using Xamarin.Forms.PlatformConfiguration;
@@ -142,5 +143,19 @@ public void TestWindowsSetAllowJavaScriptAlertsFlag()
Assert.AreEqual(defaultWebView.On().IsJavaScriptAlertEnabled(), false);
Assert.AreEqual(jsAlertsAllowedWebView.On().IsJavaScriptAlertEnabled(), true);
}
+
+ [Test]
+ public void TestSettingOfCookie()
+ {
+ var defaultWebView = new WebView();
+ var CookieContainer = new CookieContainer();
+
+ CookieContainer.Add(new Cookie("TestCookie", "My Test Cookie...", "/", "microsoft.com"));
+
+ defaultWebView.Cookies = CookieContainer;
+ defaultWebView.Source = "http://xamarin.com";
+
+ Assert.IsNotNull(defaultWebView.Cookies);
+ }
}
}
diff --git a/Xamarin.Forms.Core/BindableLayout.cs b/Xamarin.Forms.Core/BindableLayout.cs
index 79de5a87b9d..a9982515be8 100644
--- a/Xamarin.Forms.Core/BindableLayout.cs
+++ b/Xamarin.Forms.Core/BindableLayout.cs
@@ -24,6 +24,12 @@ public static class BindableLayout
defaultValueCreator: (b) => new BindableLayoutController((Layout)b),
propertyChanged: (b, o, n) => OnControllerChanged(b, (BindableLayoutController)o, (BindableLayoutController)n));
+ public static readonly BindableProperty EmptyViewProperty =
+ BindableProperty.Create("EmptyView", typeof(object), typeof(Layout), null, propertyChanged: (b, o, n) => { GetBindableLayoutController(b).EmptyView = n; });
+
+ public static readonly BindableProperty EmptyViewTemplateProperty =
+ BindableProperty.Create("EmptyViewTemplate", typeof(DataTemplate), typeof(Layout), null, propertyChanged: (b, o, n) => { GetBindableLayoutController(b).EmptyViewTemplate = (DataTemplate)n; });
+
public static void SetItemsSource(BindableObject b, IEnumerable value)
{
b.SetValue(ItemsSourceProperty, value);
@@ -54,6 +60,26 @@ public static DataTemplateSelector GetItemTemplateSelector(BindableObject b)
return (DataTemplateSelector)b.GetValue(ItemTemplateSelectorProperty);
}
+ public static object GetEmptyView(BindableObject b)
+ {
+ return b.GetValue(EmptyViewProperty);
+ }
+
+ public static void SetEmptyView(BindableObject b, object value)
+ {
+ b.SetValue(EmptyViewProperty, value);
+ }
+
+ public static DataTemplate GetEmptyViewTemplate(BindableObject b)
+ {
+ return (DataTemplate)b.GetValue(EmptyViewTemplateProperty);
+ }
+
+ public static void SetEmptyViewTemplate(BindableObject b, DataTemplate value)
+ {
+ b.SetValue(EmptyViewProperty, value);
+ }
+
static BindableLayoutController GetBindableLayoutController(BindableObject b)
{
return (BindableLayoutController)b.GetValue(BindableLayoutControllerProperty);
@@ -80,6 +106,8 @@ static void OnControllerChanged(BindableObject b, BindableLayoutController oldC,
newC.ItemsSource = GetItemsSource(b);
newC.ItemTemplate = GetItemTemplate(b);
newC.ItemTemplateSelector = GetItemTemplateSelector(b);
+ newC.EmptyView = GetEmptyView(b);
+ newC.EmptyViewTemplate = GetEmptyViewTemplate(b);
newC.EndBatchUpdate();
}
}
@@ -91,11 +119,16 @@ class BindableLayoutController
DataTemplate _itemTemplate;
DataTemplateSelector _itemTemplateSelector;
bool _isBatchUpdate;
+ object _emptyView;
+ DataTemplate _emptyViewTemplate;
+ View _currentEmptyView;
public IEnumerable ItemsSource { get => _itemsSource; set => SetItemsSource(value); }
public DataTemplate ItemTemplate { get => _itemTemplate; set => SetItemTemplate(value); }
public DataTemplateSelector ItemTemplateSelector { get => _itemTemplateSelector; set => SetItemTemplateSelector(value); }
+ public object EmptyView { get => _emptyView; set => SetEmptyView(value); }
+ public DataTemplate EmptyViewTemplate { get => _emptyViewTemplate; set => SetEmptyViewTemplate(value); }
public BindableLayoutController(Layout layout)
{
@@ -158,20 +191,43 @@ void SetItemTemplateSelector(DataTemplateSelector itemTemplateSelector)
}
}
+ void SetEmptyView(object emptyView)
+ {
+ _emptyView = emptyView;
+
+ _currentEmptyView = CreateEmptyView(_emptyView, _emptyViewTemplate);
+
+ if (!_isBatchUpdate)
+ {
+ CreateChildren();
+ }
+ }
+
+ void SetEmptyViewTemplate(DataTemplate emptyViewTemplate)
+ {
+ _emptyViewTemplate = emptyViewTemplate;
+
+ _currentEmptyView = CreateEmptyView(_emptyView, _emptyViewTemplate);
+
+ if (!_isBatchUpdate)
+ {
+ CreateChildren();
+ }
+ }
+
void CreateChildren()
{
- Layout layout;
- if (!_layoutWeakReference.TryGetTarget(out layout))
+ if (!_layoutWeakReference.TryGetTarget(out Layout layout))
{
return;
}
layout.Children.Clear();
+ UpdateEmptyView(layout);
+
if (_itemsSource == null)
- {
return;
- }
foreach (object item in _itemsSource)
{
@@ -179,6 +235,20 @@ void CreateChildren()
}
}
+ void UpdateEmptyView(Layout layout)
+ {
+ if (_currentEmptyView == null)
+ return;
+
+ if (!_itemsSource?.GetEnumerator().MoveNext() ?? true)
+ {
+ layout.Children.Add(_currentEmptyView);
+ return;
+ }
+
+ layout.Children.Remove(_currentEmptyView);
+ }
+
View CreateItemView(object item, Layout layout)
{
return CreateItemView(item, _itemTemplate ?? _itemTemplateSelector?.SelectTemplate(item, layout));
@@ -194,14 +264,35 @@ View CreateItemView(object item, DataTemplate dataTemplate)
}
else
{
- return new Label() { Text = item?.ToString() };
+ return new Label { Text = item?.ToString(), HorizontalTextAlignment = TextAlignment.Center };
}
}
+ View CreateEmptyView(object emptyView, DataTemplate dataTemplate)
+ {
+ if (!_layoutWeakReference.TryGetTarget(out Layout layout))
+ {
+ return null;
+ }
+
+ if (dataTemplate != null)
+ {
+ var view = (View)dataTemplate.CreateContent();
+ view.BindingContext = layout.BindingContext;
+ return view;
+ }
+
+ if (emptyView is View emptyLayout)
+ {
+ return emptyLayout;
+ }
+
+ return new Label { Text = emptyView?.ToString(), HorizontalTextAlignment = TextAlignment.Center };
+ }
+
void ItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
- Layout layout;
- if (!_layoutWeakReference.TryGetTarget(out layout))
+ if (!_layoutWeakReference.TryGetTarget(out Layout layout))
{
return;
}
@@ -210,6 +301,10 @@ void ItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArg
insert: (item, index, _) => layout.Children.Insert(index, CreateItemView(item, layout)),
removeAt: (item, index) => layout.Children.RemoveAt(index),
reset: CreateChildren);
+
+ // UpdateEmptyView is called from within CreateChildren, therefor skip it for Reset
+ if (e.Action != NotifyCollectionChangedAction.Reset)
+ UpdateEmptyView(layout);
}
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/NavigationPage.cs b/Xamarin.Forms.Core/NavigationPage.cs
index 28a68cc7818..321ae4774bf 100644
--- a/Xamarin.Forms.Core/NavigationPage.cs
+++ b/Xamarin.Forms.Core/NavigationPage.cs
@@ -32,6 +32,8 @@ public class NavigationPage : Page, IPageContainer, IBarElement, INavigati
[EditorBrowsable(EditorBrowsableState.Never)]
public static readonly BindableProperty TitleIconProperty = TitleIconImageSourceProperty;
+ public static readonly BindableProperty IconColorProperty = BindableProperty.CreateAttached("IconColor", typeof(Color), typeof(NavigationPage), Color.Default);
+
public static readonly BindableProperty TitleViewProperty = BindableProperty.CreateAttached("TitleView", typeof(View), typeof(NavigationPage), null, propertyChanging: TitleViewPropertyChanging);
static readonly BindablePropertyKey CurrentPagePropertyKey = BindableProperty.CreateReadOnly("CurrentPage", typeof(Page), typeof(NavigationPage), null);
@@ -159,6 +161,16 @@ public static View GetTitleView(BindableObject bindable)
return (View)bindable.GetValue(TitleViewProperty);
}
+ public static Color GetIconColor(BindableObject bindable)
+ {
+ if (bindable == null)
+ {
+ return Color.Default;
+ }
+
+ return (Color)bindable.GetValue(IconColorProperty);
+ }
+
public Task PopAsync()
{
return PopAsync(true);
@@ -278,6 +290,11 @@ public static void SetTitleView(BindableObject bindable, View value)
bindable.SetValue(TitleViewProperty, value);
}
+ public static void SetIconColor(BindableObject bindable, Color value)
+ {
+ bindable.SetValue(IconColorProperty, value);
+ }
+
protected override bool OnBackButtonPressed()
{
if (CurrentPage.SendBackButtonPressed())
diff --git a/Xamarin.Forms.Core/Picker.cs b/Xamarin.Forms.Core/Picker.cs
index 56f10fd14c1..768a28904eb 100644
--- a/Xamarin.Forms.Core/Picker.cs
+++ b/Xamarin.Forms.Core/Picker.cs
@@ -149,7 +149,7 @@ public BindingBase ItemDisplayBinding {
string GetDisplayMember(object item)
{
if (ItemDisplayBinding == null)
- return item.ToString();
+ return item == null ? string.Empty : item.ToString();
ItemDisplayBinding.Apply(item, this, s_displayProperty);
ItemDisplayBinding.Unapply();
diff --git a/Xamarin.Forms.Core/PlatformConfiguration/iOSSpecific/MasterDetailPage.cs b/Xamarin.Forms.Core/PlatformConfiguration/iOSSpecific/MasterDetailPage.cs
new file mode 100644
index 00000000000..55718e5c1eb
--- /dev/null
+++ b/Xamarin.Forms.Core/PlatformConfiguration/iOSSpecific/MasterDetailPage.cs
@@ -0,0 +1,33 @@
+
+namespace Xamarin.Forms.PlatformConfiguration.iOSSpecific
+{
+ using FormsElement = Forms.MasterDetailPage;
+
+ public static class MasterDetailPage
+ {
+ #region ApplyShadow
+ public static readonly BindableProperty ApplyShadowProperty = BindableProperty.Create("ApplyShadow", typeof(bool), typeof(MasterDetailPage), false);
+
+ public static bool GetApplyShadow(BindableObject element)
+ {
+ return (bool)element.GetValue(ApplyShadowProperty);
+ }
+
+ public static void SetApplyShadow(BindableObject element, bool value)
+ {
+ element.SetValue(ApplyShadowProperty, value);
+ }
+
+ public static IPlatformElementConfiguration SetApplyShadow(this IPlatformElementConfiguration config, bool value)
+ {
+ SetApplyShadow(config.Element, value);
+ return config;
+ }
+
+ public static bool GetApplyShadow(this IPlatformElementConfiguration config)
+ {
+ return GetApplyShadow(config.Element);
+ }
+ #endregion
+ }
+}
diff --git a/Xamarin.Forms.Core/PlatformConfiguration/iOSSpecific/SearchBar.cs b/Xamarin.Forms.Core/PlatformConfiguration/iOSSpecific/SearchBar.cs
new file mode 100644
index 00000000000..1e39611da2e
--- /dev/null
+++ b/Xamarin.Forms.Core/PlatformConfiguration/iOSSpecific/SearchBar.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Xamarin.Forms.PlatformConfiguration;
+
+namespace Xamarin.Forms.PlatformConfiguration.iOSSpecific
+{
+ using FormsElement = Xamarin.Forms.SearchBar;
+
+ public static class SearchBar
+ {
+ public static readonly BindableProperty SearchBarStyleProperty = BindableProperty.Create("SearchBarStyle", typeof(UISearchBarStyle), typeof(SearchBar), UISearchBarStyle.Default);
+
+ public static UISearchBarStyle GetSearchBarStyle(BindableObject element)
+ {
+ return (UISearchBarStyle)element.GetValue(SearchBarStyleProperty);
+ }
+
+ public static void SetSearchBarStyle(BindableObject element, UISearchBarStyle style)
+ {
+ element.SetValue(SearchBarStyleProperty, style);
+ }
+
+ public static UISearchBarStyle GetSearchBarStyle(this IPlatformElementConfiguration config)
+ {
+ return GetSearchBarStyle(config.Element);
+ }
+
+ public static IPlatformElementConfiguration SetSearchBarStyle(
+ this IPlatformElementConfiguration config, UISearchBarStyle style)
+ {
+ SetSearchBarStyle(config.Element, style);
+ return config;
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core/PlatformConfiguration/iOSSpecific/UISearchBarStyle.cs b/Xamarin.Forms.Core/PlatformConfiguration/iOSSpecific/UISearchBarStyle.cs
new file mode 100644
index 00000000000..0f285f01179
--- /dev/null
+++ b/Xamarin.Forms.Core/PlatformConfiguration/iOSSpecific/UISearchBarStyle.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Xamarin.Forms.PlatformConfiguration.iOSSpecific
+{
+ public enum UISearchBarStyle
+ {
+ Default,
+ Prominent,
+ Minimal
+ }
+}
diff --git a/Xamarin.Forms.Core/RadioButton.cs b/Xamarin.Forms.Core/RadioButton.cs
new file mode 100644
index 00000000000..9499316af19
--- /dev/null
+++ b/Xamarin.Forms.Core/RadioButton.cs
@@ -0,0 +1,185 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms
+{
+ [RenderWith(typeof(_RadioButtonRenderer))]
+ public class RadioButton : Button, IElementConfiguration
+ {
+ readonly Lazy> _platformConfigurationRegistry;
+
+ static Dictionary>> _groupNameToElements;
+
+ public const string IsCheckedVisualState = "IsChecked";
+
+ public static readonly BindableProperty IsCheckedProperty = BindableProperty.Create(
+ nameof(IsChecked), typeof(bool), typeof(RadioButton), false, propertyChanged: (b, o, n) => ((RadioButton)b).OnIsCheckedPropertyChanged((bool)n), defaultBindingMode: BindingMode.TwoWay);
+
+ public static readonly BindableProperty GroupNameProperty = BindableProperty.Create(
+ nameof(GroupName), typeof(string), typeof(RadioButton), null, propertyChanged: (b, o, n) => ((RadioButton)b).OnGroupNamePropertyChanged((string)o, (string)n));
+
+ public static readonly BindableProperty ButtonSourceProperty = BindableProperty.Create(
+ nameof(ButtonSource), typeof(ImageSource), typeof(RadioButton), null);
+
+ public event EventHandler CheckedChanged;
+
+ public bool IsChecked
+ {
+ get { return (bool)GetValue(IsCheckedProperty); }
+ set { SetValue(IsCheckedProperty, value); }
+ }
+
+ public string GroupName
+ {
+ get { return (string)GetValue(GroupNameProperty); }
+ set { SetValue(GroupNameProperty, value); }
+ }
+
+ public ImageSource ButtonSource
+ {
+ get { return (ImageSource)GetValue(ButtonSourceProperty); }
+ set { SetValue(ButtonSourceProperty, value); }
+ }
+
+ public RadioButton()
+ {
+ _platformConfigurationRegistry = new Lazy>(() => new PlatformConfigurationRegistry(this));
+ }
+
+ public new IPlatformElementConfiguration On() where T : IConfigPlatform
+ {
+ return _platformConfigurationRegistry.Value.On();
+ }
+
+ protected internal override void ChangeVisualState()
+ {
+ if (IsEnabled && IsChecked)
+ VisualStateManager.GoToState(this, IsCheckedVisualState);
+ else
+ base.ChangeVisualState();
+ }
+
+ void OnIsCheckedPropertyChanged(bool isChecked)
+ {
+ if (isChecked)
+ UpdateRadioButtonGroup();
+
+ CheckedChanged?.Invoke(this, new CheckedChangedEventArgs(isChecked));
+ ChangeVisualState();
+ }
+
+ void OnGroupNamePropertyChanged(string oldGroupName, string newGroupName)
+ {
+ // Unregister the old group name if set
+ if (!string.IsNullOrEmpty(oldGroupName))
+ Unregister(this, oldGroupName);
+
+ // Register the new group name is set
+ if (!string.IsNullOrEmpty(newGroupName))
+ Register(this, newGroupName);
+ }
+
+ void UpdateRadioButtonGroup()
+ {
+ string groupName = GroupName;
+ if (!string.IsNullOrEmpty(groupName))
+ {
+ Element rootScope = GetVisualRoot(this);
+
+ if (_groupNameToElements == null)
+ _groupNameToElements = new Dictionary>>(1);
+
+ // Get all elements bound to this key and remove this element
+ List> elements = _groupNameToElements[groupName];
+ for (int i = 0; i < elements.Count;)
+ {
+ WeakReference weakRef = elements[i];
+ if (weakRef.TryGetTarget(out RadioButton rb))
+ {
+ // Uncheck all checked RadioButtons different from the current one
+ if (rb != this && (rb.IsChecked == true) && rootScope == GetVisualRoot(rb))
+ rb.SetValueFromRenderer(IsCheckedProperty, false);
+
+ i++;
+ }
+ else
+ {
+ // Remove dead instances
+ elements.RemoveAt(i);
+ }
+ }
+ }
+ else // Logical parent should be the group
+ {
+ Element parent = Parent;
+ if (parent != null)
+ {
+ // Traverse logical children
+ IEnumerable children = parent.LogicalChildren;
+ IEnumerator itor = children.GetEnumerator();
+ while (itor.MoveNext())
+ {
+ var rb = itor.Current as RadioButton;
+ if (rb != null && rb != this && string.IsNullOrEmpty(rb.GroupName) && (rb.IsChecked == true))
+ rb.SetValueFromRenderer(IsCheckedProperty, false);
+ }
+ }
+ }
+ }
+
+ static void Register(RadioButton radioButton, string groupName)
+ {
+ if (_groupNameToElements == null)
+ _groupNameToElements = new Dictionary>>(1);
+
+ if (_groupNameToElements.TryGetValue(groupName, out List> elements))
+ {
+ // There were some elements there, remove dead ones
+ PurgeDead(elements, null);
+ }
+ else
+ {
+ elements = new List>(1);
+ _groupNameToElements[groupName] = elements;
+ }
+
+ elements.Add(new WeakReference(radioButton));
+ }
+
+ static void Unregister(RadioButton radioButton, string groupName)
+ {
+ if (_groupNameToElements == null)
+ return;
+
+ // Get all elements bound to this key and remove this element
+ if (_groupNameToElements.TryGetValue(groupName, out List> elements))
+ {
+ PurgeDead(elements, radioButton);
+
+ if (elements.Count == 0)
+ _groupNameToElements.Remove(groupName);
+ }
+ }
+
+ static void PurgeDead(List> elements, object elementToRemove)
+ {
+ for (int i = 0; i < elements.Count;)
+ {
+ if (elements[i].TryGetTarget(out RadioButton rb) && rb == elementToRemove)
+ elements.RemoveAt(i);
+ else
+ i++;
+ }
+ }
+
+ static Element GetVisualRoot(Element element)
+ {
+ Element parent = element.Parent;
+ while (parent != null && !(parent is Page))
+ parent = parent.Parent;
+ return parent;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/WebView.cs b/Xamarin.Forms.Core/WebView.cs
index ef981d5307b..16f37614d1c 100644
--- a/Xamarin.Forms.Core/WebView.cs
+++ b/Xamarin.Forms.Core/WebView.cs
@@ -6,6 +6,7 @@
using System.Text;
using System.Text.RegularExpressions;
using System.Collections.Generic;
+using System.Net;
namespace Xamarin.Forms
{
@@ -37,6 +38,8 @@ public class WebView : View, IWebViewController, IElementConfiguration
public static readonly BindableProperty CanGoForwardProperty = CanGoForwardPropertyKey.BindableProperty;
+ public static readonly BindableProperty CookiesProperty = BindableProperty.Create(nameof(Cookies), typeof(CookieContainer), typeof(WebView), default(string));
+
readonly Lazy> _platformConfigurationRegistry;
public WebView()
@@ -68,6 +71,12 @@ public bool CanGoForward
get { return (bool)GetValue(CanGoForwardProperty); }
}
+ public CookieContainer Cookies
+ {
+ get { return (CookieContainer)GetValue(CookiesProperty); }
+ set { SetValue(CookiesProperty, value); }
+ }
+
[TypeConverter(typeof(WebViewSourceTypeConverter))]
public WebViewSource Source
{
diff --git a/Xamarin.Forms.CustomAttributes/TestAttributes.cs b/Xamarin.Forms.CustomAttributes/TestAttributes.cs
index b5e0ba91476..4ac118817de 100644
--- a/Xamarin.Forms.CustomAttributes/TestAttributes.cs
+++ b/Xamarin.Forms.CustomAttributes/TestAttributes.cs
@@ -202,7 +202,8 @@ public enum Views
Editor,
DatePicker,
CheckBox,
- SwipeView
+ SwipeView,
+ RadioButton
}
public enum Layouts
@@ -763,6 +764,12 @@ public enum CheckBox
UncheckedColor
}
+ public enum RadioButton
+ {
+ IsChecked,
+ ButtonSource,
+ }
+
public enum TimePicker
{
Format,
@@ -886,5 +893,4 @@ public enum CarouselView
IsBounceEnabled
}
}
-}
-
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Maps.Android/MapRenderer.cs b/Xamarin.Forms.Maps.Android/MapRenderer.cs
index 82dff7b472c..4361921abbc 100644
--- a/Xamarin.Forms.Maps.Android/MapRenderer.cs
+++ b/Xamarin.Forms.Maps.Android/MapRenderer.cs
@@ -22,6 +22,7 @@
using Math = System.Math;
using APolyline = Android.Gms.Maps.Model.Polyline;
using APolygon = Android.Gms.Maps.Model.Polygon;
+using ACircle = Android.Gms.Maps.Model.Circle;
namespace Xamarin.Forms.Maps.Android
{
@@ -38,6 +39,7 @@ public class MapRenderer : ViewRenderer, GoogleMap.IOnCameraMoveLi
List _markers;
List _polylines;
List _polygons;
+ List _circles;
public MapRenderer(Context context) : base(context)
{
@@ -303,7 +305,22 @@ void PinOnPropertyChanged(object sender, PropertyChangedEventArgs e)
protected Marker GetMarkerForPin(Pin pin)
{
- return _markers?.Find(m => m.Id == (string)pin.MarkerId);
+ Marker targetMarker = null;
+
+ if (_markers != null)
+ {
+ for (int i = 0; i < _markers.Count; i++)
+ {
+ var marker = _markers[i];
+ if (marker.Id == (string)pin.MarkerId)
+ {
+ targetMarker = marker;
+ break;
+ }
+ }
+ }
+
+ return targetMarker;
}
protected Pin GetPinForMarker(Marker marker)
@@ -413,8 +430,14 @@ void OnPinCollectionChanged(object sender, NotifyCollectionChangedEventArgs noti
AddPins(notifyCollectionChangedEventArgs.NewItems);
break;
case NotifyCollectionChangedAction.Reset:
- _markers?.ForEach(m => m.Remove());
- _markers = null;
+ if (_markers != null)
+ {
+ for (int i = 0; i < _markers.Count; i++)
+ _markers[i].Remove();
+
+ _markers = null;
+ }
+
AddPins((IList)Element.Pins);
break;
case NotifyCollectionChangedAction.Move:
@@ -511,6 +534,11 @@ void MapElementPropertyChanged(object sender, PropertyChangedEventArgs e)
PolygonOnPropertyChanged(polygon, e);
break;
}
+ case Circle circle:
+ {
+ CircleOnPropertyChanged(circle, e);
+ break;
+ }
}
}
@@ -531,14 +559,26 @@ void OnMapElementCollectionChanged(object sender, NotifyCollectionChangedEventAr
case NotifyCollectionChangedAction.Reset:
if (_polylines != null)
{
- foreach (var nativePolyline in _polylines)
- {
- nativePolyline.Remove();
-
- var formsPolyline = GetFormsPolyline(nativePolyline);
- if (formsPolyline != null)
- formsPolyline.PropertyChanged -= MapElementPropertyChanged;
- }
+ for(int i = 0; i < _polylines.Count; i++)
+ _polylines[i].Remove();
+
+ _polylines = null;
+ }
+
+ if (_polygons != null)
+ {
+ for (int i = 0; i < _polygons.Count; i++)
+ _polygons[i].Remove();
+
+ _polygons = null;
+ }
+
+ if (_circles != null)
+ {
+ for (int i = 0; i < _circles.Count; i++)
+ _circles[i].Remove();
+
+ _circles = null;
}
AddMapElements(Element.MapElements);
@@ -560,6 +600,9 @@ void AddMapElements(IEnumerable mapElements)
case Polygon polygon:
AddPolygon(polygon);
break;
+ case Circle circle:
+ AddCircle(circle);
+ break;
}
}
}
@@ -578,6 +621,9 @@ void RemoveMapElements(IEnumerable mapElements)
case Polygon polygon:
RemovePolygon(polygon);
break;
+ case Circle circle:
+ RemoveCircle(circle);
+ break;
}
}
}
@@ -601,7 +647,22 @@ protected virtual PolylineOptions CreatePolylineOptions(Polyline polyline)
protected APolyline GetNativePolyline(Polyline polyline)
{
- return _polylines?.Find(p => p.Id == (string)polyline.MapElementId);
+ APolyline targetPolyline = null;
+
+ if (_polylines != null)
+ {
+ for (int i = 0; i < _polylines.Count; i++)
+ {
+ var native = _polylines[i];
+ if (native.Id == (string)polyline.MapElementId)
+ {
+ targetPolyline = native;
+ break;
+ }
+ }
+ }
+
+ return targetPolyline;
}
protected Polyline GetFormsPolyline(APolyline polyline)
@@ -708,7 +769,22 @@ protected virtual PolygonOptions CreatePolygonOptions(Polygon polygon)
protected APolygon GetNativePolygon(Polygon polygon)
{
- return _polygons?.Find(p => p.Id == (string)polygon.MapElementId);
+ APolygon targetPolygon = null;
+
+ if (_polygons != null)
+ {
+ for (int i = 0; i < _polygons.Count; i++)
+ {
+ var native = _polygons[i];
+ if (native.Id == (string)polygon.MapElementId)
+ {
+ targetPolygon = native;
+ break;
+ }
+ }
+ }
+
+ return targetPolygon;
}
protected Polygon GetFormsPolygon(APolygon polygon)
@@ -787,6 +863,127 @@ void RemovePolygon(Polygon polygon)
#endregion
+ #region Circles
+
+ protected virtual CircleOptions CreateCircleOptions(Circle circle)
+ {
+ var opts = new CircleOptions()
+ .InvokeCenter(new LatLng(circle.Center.Latitude, circle.Center.Longitude))
+ .InvokeRadius(circle.Radius.Meters)
+ .InvokeStrokeWidth(circle.StrokeWidth);
+
+ if (!circle.StrokeColor.IsDefault)
+ opts.InvokeStrokeColor(circle.StrokeColor.ToAndroid());
+
+ if (!circle.FillColor.IsDefault)
+ opts.InvokeFillColor(circle.FillColor.ToAndroid());
+
+ return opts;
+ }
+
+ protected ACircle GetNativeCircle(Circle circle)
+ {
+ ACircle targetCircle = null;
+
+ if (_circles != null)
+ {
+ for (int i = 0; i < _circles.Count; i++)
+ {
+ var native = _circles[i];
+ if (native.Id == (string)circle.MapElementId)
+ {
+ targetCircle = native;
+ break;
+ }
+ }
+ }
+
+ return targetCircle;
+ }
+
+ protected Circle GetFormsCircle(ACircle circle)
+ {
+ Circle targetCircle = null;
+
+ for (int i = 0; i < Element.MapElements.Count; i++)
+ {
+ var mapElement = Element.MapElements[i];
+ if ((string)mapElement.MapElementId == circle.Id)
+ {
+ targetCircle = mapElement as Circle;
+ break;
+ }
+ }
+
+ return targetCircle;
+ }
+
+ void CircleOnPropertyChanged(Circle formsCircle, PropertyChangedEventArgs e)
+ {
+ var nativeCircle = GetNativeCircle(formsCircle);
+
+ if (nativeCircle == null)
+ {
+ return;
+ }
+
+ if (e.PropertyName == Circle.FillColorProperty.PropertyName)
+ {
+ nativeCircle.FillColor = formsCircle.FillColor.ToAndroid();
+ }
+ else if (e.PropertyName == Circle.CenterProperty.PropertyName)
+ {
+ nativeCircle.Center = new LatLng(formsCircle.Center.Latitude, formsCircle.Center.Longitude);
+ }
+ else if (e.PropertyName == Circle.RadiusProperty.PropertyName)
+ {
+ nativeCircle.Radius = formsCircle.Radius.Meters;
+ }
+ else if (e.PropertyName == MapElement.StrokeColorProperty.PropertyName)
+ {
+ nativeCircle.StrokeColor = formsCircle.StrokeColor.ToAndroid();
+ }
+ else if(e.PropertyName == MapElement.StrokeWidthProperty.PropertyName)
+ {
+ nativeCircle.StrokeWidth = formsCircle.StrokeWidth;
+ }
+ }
+
+ void AddCircle(Circle circle)
+ {
+ var map = NativeMap;
+ if (map == null)
+ {
+ return;
+ }
+
+ if (_circles == null)
+ {
+ _circles = new List();
+ }
+
+ var options = CreateCircleOptions(circle);
+ var nativeCircle = map.AddCircle(options);
+
+ circle.MapElementId = nativeCircle.Id;
+
+ _circles.Add(nativeCircle);
+ }
+
+ void RemoveCircle(Circle circle)
+ {
+ var native = GetNativeCircle(circle);
+
+ if (native != null)
+ {
+ native.Remove();
+ _circles.Remove(native);
+ }
+ }
+
+ #endregion
+
+
void SetUserVisible()
{
GoogleMap map = NativeMap;
diff --git a/Xamarin.Forms.Maps.UWP/MapRenderer.cs b/Xamarin.Forms.Maps.UWP/MapRenderer.cs
index 285cf73beee..a557fe5c19b 100644
--- a/Xamarin.Forms.Maps.UWP/MapRenderer.cs
+++ b/Xamarin.Forms.Maps.UWP/MapRenderer.cs
@@ -214,6 +214,9 @@ void LoadMapElements(IEnumerable mapElements)
case Polygon polygon:
nativeMapElement = LoadPolygon(polygon);
break;
+ case Circle circle:
+ nativeMapElement = LoadCircle(circle);
+ break;
}
Control.MapElements.Add(nativeMapElement);
@@ -242,6 +245,9 @@ void MapElementPropertyChanged(object sender, PropertyChangedEventArgs e)
case Polygon polygon:
OnPolygonPropertyChanged(polygon, e);
break;
+ case Circle circle:
+ OnCirclePropertyChanged(circle, e);
+ break;
}
}
@@ -344,6 +350,49 @@ void OnPolygonPropertyChanged(Polygon polygon, PropertyChangedEventArgs e)
#endregion
+ #region Circles
+
+ protected virtual MapPolygon LoadCircle(Circle circle)
+ {
+ return new MapPolygon()
+ {
+ Path = PositionsToGeopath(circle.ToCircumferencePositions()),
+ StrokeColor = circle.StrokeColor.IsDefault ? Colors.Black : circle.StrokeColor.ToWindowsColor(),
+ StrokeThickness = circle.StrokeWidth,
+ FillColor = circle.FillColor.ToWindowsColor()
+ };
+ }
+
+ void OnCirclePropertyChanged(Circle circle, PropertyChangedEventArgs e)
+ {
+ var mapPolygon = (MapPolygon)circle.MapElementId;
+
+ if (mapPolygon == null)
+ {
+ return;
+ }
+
+ if (e.PropertyName == MapElement.StrokeColorProperty.PropertyName)
+ {
+ mapPolygon.StrokeColor = circle.StrokeColor.IsDefault ? Colors.Black : circle.StrokeColor.ToWindowsColor();
+ }
+ else if (e.PropertyName == MapElement.StrokeWidthProperty.PropertyName)
+ {
+ mapPolygon.StrokeThickness = circle.StrokeWidth;
+ }
+ else if (e.PropertyName == Circle.FillColorProperty.PropertyName)
+ {
+ mapPolygon.FillColor = circle.FillColor.ToWindowsColor();
+ }
+ else if (e.PropertyName == Circle.CenterProperty.PropertyName ||
+ e.PropertyName == Circle.RadiusProperty.PropertyName)
+ {
+ mapPolygon.Path = PositionsToGeopath(circle.ToCircumferencePositions());
+ }
+ }
+
+ #endregion
+
async Task UpdateIsShowingUser(bool moveToLocation = true)
{
if (Control == null || Element == null)
diff --git a/Xamarin.Forms.Maps.iOS/MapRenderer.cs b/Xamarin.Forms.Maps.iOS/MapRenderer.cs
index 57dc77fdf9b..e718e384104 100644
--- a/Xamarin.Forms.Maps.iOS/MapRenderer.cs
+++ b/Xamarin.Forms.Maps.iOS/MapRenderer.cs
@@ -531,6 +531,11 @@ void AddMapElements(IEnumerable mapElements)
.Select(position => new CLLocationCoordinate2D(position.Latitude, position.Longitude))
.ToArray());
break;
+ case Circle circle:
+ overlay = MKCircle.Circle(
+ new CLLocationCoordinate2D(circle.Center.Latitude, circle.Center.Longitude),
+ circle.Radius.Meters);
+ break;
}
element.MapElementId = overlay;
@@ -566,6 +571,8 @@ protected virtual MKOverlayRenderer GetViewForOverlay(MKMapView mapview, IMKOver
return GetViewForPolyline(polyline);
case MKPolygon polygon:
return GetViewForPolygon(polygon);
+ case MKCircle circle:
+ return GetViewForCircle(circle);
}
return null;
@@ -634,5 +641,38 @@ protected virtual MKPolygonRenderer GetViewForPolygon(MKPolygon mkPolygon)
LineWidth = targetPolygon.StrokeWidth
};
}
+
+ protected virtual MKCircleRenderer GetViewForCircle(MKCircle mkCircle)
+ {
+ var map = (Map)Element;
+ Circle targetCircle = null;
+
+ for (int i = 0; i < map.MapElements.Count; i++)
+ {
+ var element = map.MapElements[i];
+ if (ReferenceEquals(element.MapElementId, mkCircle))
+ {
+ targetCircle = (Circle)element;
+ break;
+ }
+ }
+
+ if (targetCircle == null)
+ {
+ return null;
+ }
+
+ return new MKCircleRenderer(mkCircle)
+ {
+#if __MOBILE__
+ StrokeColor = targetCircle.StrokeColor.ToUIColor(Color.Black),
+ FillColor = targetCircle.FillColor.ToUIColor(),
+#else
+ StrokeColor = targetCircle.StrokeColor.ToNSColor(Color.Black),
+ FillColor = targetCircle.FillColor.ToNSColor(),
+#endif
+ LineWidth = targetCircle.StrokeWidth
+ };
+ }
}
}
diff --git a/Xamarin.Forms.Maps/Circle.cs b/Xamarin.Forms.Maps/Circle.cs
new file mode 100644
index 00000000000..d36a2aa5a53
--- /dev/null
+++ b/Xamarin.Forms.Maps/Circle.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Xamarin.Forms.Maps
+{
+ public class Circle : MapElement
+ {
+ public static readonly BindableProperty CenterProperty = BindableProperty.Create(
+ nameof(Center),
+ typeof(Position),
+ typeof(Circle),
+ default(Position));
+
+ public static readonly BindableProperty RadiusProperty = BindableProperty.Create(
+ nameof(Radius),
+ typeof(Distance),
+ typeof(Circle),
+ default(Distance));
+
+ public static readonly BindableProperty FillColorProperty = BindableProperty.Create(
+ nameof(FillColor),
+ typeof(Color),
+ typeof(Circle),
+ Color.Default);
+
+ public Position Center
+ {
+ get => (Position)GetValue(CenterProperty);
+ set => SetValue(CenterProperty, value);
+ }
+
+ public Distance Radius
+ {
+ get => (Distance)GetValue(RadiusProperty);
+ set => SetValue(RadiusProperty, value);
+ }
+
+ public Color FillColor
+ {
+ get => (Color)GetValue(FillColorProperty);
+ set => SetValue(FillColorProperty, value);
+ }
+ }
+}
diff --git a/Xamarin.Forms.Maps/Distance.cs b/Xamarin.Forms.Maps/Distance.cs
index 7125f086999..f92cfe579d2 100644
--- a/Xamarin.Forms.Maps/Distance.cs
+++ b/Xamarin.Forms.Maps/Distance.cs
@@ -1,4 +1,5 @@
-using System.Diagnostics;
+using System;
+using System.Diagnostics;
namespace Xamarin.Forms.Maps
{
@@ -48,6 +49,26 @@ public static Distance FromKilometers(double kilometers)
return new Distance(kilometers * MetersPerKilometer);
}
+ public static Distance BetweenPositions(Position position1, Position position2)
+ {
+ var latitude1 = position1.Latitude.ToRadians();
+ var longitude1 = position1.Longitude.ToRadians();
+
+ var latitude2 = position2.Latitude.ToRadians();
+ var longitude2 = position2.Longitude.ToRadians();
+
+ var distance = Math.Sin((latitude2 - latitude1) / 2.0);
+ distance *= distance;
+
+ var intermediate = Math.Sin((longitude2 - longitude1) / 2.0);
+ intermediate *= intermediate;
+
+ distance = distance + Math.Cos(latitude1) * Math.Cos(latitude2) * intermediate;
+ distance = 2 * GeographyUtils.EarthRadiusKm * Math.Atan2(Math.Sqrt(distance), Math.Sqrt(1 - distance));
+
+ return FromKilometers(distance);
+ }
+
public bool Equals(Distance other)
{
return Meters.Equals(other.Meters);
diff --git a/Xamarin.Forms.Maps/GeographyUtils.cs b/Xamarin.Forms.Maps/GeographyUtils.cs
new file mode 100644
index 00000000000..33bf54ec840
--- /dev/null
+++ b/Xamarin.Forms.Maps/GeographyUtils.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+
+namespace Xamarin.Forms.Maps
+{
+ public static class GeographyUtils
+ {
+ internal const double EarthRadiusKm = 6371;
+
+ public static double ToRadians(this double degrees)
+ {
+ return degrees * Math.PI / 180.0;
+ }
+
+ public static double ToDegrees(this double radians)
+ {
+ return radians / Math.PI * 180.0;
+ }
+
+ public static List ToCircumferencePositions(this Circle circle)
+ {
+ var positions = new List();
+ double centerLatitude = circle.Center.Latitude.ToRadians();
+ double centerLongitude = circle.Center.Longitude.ToRadians();
+ double distance = circle.Radius.Kilometers / GeographyUtils.EarthRadiusKm;
+
+ for (int angle = 0; angle <= 360; angle++)
+ {
+ double angleInRadians = ((double)angle).ToRadians();
+ double latitude = Math.Asin(Math.Sin(centerLatitude) * Math.Cos(distance) +
+ Math.Cos(centerLatitude) * Math.Sin(distance) * Math.Cos(angleInRadians));
+ double longitude = centerLongitude +
+ Math.Atan2(Math.Sin(angleInRadians) * Math.Sin(distance) * Math.Cos(centerLatitude),
+ Math.Cos(distance) - Math.Sin(centerLatitude) * Math.Sin(latitude));
+
+ positions.Add(new Position(latitude.ToDegrees(), longitude.ToDegrees()));
+ }
+
+ return positions;
+ }
+ }
+}
diff --git a/Xamarin.Forms.Maps/MapSpan.cs b/Xamarin.Forms.Maps/MapSpan.cs
index 883e630f733..10a9c6219b8 100644
--- a/Xamarin.Forms.Maps/MapSpan.cs
+++ b/Xamarin.Forms.Maps/MapSpan.cs
@@ -4,8 +4,7 @@ namespace Xamarin.Forms.Maps
{
public sealed class MapSpan
{
- const double EarthRadiusKm = 6371;
- const double EarthCircumferenceKm = EarthRadiusKm * 2 * Math.PI;
+ const double EarthCircumferenceKm = GeographyUtils.EarthRadiusKm * 2 * Math.PI;
const double MinimumRangeDegrees = 0.001 / EarthCircumferenceKm * 360; // 1 meter
public MapSpan(Position center, double latitudeDegrees, double longitudeDegrees)
diff --git a/Xamarin.Forms.Material.Android/MaterialButtonRenderer.cs b/Xamarin.Forms.Material.Android/MaterialButtonRenderer.cs
index 4cc7a986e7e..f117ad15fca 100644
--- a/Xamarin.Forms.Material.Android/MaterialButtonRenderer.cs
+++ b/Xamarin.Forms.Material.Android/MaterialButtonRenderer.cs
@@ -27,7 +27,6 @@
using AView = Android.Views.View;
using Xamarin.Forms.Platform.Android;
-
namespace Xamarin.Forms.Material.Android
{
public class MaterialButtonRenderer : MButton,
diff --git a/Xamarin.Forms.Material.Android/MaterialFormsTextInputLayoutBase.cs b/Xamarin.Forms.Material.Android/MaterialFormsTextInputLayoutBase.cs
index 9bff66c5635..df715afc8fd 100644
--- a/Xamarin.Forms.Material.Android/MaterialFormsTextInputLayoutBase.cs
+++ b/Xamarin.Forms.Material.Android/MaterialFormsTextInputLayoutBase.cs
@@ -67,7 +67,7 @@ void ResetTextColors(Color formsTextColor, Color formsPlaceHolderColor)
EditText.SetTextColor(new ColorStateList(s_colorStates, new[] { textColor, textColor }));
}
- internal void ApplyTheme(Color formsTextColor, Color formsPlaceHolderColor)
+ public virtual void ApplyTheme(Color formsTextColor, Color formsPlaceHolderColor)
{
if (_disposed)
return;
@@ -116,7 +116,7 @@ void OnFocusChange(object sender, FocusChangeEventArgs e)
}
- internal void SetHint(string hint, VisualElement element)
+ public virtual void SetHint(string hint, VisualElement element)
{
HintEnabled = !string.IsNullOrWhiteSpace(hint);
if (HintEnabled)
diff --git a/Xamarin.Forms.Platform.Android/AppCompat/ButtonRenderer.cs b/Xamarin.Forms.Platform.Android/AppCompat/ButtonRenderer.cs
index 15706539272..78e9ccc6960 100644
--- a/Xamarin.Forms.Platform.Android/AppCompat/ButtonRenderer.cs
+++ b/Xamarin.Forms.Platform.Android/AppCompat/ButtonRenderer.cs
@@ -10,7 +10,6 @@
using Android.Util;
using Android.Views;
using Xamarin.Forms.Platform.Android.FastRenderers;
-using Xamarin.Forms.Internals;
using Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
using AColor = Android.Graphics.Color;
using AView = Android.Views.View;
diff --git a/Xamarin.Forms.Platform.Android/AppCompat/FormsAppCompatActivity.cs b/Xamarin.Forms.Platform.Android/AppCompat/FormsAppCompatActivity.cs
index 4dc706bbd80..7afb696cf45 100644
--- a/Xamarin.Forms.Platform.Android/AppCompat/FormsAppCompatActivity.cs
+++ b/Xamarin.Forms.Platform.Android/AppCompat/FormsAppCompatActivity.cs
@@ -316,6 +316,7 @@ void OnCreate(
_powerSaveModeBroadcastReceiver = new PowerSaveModeBroadcastReceiver();
}
+ ContextExtensions.SetDesignerContext(_layout);
Profile.FrameEnd();
}
@@ -511,7 +512,6 @@ void InternalSetPage(Page page)
PopupManager.ResetBusyCount(this);
Platform = new AppCompat.Platform(this);
-
Platform.SetPage(page);
_layout.AddView(Platform);
_layout.BringToFront();
diff --git a/Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs b/Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs
index 0c0f8551fd2..c6724dad7ea 100644
--- a/Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs
+++ b/Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs
@@ -573,6 +573,8 @@ void CurrentOnPropertyChanged(object sender, PropertyChangedEventArgs e)
else if (e.PropertyName == NavigationPage.TitleIconImageSourceProperty.PropertyName ||
e.PropertyName == NavigationPage.TitleViewProperty.PropertyName)
UpdateToolbar();
+ else if (e.PropertyName == NavigationPage.IconColorProperty.PropertyName)
+ UpdateToolbar();
}
#pragma warning disable 1998 // considered for removal
@@ -1019,6 +1021,10 @@ void UpdateToolbar()
if (!textColor.IsDefault)
bar.SetTitleTextColor(textColor.ToAndroid().ToArgb());
+ Color navIconColor = NavigationPage.GetIconColor(Current);
+ if (!navIconColor.IsDefault && bar.NavigationIcon != null)
+ DrawableExtensions.SetColorFilter(bar.NavigationIcon, navIconColor, FilterMode.SrcAtop);
+
bar.Title = currentPage?.Title ?? string.Empty;
if (_toolbar.NavigationIcon != null && !textColor.IsDefault)
diff --git a/Xamarin.Forms.Platform.Android/AppCompat/RadioButtonRenderer.cs b/Xamarin.Forms.Platform.Android/AppCompat/RadioButtonRenderer.cs
new file mode 100644
index 00000000000..5a032f98306
--- /dev/null
+++ b/Xamarin.Forms.Platform.Android/AppCompat/RadioButtonRenderer.cs
@@ -0,0 +1,399 @@
+using System;
+using System.ComponentModel;
+using Android.Content;
+using Android.Graphics;
+#if __ANDROID_29__
+using AndroidX.Core.View;
+using AndroidX.AppCompat.Widget;
+#else
+using Android.Support.V7.Widget;
+using Android.Support.V4.View;
+#endif
+using Android.Util;
+using Android.Views;
+using Xamarin.Forms.Internals;
+using Xamarin.Forms.Platform.Android.FastRenderers;
+using Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
+using AColor = Android.Graphics.Color;
+using AView = Android.Views.View;
+using Android.Graphics.Drawables;
+using Android.Widget;
+
+namespace Xamarin.Forms.Platform.Android
+{
+ public class RadioButtonRenderer : AppCompatRadioButton,
+ IBorderVisualElementRenderer, IButtonLayoutRenderer, IVisualElementRenderer, IViewRenderer, ITabStop,
+ AView.IOnAttachStateChangeListener, AView.IOnFocusChangeListener, AView.IOnClickListener, AView.IOnTouchListener,
+ CompoundButton.IOnCheckedChangeListener
+ {
+ float _defaultFontSize;
+ int? _defaultLabelFor;
+ Typeface _defaultTypeface;
+ bool _isDisposed;
+ bool _inputTransparent;
+ Lazy _textColorSwitcher;
+ AutomationPropertiesProvider _automationPropertiesProvider;
+ VisualElementTracker _tracker;
+ VisualElementRenderer _visualElementRenderer;
+ BorderBackgroundManager _backgroundTracker;
+ ButtonLayoutManager _buttonLayoutManager;
+ IPlatformElementConfiguration _platformElementConfiguration;
+ Button _button;
+
+ public event EventHandler ElementChanged;
+ public event EventHandler ElementPropertyChanged;
+
+ public RadioButtonRenderer(Context context) : base(context)
+ {
+ Initialize();
+ }
+
+ protected Button Element => Button;
+ protected AppCompatRadioButton Control => this;
+
+ VisualElement IBorderVisualElementRenderer.Element => Element;
+
+ VisualElement IVisualElementRenderer.Element => Element;
+ AView IVisualElementRenderer.View => this;
+ ViewGroup IVisualElementRenderer.ViewGroup => null;
+ VisualElementTracker IVisualElementRenderer.Tracker => _tracker;
+
+ Button Button
+ {
+ get => _button;
+ set
+ {
+ _button = value;
+ _platformElementConfiguration = null;
+ }
+ }
+
+ AView ITabStop.TabStop => this;
+
+ void IOnClickListener.OnClick(AView v) => ButtonElementManager.OnClick(Button, Button, v);
+
+ bool IOnTouchListener.OnTouch(AView v, MotionEvent e) => ButtonElementManager.OnTouch(Button, Button, v, e);
+
+ void IOnAttachStateChangeListener.OnViewAttachedToWindow(AView attachedView) =>
+ _buttonLayoutManager.OnViewAttachedToWindow(attachedView);
+
+ void IOnAttachStateChangeListener.OnViewDetachedFromWindow(AView detachedView) =>
+ _buttonLayoutManager.OnViewDetachedFromWindow(detachedView);
+
+ void IOnFocusChangeListener.OnFocusChange(AView v, bool hasFocus)
+ {
+ ((IElementController)Button).SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, hasFocus);
+ }
+
+ SizeRequest IVisualElementRenderer.GetDesiredSize(int widthConstraint, int heightConstraint)
+ {
+ return _buttonLayoutManager.GetDesiredSize(widthConstraint, heightConstraint);
+ }
+
+ void IVisualElementRenderer.SetElement(VisualElement element)
+ {
+ if (element == null)
+ {
+ throw new ArgumentNullException(nameof(element));
+ }
+
+ if (!(element is Button))
+ {
+ throw new ArgumentException($"{nameof(element)} must be of type {nameof(Button)}");
+ }
+
+ VisualElement oldElement = Button;
+ Button = (Button)element;
+
+ Performance.Start(out string reference);
+
+ if (oldElement != null)
+ {
+ oldElement.PropertyChanged -= OnElementPropertyChanged;
+ }
+
+
+ element.PropertyChanged += OnElementPropertyChanged;
+
+ if (_tracker == null)
+ {
+ // Can't set up the tracker in the constructor because it access the Element (for now)
+ SetTracker(new VisualElementTracker(this));
+ }
+ if (_visualElementRenderer == null)
+ {
+ _visualElementRenderer = new VisualElementRenderer(this);
+ }
+
+ OnElementChanged(new ElementChangedEventArgs(oldElement as Button, Button));
+
+ SendVisualElementInitialized(element, this);
+
+ Performance.Stop(reference);
+ }
+
+ void IVisualElementRenderer.SetLabelFor(int? id)
+ {
+ if (_defaultLabelFor == null)
+ {
+ _defaultLabelFor = ViewCompat.GetLabelFor(this);
+ }
+
+ ViewCompat.SetLabelFor(this, (int)(id ?? _defaultLabelFor));
+ }
+
+ void IVisualElementRenderer.UpdateLayout() => _tracker?.UpdateLayout();
+
+ void IViewRenderer.MeasureExactly()
+ {
+ ViewRenderer.MeasureExactly(this, Element, Context);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (_isDisposed)
+ {
+ return;
+ }
+
+ _isDisposed = true;
+
+ if (disposing)
+ {
+ SetOnClickListener(null);
+ SetOnTouchListener(null);
+ RemoveOnAttachStateChangeListener(this);
+ OnFocusChangeListener = null;
+ SetOnCheckedChangeListener(null);
+
+ if (Element != null)
+ {
+ Element.PropertyChanged -= OnElementPropertyChanged;
+ }
+
+ _automationPropertiesProvider?.Dispose();
+ _tracker?.Dispose();
+ _visualElementRenderer?.Dispose();
+ _backgroundTracker?.Dispose();
+ _backgroundTracker = null;
+ _buttonLayoutManager?.Dispose();
+ _buttonLayoutManager = null;
+
+ if (Element != null)
+ {
+ if (Platform.GetRenderer(Element) == this)
+ Element.ClearValue(Platform.RendererProperty);
+ }
+ }
+
+ base.Dispose(disposing);
+ }
+
+ public override bool OnTouchEvent(MotionEvent e)
+ {
+ if (!Enabled || (_inputTransparent && Enabled))
+ return false;
+
+ return base.OnTouchEvent(e);
+ }
+
+ protected virtual void OnElementChanged(ElementChangedEventArgs e)
+ {
+ if (e.NewElement != null && !_isDisposed)
+ {
+ this.EnsureId();
+
+ _textColorSwitcher = new Lazy(
+ () => new TextColorSwitcher(TextColors, e.NewElement.UseLegacyColorManagement()));
+
+ UpdateFont();
+ UpdateTextColor();
+ UpdateInputTransparent();
+ UpdateBackgroundColor();
+ _buttonLayoutManager?.Update();
+ UpdateButtonImage(true);
+ UpdateIsChecked();
+ ElevationHelper.SetElevation(this, e.NewElement);
+ }
+
+ ElementChanged?.Invoke(this, new VisualElementChangedEventArgs(e.OldElement, e.NewElement));
+ }
+
+ protected virtual void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == Button.TextColorProperty.PropertyName)
+ {
+ UpdateTextColor();
+ }
+ else if (e.PropertyName == Button.FontProperty.PropertyName)
+ {
+ UpdateFont();
+ }
+ else if (e.PropertyName == VisualElement.InputTransparentProperty.PropertyName)
+ {
+ UpdateInputTransparent();
+ }
+ else if (e.PropertyName == RadioButton.IsCheckedProperty.PropertyName)
+ {
+ UpdateIsChecked();
+ }
+ else if (e.PropertyName == RadioButton.ButtonSourceProperty.PropertyName)
+ {
+ UpdateButtonImage(false);
+ }
+
+ ElementPropertyChanged?.Invoke(this, e);
+ }
+
+ protected override void OnLayout(bool changed, int l, int t, int r, int b)
+ {
+ _buttonLayoutManager?.OnLayout(changed, l, t, r, b);
+ base.OnLayout(changed, l, t, r, b);
+ }
+
+ void SetTracker(VisualElementTracker tracker)
+ {
+ _tracker = tracker;
+ }
+
+ void UpdateBackgroundColor()
+ {
+ _backgroundTracker?.UpdateDrawable();
+ }
+
+ internal void OnNativeFocusChanged(bool hasFocus)
+ {
+ }
+
+ internal void SendVisualElementInitialized(VisualElement element, AView nativeView)
+ {
+ element.SendViewInitialized(nativeView);
+ }
+
+ void Initialize()
+ {
+ _automationPropertiesProvider = new AutomationPropertiesProvider(this);
+ _buttonLayoutManager = new ButtonLayoutManager(this);
+ _backgroundTracker = new BorderBackgroundManager(this);
+
+ SoundEffectsEnabled = false;
+ SetOnClickListener(this);
+ SetOnTouchListener(this);
+ AddOnAttachStateChangeListener(this);
+ OnFocusChangeListener = this;
+ SetOnCheckedChangeListener(this);
+
+ Tag = this;
+ }
+
+ void UpdateFont()
+ {
+ if (Element == null || _isDisposed)
+ {
+ return;
+ }
+
+ Font font = Button.Font;
+
+ if (font == Font.Default && _defaultFontSize == 0f)
+ {
+ return;
+ }
+
+ if (_defaultFontSize == 0f)
+ {
+ _defaultTypeface = Typeface;
+ _defaultFontSize = TextSize;
+ }
+
+ if (font == Font.Default)
+ {
+ Typeface = _defaultTypeface;
+ SetTextSize(ComplexUnitType.Px, _defaultFontSize);
+ }
+ else
+ {
+ Typeface = font.ToTypeface();
+ SetTextSize(ComplexUnitType.Sp, font.ToScaledPixel());
+ }
+ }
+
+ void UpdateInputTransparent()
+ {
+ if (Element == null || _isDisposed)
+ {
+ return;
+ }
+
+ _inputTransparent = Element.InputTransparent;
+ }
+
+ void UpdateTextColor()
+ {
+ if (Element == null || _isDisposed || _textColorSwitcher == null)
+ {
+ return;
+ }
+
+ _textColorSwitcher.Value.UpdateTextColor(this, Button.TextColor);
+ }
+
+ void UpdateButtonImage(bool isInitializing)
+ {
+ if (Element == null || _isDisposed)
+ return;
+
+ ImageSource buttonSource = ((RadioButton)Element).ButtonSource;
+ if (buttonSource != null && !buttonSource.IsEmpty)
+ {
+ Drawable currButtonImage = Control.ButtonDrawable;
+
+ this.ApplyDrawableAsync(RadioButton.ButtonSourceProperty, Context, image =>
+ {
+ if (image == currButtonImage)
+ return;
+ Control.SetButtonDrawable(image);
+
+ Element.InvalidateMeasureNonVirtual(InvalidationTrigger.MeasureChanged);
+ });
+ }
+ else if(!isInitializing)
+ Control.SetButtonDrawable(null);
+ }
+
+ void UpdateIsChecked()
+ {
+ if (Element == null || Control == null)
+ return;
+
+ Checked = ((RadioButton)Element).IsChecked;
+ }
+
+ void IOnCheckedChangeListener.OnCheckedChanged(CompoundButton buttonView, bool isChecked)
+ {
+ ((IElementController)Element).SetValueFromRenderer(RadioButton.IsCheckedProperty, isChecked);
+ }
+
+ float IBorderVisualElementRenderer.ShadowRadius => ShadowRadius;
+ float IBorderVisualElementRenderer.ShadowDx => ShadowDx;
+ float IBorderVisualElementRenderer.ShadowDy => ShadowDy;
+ AColor IBorderVisualElementRenderer.ShadowColor => ShadowColor;
+ bool IBorderVisualElementRenderer.UseDefaultPadding() => OnThisPlatform().UseDefaultPadding();
+ bool IBorderVisualElementRenderer.UseDefaultShadow() => OnThisPlatform().UseDefaultShadow();
+ bool IBorderVisualElementRenderer.IsShadowEnabled() => true;
+ AView IBorderVisualElementRenderer.View => this;
+
+ IPlatformElementConfiguration OnThisPlatform()
+ {
+ if (_platformElementConfiguration == null)
+ _platformElementConfiguration = Button.OnThisPlatform();
+
+ return _platformElementConfiguration;
+ }
+
+ AppCompatButton IButtonLayoutRenderer.View => null;
+
+ Button IButtonLayoutRenderer.Element => this.Element;
+
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/ButtonLayoutManager.cs b/Xamarin.Forms.Platform.Android/ButtonLayoutManager.cs
index ba34b62d95b..a89af94f3d0 100644
--- a/Xamarin.Forms.Platform.Android/ButtonLayoutManager.cs
+++ b/Xamarin.Forms.Platform.Android/ButtonLayoutManager.cs
@@ -14,6 +14,7 @@
#endif
using Xamarin.Forms.Internals;
using AView = Android.Views.View;
+using AButton = Android.Widget.Button;
namespace Xamarin.Forms.Platform.Android
{
@@ -59,7 +60,7 @@ public ButtonLayoutManager(IButtonLayoutRenderer renderer,
_maintainLegacyMeasurements = maintainLegacyMeasurements;
}
- AppCompatButton View => _renderer?.View;
+ AButton View => _renderer?.View ?? _renderer as AButton;
Context Context => _renderer?.View?.Context;
@@ -111,7 +112,7 @@ public void OnLayout(bool changed, int left, int top, int right, int bottom)
if (_disposed || _renderer == null || _element == null)
return;
- AppCompatButton view = View;
+ AButton view = View;
if (view == null)
return;
@@ -222,7 +223,7 @@ void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
void UpdatePadding()
{
- AppCompatButton view = View;
+ AButton view = View;
if (view == null)
return;
@@ -260,7 +261,7 @@ bool UpdateTextAndImage()
if (_disposed || _renderer == null || _element == null)
return false;
- AppCompatButton view = View;
+ AButton view = View;
if (view == null)
return false;
@@ -282,7 +283,7 @@ void UpdateImage()
if (_disposed || _renderer == null || _element == null)
return;
- AppCompatButton view = View;
+ AButton view = View;
if (view == null)
return;
diff --git a/Xamarin.Forms.Platform.Android/Cells/ViewCellRenderer.cs b/Xamarin.Forms.Platform.Android/Cells/ViewCellRenderer.cs
index c335f8f0f5c..76c93ca9e84 100644
--- a/Xamarin.Forms.Platform.Android/Cells/ViewCellRenderer.cs
+++ b/Xamarin.Forms.Platform.Android/Cells/ViewCellRenderer.cs
@@ -20,7 +20,7 @@ protected override AView GetCellCore(Cell item, AView convertView, ViewGroup par
if (container != null)
{
container.Update(cell);
- Performance.Stop(reference);
+ Performance.Stop(reference, "GetCellCore");
return container;
}
diff --git a/Xamarin.Forms.Platform.Android/ContextExtensions.cs b/Xamarin.Forms.Platform.Android/ContextExtensions.cs
index fdb05ebdaf2..18821cd3f52 100644
--- a/Xamarin.Forms.Platform.Android/ContextExtensions.cs
+++ b/Xamarin.Forms.Platform.Android/ContextExtensions.cs
@@ -130,14 +130,32 @@ internal static bool IsDesignerContext(this Context context)
if (_isDesignerContext.HasValue)
return _isDesignerContext.Value;
+ context.SetDesignerContext();
+ return _isDesignerContext.Value;
+ }
+
+ internal static void SetDesignerContext(this Context context)
+ {
+ if (_isDesignerContext.HasValue)
+ return;
+
if (context == null)
_isDesignerContext = false;
else if ($"{context}".Contains("com.android.layoutlib.bridge.android.BridgeContext"))
_isDesignerContext = true;
- else if (context is ContextWrapper contextWrapper)
- return contextWrapper.BaseContext.IsDesignerContext();
else
_isDesignerContext = false;
+ }
+
+ internal static void SetDesignerContext(global::Android.Views.View view)
+ {
+ _isDesignerContext = view.IsInEditMode;
+ }
+
+ internal static bool IsDesignerContext(this global::Android.Views.View view)
+ {
+ if (!_isDesignerContext.HasValue)
+ SetDesignerContext(view);
return _isDesignerContext.Value;
}
diff --git a/Xamarin.Forms.Platform.Android/Extensions/TextViewExtensions.cs b/Xamarin.Forms.Platform.Android/Extensions/TextViewExtensions.cs
index e3a7096cfc0..46425d8ef6d 100644
--- a/Xamarin.Forms.Platform.Android/Extensions/TextViewExtensions.cs
+++ b/Xamarin.Forms.Platform.Android/Extensions/TextViewExtensions.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using Android.Text;
using Android.Widget;
using System.Collections.Generic;
@@ -74,22 +74,20 @@ public static void SetLineBreakMode(this TextView textView, Label label)
public static void RecalculateSpanPositions(this TextView textView, Label element, SpannableString spannableString, SizeRequest finalSize)
{
- var layout = textView.Layout;
- if (layout == null)
+ if (element?.FormattedText?.Spans == null || element.FormattedText.Spans.Count == 0)
return;
- if (element?.FormattedText?.Spans == null || element.FormattedText.Spans.Count == 0)
+ var labelWidth = finalSize.Request.Width;
+ if (labelWidth <= 0 || finalSize.Request.Height <= 0)
return;
if (spannableString == null || spannableString.IsDisposed())
return;
- var labelWidth = finalSize.Request.Width;
- if (labelWidth <= 0 || finalSize.Request.Height <= 0)
+ var layout = textView.Layout;
+ if (layout == null)
return;
- var text = spannableString.ToString();
-
int next = 0;
int count = 0;
IList totalLineHeights = new List();
diff --git a/Xamarin.Forms.Platform.Android/FastRenderers/AutomationPropertiesProvider.cs b/Xamarin.Forms.Platform.Android/FastRenderers/AutomationPropertiesProvider.cs
index 9406e4d4540..edbef0b81ca 100644
--- a/Xamarin.Forms.Platform.Android/FastRenderers/AutomationPropertiesProvider.cs
+++ b/Xamarin.Forms.Platform.Android/FastRenderers/AutomationPropertiesProvider.cs
@@ -1,5 +1,10 @@
using System;
using System.ComponentModel;
+#if __ANDROID_29__
+using AndroidX.Core.View;
+#else
+using Android.Support.V4.View;
+#endif
using Android.Views;
using Android.Widget;
using AView = Android.Views.View;
@@ -55,15 +60,31 @@ internal static void SetBasicContentDescription(
if (defaultContentDescription == null)
defaultContentDescription = control.ContentDescription;
-
+
string value = ConcatenateNameAndHelpText(bindableObject);
+ string contentDescription = !string.IsNullOrWhiteSpace(value) ? value : defaultContentDescription;
+ string automationId = (bindableObject as Element)?.AutomationId;
- var contentDescription = !string.IsNullOrWhiteSpace(value) ? value : defaultContentDescription;
-
- if (String.IsNullOrWhiteSpace(contentDescription) && bindableObject is Element element)
- contentDescription = element.AutomationId;
-
- control.ContentDescription = contentDescription;
+ if (!string.IsNullOrWhiteSpace(automationId) && !string.IsNullOrWhiteSpace(contentDescription))
+ {
+ var target = control;
+ if (control is IButtonLayoutRenderer buttonLayoutRenderer)
+ {
+ target = buttonLayoutRenderer.View;
+ }
+ else if (control is AppCompat.SwitchRenderer switchRenderer)
+ {
+ target = switchRenderer.Control;
+ }
+ target.ContentDescription = automationId;
+ ViewCompat.SetAccessibilityDelegate(target, new NameAndHelpTextAccessibilityDelegate {
+ AccessibilityText = contentDescription
+ });
+ }
+ else
+ {
+ control.ContentDescription = string.IsNullOrWhiteSpace(contentDescription) ? automationId : contentDescription;
+ }
}
internal static void SetContentDescription(
@@ -238,15 +259,13 @@ internal static void AccessibilitySettingsChanged(AView control, Element element
internal static string ConcatenateNameAndHelpText(BindableObject Element)
{
- var name = (string)Element.GetValue(AutomationProperties.NameProperty);
+ var name = (string)Element.GetValue(AutomationProperties.NameProperty) ?? (Element as Button)?.Text;
var helpText = (string)Element.GetValue(AutomationProperties.HelpTextProperty);
if (string.IsNullOrWhiteSpace(name))
return helpText;
- if (string.IsNullOrWhiteSpace(helpText))
- return name;
- return $"{name}. {helpText}";
+ return string.IsNullOrWhiteSpace(helpText) ? $"{name}" : $"{name}. {helpText}";
}
void OnElementChanged(object sender, VisualElementChangedEventArgs e)
diff --git a/Xamarin.Forms.Platform.Android/FastRenderers/ButtonRenderer.cs b/Xamarin.Forms.Platform.Android/FastRenderers/ButtonRenderer.cs
index 1748ee2625d..8df9e85360f 100644
--- a/Xamarin.Forms.Platform.Android/FastRenderers/ButtonRenderer.cs
+++ b/Xamarin.Forms.Platform.Android/FastRenderers/ButtonRenderer.cs
@@ -104,7 +104,10 @@ SizeRequest IVisualElementRenderer.GetDesiredSize(int widthConstraint, int heigh
var result = _buttonLayoutManager.GetDesiredSize(widthConstraint, heightConstraint);
- Control.Hint = hint;
+ if (Control.Hint != hint)
+ {
+ Control.Hint = hint;
+ }
return result;
}
diff --git a/Xamarin.Forms.Platform.Android/FastRenderers/NameAndHelpTextAccessibilityDelegate.cs b/Xamarin.Forms.Platform.Android/FastRenderers/NameAndHelpTextAccessibilityDelegate.cs
new file mode 100644
index 00000000000..dab49821db4
--- /dev/null
+++ b/Xamarin.Forms.Platform.Android/FastRenderers/NameAndHelpTextAccessibilityDelegate.cs
@@ -0,0 +1,34 @@
+using System;
+using Android.Runtime;
+
+#if __ANDROID_29__
+using AndroidX.Core.View;
+using AndroidX.Core.View.Accessibiity;
+#else
+using Android.Support.V4.View;
+using Android.Support.V4.View.Accessibility;
+#endif
+
+namespace Xamarin.Forms.Platform.Android.FastRenderers
+{
+ public class NameAndHelpTextAccessibilityDelegate : AccessibilityDelegateCompat
+ {
+
+ public string AccessibilityText { get; set; }
+
+ public NameAndHelpTextAccessibilityDelegate()
+ {
+ }
+
+ protected NameAndHelpTextAccessibilityDelegate(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
+ {
+ }
+
+ public override void OnInitializeAccessibilityNodeInfo(global::Android.Views.View host, AccessibilityNodeInfoCompat info)
+ {
+ base.OnInitializeAccessibilityNodeInfo(host, info);
+ info.ContentDescription = AccessibilityText;
+ }
+
+ }
+}
diff --git a/Xamarin.Forms.Platform.Android/IButtonLayoutRenderer.cs b/Xamarin.Forms.Platform.Android/IButtonLayoutRenderer.cs
index 91acbc20752..f814f7ce99d 100644
--- a/Xamarin.Forms.Platform.Android/IButtonLayoutRenderer.cs
+++ b/Xamarin.Forms.Platform.Android/IButtonLayoutRenderer.cs
@@ -13,4 +13,4 @@ public interface IButtonLayoutRenderer
Button Element { get; }
event EventHandler ElementChanged;
}
-}
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/Renderers/BottomNavigationViewUtils.cs b/Xamarin.Forms.Platform.Android/Renderers/BottomNavigationViewUtils.cs
index 8785e8342c6..39d83df9c91 100644
--- a/Xamarin.Forms.Platform.Android/Renderers/BottomNavigationViewUtils.cs
+++ b/Xamarin.Forms.Platform.Android/Renderers/BottomNavigationViewUtils.cs
@@ -1,16 +1,9 @@
-using System;
+using System;
using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-
-using Android.App;
using Android.Content;
-using Android.OS;
-using Android.Runtime;
using Android.Views;
using Android.Widget;
using AColor = Android.Graphics.Color;
-using AView = Android.Views.View;
using ColorStateList = Android.Content.Res.ColorStateList;
using IMenu = Android.Views.IMenu;
using LP = Android.Views.ViewGroup.LayoutParams;
@@ -74,7 +67,7 @@ internal static async void SetupMenu(
var item = items[i];
using (var title = new Java.Lang.String(item.title))
{
- var menuItem = menu.Add(0, i, 0, title);
+ var menuItem = menu.Add(0, Platform.GenerateViewId(), 0, title);
menuItems.Add(menuItem);
loadTasks.Add(SetMenuItemIcon(menuItem, item.icon, context));
UpdateEnabled(item.tabEnabled, menuItem);
diff --git a/Xamarin.Forms.Platform.Android/Renderers/FormsWebViewClient.cs b/Xamarin.Forms.Platform.Android/Renderers/FormsWebViewClient.cs
index 6c4fb96afc7..a805a0bf9a9 100644
--- a/Xamarin.Forms.Platform.Android/Renderers/FormsWebViewClient.cs
+++ b/Xamarin.Forms.Platform.Android/Renderers/FormsWebViewClient.cs
@@ -37,6 +37,18 @@ public override void OnPageStarted(WView view, string url, Bitmap favicon)
if (_renderer == null || string.IsNullOrWhiteSpace(url) || url == WebViewRenderer.AssetBaseUrl)
return;
+ var cookieManager = CookieManager.Instance;
+ cookieManager.SetAcceptCookie(true);
+ cookieManager.RemoveAllCookie();
+ var cookies = _renderer.Element.Cookies?.GetCookies(new System.Uri(url));
+ for (var i = 0; i < (cookies?.Count ?? -1); i++)
+ {
+ string cookieValue = cookies[i].Value;
+ string cookieDomain = cookies[i].Domain;
+ string cookieName = cookies[i].Name;
+ cookieManager.SetCookie(cookieDomain, cookieName + "=" + cookieValue);
+ }
+
var cancel = false;
if (!url.Equals(_renderer.UrlCanceled, StringComparison.OrdinalIgnoreCase))
cancel = SendNavigatingCanceled(url);
diff --git a/Xamarin.Forms.Platform.Android/Renderers/ListViewAdapter.cs b/Xamarin.Forms.Platform.Android/Renderers/ListViewAdapter.cs
index 45358c0ce2f..4a979d4f756 100644
--- a/Xamarin.Forms.Platform.Android/Renderers/ListViewAdapter.cs
+++ b/Xamarin.Forms.Platform.Android/Renderers/ListViewAdapter.cs
@@ -223,8 +223,13 @@ public override AView GetView(int position, AView convertView, ViewGroup parent)
if (cell == null)
{
cell = GetCellForPosition(position);
+
if (cell == null)
+ {
+ Performance.Stop(reference);
+
return new AView(_context);
+ }
}
}
diff --git a/Xamarin.Forms.Platform.Android/Renderers/SearchBarRenderer.cs b/Xamarin.Forms.Platform.Android/Renderers/SearchBarRenderer.cs
index 5bac13f8589..c955124412d 100644
--- a/Xamarin.Forms.Platform.Android/Renderers/SearchBarRenderer.cs
+++ b/Xamarin.Forms.Platform.Android/Renderers/SearchBarRenderer.cs
@@ -61,7 +61,7 @@ public override SizeRequest GetDesiredSize(int widthConstraint, int heightConstr
protected override SearchView CreateNativeControl()
{
- var context = (Context as ContextThemeWrapper).BaseContext ?? Context;
+ var context = (Context as ContextThemeWrapper)?.BaseContext ?? Context;
return new SearchView(context);
}
diff --git a/Xamarin.Forms.Platform.Android/VisualElementPackager.cs b/Xamarin.Forms.Platform.Android/VisualElementPackager.cs
index 1c846854916..e03f46305f8 100644
--- a/Xamarin.Forms.Platform.Android/VisualElementPackager.cs
+++ b/Xamarin.Forms.Platform.Android/VisualElementPackager.cs
@@ -155,9 +155,9 @@ void AddChild(VisualElement view, IVisualElementRenderer oldRenderer = null, Ren
_childViews.Add(renderer);
}
Performance.Stop(reference, "Add view");
-
- Performance.Stop(reference);
}
+
+ Performance.Stop(reference);
}
void EnsureChildOrder() => EnsureChildOrder(false);
diff --git a/Xamarin.Forms.Platform.MacOS/Properties/AssemblyInfo.cs b/Xamarin.Forms.Platform.MacOS/Properties/AssemblyInfo.cs
index 682ed78c08a..9434fb423ba 100644
--- a/Xamarin.Forms.Platform.MacOS/Properties/AssemblyInfo.cs
+++ b/Xamarin.Forms.Platform.MacOS/Properties/AssemblyInfo.cs
@@ -1,4 +1,5 @@
using Xamarin.Forms;
+using Xamarin.Forms.Internals;
using Xamarin.Forms.Platform.MacOS;
[assembly: Dependency(typeof(Deserializer))]
@@ -14,6 +15,7 @@
[assembly: ExportRenderer(typeof(NavigationPage), typeof(NavigationPageRenderer))]
[assembly: ExportRenderer(typeof(Label), typeof(LabelRenderer))]
[assembly: ExportRenderer(typeof(Button), typeof(ButtonRenderer))]
+[assembly: ExportRenderer(typeof(RadioButton), typeof(RadioButtonRenderer))]
[assembly: ExportRenderer(typeof(BoxView), typeof(BoxViewRenderer))]
[assembly: ExportRenderer(typeof(ScrollView), typeof(ScrollViewRenderer))]
[assembly: ExportRenderer(typeof(ActivityIndicator), typeof(ActivityIndicatorRenderer))]
@@ -41,4 +43,5 @@
[assembly: ExportCell(typeof(ImageCell), typeof(ImageCellRenderer))]
[assembly: ExportCell(typeof(EntryCell), typeof(EntryCellRenderer))]
[assembly: ExportCell(typeof(ViewCell), typeof(ViewCellRenderer))]
-[assembly: ExportCell(typeof(SwitchCell), typeof(SwitchCellRenderer))]
\ No newline at end of file
+[assembly: ExportCell(typeof(SwitchCell), typeof(SwitchCellRenderer))]
+[assembly: Preserve]
diff --git a/Xamarin.Forms.Platform.MacOS/Renderers/RadioButtonRenderer.cs b/Xamarin.Forms.Platform.MacOS/Renderers/RadioButtonRenderer.cs
new file mode 100644
index 00000000000..836214cd457
--- /dev/null
+++ b/Xamarin.Forms.Platform.MacOS/Renderers/RadioButtonRenderer.cs
@@ -0,0 +1,186 @@
+using System;
+using System.ComponentModel;
+using AppKit;
+using Foundation;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+ public class RadioButtonRenderer : ViewRenderer
+ {
+ class FormsNSButton : NSButton
+ {
+ public event Action Pressed;
+
+ public event Action Released;
+
+ public override void MouseDown(NSEvent theEvent)
+ {
+ Pressed?.Invoke();
+
+ base.MouseDown(theEvent);
+
+ Released?.Invoke();
+ }
+ }
+
+ static readonly IntPtr _tokenObserveState = (IntPtr)1;
+
+ protected override void Dispose(bool disposing)
+ {
+ if (Control != null)
+ Control.Activated -= OnButtonActivated;
+
+ var formsButton = Control as FormsNSButton;
+ if (formsButton != null)
+ {
+ formsButton.Pressed -= HandleButtonPressed;
+ formsButton.Released -= HandleButtonReleased;
+ }
+
+ ObserveStateChange(false);
+
+ base.Dispose(disposing);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs e)
+ {
+ base.OnElementChanged(e);
+
+ if (e.NewElement != null)
+ {
+ if (Control == null)
+ {
+ var btn = new FormsNSButton();
+ btn.SetButtonType(NSButtonType.Radio);
+ btn.Pressed += HandleButtonPressed;
+ btn.Released += HandleButtonReleased;
+ SetNativeControl(btn);
+ ObserveStateChange(true);
+
+ Control.Activated += OnButtonActivated;
+ }
+
+ UpdateText();
+ UpdateFont();
+ UpdateBorder();
+ }
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+
+ if (e.PropertyName == RadioButton.TextProperty.PropertyName || e.PropertyName == RadioButton.TextColorProperty.PropertyName)
+ UpdateText();
+ else if (e.PropertyName == RadioButton.FontProperty.PropertyName)
+ UpdateFont();
+ else if (e.PropertyName == RadioButton.BorderWidthProperty.PropertyName ||
+ e.PropertyName == RadioButton.CornerRadiusProperty.PropertyName ||
+ e.PropertyName == RadioButton.BorderColorProperty.PropertyName)
+ UpdateBorder();
+ else if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
+ UpdateBackgroundVisibility();
+ else if (e.PropertyName == RadioButton.IsCheckedProperty.PropertyName)
+ UpdateCheck();
+ }
+
+ void OnButtonActivated(object sender, EventArgs eventArgs)
+ {
+ ((IButtonController)Element)?.SendClicked();
+ }
+
+ void UpdateBackgroundVisibility()
+ {
+ var model = Element;
+ var shouldDrawImage = model.BackgroundColor == Color.Default;
+ if (!shouldDrawImage)
+ Control.Cell.BackgroundColor = model.BackgroundColor.ToNSColor();
+ }
+
+ void UpdateBorder()
+ {
+ var uiButton = Control;
+ var button = Element;
+
+ if (button.BorderColor != Color.Default)
+ uiButton.Layer.BorderColor = button.BorderColor.ToCGColor();
+
+ uiButton.Layer.BorderWidth = (float)button.BorderWidth;
+ uiButton.Layer.CornerRadius = button.CornerRadius;
+
+ UpdateBackgroundVisibility();
+ }
+
+ void UpdateFont()
+ {
+ Control.Font = Element.Font.ToNSFont();
+ }
+
+ void UpdateText()
+ {
+ var color = Element.TextColor;
+ if (color == Color.Default)
+ {
+ Control.Title = Element.Text ?? "";
+ }
+ else
+ {
+ var textWithColor = new NSAttributedString(Element.Text ?? "", font: Element.Font.ToNSFont(), foregroundColor: color.ToNSColor(), paragraphStyle: new NSMutableParagraphStyle() { Alignment = NSTextAlignment.Center });
+ Control.AttributedTitle = textWithColor;
+ }
+ }
+
+ void UpdateCheck()
+ {
+ Control.State = Element.IsChecked ? NSCellStateValue.On : NSCellStateValue.Off;
+ }
+
+ void HandleButtonPressed()
+ {
+ Element?.SendPressed();
+
+ if (!Element.IsChecked)
+ Element.IsChecked = !Element.IsChecked;
+ }
+
+ void HandleButtonReleased()
+ {
+ Element?.SendReleased();
+ }
+
+ void ObserveStateChange(bool observe)
+ {
+ if (observe)
+ {
+ AddObserver(this, "state", NSKeyValueObservingOptions.New | NSKeyValueObservingOptions.Old, _tokenObserveState);
+ }
+ else
+ {
+ RemoveObserver(this, "state", _tokenObserveState);
+ }
+ }
+
+ public override void ObserveValue(NSString keyPath, NSObject ofObject, NSDictionary change, IntPtr ctx)
+ {
+ if (ctx == _tokenObserveState)
+ {
+ OnStateChanged();
+ }
+ else
+ {
+ // invoke the base implementation for unhandled events
+ base.ObserveValue(keyPath, ofObject, change, ctx);
+ }
+ }
+
+ void OnStateChanged()
+ {
+ if (Element == null || Control == null)
+ {
+ return;
+ }
+
+ Element.IsChecked = Control.State == NSCellStateValue.On;
+ }
+ }
+}
diff --git a/Xamarin.Forms.Platform.MacOS/Xamarin.Forms.Platform.macOS.csproj b/Xamarin.Forms.Platform.MacOS/Xamarin.Forms.Platform.macOS.csproj
index 3d2f2b20979..afb85cb76dc 100644
--- a/Xamarin.Forms.Platform.MacOS/Xamarin.Forms.Platform.macOS.csproj
+++ b/Xamarin.Forms.Platform.MacOS/Xamarin.Forms.Platform.macOS.csproj
@@ -115,6 +115,7 @@
+
diff --git a/Xamarin.Forms.Platform.UAP/BoxViewBorderRenderer.cs b/Xamarin.Forms.Platform.UAP/BoxViewBorderRenderer.cs
new file mode 100644
index 00000000000..285d9bcda9a
--- /dev/null
+++ b/Xamarin.Forms.Platform.UAP/BoxViewBorderRenderer.cs
@@ -0,0 +1,68 @@
+using System.ComponentModel;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Automation.Peers;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Shapes;
+
+namespace Xamarin.Forms.Platform.UWP
+{
+ public class BoxViewBorderRenderer : ViewRenderer
+ {
+ protected override void OnElementChanged(ElementChangedEventArgs e)
+ {
+ base.OnElementChanged(e);
+
+ if (e.NewElement != null)
+ {
+ if (Control == null)
+ {
+ var rect = new Border
+ {
+ DataContext = Element
+ };
+
+ rect.SetBinding(Shape.FillProperty, new Windows.UI.Xaml.Data.Binding { Converter = new ColorConverter(), Path = new PropertyPath("Color") });
+
+ SetNativeControl(rect);
+ }
+
+ SetCornerRadius(Element.CornerRadius);
+ }
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+
+ if (e.PropertyName == BoxView.CornerRadiusProperty.PropertyName)
+ SetCornerRadius(Element.CornerRadius);
+ }
+
+ protected override AutomationPeer OnCreateAutomationPeer()
+ {
+ // We need an automation peer so we can interact with this in automated tests
+ if (Control == null)
+ {
+ return new FrameworkElementAutomationPeer(this);
+ }
+
+ return new FrameworkElementAutomationPeer(Control);
+ }
+
+ protected override void UpdateBackgroundColor()
+ {
+ //background color change must be handled separately
+ //because the background would protrude through the border if the corners are rounded
+ //as the background would be applied to the renderer's FrameworkElement
+ if (Control == null)
+ return;
+ Color backgroundColor = Element.BackgroundColor;
+ Control.Background = backgroundColor.IsDefault ? null : backgroundColor.ToBrush();
+ }
+
+ void SetCornerRadius(CornerRadius cornerRadius)
+ {
+ Control.CornerRadius = new Windows.UI.Xaml.CornerRadius(cornerRadius.TopLeft, cornerRadius.TopRight, cornerRadius.BottomRight, cornerRadius.BottomLeft);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Platform.UAP/BoxViewRenderer.cs b/Xamarin.Forms.Platform.UAP/BoxViewRenderer.cs
index c363da68d6a..2bcdae81fdf 100644
--- a/Xamarin.Forms.Platform.UAP/BoxViewRenderer.cs
+++ b/Xamarin.Forms.Platform.UAP/BoxViewRenderer.cs
@@ -1,10 +1,12 @@
-using System.ComponentModel;
+using System;
+using System.ComponentModel;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Automation.Peers;
using Windows.UI.Xaml.Shapes;
namespace Xamarin.Forms.Platform.UWP
{
+ [Obsolete("BoxViewRenderer is obsolete as of version 4.6.0. Please use BoxViewBorderRenderer instead.", false)]
public class BoxViewRenderer : ViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs e)
@@ -21,7 +23,7 @@ protected override void OnElementChanged(ElementChangedEventArgs e)
};
rect.SetBinding(Shape.FillProperty, new Windows.UI.Xaml.Data.Binding { Converter = new ColorConverter(), Path = new PropertyPath("Color") });
-
+
SetNativeControl(rect);
}
@@ -54,4 +56,4 @@ private void SetCornerRadius(CornerRadius cornerRadius)
Control.RadiusY = cornerRadius.BottomRight;
}
}
-}
\ No newline at end of file
+}
diff --git a/Xamarin.Forms.Platform.UAP/FontExtensions.cs b/Xamarin.Forms.Platform.UAP/FontExtensions.cs
index 81457584662..0b699551ad1 100644
--- a/Xamarin.Forms.Platform.UAP/FontExtensions.cs
+++ b/Xamarin.Forms.Platform.UAP/FontExtensions.cs
@@ -12,6 +12,7 @@ namespace Xamarin.Forms.Platform.UWP
public static class FontExtensions
{
static Dictionary FontFamilies = new Dictionary();
+ static double DefaultFontSize = double.NegativeInfinity;
public static void ApplyFont(this Control self, Font font)
{
@@ -52,7 +53,11 @@ internal static double GetFontSize(this NamedSize size)
switch (size)
{
case NamedSize.Default:
- return (double)WApplication.Current.Resources["ControlContentThemeFontSize"];
+ if(DefaultFontSize == double.NegativeInfinity)
+ {
+ DefaultFontSize = (double)WApplication.Current.Resources["ControlContentThemeFontSize"];
+ }
+ return DefaultFontSize;
case NamedSize.Micro:
return 15.667;
case NamedSize.Small:
diff --git a/Xamarin.Forms.Platform.UAP/FormsRadioButton.cs b/Xamarin.Forms.Platform.UAP/FormsRadioButton.cs
new file mode 100644
index 00000000000..d283c88ca81
--- /dev/null
+++ b/Xamarin.Forms.Platform.UAP/FormsRadioButton.cs
@@ -0,0 +1,77 @@
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Media;
+using WContentPresenter = Windows.UI.Xaml.Controls.ContentPresenter;
+
+namespace Xamarin.Forms.Platform.UWP
+{
+ public class FormsRadioButton : Windows.UI.Xaml.Controls.RadioButton
+ {
+ public static readonly DependencyProperty BorderRadiusProperty = DependencyProperty.Register(nameof(BorderRadius), typeof(int), typeof(FormsButton),
+ new PropertyMetadata(default(int), OnBorderRadiusChanged));
+
+ public static readonly DependencyProperty BackgroundColorProperty = DependencyProperty.Register(nameof(BackgroundColor), typeof(Brush), typeof(FormsButton),
+ new PropertyMetadata(default(Brush), OnBackgroundColorChanged));
+
+ WContentPresenter _contentPresenter;
+
+ public Brush BackgroundColor
+ {
+ get
+ {
+ return (Brush)GetValue(BackgroundColorProperty);
+ }
+ set
+ {
+ SetValue(BackgroundColorProperty, value);
+ }
+ }
+
+ public int BorderRadius
+ {
+ get
+ {
+ return (int)GetValue(BorderRadiusProperty);
+ }
+ set
+ {
+ SetValue(BorderRadiusProperty, value);
+ }
+ }
+
+ protected override void OnApplyTemplate()
+ {
+ base.OnApplyTemplate();
+
+ _contentPresenter = GetTemplateChild("ContentPresenter") as WContentPresenter;
+
+ UpdateBackgroundColor();
+ UpdateBorderRadius();
+ }
+
+ static void OnBackgroundColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ ((FormsRadioButton)d).UpdateBackgroundColor();
+ }
+
+ static void OnBorderRadiusChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ ((FormsRadioButton)d).UpdateBorderRadius();
+ }
+
+ void UpdateBackgroundColor()
+ {
+ if (BackgroundColor == null)
+ BackgroundColor = Background;
+
+ if (_contentPresenter != null)
+ _contentPresenter.Background = BackgroundColor;
+ Background = Color.Transparent.ToBrush();
+ }
+
+ void UpdateBorderRadius()
+ {
+ if (_contentPresenter != null)
+ _contentPresenter.CornerRadius = new Windows.UI.Xaml.CornerRadius(BorderRadius);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Platform.UAP/Platform.cs b/Xamarin.Forms.Platform.UAP/Platform.cs
index f8e803aa6e7..f7c62f39d45 100644
--- a/Xamarin.Forms.Platform.UAP/Platform.cs
+++ b/Xamarin.Forms.Platform.UAP/Platform.cs
@@ -24,6 +24,7 @@ public abstract class Platform : INavigation
#pragma warning restore CS0618 // Type or member is obsolete
{
static Task s_currentAlert;
+ static Task s_currentPrompt;
internal static readonly BindableProperty RendererProperty = BindableProperty.CreateAttached("Renderer",
typeof(IVisualElementRenderer), typeof(Windows.Foundation.Metadata.Platform), default(IVisualElementRenderer));
@@ -493,6 +494,7 @@ internal static void SubscribeAlertsAndActionSheets()
{
MessagingCenter.Subscribe(Window.Current, Page.AlertSignalName, OnPageAlert);
MessagingCenter.Subscribe(Window.Current, Page.ActionSheetSignalName, OnPageActionSheet);
+ MessagingCenter.Subscribe(Window.Current, Page.PromptSignalName, OnPagePrompt);
}
static void OnPageActionSheet(object sender, ActionSheetArguments options)
@@ -530,6 +532,45 @@ static void OnPageActionSheet(object sender, ActionSheetArguments options)
}
}
+ static async void OnPagePrompt(Page sender, PromptArguments options)
+ {
+ var promptDialog = new PromptDialog
+ {
+ Title = options.Title ?? string.Empty,
+ Message = options.Message ?? string.Empty,
+ Input = options.InitialValue ?? string.Empty,
+ Placeholder = options.Placeholder ?? string.Empty,
+ MaxLength = options.MaxLength >= 0 ? options.MaxLength : 0,
+ InputScope = options.Keyboard.ToInputScope()
+ };
+
+ if (options.Cancel != null)
+ promptDialog.SecondaryButtonText = options.Cancel;
+
+ if (options.Accept != null)
+ promptDialog.PrimaryButtonText = options.Accept;
+
+ var currentAlert = s_currentPrompt;
+ while (currentAlert != null)
+ {
+ await currentAlert;
+ currentAlert = s_currentPrompt;
+ }
+
+ s_currentPrompt = ShowPrompt(promptDialog);
+ options.SetResult(await s_currentPrompt.ConfigureAwait(false));
+ s_currentPrompt = null;
+ }
+
+ static async Task ShowPrompt(PromptDialog prompt)
+ {
+ ContentDialogResult result = await prompt.ShowAsync();
+
+ if (result == ContentDialogResult.Primary)
+ return prompt.Input;
+ return null;
+ }
+
static async void OnPageAlert(Page sender, AlertArguments options)
{
string content = options.Message ?? string.Empty;
diff --git a/Xamarin.Forms.Platform.UAP/PromptDialog.xaml b/Xamarin.Forms.Platform.UAP/PromptDialog.xaml
new file mode 100644
index 00000000000..2c210eeb9d0
--- /dev/null
+++ b/Xamarin.Forms.Platform.UAP/PromptDialog.xaml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
diff --git a/Xamarin.Forms.Platform.UAP/PromptDialog.xaml.cs b/Xamarin.Forms.Platform.UAP/PromptDialog.xaml.cs
new file mode 100644
index 00000000000..12ba5506611
--- /dev/null
+++ b/Xamarin.Forms.Platform.UAP/PromptDialog.xaml.cs
@@ -0,0 +1,45 @@
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Input;
+
+// The Content Dialog item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
+
+namespace Xamarin.Forms.Platform.UWP
+{
+ public sealed partial class PromptDialog : ContentDialog
+ {
+ public PromptDialog()
+ {
+ this.InitializeComponent();
+ }
+
+ public string Message
+ {
+ get => TextBlockMessage.Text;
+ set => TextBlockMessage.Text = value;
+ }
+
+ public string Input
+ {
+ get => TextBoxInput.Text;
+ set => TextBoxInput.Text = value;
+ }
+
+ public string Placeholder
+ {
+ get => TextBoxInput.PlaceholderText;
+ set => TextBoxInput.PlaceholderText = value;
+ }
+
+ public int MaxLength
+ {
+ get => TextBoxInput.MaxLength;
+ set => TextBoxInput.MaxLength = value;
+ }
+
+ public InputScope InputScope
+ {
+ get => TextBoxInput.InputScope;
+ set => TextBoxInput.InputScope = value;
+ }
+ }
+}
diff --git a/Xamarin.Forms.Platform.UAP/Properties/AssemblyInfo.cs b/Xamarin.Forms.Platform.UAP/Properties/AssemblyInfo.cs
index b210d2d177f..a558673023a 100755
--- a/Xamarin.Forms.Platform.UAP/Properties/AssemblyInfo.cs
+++ b/Xamarin.Forms.Platform.UAP/Properties/AssemblyInfo.cs
@@ -7,11 +7,12 @@
// Views
[assembly: ExportRenderer(typeof(Layout), typeof(LayoutRenderer))]
-[assembly: ExportRenderer(typeof(BoxView), typeof(BoxViewRenderer))]
+[assembly: ExportRenderer(typeof(BoxView), typeof(BoxViewBorderRenderer))]
[assembly: ExportRenderer(typeof(Image), typeof(ImageRenderer))]
[assembly: ExportRenderer(typeof(ImageButton), typeof(ImageButtonRenderer))]
[assembly: ExportRenderer(typeof(Label), typeof(LabelRenderer))]
[assembly: ExportRenderer(typeof(Button), typeof(ButtonRenderer))]
+[assembly: ExportRenderer(typeof(RadioButton), typeof(RadioButtonRenderer))]
[assembly: ExportRenderer(typeof(ListView), typeof(ListViewRenderer))]
[assembly: ExportRenderer(typeof(CarouselView), typeof(CarouselViewRenderer))]
[assembly: ExportRenderer(typeof(CollectionView), typeof(CollectionViewRenderer))]
diff --git a/Xamarin.Forms.Platform.UAP/RadioButtonRenderer.cs b/Xamarin.Forms.Platform.UAP/RadioButtonRenderer.cs
new file mode 100644
index 00000000000..a2a8e32344d
--- /dev/null
+++ b/Xamarin.Forms.Platform.UAP/RadioButtonRenderer.cs
@@ -0,0 +1,213 @@
+using System.ComponentModel;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Media;
+using WThickness = Windows.UI.Xaml.Thickness;
+
+namespace Xamarin.Forms.Platform.UWP
+{
+ public class RadioButtonRenderer : ViewRenderer
+ {
+ bool _fontApplied;
+
+ protected override void OnElementChanged(ElementChangedEventArgs e)
+ {
+ base.OnElementChanged(e);
+
+ if (e.NewElement != null)
+ {
+ if (Control == null)
+ {
+ var button = new FormsRadioButton();
+
+ button.Click += OnButtonClick;
+ button.AddHandler(PointerPressedEvent, new PointerEventHandler(OnPointerPressed), true);
+ button.Loaded += ButtonOnLoaded;
+ button.Checked += OnRadioButtonCheckedOrUnchecked;
+ button.Unchecked += OnRadioButtonCheckedOrUnchecked;
+
+ SetNativeControl(button);
+ }
+ else
+ {
+ WireUpFormsVsm();
+ }
+
+ UpdateContent();
+
+ //TODO: We may want to revisit this strategy later. If a user wants to reset any of these to the default, the UI won't update.
+ if (Element.IsSet(VisualElement.BackgroundColorProperty) && Element.BackgroundColor != (Color)VisualElement.BackgroundColorProperty.DefaultValue)
+ UpdateBackground();
+
+ if (Element.IsSet(RadioButton.TextColorProperty) && Element.TextColor != (Color)RadioButton.TextColorProperty.DefaultValue)
+ UpdateTextColor();
+
+ if (Element.IsSet(RadioButton.BorderColorProperty) && Element.BorderColor != (Color)RadioButton.BorderColorProperty.DefaultValue)
+ UpdateBorderColor();
+
+ if (Element.IsSet(RadioButton.BorderWidthProperty) && Element.BorderWidth != (double)RadioButton.BorderWidthProperty.DefaultValue)
+ UpdateBorderWidth();
+
+ if (Element.IsSet(RadioButton.CornerRadiusProperty) && Element.CornerRadius != (int)RadioButton.CornerRadiusProperty.DefaultValue)
+ UpdateBorderRadius();
+
+ if (Element.IsSet(RadioButton.PaddingProperty) && Element.Padding != (Thickness)RadioButton.PaddingProperty.DefaultValue)
+ UpdatePadding();
+
+ UpdateFont();
+ UpdateCheck();
+ }
+ }
+
+ void ButtonOnLoaded(object o, RoutedEventArgs routedEventArgs)
+ {
+ WireUpFormsVsm();
+ }
+
+ void WireUpFormsVsm()
+ {
+ if (Element.UseFormsVsm())
+ {
+ InterceptVisualStateManager.Hook(Control.GetFirstDescendant(), Control, Element);
+ }
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+
+ if (e.PropertyName == RadioButton.TextProperty.PropertyName || e.PropertyName == Button.ImageSourceProperty.PropertyName)
+ {
+ UpdateContent();
+ }
+ else if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
+ {
+ UpdateBackground();
+ }
+ else if (e.PropertyName == RadioButton.TextColorProperty.PropertyName)
+ {
+ UpdateTextColor();
+ }
+ else if (e.PropertyName == RadioButton.FontProperty.PropertyName)
+ {
+ UpdateFont();
+ }
+ else if (e.PropertyName == RadioButton.BorderColorProperty.PropertyName)
+ {
+ UpdateBorderColor();
+ }
+ else if (e.PropertyName == RadioButton.BorderWidthProperty.PropertyName)
+ {
+ UpdateBorderWidth();
+ }
+ else if (e.PropertyName == RadioButton.CornerRadiusProperty.PropertyName)
+ {
+ UpdateBorderRadius();
+ }
+ else if (e.PropertyName == RadioButton.PaddingProperty.PropertyName)
+ {
+ UpdatePadding();
+ }
+ else if (e.PropertyName == RadioButton.IsCheckedProperty.PropertyName)
+ {
+ UpdateCheck();
+ }
+ }
+
+ protected override void UpdateBackgroundColor()
+ {
+ // Button is a special case; we don't want to set the Control's background
+ // because it goes outside the bounds of the Border/ContentPresenter,
+ // which is where we might change the BorderRadius to create a rounded shape.
+ return;
+ }
+
+ protected override bool PreventGestureBubbling { get; set; } = true;
+
+ void OnButtonClick(object sender, RoutedEventArgs e)
+ {
+ ((IButtonController)Element)?.SendReleased();
+ ((IButtonController)Element)?.SendClicked();
+ }
+
+ void OnPointerPressed(object sender, RoutedEventArgs e)
+ {
+ ((IButtonController)Element)?.SendPressed();
+ }
+
+ void OnRadioButtonCheckedOrUnchecked(object sender, RoutedEventArgs e)
+ {
+ if (Element == null || Control == null)
+ {
+ return;
+ }
+
+ Element.IsChecked = Control.IsChecked == true;
+ }
+
+ void UpdateBackground()
+ {
+ Control.BackgroundColor = Element.BackgroundColor != Color.Default ? Element.BackgroundColor.ToBrush() : (Brush)Windows.UI.Xaml.Application.Current.Resources["ButtonBackgroundThemeBrush"];
+ }
+
+ void UpdateBorderColor()
+ {
+ Control.BorderBrush = Element.BorderColor != Color.Default ? Element.BorderColor.ToBrush() : (Brush)Windows.UI.Xaml.Application.Current.Resources["ButtonBorderThemeBrush"];
+ }
+
+ void UpdateBorderRadius()
+ {
+ Control.BorderRadius = Element.CornerRadius;
+ }
+
+ void UpdateBorderWidth()
+ {
+ Control.BorderThickness = Element.BorderWidth == (double)RadioButton.BorderWidthProperty.DefaultValue ? new WThickness(3) : new WThickness(Element.BorderWidth);
+ }
+
+ void UpdateContent()
+ {
+ var text = Element.Text;
+ Control.Content = text;
+ }
+
+ void UpdateFont()
+ {
+ if (Control == null || Element == null)
+ return;
+
+ if (Element.Font == Font.Default && !_fontApplied)
+ return;
+
+ Font fontToApply = Element.Font == Font.Default ? Font.SystemFontOfSize(NamedSize.Medium) : Element.Font;
+
+ Control.ApplyFont(fontToApply);
+ _fontApplied = true;
+ }
+
+ void UpdateTextColor()
+ {
+ Control.Foreground = Element.TextColor != Color.Default ? Element.TextColor.ToBrush() : (Brush)Windows.UI.Xaml.Application.Current.Resources["DefaultTextForegroundThemeBrush"];
+ }
+
+ void UpdatePadding()
+ {
+ Control.Padding = new WThickness(
+ Element.Padding.Left,
+ Element.Padding.Top,
+ Element.Padding.Right,
+ Element.Padding.Bottom
+ );
+ }
+
+ void UpdateCheck()
+ {
+ if (Control == null || Element == null)
+ {
+ return;
+ }
+
+ Control.IsChecked = Element.IsChecked;
+ }
+ }
+}
diff --git a/Xamarin.Forms.Platform.UAP/WebViewRenderer.cs b/Xamarin.Forms.Platform.UAP/WebViewRenderer.cs
index 6ba1d0a85c9..6d433b3f3b8 100644
--- a/Xamarin.Forms.Platform.UAP/WebViewRenderer.cs
+++ b/Xamarin.Forms.Platform.UAP/WebViewRenderer.cs
@@ -6,7 +6,8 @@
using static System.String;
using Xamarin.Forms.PlatformConfiguration.WindowsSpecific;
using System.Threading.Tasks;
-
+using System.Net;
+using Windows.Web.Http;
namespace Xamarin.Forms.Platform.UWP
{
@@ -66,10 +67,42 @@ public void LoadUrl(string url)
if (!uri.IsAbsoluteUri)
{
- uri = new Uri(LocalScheme + url, UriKind.RelativeOrAbsolute);
+ uri = new Uri(LocalScheme + url, UriKind.RelativeOrAbsolute);
}
- Control.Source = uri;
+ if (Element.Cookies?.Count > 0)
+ {
+ //Set the Cookies...
+ var filter = new Windows.Web.Http.Filters.HttpBaseProtocolFilter();
+ foreach (Cookie cookie in Element.Cookies.GetCookies(uri))
+ {
+ HttpCookie httpCookie = new HttpCookie(cookie.Name, cookie.Domain, cookie.Path);
+ httpCookie.Value = cookie.Value;
+ filter.CookieManager.SetCookie(httpCookie, false);
+ }
+
+ try
+ {
+ var httpRequestMessage = new Windows.Web.Http.HttpRequestMessage(Windows.Web.Http.HttpMethod.Get, uri);
+ Control.NavigateWithHttpRequestMessage(httpRequestMessage);
+ }
+ catch (System.Exception exc)
+ {
+ Internals.Log.Warning(nameof(WebViewRenderer), $"Failed to load: {uri} {exc}");
+ }
+ }
+ else
+ {
+ try
+ {
+ //No Cookies so just navigate...
+ Control.Source = uri;
+ }
+ catch (System.Exception exc)
+ {
+ Internals.Log.Warning(nameof(WebViewRenderer), $"Failed to load: {uri} {exc}");
+ }
+ }
}
protected override void Dispose(bool disposing)
diff --git a/Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj b/Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj
index c732d95439c..f156ac08f5f 100644
--- a/Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj
+++ b/Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj
@@ -68,6 +68,10 @@
Designer
MSBuild:Compile
+
+ Designer
+ MSBuild:Compile
+
MSBuild:Compile
Designer
@@ -111,4 +115,4 @@
1.20.0
-
+
\ No newline at end of file
diff --git a/Xamarin.Forms.Platform.WPF/Assets/Converters.xaml b/Xamarin.Forms.Platform.WPF/Assets/Converters.xaml
index 151b32d0d7d..2dc2a7d4fd2 100644
--- a/Xamarin.Forms.Platform.WPF/Assets/Converters.xaml
+++ b/Xamarin.Forms.Platform.WPF/Assets/Converters.xaml
@@ -5,5 +5,6 @@
-
+
+
\ No newline at end of file
diff --git a/Xamarin.Forms.Platform.WPF/Controls/FormsFontIcon.cs b/Xamarin.Forms.Platform.WPF/Controls/FormsFontIcon.cs
new file mode 100644
index 00000000000..6b3190935a2
--- /dev/null
+++ b/Xamarin.Forms.Platform.WPF/Controls/FormsFontIcon.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace Xamarin.Forms.Platform.WPF.Controls
+{
+ public class FormsFontIcon : FormsElementIcon
+ {
+ public static readonly DependencyProperty SourceProperty = DependencyProperty.Register("UriSource", typeof(FontImageSource), typeof(FormsFontIcon), new PropertyMetadata(OnSourceChanged));
+
+ public FontImageSource Source
+ {
+ get { return (FontImageSource)GetValue(SourceProperty); }
+ set { SetValue(SourceProperty, value); }
+ }
+
+ public FormsFontIcon()
+ {
+ this.DefaultStyleKey = typeof(FormsFontIcon);
+ }
+
+ private static void OnSourceChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
+ {
+ ((FormsFontIcon)o).OnSourceChanged(e.OldValue, e.NewValue);
+ }
+
+ private void OnSourceChanged(object oldValue, object newValue)
+ {
+ if (newValue is FontImageSource src)
+ {
+ Source = src;
+ }
+ }
+ }
+}
diff --git a/Xamarin.Forms.Platform.WPF/Controls/FormsProgressRing.xaml b/Xamarin.Forms.Platform.WPF/Controls/FormsProgressRing.xaml
new file mode 100644
index 00000000000..1aeb3a957a0
--- /dev/null
+++ b/Xamarin.Forms.Platform.WPF/Controls/FormsProgressRing.xaml
@@ -0,0 +1,137 @@
+
+
+
+
+
+
+
+ Visible
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Xamarin.Forms.Platform.WPF/Controls/FormsProgressRing.xaml.cs b/Xamarin.Forms.Platform.WPF/Controls/FormsProgressRing.xaml.cs
new file mode 100644
index 00000000000..60afa1ba9c4
--- /dev/null
+++ b/Xamarin.Forms.Platform.WPF/Controls/FormsProgressRing.xaml.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media.Animation;
+
+namespace Xamarin.Forms.Platform.WPF.Controls
+{
+ public partial class FormsProgressRing : UserControl
+ {
+ public static readonly DependencyProperty IsActiveProperty = DependencyProperty.Register("IsActive", typeof(bool), typeof(FormsProgressRing), new PropertyMetadata(false, new PropertyChangedCallback(IsActiveChanged)));
+
+ Storyboard animation;
+
+ public FormsProgressRing()
+ {
+ InitializeComponent();
+
+ animation = (Storyboard)Resources["ProgressRingStoryboard"];
+ }
+
+ public bool IsActive
+ {
+ get
+ {
+ return (bool)GetValue(IsActiveProperty);
+ }
+
+ set
+ {
+ SetValue(IsActiveProperty, value);
+ }
+ }
+
+ static void IsActiveChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
+ {
+ ((FormsProgressRing)sender).OnIsActiveChanged(Convert.ToBoolean(e.NewValue));
+ }
+
+ void OnIsActiveChanged(bool newValue)
+ {
+ if (newValue)
+ {
+ animation.Begin();
+ }
+ else
+ {
+ animation.Stop();
+ }
+ }
+
+ protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
+ {
+ // force the ring to the largest square which is fully visible in the control
+ Ring.Width = Math.Min(ActualWidth, ActualHeight);
+ Ring.Height = Math.Min(ActualWidth, ActualHeight);
+ base.OnRenderSizeChanged(sizeInfo);
+ }
+ }
+}
diff --git a/Xamarin.Forms.Platform.WPF/Converters/FontFamilyConverter.cs b/Xamarin.Forms.Platform.WPF/Converters/FontFamilyConverter.cs
new file mode 100644
index 00000000000..df32defede9
--- /dev/null
+++ b/Xamarin.Forms.Platform.WPF/Converters/FontFamilyConverter.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Globalization;
+using System.Windows.Media;
+
+namespace Xamarin.Forms.Platform.WPF.Converters
+{
+ public class FontFamilyConverter : System.Windows.Data.IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is string val && !string.IsNullOrEmpty(val))
+ {
+ return new FontFamily(new Uri("pack://application:,,,"), val);
+ }
+
+ return null;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Platform.WPF/Converters/FontIconColorConverter.cs b/Xamarin.Forms.Platform.WPF/Converters/FontIconColorConverter.cs
new file mode 100644
index 00000000000..21d8a8ac2a5
--- /dev/null
+++ b/Xamarin.Forms.Platform.WPF/Converters/FontIconColorConverter.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Globalization;
+
+namespace Xamarin.Forms.Platform.WPF.Converters
+{
+ public sealed class FontIconColorConverter : System.Windows.Data.IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is Color c && !c.IsDefault)
+ {
+ return c.ToBrush();
+ }
+
+ return Color.White.ToBrush();
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Platform.WPF/Converters/IconConveter.cs b/Xamarin.Forms.Platform.WPF/Converters/IconConveter.cs
index a2fe97205a7..5b8b2c1fc06 100644
--- a/Xamarin.Forms.Platform.WPF/Converters/IconConveter.cs
+++ b/Xamarin.Forms.Platform.WPF/Converters/IconConveter.cs
@@ -1,6 +1,7 @@
using System;
using System.Globalization;
using System.IO;
+using System.Windows;
using System.Windows.Media;
using Xamarin.Forms.Platform.WPF.Controls;
using Xamarin.Forms.Platform.WPF.Enums;
@@ -20,6 +21,10 @@ public object Convert(object value, Type targetType, object parameter, CultureIn
else if (Path.GetExtension(imageSource.File) != null)
return new FormsBitmapIcon() { UriSource = new Uri(imageSource.File, UriKind.RelativeOrAbsolute) };
}
+ else if (value is FontImageSource fontsource)
+ {
+ return new FormsFontIcon() { Source = fontsource };
+ }
return null;
}
diff --git a/Xamarin.Forms.Platform.WPF/Renderers/ActivityIndicatorRenderer.cs b/Xamarin.Forms.Platform.WPF/Renderers/ActivityIndicatorRenderer.cs
index a83c4b6fb7e..4f6af84e275 100644
--- a/Xamarin.Forms.Platform.WPF/Renderers/ActivityIndicatorRenderer.cs
+++ b/Xamarin.Forms.Platform.WPF/Renderers/ActivityIndicatorRenderer.cs
@@ -1,14 +1,10 @@
using System;
-using System.Collections.Generic;
using System.ComponentModel;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using WProgressBar = System.Windows.Controls.ProgressBar;
+using Xamarin.Forms.Platform.WPF.Controls;
namespace Xamarin.Forms.Platform.WPF
{
- public class ActivityIndicatorRenderer : ViewRenderer
+ public class ActivityIndicatorRenderer : ViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs e)
{
@@ -16,10 +12,10 @@ protected override void OnElementChanged(ElementChangedEventArgs
+ public class ListViewRenderer : ViewRenderer
{
class ScrollViewerBehavior
{
@@ -29,11 +27,18 @@ static void OnVerticalOffsetChanged(DependencyObject target, DependencyPropertyC
scrollViewer?.ScrollToVerticalOffset((double)e.NewValue);
}
}
+
ITemplatedItemsView TemplatedItemsView => Element;
WpfScrollBarVisibility? _defaultHorizontalScrollVisibility;
WpfScrollBarVisibility? _defaultVerticalScrollVisibility;
ScrollViewer _scrollViewer;
+ // Header and Footer
+ readonly WGrid _grid = new WGrid();
+ WList _listview = null;
+ IVisualElementRenderer _headerRenderer;
+ IVisualElementRenderer _footerRenderer;
+
public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
{
SizeRequest result = base.GetDesiredSize(widthConstraint, heightConstraint);
@@ -51,18 +56,24 @@ protected override void OnElementChanged(ElementChangedEventArgs e)
var templatedItems = ((ITemplatedItemsView)e.OldElement).TemplatedItems;
templatedItems.CollectionChanged -= OnCollectionChanged;
templatedItems.GroupedCollectionChanged -= OnGroupedCollectionChanged;
- if (Control != null)
+ if (_listview != null)
{
- Control.MouseUp -= OnNativeMouseUp;
- Control.KeyUp -= OnNativeKeyUp;
- Control.TouchUp -= OnNativeTouchUp;
- Control.StylusUp -= OnNativeStylusUp;
- Control.Loaded -= ControlOnLoaded;
+ _listview.MouseUp -= OnNativeMouseUp;
+ _listview.KeyUp -= OnNativeKeyUp;
+ _listview.TouchUp -= OnNativeTouchUp;
+ _listview.StylusUp -= OnNativeStylusUp;
+ _listview.Loaded -= ControlOnLoaded;
}
+
if (_scrollViewer != null)
{
_scrollViewer.ScrollChanged -= SendScrolled;
}
+
+ if(Control is object)
+ {
+ Control.SizeChanged -= Grid_SizeChanged;
+ }
}
if (e.NewElement != null)
@@ -70,24 +81,34 @@ protected override void OnElementChanged(ElementChangedEventArgs e)
e.NewElement.ItemSelected += OnElementItemSelected;
e.NewElement.ScrollToRequested += OnElementScrollToRequested;
- if (Control == null) // Construct and SetNativeControl and suscribe control event
+ if (_listview == null) // Construct and SetNativeControl and suscribe control event
{
- var listView = new WList
+ _listview = new WList
{
DataContext = Element,
ItemTemplate = (System.Windows.DataTemplate)System.Windows.Application.Current.Resources["CellTemplate"],
Style = (System.Windows.Style)System.Windows.Application.Current.Resources["ListViewTemplate"]
};
- VirtualizingPanel.SetVirtualizationMode(listView, VirtualizationMode.Recycling);
- VirtualizingPanel.SetScrollUnit(listView, ScrollUnit.Pixel);
- SetNativeControl(listView);
-
- Control.MouseUp += OnNativeMouseUp;
- Control.KeyUp += OnNativeKeyUp;
- Control.TouchUp += OnNativeTouchUp;
- Control.StylusUp += OnNativeStylusUp;
- Control.Loaded += ControlOnLoaded;
+ VirtualizingPanel.SetVirtualizationMode(_listview, VirtualizationMode.Recycling);
+ VirtualizingPanel.SetScrollUnit(_listview, ScrollUnit.Pixel);
+
+ SetNativeControl(_grid);
+
+ // Setup grid for header/listview/footer
+ Control.RowDefinitions.Add(new System.Windows.Controls.RowDefinition { Height = System.Windows.GridLength.Auto });
+ Control.RowDefinitions.Add(new System.Windows.Controls.RowDefinition { Height = new System.Windows.GridLength(1, System.Windows.GridUnitType.Star) });
+ Control.RowDefinitions.Add(new System.Windows.Controls.RowDefinition { Height = System.Windows.GridLength.Auto });
+
+ WGrid.SetRow(_listview, 1);
+
+ Control.Children.Add(_listview);
+
+ _listview.MouseUp += OnNativeMouseUp;
+ _listview.KeyUp += OnNativeKeyUp;
+ _listview.TouchUp += OnNativeTouchUp;
+ _listview.StylusUp += OnNativeStylusUp;
+ _listview.Loaded += ControlOnLoaded;
}
// Suscribe element events
@@ -95,7 +116,11 @@ protected override void OnElementChanged(ElementChangedEventArgs e)
templatedItems.CollectionChanged += OnCollectionChanged;
templatedItems.GroupedCollectionChanged += OnGroupedCollectionChanged;
+ Control.SizeChanged += Grid_SizeChanged;
+
// Update control properties
+ UpdateHeader();
+ UpdateFooter();
UpdateItemSource();
UpdateHorizontalScrollBarVisibility();
UpdateVerticalScrollBarVisibility();
@@ -107,9 +132,80 @@ protected override void OnElementChanged(ElementChangedEventArgs e)
base.OnElementChanged(e);
}
+ // If the control size changes, then re-layout the header and footer.
+ private void Grid_SizeChanged(object sender, SizeChangedEventArgs e)
+ {
+ if (_headerRenderer is object)
+ {
+ _headerRenderer.GetNativeElement().Width = e.NewSize.Width;
+ _headerRenderer.GetNativeElement().UpdateLayout();
+
+ var header = Element.HeaderElement;
+ var headerView = (VisualElement)header;
+ SizeRequest request = headerView.Measure(4096, 4096);
+
+ Layout.LayoutChildIntoBoundingRegion(headerView, new Rectangle(0, 0, e.NewSize.Width, request.Request.Height));
+ }
+
+ if (_footerRenderer is object)
+ {
+ _footerRenderer.GetNativeElement().Width = e.NewSize.Width;
+ _footerRenderer.GetNativeElement().UpdateLayout();
+
+ var footer = Element.FooterElement;
+ var footerView = (VisualElement)footer;
+ SizeRequest request = footerView.Measure(4096, 4096);
+
+ Layout.LayoutChildIntoBoundingRegion(footerView, new Rectangle(0, 0, e.NewSize.Width, request.Request.Height));
+ }
+ }
+
+ void UpdateHeader()
+ {
+ var header = Element.HeaderElement;
+ var headerView = (VisualElement)header;
+
+ if (_headerRenderer is object)
+ {
+ Control.Children.Remove(_headerRenderer.GetNativeElement());
+ _headerRenderer.Dispose();
+ }
+
+ if (headerView is null)
+ return;
+
+ _headerRenderer = Platform.CreateRenderer(headerView);
+ Platform.SetRenderer(headerView, _headerRenderer);
+
+ WGrid.SetRow(_headerRenderer.GetNativeElement(), 0);
+ Control.Children.Add(_headerRenderer.GetNativeElement());
+
+ }
+
+ void UpdateFooter()
+ {
+ var footer = Element.FooterElement;
+ var footerView = (VisualElement)footer;
+
+ if (_footerRenderer is object)
+ {
+ Control.Children.Remove(_footerRenderer.GetNativeElement());
+ _footerRenderer.Dispose();
+ }
+
+ if (footerView is null)
+ return;
+
+ _footerRenderer = Platform.CreateRenderer(footerView);
+ Platform.SetRenderer(footerView, _footerRenderer);
+
+ WGrid.SetRow(_footerRenderer.GetNativeElement(), 2);
+ Control.Children.Add(_footerRenderer.GetNativeElement());
+ }
+
void ControlOnLoaded(object sender, RoutedEventArgs e)
{
- _scrollViewer = Control.FindVisualChild();
+ _scrollViewer = _listview.FindVisualChild();
if (_scrollViewer != null)
{
_scrollViewer.ScrollChanged += SendScrolled;
@@ -140,6 +236,14 @@ protected override void OnElementPropertyChanged(object sender, PropertyChangedE
{
UpdateHorizontalScrollBarVisibility();
}
+ else if (e.PropertyName == ListView.HeaderProperty.PropertyName || e.PropertyName == "HeaderElement")
+ {
+ UpdateHeader();
+ }
+ else if (e.PropertyName == ListView.FooterProperty.PropertyName || e.PropertyName == "FooterElement")
+ {
+ UpdateFooter();
+ }
}
void UpdateItemSource()
@@ -163,7 +267,7 @@ void UpdateItemSource()
index++;
}
- Control.ItemsSource = items;
+ _listview.ItemsSource = items;
}
else
{
@@ -172,25 +276,25 @@ void UpdateItemSource()
items.Add(item);
}
- Control.ItemsSource = items;
+ _listview.ItemsSource = items;
}
}
void UpdateVerticalScrollBarVisibility()
{
if (_defaultVerticalScrollVisibility == null)
- _defaultVerticalScrollVisibility = ScrollViewer.GetVerticalScrollBarVisibility(Control);
+ _defaultVerticalScrollVisibility = ScrollViewer.GetVerticalScrollBarVisibility(_listview);
switch (Element.VerticalScrollBarVisibility)
{
case (ScrollBarVisibility.Always):
- ScrollViewer.SetVerticalScrollBarVisibility(Control, WpfScrollBarVisibility.Visible);
+ ScrollViewer.SetVerticalScrollBarVisibility(_listview, WpfScrollBarVisibility.Visible);
break;
case (ScrollBarVisibility.Never):
- ScrollViewer.SetVerticalScrollBarVisibility(Control, WpfScrollBarVisibility.Hidden);
+ ScrollViewer.SetVerticalScrollBarVisibility(_listview, WpfScrollBarVisibility.Hidden);
break;
case (ScrollBarVisibility.Default):
- ScrollViewer.SetVerticalScrollBarVisibility(Control, (WpfScrollBarVisibility)_defaultVerticalScrollVisibility);
+ ScrollViewer.SetVerticalScrollBarVisibility(_listview, (WpfScrollBarVisibility)_defaultVerticalScrollVisibility);
break;
}
}
@@ -198,33 +302,33 @@ void UpdateVerticalScrollBarVisibility()
void UpdateHorizontalScrollBarVisibility()
{
if (_defaultHorizontalScrollVisibility == null)
- _defaultHorizontalScrollVisibility = ScrollViewer.GetHorizontalScrollBarVisibility(Control);
+ _defaultHorizontalScrollVisibility = ScrollViewer.GetHorizontalScrollBarVisibility(_listview);
switch (Element.HorizontalScrollBarVisibility)
{
case (ScrollBarVisibility.Always):
- ScrollViewer.SetHorizontalScrollBarVisibility(Control, WpfScrollBarVisibility.Visible);
+ ScrollViewer.SetHorizontalScrollBarVisibility(_listview, WpfScrollBarVisibility.Visible);
break;
case (ScrollBarVisibility.Never):
- ScrollViewer.SetHorizontalScrollBarVisibility(Control, WpfScrollBarVisibility.Hidden);
+ ScrollViewer.SetHorizontalScrollBarVisibility(_listview, WpfScrollBarVisibility.Hidden);
break;
case (ScrollBarVisibility.Default):
- ScrollViewer.SetHorizontalScrollBarVisibility(Control, (WpfScrollBarVisibility)_defaultHorizontalScrollVisibility);
+ ScrollViewer.SetHorizontalScrollBarVisibility(_listview, (WpfScrollBarVisibility)_defaultHorizontalScrollVisibility);
break;
}
}
void OnNativeKeyUp(object sender, KeyEventArgs e)
- => Element.NotifyRowTapped(Control.SelectedIndex, cell: null);
+ => Element.NotifyRowTapped(_listview.SelectedIndex, cell: null);
void OnNativeMouseUp(object sender, MouseButtonEventArgs e)
- => Element.NotifyRowTapped(Control.SelectedIndex, cell: null);
+ => Element.NotifyRowTapped(_listview.SelectedIndex, cell: null);
void OnNativeTouchUp(object sender, TouchEventArgs e)
- => Element.NotifyRowTapped(Control.SelectedIndex, cell: null);
+ => Element.NotifyRowTapped(_listview.SelectedIndex, cell: null);
void OnNativeStylusUp(object sender, StylusEventArgs e)
- => Element.NotifyRowTapped(Control.SelectedIndex, cell: null);
+ => Element.NotifyRowTapped(_listview.SelectedIndex, cell: null);
bool _isDisposed;
@@ -235,13 +339,13 @@ protected override void Dispose(bool disposing)
if (disposing)
{
- if (Control != null)
+ if (_listview != null)
{
- Control.MouseUp -= OnNativeMouseUp;
- Control.KeyUp -= OnNativeKeyUp;
- Control.TouchUp -= OnNativeTouchUp;
- Control.StylusUp -= OnNativeStylusUp;
- Control.Loaded -= ControlOnLoaded;
+ _listview.MouseUp -= OnNativeMouseUp;
+ _listview.KeyUp -= OnNativeKeyUp;
+ _listview.TouchUp -= OnNativeTouchUp;
+ _listview.StylusUp -= OnNativeStylusUp;
+ _listview.Loaded -= ControlOnLoaded;
}
if (_scrollViewer != null)
{
@@ -253,6 +357,14 @@ protected override void Dispose(bool disposing)
TemplatedItemsView.TemplatedItems.CollectionChanged -= OnCollectionChanged;
TemplatedItemsView.TemplatedItems.GroupedCollectionChanged -= OnGroupedCollectionChanged;
}
+
+ if (Control is object)
+ {
+ Control.SizeChanged -= Grid_SizeChanged;
+ }
+
+ _footerRenderer?.Dispose();
+ _headerRenderer?.Dispose();
}
_isDisposed = true;
@@ -266,7 +378,7 @@ void OnElementItemSelected(object sender, SelectedItemChangedEventArgs e)
if (e.SelectedItem == null)
{
- Control.SelectedIndex = -1;
+ _listview.SelectedIndex = -1;
return;
}
@@ -286,7 +398,7 @@ void OnElementItemSelected(object sender, SelectedItemChangedEventArgs e)
index = templatedItems.GetGlobalIndexOfItem(e.SelectedItem);
}
- Control.SelectedIndex = index;
+ _listview.SelectedIndex = index;
}
void OnCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
@@ -300,16 +412,16 @@ void OnGroupedCollectionChanged(object sender, System.Collections.Specialized.No
}
void ScrollTo(object group, object item, ScrollToPosition toPosition, bool shouldAnimate)
{
- var viewer = Control.FindVisualChild();
+ var viewer = _listview.FindVisualChild();
if (viewer == null)
{
RoutedEventHandler loadedHandler = null;
loadedHandler = (o, e) =>
{
- Control.Loaded -= loadedHandler;
+ _listview.Loaded -= loadedHandler;
Device.BeginInvokeOnMainThread(() => { ScrollTo(group, item, toPosition, shouldAnimate); });
};
- Control.Loaded += loadedHandler;
+ _listview.Loaded += loadedHandler;
return;
}
var templatedItems = TemplatedItemsView.TemplatedItems;
@@ -322,7 +434,7 @@ void ScrollTo(object group, object item, ScrollToPosition toPosition, bool shoul
Device.BeginInvokeOnMainThread(() =>
{
- ScrollToPositionInView(Control, viewer, c, toPosition, shouldAnimate);
+ ScrollToPositionInView(_listview, viewer, c, toPosition, shouldAnimate);
});
}
static void ScrollToPositionInView(WList control, ScrollViewer sv, object item, ScrollToPosition position, bool animated)
diff --git a/Xamarin.Forms.Platform.WPF/Themes/FormsFontIcon.xaml b/Xamarin.Forms.Platform.WPF/Themes/FormsFontIcon.xaml
new file mode 100644
index 00000000000..d867dfcd241
--- /dev/null
+++ b/Xamarin.Forms.Platform.WPF/Themes/FormsFontIcon.xaml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Xamarin.Forms.Platform.WPF/Themes/Generic.xaml b/Xamarin.Forms.Platform.WPF/Themes/Generic.xaml
index 6cf236e5f56..c14cd3a954e 100644
--- a/Xamarin.Forms.Platform.WPF/Themes/Generic.xaml
+++ b/Xamarin.Forms.Platform.WPF/Themes/Generic.xaml
@@ -22,7 +22,7 @@
-
+
diff --git a/Xamarin.Forms.Platform.WPF/Xamarin.Forms.Platform.WPF.csproj b/Xamarin.Forms.Platform.WPF/Xamarin.Forms.Platform.WPF.csproj
index aa93b846d86..0372f21eb5e 100644
--- a/Xamarin.Forms.Platform.WPF/Xamarin.Forms.Platform.WPF.csproj
+++ b/Xamarin.Forms.Platform.WPF/Xamarin.Forms.Platform.WPF.csproj
@@ -62,6 +62,7 @@
+
@@ -74,6 +75,9 @@
+
+ FormsProgressRing.xaml
+
@@ -83,6 +87,8 @@
+
+
@@ -227,6 +233,13 @@
MSBuild:Compile
Designer
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+
MSBuild:Compile
Designer
diff --git a/Xamarin.Forms.Platform.iOS/Extensions/Extensions.cs b/Xamarin.Forms.Platform.iOS/Extensions/Extensions.cs
index eae7dcdd355..d756e5b922d 100644
--- a/Xamarin.Forms.Platform.iOS/Extensions/Extensions.cs
+++ b/Xamarin.Forms.Platform.iOS/Extensions/Extensions.cs
@@ -89,6 +89,21 @@ internal static UIModalPresentationStyle ToNativeModalPresentationStyle(this Pla
}
}
+ internal static UISearchBarStyle ToNativeSearchBarStyle(this PlatformConfiguration.iOSSpecific.UISearchBarStyle style)
+ {
+ switch (style)
+ {
+ case PlatformConfiguration.iOSSpecific.UISearchBarStyle.Default:
+ return UISearchBarStyle.Default;
+ case PlatformConfiguration.iOSSpecific.UISearchBarStyle.Prominent:
+ return UISearchBarStyle.Prominent;
+ case PlatformConfiguration.iOSSpecific.UISearchBarStyle.Minimal:
+ return UISearchBarStyle.Minimal;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(style));
+ }
+ }
+
internal static UIReturnKeyType ToUIReturnKeyType(this ReturnType returnType)
{
switch (returnType)
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/FrameRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/FrameRenderer.cs
index 8772f57d814..f02e1c25524 100644
--- a/Xamarin.Forms.Platform.iOS/Renderers/FrameRenderer.cs
+++ b/Xamarin.Forms.Platform.iOS/Renderers/FrameRenderer.cs
@@ -87,7 +87,7 @@ public override void LayoutSubviews()
{
if (_shadowView != null)
{
- if (_shadowView.Superview == null)
+ if (_shadowView.Superview == null && Superview != null)
Superview.InsertSubviewBelow(_shadowView, this);
_shadowView?.SetNeedsLayout();
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/NavigationRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/NavigationRenderer.cs
index 6a9af896ce6..a3ab27ba5d3 100644
--- a/Xamarin.Forms.Platform.iOS/Renderers/NavigationRenderer.cs
+++ b/Xamarin.Forms.Platform.iOS/Renderers/NavigationRenderer.cs
@@ -482,7 +482,7 @@ void UpdateHideNavigationBarSeparator()
if (!Forms.IsiOS11OrNewer)
{
- // For iOS 10 and lower, you need to set the background image.
+ // For iOS 10 and lower, you need to set the background image.
// If you set this for iOS11, you'll remove the background color.
if (_defaultNavBarBackImage == null)
_defaultNavBarBackImage = NavigationBar.GetBackgroundImage(UIBarMetrics.Default);
@@ -570,7 +570,7 @@ void RemovePage(Page page)
// know iOS has processed it, and make sure any updates use that.
// In the future we may want to make RemovePageAsync and deprecate RemovePage to handle cases where Push/Pop is called
- // during a remove cycle.
+ // during a remove cycle.
if (_removeControllers == null)
{
@@ -689,9 +689,13 @@ void UpdateBarTextColor()
}
// set Tint color (i. e. Back Button arrow and Text)
- NavigationBar.TintColor = barTextColor == Color.Default || NavPage.OnThisPlatform().GetStatusBarTextColorMode() == StatusBarTextColorMode.DoNotAdjust
+ var iconColor = NavigationPage.GetIconColor(Current);
+ if (iconColor.IsDefault)
+ iconColor = barTextColor;
+
+ NavigationBar.TintColor = iconColor == Color.Default || NavPage.OnThisPlatform().GetStatusBarTextColorMode() == StatusBarTextColorMode.DoNotAdjust
? UINavigationBar.Appearance.TintColor
- : barTextColor.ToUIColor();
+ : iconColor.ToUIColor();
}
void SetStatusBarStyle()
@@ -956,6 +960,7 @@ public Page Child
UpdateHasBackButton();
UpdateLargeTitles();
+ UpdateIconColor();
}
}
@@ -992,7 +997,7 @@ public override void ViewDidDisappear(bool animated)
item.TintColor = tintColor == null ? UIColor.Clear : null;
item.TintColor = tintColor;
}
-
+
Disappearing?.Invoke(this, EventArgs.Empty);
}
@@ -1083,6 +1088,8 @@ void HandleChildPropertyChanged(object sender, PropertyChangedEventArgs e)
else if (e.PropertyName == NavigationPage.TitleIconImageSourceProperty.PropertyName ||
e.PropertyName == NavigationPage.TitleViewProperty.PropertyName)
UpdateTitleArea(Child);
+ else if (e.PropertyName == NavigationPage.IconColorProperty.PropertyName)
+ UpdateIconColor();
}
@@ -1161,6 +1168,12 @@ internal void UpdateTitleArea(Page page)
}
}
+ void UpdateIconColor()
+ {
+ if (_navigation.TryGetTarget(out NavigationRenderer navigationRenderer))
+ navigationRenderer.UpdateBarTextColor();
+ }
+
async void UpdateTitleImage(Container titleViewContainer, ImageSource titleIcon)
{
if (titleViewContainer == null)
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/PhoneMasterDetailRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/PhoneMasterDetailRenderer.cs
index a1b6fe05959..16615cb70ca 100644
--- a/Xamarin.Forms.Platform.iOS/Renderers/PhoneMasterDetailRenderer.cs
+++ b/Xamarin.Forms.Platform.iOS/Renderers/PhoneMasterDetailRenderer.cs
@@ -24,6 +24,7 @@ public class PhoneMasterDetailRenderer : UIViewController, IVisualElementRendere
UIGestureRecognizer _tapGesture;
VisualElementTracker _tracker;
+ bool _applyShadow;
Page Page => Element as Page;
@@ -236,13 +237,20 @@ void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
UpdateBackground();
else if (e.PropertyName == Page.BackgroundImageSourceProperty.PropertyName)
UpdateBackground();
+ else if (e.PropertyName == PlatformConfiguration.iOSSpecific.MasterDetailPage.ApplyShadowProperty.PropertyName)
+ UpdateApplyShadow(((MasterDetailPage)Element).OnThisPlatform().GetApplyShadow());
}
void LayoutChildren(bool animated)
{
var frame = Element.Bounds.ToRectangleF();
var masterFrame = frame;
+ nfloat opacity = 1;
masterFrame.Width = (int)(Math.Min(masterFrame.Width, masterFrame.Height) * 0.8);
+ var detailRenderer = Platform.GetRenderer(MasterDetailPage.Detail);
+ if (detailRenderer == null)
+ return;
+ var detailView = detailRenderer.ViewController.View;
var isRTL = (Element as IVisualElementController)?.EffectiveFlowDirection.IsRightToLeft() == true;
if (isRTL)
@@ -254,7 +262,11 @@ void LayoutChildren(bool animated)
var target = frame;
if (Presented)
+ {
target.X += masterFrame.Width;
+ if (_applyShadow)
+ opacity = 0.5f;
+ }
if (isRTL)
{
@@ -266,12 +278,16 @@ void LayoutChildren(bool animated)
UIView.BeginAnimations("Flyout");
var view = _detailController.View;
view.Frame = target;
+ detailView.Layer.Opacity = (float)opacity;
UIView.SetAnimationCurve(UIViewAnimationCurve.EaseOut);
UIView.SetAnimationDuration(250);
UIView.CommitAnimations();
}
else
+ {
_detailController.View.Frame = target;
+ detailView.Layer.Opacity = (float)opacity;
+ }
MasterDetailPage.MasterBounds = new Rectangle(masterFrame.X, 0, masterFrame.Width, masterFrame.Height);
MasterDetailPage.DetailBounds = new Rectangle(0, 0, frame.Width, frame.Height);
@@ -342,6 +358,8 @@ void UpdateMasterDetailContainers()
SetNeedsStatusBarAppearanceUpdate();
if (Forms.RespondsToSetNeedsUpdateOfHomeIndicatorAutoHidden)
SetNeedsUpdateOfHomeIndicatorAutoHidden();
+
+ detailRenderer.ViewController.View.Superview.BackgroundColor = Xamarin.Forms.Color.Black.ToUIColor();
}
void UpdateLeftBarButton()
@@ -357,6 +375,11 @@ void UpdateLeftBarButton()
NavigationRenderer.SetMasterLeftBarButton(firstPage, masterDetailPage);
}
+ void UpdateApplyShadow(bool value)
+ {
+ _applyShadow = value;
+ }
+
public override UIViewController ChildViewControllerForStatusBarHidden()
{
if (((MasterDetailPage)Element).Detail != null)
@@ -423,6 +446,11 @@ bool shouldReceive(UIGestureRecognizer g, UITouch t)
targetFrame.X = (nfloat)Math.Min(_masterController.View.Frame.Width, Math.Max(0, motion));
targetFrame.X = targetFrame.X * directionModifier;
+ if (_applyShadow)
+ {
+ var openProgress = targetFrame.X / _masterController.View.Frame.Width;
+ ApplyDetailShadow((nfloat)openProgress);
+ }
detailView.Frame = targetFrame;
break;
@@ -476,5 +504,12 @@ void IEffectControlProvider.RegisterEffect(Effect effect)
{
VisualElementRenderer.RegisterEffect(effect, View);
}
+
+ void ApplyDetailShadow(nfloat percent)
+ {
+ var detailView = Platform.GetRenderer(MasterDetailPage.Detail).ViewController.View;
+ var opacity = (nfloat)(0.5 + (0.5 * (1 - percent)));
+ detailView.Layer.Opacity = (float)opacity;
+ }
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/RadioButtonCALayer.cs b/Xamarin.Forms.Platform.iOS/Renderers/RadioButtonCALayer.cs
new file mode 100644
index 00000000000..120fd872b1f
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/RadioButtonCALayer.cs
@@ -0,0 +1,127 @@
+using CoreAnimation;
+using CoreGraphics;
+using UIKit;
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class RadioButtonCALayer : CALayer
+ {
+ const float _checkLineWidth = 2f;
+ const float _containerLineWidth = 2f;
+ const bool _fillsOnChecked = true;
+ const float _checkSize = 25f;
+ const float _containerInset = 5f;
+ const float _checkInset = 9f;
+ const UIColor _checkBorderStrokeColor = null;
+ const UIColor _checkBorderFillColor = null;
+ const UIColor _checkMarkStrokeColor = null;
+ const UIColor _checkMarkFillColor = null;
+
+ readonly UIButton _nativeControl;
+ readonly RadioButton _radioButton;
+ readonly CAShapeLayer _checkLayer = new CAShapeLayer();
+ readonly CAShapeLayer _containerLayer = new CAShapeLayer();
+
+ public RadioButtonCALayer(RadioButton radioButton, UIButton nativeControl)
+ {
+ NeedsDisplayOnBoundsChange = true;
+ _radioButton = radioButton;
+ _nativeControl = nativeControl;
+ InitializeLayers();
+ Frame = GetCheckFrame(_nativeControl.Bounds);
+ }
+
+ public override void Display()
+ {
+ base.Display();
+ ColorLayers();
+ }
+
+ public override void LayoutSublayers()
+ {
+ base.LayoutSublayers();
+ LayoutLayers();
+ }
+
+ void InitializeLayers()
+ {
+ _nativeControl.Layer.AddSublayer(_containerLayer);
+ _nativeControl.Layer.AddSublayer(_checkLayer);
+ }
+
+ void ColorLayers()
+ {
+ if (_nativeControl.Enabled)
+ {
+ if (_radioButton.IsChecked)
+ {
+ _containerLayer.StrokeColor = _checkBorderStrokeColor?.CGColor ?? _nativeControl.CurrentTitleColor.CGColor;
+ _containerLayer.FillColor = _checkMarkFillColor?.CGColor ?? UIColor.White.CGColor;
+ _checkLayer.FillColor = _checkBorderFillColor?.CGColor ?? _nativeControl.CurrentTitleColor.CGColor;
+ _checkLayer.StrokeColor = _checkMarkStrokeColor?.CGColor ?? _nativeControl.CurrentTitleColor.CGColor;
+ }
+ else
+ {
+ _containerLayer.StrokeColor = _checkBorderStrokeColor?.CGColor ?? _nativeControl.CurrentTitleColor.CGColor;
+ _containerLayer.FillColor = UIColor.Clear.CGColor;
+ _checkLayer.FillColor = UIColor.Clear.CGColor;
+ _checkLayer.StrokeColor = UIColor.Clear.CGColor;
+ }
+ }
+ else
+ {
+ // When disabled, use tint disabled color
+ if (_radioButton.IsChecked)
+ {
+ _containerLayer.StrokeColor = _nativeControl.CurrentTitleColor.CGColor;
+ _containerLayer.FillColor = _nativeControl.CurrentTitleColor.CGColor;
+ _checkLayer.FillColor = _nativeControl.CurrentTitleColor.CGColor;
+ _checkLayer.StrokeColor = _nativeControl.CurrentTitleColor.CGColor;
+ }
+ else
+ {
+ _containerLayer.StrokeColor = _nativeControl.CurrentTitleColor.CGColor;
+ _containerLayer.FillColor = UIColor.Clear.CGColor;
+ _checkLayer.FillColor = UIColor.Clear.CGColor;
+ _checkLayer.StrokeColor = UIColor.Clear.CGColor;
+ }
+ }
+ }
+
+ void LayoutLayers()
+ {
+ CGRect checkFrame = GetCheckFrame(_nativeControl.Bounds);
+ _containerLayer.Frame = _nativeControl.Bounds;
+ _containerLayer.LineWidth = _containerLineWidth;
+ _containerLayer.Path = GetContainerPath(checkFrame);
+
+ _checkLayer.Frame = _nativeControl.Bounds;
+ _checkLayer.LineWidth = _checkLineWidth;
+ _checkLayer.Path = GetCheckPath(checkFrame);
+ }
+
+ protected virtual CGRect GetFrame(CGRect bounds)
+ {
+ return new CGRect(0, bounds.Height / 2 - _checkSize / 2, _checkSize, _checkSize);
+ }
+
+ protected virtual CGRect GetCheckFrame(CGRect bounds)
+ {
+ return new CGRect(0, bounds.Height / 2 - _checkSize / 2, _checkSize, _checkSize);
+ }
+
+ protected virtual CGPath GetContainerPath(CGRect frame)
+ {
+ frame = CGRect.Inflate(frame, -_containerInset, -_containerInset);
+ frame.X -= _containerInset - 1;
+ return UIBezierPath.FromOval(frame).CGPath;
+ }
+
+ protected virtual CGPath GetCheckPath(CGRect frame)
+ {
+ frame = CGRect.Inflate(frame, -_checkInset, -_checkInset);
+ frame.X -= _containerInset - 1;
+ return UIBezierPath.FromOval(frame).CGPath;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/RadioButtonRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/RadioButtonRenderer.cs
new file mode 100644
index 00000000000..2b68cd37760
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/Renderers/RadioButtonRenderer.cs
@@ -0,0 +1,298 @@
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+using CoreAnimation;
+using Foundation;
+using UIKit;
+using SizeF = CoreGraphics.CGSize;
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class RadioButtonRenderer : ViewRenderer
+ {
+ UIColor _buttonTextColorDefaultDisabled;
+ UIColor _buttonTextColorDefaultHighlighted;
+ UIColor _buttonTextColorDefaultNormal;
+ bool _useLegacyColorManagement;
+ bool _titleChanged;
+ SizeF _titleSize;
+ UIEdgeInsets _paddingDelta = new UIEdgeInsets();
+ CALayer _radioButtonLayer;
+
+ // This looks like it should be a const under iOS Classic,
+ // but that doesn't work under iOS
+ // ReSharper disable once BuiltInTypeReferenceStyle
+ // Under iOS Classic Resharper wants to suggest this use the built-in type ref
+ // but under iOS that suggestion won't work
+ readonly nfloat _minimumButtonHeight = 44; // Apple docs
+
+ static readonly UIControlState[] s_controlStates = { UIControlState.Normal, UIControlState.Highlighted, UIControlState.Disabled };
+
+ public bool IsDisposed { get; private set; }
+
+ public RadioButtonRenderer() : base()
+ {
+ BorderElementManager.Init(this);
+ }
+
+ public override SizeF SizeThatFits(SizeF size)
+ {
+ var result = base.SizeThatFits(size);
+
+ if (result.Height < _minimumButtonHeight)
+ {
+ result.Height = _minimumButtonHeight;
+ }
+
+ return result;
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (IsDisposed)
+ return;
+ if (Control != null)
+ {
+ Control.TouchUpInside -= OnButtonTouchUpInside;
+ Control.TouchDown -= OnButtonTouchDown;
+ BorderElementManager.Dispose(this);
+ }
+
+ IsDisposed = true;
+
+ base.Dispose(disposing);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs e)
+ {
+ base.OnElementChanged(e);
+
+ if (e.NewElement != null)
+ {
+ if (Control == null)
+ {
+ SetNativeControl(CreateNativeControl());
+ SetRadioBoxLayer(CreateRadioBoxLayer());
+ Control.HorizontalAlignment = UIControlContentHorizontalAlignment.Left;
+
+ Debug.Assert(Control != null, "Control != null");
+
+ SetControlPropertiesFromProxy();
+
+ _useLegacyColorManagement = e.NewElement.UseLegacyColorManagement();
+
+ _buttonTextColorDefaultNormal = Control.TitleColor(UIControlState.Normal);
+ _buttonTextColorDefaultHighlighted = Control.TitleColor(UIControlState.Highlighted);
+ _buttonTextColorDefaultDisabled = Control.TitleColor(UIControlState.Disabled);
+
+ Control.TouchUpInside += OnButtonTouchUpInside;
+ Control.TouchDown += OnButtonTouchDown;
+ }
+
+ UpdateText();
+ UpdateFont();
+ UpdateTextColor();
+ UpdatePadding();
+ }
+ }
+
+ protected override UIButton CreateNativeControl()
+ {
+ return new UIButton(UIButtonType.System);
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+
+ if (e.PropertyName == RadioButton.TextProperty.PropertyName)
+ UpdateText();
+ else if (e.PropertyName == RadioButton.TextColorProperty.PropertyName)
+ UpdateTextColor();
+ else if (e.PropertyName == RadioButton.FontProperty.PropertyName)
+ UpdateFont();
+ else if (e.PropertyName == RadioButton.PaddingProperty.PropertyName)
+ UpdatePadding();
+ else if (e.PropertyName == VisualElement.IsEnabledProperty.PropertyName)
+ _radioButtonLayer.SetNeedsDisplay();
+ else if (e.PropertyName == RadioButton.IsCheckedProperty.PropertyName)
+ _radioButtonLayer.SetNeedsDisplay();
+ }
+
+ protected override void SetAccessibilityLabel()
+ {
+ // If we have not specified an AccessibilityLabel and the AccessibilityLabel is currently bound to the Title,
+ // exit this method so we don't set the AccessibilityLabel value and break the binding.
+ // This may pose a problem for users who want to explicitly set the AccessibilityLabel to null, but this
+ // will prevent us from inadvertently breaking UI Tests that are using Query.Marked to get the dynamic Title
+ // of the Button.
+
+ var elemValue = (string)Element?.GetValue(AutomationProperties.NameProperty);
+ if (string.IsNullOrWhiteSpace(elemValue) && Control?.AccessibilityLabel == Control?.Title(UIControlState.Normal))
+ return;
+
+ base.SetAccessibilityLabel();
+ }
+
+ protected virtual CALayer CreateRadioBoxLayer()
+ {
+ return new RadioButtonCALayer(Element, Control);
+ }
+
+ void SetRadioBoxLayer(CALayer layer)
+ {
+ _radioButtonLayer = layer;
+ Control.Layer.AddSublayer(_radioButtonLayer);
+ _radioButtonLayer.SetNeedsLayout();
+ _radioButtonLayer.SetNeedsDisplay();
+ }
+
+ void SetControlPropertiesFromProxy()
+ {
+ foreach (UIControlState uiControlState in s_controlStates)
+ {
+ Control.SetTitleColor(UIButton.Appearance.TitleColor(uiControlState), uiControlState); // if new values are null, old values are preserved.
+ Control.SetTitleShadowColor(UIButton.Appearance.TitleShadowColor(uiControlState), uiControlState);
+ Control.SetBackgroundImage(UIButton.Appearance.BackgroundImageForState(uiControlState), uiControlState);
+ }
+ }
+
+ void OnButtonTouchUpInside(object sender, EventArgs eventArgs)
+ {
+ ButtonElementManager.OnButtonTouchUpInside(this.Element);
+
+ if (!Element.IsChecked)
+ Element.IsChecked = !Element.IsChecked;
+
+ _radioButtonLayer.SetNeedsDisplay();
+ }
+
+ void OnButtonTouchDown(object sender, EventArgs eventArgs)
+ {
+ ButtonElementManager.OnButtonTouchDown(this.Element);
+ }
+
+ void UpdateFont()
+ {
+ Control.TitleLabel.Font = Element.ToUIFont();
+ }
+
+ void UpdateText()
+ {
+ var newText = Element.Text;
+
+ if (Control.Title(UIControlState.Normal) != newText)
+ {
+ Control.SetTitle(Element.Text, UIControlState.Normal);
+ _titleChanged = true;
+ }
+ }
+
+ void UpdateTextColor()
+ {
+ if (Element.TextColor == Color.Default)
+ {
+ Control.SetTitleColor(_buttonTextColorDefaultNormal, UIControlState.Normal);
+ Control.SetTitleColor(_buttonTextColorDefaultHighlighted, UIControlState.Highlighted);
+ Control.SetTitleColor(_buttonTextColorDefaultDisabled, UIControlState.Disabled);
+ }
+ else
+ {
+ var color = Element.TextColor.ToUIColor();
+
+ Control.SetTitleColor(color, UIControlState.Normal);
+ Control.SetTitleColor(color, UIControlState.Highlighted);
+ Control.SetTitleColor(_useLegacyColorManagement ? _buttonTextColorDefaultDisabled : color, UIControlState.Disabled);
+
+ Control.TintColor = color;
+ }
+ }
+
+ protected virtual void UpdatePadding(UIButton button = null)
+ {
+ var uiElement = button ?? Control;
+ if (uiElement == null)
+ return;
+ uiElement.ContentEdgeInsets = new UIEdgeInsets(
+ (float)(Element.Padding.Top + _paddingDelta.Top),
+ (float)(Element.Padding.Left + _paddingDelta.Left),
+ (float)(Element.Padding.Bottom + _paddingDelta.Bottom),
+ (float)(Element.Padding.Right + _paddingDelta.Right)
+ );
+
+ // Make room for radio box
+ var contentEdgeInsets = uiElement.ContentEdgeInsets;
+ contentEdgeInsets.Left += _radioButtonLayer.Frame.Left + _radioButtonLayer.Frame.Width;
+ uiElement.ContentEdgeInsets = contentEdgeInsets;
+ }
+
+ void UpdateContentEdge(UIButton button, UIEdgeInsets? delta = null)
+ {
+ _paddingDelta = delta ?? new UIEdgeInsets();
+ UpdatePadding(button);
+ }
+
+ void ClearEdgeInsets(UIButton button)
+ {
+ if (button == null)
+ return;
+
+ Control.TitleEdgeInsets = new UIEdgeInsets(0, 0, 0, 0);
+ UpdateContentEdge(Control);
+ }
+
+ void ComputeEdgeInsets(UIButton button, Button.ButtonContentLayout layout)
+ {
+ if (button?.ImageView?.Image == null || string.IsNullOrEmpty(button.TitleLabel?.Text))
+ return;
+
+ var position = layout.Position;
+ var spacing = (nfloat)(layout.Spacing / 2);
+
+ if (position == Button.ButtonContentLayout.ImagePosition.Left)
+ {
+ button.TitleEdgeInsets = new UIEdgeInsets(0, spacing, 0, -spacing);
+ UpdateContentEdge(button, new UIEdgeInsets(0, 2 * spacing, 0, 2 * spacing));
+ return;
+ }
+
+ if (_titleChanged)
+ {
+ var stringToMeasure = new NSString(button.TitleLabel.Text);
+ UIStringAttributes attribs = new UIStringAttributes { Font = button.TitleLabel.Font };
+ _titleSize = stringToMeasure.GetSizeUsingAttributes(attribs);
+ _titleChanged = false;
+ }
+
+ var labelWidth = _titleSize.Width;
+ var imageWidth = button.ImageView.Image.Size.Width;
+
+ if (position == Button.ButtonContentLayout.ImagePosition.Right)
+ {
+ button.ImageEdgeInsets = new UIEdgeInsets(0, labelWidth + spacing, 0, -labelWidth - spacing);
+ button.TitleEdgeInsets = new UIEdgeInsets(0, -imageWidth - spacing, 0, imageWidth + spacing);
+ UpdateContentEdge(button, new UIEdgeInsets(0, 2 * spacing, 0, 2 * spacing));
+ return;
+ }
+
+ var imageVertOffset = (_titleSize.Height / 2);
+ var titleVertOffset = (button.ImageView.Image.Size.Height / 2);
+
+ var edgeOffset = (float)Math.Min(imageVertOffset, titleVertOffset);
+
+ UpdateContentEdge(button, new UIEdgeInsets(edgeOffset, 0, edgeOffset, 0));
+
+ var horizontalImageOffset = labelWidth / 2;
+ var horizontalTitleOffset = imageWidth / 2;
+
+ if (position == Button.ButtonContentLayout.ImagePosition.Bottom)
+ {
+ imageVertOffset = -imageVertOffset;
+ titleVertOffset = -titleVertOffset;
+ }
+
+ button.ImageEdgeInsets = new UIEdgeInsets(-imageVertOffset, horizontalImageOffset, imageVertOffset, -horizontalImageOffset);
+ button.TitleEdgeInsets = new UIEdgeInsets(titleVertOffset, -horizontalTitleOffset, -titleVertOffset, horizontalTitleOffset);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/ScrollViewRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/ScrollViewRenderer.cs
index 5c3eb0c7f43..c2687b48192 100644
--- a/Xamarin.Forms.Platform.iOS/Renderers/ScrollViewRenderer.cs
+++ b/Xamarin.Forms.Platform.iOS/Renderers/ScrollViewRenderer.cs
@@ -124,7 +124,7 @@ public override void LayoutSubviews()
_insetTracker?.OnLayoutSubviews();
base.LayoutSubviews();
- if(Superview != null)
+ if(Superview != null && ScrollView != null)
{
if (_requestedScroll != null)
{
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/SearchBarRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/SearchBarRenderer.cs
index 5e6f6fecdb8..864ac872880 100644
--- a/Xamarin.Forms.Platform.iOS/Renderers/SearchBarRenderer.cs
+++ b/Xamarin.Forms.Platform.iOS/Renderers/SearchBarRenderer.cs
@@ -4,6 +4,7 @@
using CoreGraphics;
using Foundation;
using UIKit;
+using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
namespace Xamarin.Forms.Platform.iOS
{
@@ -87,6 +88,7 @@ protected override void OnElementChanged(ElementChangedEventArgs e)
UpdateCharacterSpacing();
UpdateMaxLength();
UpdateKeyboard();
+ UpdateSearchBarStyle();
}
base.OnElementChanged(e);
@@ -135,6 +137,8 @@ protected override void OnElementPropertyChanged(object sender, PropertyChangedE
UpdateKeyboard();
else if(e.PropertyName == Xamarin.Forms.InputView.IsSpellCheckEnabledProperty.PropertyName)
UpdateKeyboard();
+ else if(e.PropertyName == PlatformConfiguration.iOSSpecific.SearchBar.SearchBarStyleProperty.PropertyName)
+ UpdateSearchBarStyle();
}
protected override void SetBackgroundColor(Color color)
@@ -403,5 +407,10 @@ UIToolbar CreateNumericKeyboardAccessoryView()
return accessoryView;
}
+
+ void UpdateSearchBarStyle()
+ {
+ Control.SearchBarStyle = Element.OnThisPlatform().GetSearchBarStyle().ToNativeSearchBarStyle();
+ }
}
}
\ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/SliderRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/SliderRenderer.cs
index 08fcbb07bb3..a3db4e5ae02 100644
--- a/Xamarin.Forms.Platform.iOS/Renderers/SliderRenderer.cs
+++ b/Xamarin.Forms.Platform.iOS/Renderers/SliderRenderer.cs
@@ -12,6 +12,7 @@ public class SliderRenderer : ViewRenderer
SizeF _fitSize;
UIColor defaultmintrackcolor, defaultmaxtrackcolor, defaultthumbcolor;
UITapGestureRecognizer _sliderTapRecognizer;
+ bool _disposed;
[Internals.Preserve(Conditional = true)]
public SliderRenderer()
@@ -26,17 +27,27 @@ public override SizeF SizeThatFits(SizeF size)
protected override void Dispose(bool disposing)
{
- if (Control != null)
+ if (_disposed)
{
- Control.ValueChanged -= OnControlValueChanged;
- if (_sliderTapRecognizer != null)
+ return;
+ }
+
+ _disposed = true;
+
+ if (disposing)
+ {
+ if (Control != null)
{
- Control.RemoveGestureRecognizer(_sliderTapRecognizer);
- _sliderTapRecognizer = null;
- }
+ Control.ValueChanged -= OnControlValueChanged;
+ if (_sliderTapRecognizer != null)
+ {
+ Control.RemoveGestureRecognizer(_sliderTapRecognizer);
+ _sliderTapRecognizer = null;
+ }
- Control.RemoveTarget(OnTouchDownControlEvent, UIControlEvent.TouchDown);
- Control.RemoveTarget(OnTouchUpControlEvent, UIControlEvent.TouchUpInside | UIControlEvent.TouchUpOutside);
+ Control.RemoveTarget(OnTouchDownControlEvent, UIControlEvent.TouchDown);
+ Control.RemoveTarget(OnTouchUpControlEvent, UIControlEvent.TouchUpInside | UIControlEvent.TouchUpOutside);
+ }
}
base.Dispose(disposing);
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/StepperRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/StepperRenderer.cs
index 62d0820f73f..874463e21a3 100644
--- a/Xamarin.Forms.Platform.iOS/Renderers/StepperRenderer.cs
+++ b/Xamarin.Forms.Platform.iOS/Renderers/StepperRenderer.cs
@@ -7,16 +7,27 @@ namespace Xamarin.Forms.Platform.iOS
{
public class StepperRenderer : ViewRenderer
{
+ bool _disposed;
+
[Internals.Preserve(Conditional = true)]
public StepperRenderer()
{
-
}
protected override void Dispose(bool disposing)
{
- if (Control != null)
- Control.ValueChanged -= OnValueChanged;
+ if (_disposed)
+ {
+ return;
+ }
+
+ _disposed = true;
+
+ if(disposing)
+ {
+ if (Control != null)
+ Control.ValueChanged -= OnValueChanged;
+ }
base.Dispose(disposing);
}
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/TabbedRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/TabbedRenderer.cs
index bf9cc2c54e1..37b5e6284e7 100644
--- a/Xamarin.Forms.Platform.iOS/Renderers/TabbedRenderer.cs
+++ b/Xamarin.Forms.Platform.iOS/Renderers/TabbedRenderer.cs
@@ -473,9 +473,20 @@ void UpdateSelectedTabColors()
return;
if (Tabbed.IsSet(TabbedPage.SelectedTabColorProperty) && Tabbed.SelectedTabColor != Color.Default)
- TabBar.SelectedImageTintColor = Tabbed.SelectedTabColor.ToUIColor();
+ {
+ if (Forms.IsiOS10OrNewer)
+ TabBar.TintColor = Tabbed.SelectedTabColor.ToUIColor();
+ else
+ TabBar.SelectedImageTintColor = Tabbed.SelectedTabColor.ToUIColor();
+
+ }
else
- TabBar.SelectedImageTintColor = UITabBar.Appearance.SelectedImageTintColor;
+ {
+ if (Forms.IsiOS10OrNewer)
+ TabBar.TintColor = UITabBar.Appearance.TintColor;
+ else
+ TabBar.SelectedImageTintColor = UITabBar.Appearance.SelectedImageTintColor;
+ }
if (!Forms.IsiOS10OrNewer)
return;
@@ -499,4 +510,4 @@ protected virtual async Task> GetIcon(Page page)
return icon == null ? null : Tuple.Create(icon, (UIImage)null);
}
}
-}
\ No newline at end of file
+}
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/WebViewRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/WebViewRenderer.cs
index 26ff1d9abaf..efd71a4c9c5 100644
--- a/Xamarin.Forms.Platform.iOS/Renderers/WebViewRenderer.cs
+++ b/Xamarin.Forms.Platform.iOS/Renderers/WebViewRenderer.cs
@@ -1,6 +1,9 @@
using System;
+using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
+using System.Linq;
+using System.Net;
using System.Threading.Tasks;
using Foundation;
using UIKit;
@@ -271,6 +274,29 @@ public override bool ShouldStartLoad(UIWebView webView, NSUrlRequest request, UI
var lastUrl = request.Url.ToString();
var args = new WebNavigatingEventArgs(navEvent, new UrlWebViewSource { Url = lastUrl }, lastUrl);
+ // Set cookies here
+ var cookieJar = NSHttpCookieStorage.SharedStorage;
+ cookieJar.AcceptPolicy = NSHttpCookieAcceptPolicy.Always;
+
+ //clean up old cookies
+ foreach (var aCookie in cookieJar.Cookies)
+ {
+ cookieJar.DeleteCookie(aCookie);
+ }
+ //set up the new cookies
+ if (WebView.Cookies != null)
+ {
+ var jCookies = WebView.Cookies.GetCookies(request.Url);
+ IList eCookies =
+ (from object jCookie in jCookies
+ where jCookie != null
+ select (Cookie)jCookie
+ into netCookie
+ select new NSHttpCookie(netCookie)).ToList();
+
+ cookieJar.SetCookies(eCookies.ToArray(), request.Url, request.Url);
+ }
+
WebView.SendNavigating(args);
_renderer.UpdateCanGoBackForward();
return !args.Cancel;
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/WkWebViewRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/WkWebViewRenderer.cs
index 14b792094ad..d6839762390 100644
--- a/Xamarin.Forms.Platform.iOS/Renderers/WkWebViewRenderer.cs
+++ b/Xamarin.Forms.Platform.iOS/Renderers/WkWebViewRenderer.cs
@@ -1,6 +1,8 @@
using System;
+using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
+using System.Text;
using System.Threading.Tasks;
using Foundation;
using ObjCRuntime;
@@ -94,12 +96,39 @@ public void LoadHtml(string html, string baseUrl)
LoadHtmlString(html, baseUrl == null ? new NSUrl(NSBundle.MainBundle.BundlePath, true) : new NSUrl(baseUrl, true));
}
- public void LoadUrl(string url)
+ public async void LoadUrl(string url)
{
var uri = new Uri(url);
var safeHostUri = new Uri($"{uri.Scheme}://{uri.Authority}", UriKind.Absolute);
var safeRelativeUri = new Uri($"{uri.PathAndQuery}{uri.Fragment}", UriKind.Relative);
- LoadRequest(new NSUrlRequest(new Uri(safeHostUri, safeRelativeUri)));
+ NSUrlRequest request = new NSUrlRequest(new Uri(safeHostUri, safeRelativeUri));
+
+ if (WebView.Cookies != null)
+ {
+ if (Forms.IsiOS11OrNewer)
+ {
+ var existingCookies = await Configuration.WebsiteDataStore.HttpCookieStore.GetAllCookiesAsync();
+
+ foreach (var cookie in existingCookies)
+ await Configuration.WebsiteDataStore.HttpCookieStore.DeleteCookieAsync(cookie);
+
+
+ var jCookies = WebView.Cookies.GetCookies(uri);
+
+ foreach (System.Net.Cookie jCookie in jCookies)
+ {
+ await Configuration.WebsiteDataStore.HttpCookieStore.SetCookieAsync(new NSHttpCookie(jCookie));
+ }
+ }
+ else if(WebView.Cookies.Count > 0)
+ {
+ WKUserScript wKUserScript = new WKUserScript(new NSString(GetCookieString(uri)), WKUserScriptInjectionTime.AtDocumentStart, false);
+ Configuration.UserContentController.AddUserScript(wKUserScript);
+
+ }
+ }
+
+ LoadRequest(request);
}
public override void LayoutSubviews()
@@ -199,6 +228,49 @@ void UpdateCanGoBackForward()
((IWebViewController)WebView).CanGoForward = CanGoForward;
}
+
+
+ string GetCookieString(Uri url)
+ {
+ var jCookies = WebView.Cookies.GetCookies(url);
+
+ StringBuilder cookieBuilder = new StringBuilder();
+ foreach (System.Net.Cookie jCookie in jCookies)
+ {
+
+ cookieBuilder.Append("document.cookie = '");
+ cookieBuilder.Append(jCookie.Name);
+ cookieBuilder.Append("=");
+ cookieBuilder.Append(jCookie.Value);
+
+ if (!jCookie.Expired)
+ {
+ cookieBuilder.Append($"; Max-Age={jCookie.Expires.Subtract(DateTime.UtcNow).TotalSeconds}");
+ }
+
+ if (!String.IsNullOrWhiteSpace(jCookie.Domain))
+ {
+ cookieBuilder.Append($"; Domain={jCookie.Domain}");
+ }
+ if (!String.IsNullOrWhiteSpace(jCookie.Domain))
+ {
+ cookieBuilder.Append($"; Path={jCookie.Path}");
+ }
+ if (jCookie.Secure)
+ {
+ cookieBuilder.Append($"; Secure");
+ }
+ if (jCookie.HttpOnly)
+ {
+ cookieBuilder.Append($"; HttpOnly");
+ }
+
+ cookieBuilder.Append("';");
+ }
+
+ return cookieBuilder.ToString();
+ }
+
class CustomWebViewNavigationDelegate : WKNavigationDelegate
{
readonly WkWebViewRenderer _renderer;
@@ -240,6 +312,7 @@ public override void DidFinishNavigation(WKWebView webView, WKNavigation navigat
WebView.SendNavigated(args);
_renderer.UpdateCanGoBackForward();
+
}
public override void DidStartProvisionalNavigation(WKWebView webView, WKNavigation navigation)
diff --git a/Xamarin.Forms.Platform.iOS/VisualElementTracker.cs b/Xamarin.Forms.Platform.iOS/VisualElementTracker.cs
index 2abbb983f58..81daffd9d16 100644
--- a/Xamarin.Forms.Platform.iOS/VisualElementTracker.cs
+++ b/Xamarin.Forms.Platform.iOS/VisualElementTracker.cs
@@ -277,7 +277,7 @@ void update()
const double epsilon = 0.001;
#if !__MOBILE__
- // fix position, position in macos is aslo relative to anchor point
+ // fix position, position in macos is also relative to anchor point
// but it's (0,0) by default, so we don't need to substract 0.5
transform = transform.Translate(anchorX * width, 0, 0);
transform = transform.Translate(0, anchorY * height, 0);
diff --git a/Xamarin.Forms.Platform.iOS/Xamarin.Forms.Platform.iOS.csproj b/Xamarin.Forms.Platform.iOS/Xamarin.Forms.Platform.iOS.csproj
index 59a288e0582..9b82bd35756 100644
--- a/Xamarin.Forms.Platform.iOS/Xamarin.Forms.Platform.iOS.csproj
+++ b/Xamarin.Forms.Platform.iOS/Xamarin.Forms.Platform.iOS.csproj
@@ -178,6 +178,8 @@
+
+
diff --git a/Xamarin.Forms.Platform/Xamarin.Forms.Platform.cs b/Xamarin.Forms.Platform/Xamarin.Forms.Platform.cs
index e862e04965b..baf9fe21adb 100644
--- a/Xamarin.Forms.Platform/Xamarin.Forms.Platform.cs
+++ b/Xamarin.Forms.Platform/Xamarin.Forms.Platform.cs
@@ -35,6 +35,10 @@ internal class _ButtonRenderer
{
}
+ internal class _RadioButtonRenderer
+ {
+ }
+
internal class _TableViewRenderer
{
}
| |