diff --git a/src/Compatibility/Core/src/Android/AppCompat/CheckBoxRendererBase.cs b/src/Compatibility/Core/src/Android/AppCompat/CheckBoxRendererBase.cs index ec2d6f5d5947..f297f90da05a 100644 --- a/src/Compatibility/Core/src/Android/AppCompat/CheckBoxRendererBase.cs +++ b/src/Compatibility/Core/src/Android/AppCompat/CheckBoxRendererBase.cs @@ -29,6 +29,7 @@ public class CheckBoxRendererBase : IPlatformElementConfiguration _platformElementConfiguration; CheckBox _checkBox; + [PortHandler] static int[][] _checkedStates = new int[][] { new int[] { AAttribute.StateEnabled, AAttribute.StateChecked }, @@ -185,6 +186,7 @@ void IOnCheckedChangeListener.OnCheckedChanged(CompoundButton buttonView, bool i ((IElementController)Element).SetValueFromRenderer(CheckBox.IsCheckedProperty, isChecked); } + [PortHandler] void UpdateIsChecked() { if (Element == null || Control == null) @@ -210,6 +212,7 @@ protected virtual ColorStateList GetColorStateList() return list; } + [PortHandler] void UpdateBackgroundColor() { if (Element.BackgroundColor == Color.Default) @@ -225,6 +228,7 @@ void UpdateBackground() this.UpdateBackground(background); } + [PortHandler] void UpdateOnColor() { if (Element == null || Control == null) diff --git a/src/Compatibility/Core/src/iOS/Renderers/CheckBoxRendererBase.cs b/src/Compatibility/Core/src/iOS/Renderers/CheckBoxRendererBase.cs index c9d2db419552..645e03951fd6 100644 --- a/src/Compatibility/Core/src/iOS/Renderers/CheckBoxRendererBase.cs +++ b/src/Compatibility/Core/src/iOS/Renderers/CheckBoxRendererBase.cs @@ -121,6 +121,7 @@ protected override void OnElementChanged(ElementChangedEventArgs e) base.OnElementChanged(e); } + [PortHandler] protected virtual void UpdateTintColor() { if (Element == null) @@ -129,6 +130,7 @@ protected virtual void UpdateTintColor() Control.CheckBoxTintColor = Element.Color; } + [PortHandler] void OnControlCheckedChanged(object sender, EventArgs e) { Element.IsChecked = Control.IsChecked; diff --git a/src/Compatibility/Core/src/iOS/Renderers/FormsCheckBox.cs b/src/Compatibility/Core/src/iOS/Renderers/FormsCheckBox.cs index 7359ff4708f6..26eb53c8fdad 100644 --- a/src/Compatibility/Core/src/iOS/Renderers/FormsCheckBox.cs +++ b/src/Compatibility/Core/src/iOS/Renderers/FormsCheckBox.cs @@ -4,6 +4,7 @@ namespace Microsoft.Maui.Controls.Compatibility.Platform.iOS { + [PortHandler("NativeCheckBox")] public class FormsCheckBox : UIButton { static UIImage _checked; diff --git a/src/Controls/samples/Controls.Sample/Pages/MainPage.cs b/src/Controls/samples/Controls.Sample/Pages/MainPage.cs index 889bcbfac4c6..3c17134a9f37 100644 --- a/src/Controls/samples/Controls.Sample/Pages/MainPage.cs +++ b/src/Controls/samples/Controls.Sample/Pages/MainPage.cs @@ -79,6 +79,10 @@ void SetupMauiLayout() verticalStack.Add(horizontalStack); + verticalStack.Add(new CheckBox()); + verticalStack.Add(new CheckBox { BackgroundColor = Color.LightPink }); + verticalStack.Add(new CheckBox { IsChecked = true, Color = Color.Aquamarine }); + verticalStack.Add(new Editor()); verticalStack.Add(new Editor { Text = "Editor" }); @@ -89,6 +93,7 @@ void SetupMauiLayout() }; verticalStack.Add(entry); + verticalStack.Add(new Entry { Text = "Entry", TextColor = Color.DarkRed }); verticalStack.Add(new Entry { IsPassword = true, TextColor = Color.Black }); verticalStack.Add(new Entry { IsTextPredictionEnabled = false }); diff --git a/src/Controls/src/Core/CheckBox.cs b/src/Controls/src/Core/CheckBox.cs index 727dd02db742..6fc4621753d1 100644 --- a/src/Controls/src/Core/CheckBox.cs +++ b/src/Controls/src/Core/CheckBox.cs @@ -2,7 +2,7 @@ namespace Microsoft.Maui.Controls { - public class CheckBox : View, IElementConfiguration, IBorderElement, IColorElement + public partial class CheckBox : View, IElementConfiguration, IBorderElement, IColorElement { readonly Lazy> _platformConfigurationRegistry; public const string IsCheckedVisualState = "IsChecked"; diff --git a/src/Controls/src/Core/HandlerImpl/CheckBox.Impl.cs b/src/Controls/src/Core/HandlerImpl/CheckBox.Impl.cs new file mode 100644 index 000000000000..4c0bca9b90fc --- /dev/null +++ b/src/Controls/src/Core/HandlerImpl/CheckBox.Impl.cs @@ -0,0 +1,7 @@ +namespace Microsoft.Maui.Controls +{ + public partial class CheckBox : ICheck + { + + } +} \ No newline at end of file diff --git a/src/Core/src/Core/ICheck.cs b/src/Core/src/Core/ICheck.cs new file mode 100644 index 000000000000..f437547f77b5 --- /dev/null +++ b/src/Core/src/Core/ICheck.cs @@ -0,0 +1,8 @@ +namespace Microsoft.Maui +{ + public interface ICheck : IView + { + bool IsChecked { get; set; } + Color Color { get; } + } +} \ No newline at end of file diff --git a/src/Core/src/Handlers/CheckBox/CheckBoxHandler.Android.cs b/src/Core/src/Handlers/CheckBox/CheckBoxHandler.Android.cs new file mode 100644 index 000000000000..85d09c5bdf6c --- /dev/null +++ b/src/Core/src/Handlers/CheckBox/CheckBoxHandler.Android.cs @@ -0,0 +1,57 @@ +using Android.Widget; +using AndroidX.AppCompat.Widget; + +namespace Microsoft.Maui.Handlers +{ + public partial class CheckBoxHandler : AbstractViewHandler + { + CheckedChangeListener ChangeListener { get; } = new CheckedChangeListener(); + + protected override AppCompatCheckBox CreateNativeView() + { + var nativeCheckBox = new AppCompatCheckBox(Context) + { + SoundEffectsEnabled = false + }; + + nativeCheckBox.SetClipToOutline(true); + + return nativeCheckBox; + } + + protected override void ConnectHandler(AppCompatCheckBox nativeView) + { + ChangeListener.Handler = this; + nativeView.SetOnCheckedChangeListener(ChangeListener); + } + + protected override void DisconnectHandler(AppCompatCheckBox nativeView) + { + ChangeListener.Handler = null; + nativeView.SetOnCheckedChangeListener(null); + } + + void OnCheckedChanged(bool isChecked) + { + if (VirtualView != null) + VirtualView.IsChecked = isChecked; + } + + internal class CheckedChangeListener : Java.Lang.Object, CompoundButton.IOnCheckedChangeListener + { + public CheckBoxHandler? Handler { get; set; } + + public CheckedChangeListener() + { + } + + public void OnCheckedChanged(CompoundButton? nativeCheckBox, bool isChecked) + { + if (Handler == null || nativeCheckBox == null) + return; + + Handler.OnCheckedChanged(isChecked); + } + } + } +} \ No newline at end of file diff --git a/src/Core/src/Handlers/CheckBox/CheckBoxHandler.Standard.cs b/src/Core/src/Handlers/CheckBox/CheckBoxHandler.Standard.cs new file mode 100644 index 000000000000..ac46bf9aacc8 --- /dev/null +++ b/src/Core/src/Handlers/CheckBox/CheckBoxHandler.Standard.cs @@ -0,0 +1,9 @@ +using System; + +namespace Microsoft.Maui.Handlers +{ + public partial class CheckBoxHandler : AbstractViewHandler + { + protected override object CreateNativeView() => throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/Core/src/Handlers/CheckBox/CheckBoxHandler.cs b/src/Core/src/Handlers/CheckBox/CheckBoxHandler.cs new file mode 100644 index 000000000000..247ff40fe104 --- /dev/null +++ b/src/Core/src/Handlers/CheckBox/CheckBoxHandler.cs @@ -0,0 +1,39 @@ +namespace Microsoft.Maui.Handlers +{ + public partial class CheckBoxHandler + { + public static PropertyMapper CheckBoxMapper = new PropertyMapper(ViewHandler.ViewMapper) + { +#if MONOANDROID + [nameof(ICheck.BackgroundColor)] = MapBackgroundColor, +#endif + [nameof(ICheck.IsChecked)] = MapIsChecked, + [nameof(ICheck.Color)] = MapColor + }; + + public CheckBoxHandler() : base(CheckBoxMapper) + { + + } + + public CheckBoxHandler(PropertyMapper mapper) : base(mapper ?? CheckBoxMapper) + { + + } +#if MONOANDROID + public static void MapBackgroundColor(CheckBoxHandler handler, ICheck check) + { + handler.TypedNativeView?.UpdateBackgroundColor(check); + } +#endif + public static void MapIsChecked(CheckBoxHandler handler, ICheck check) + { + handler.TypedNativeView?.UpdateIsChecked(check); + } + + public static void MapColor(CheckBoxHandler handler, ICheck check) + { + handler.TypedNativeView?.UpdateColor(check); + } + } +} \ No newline at end of file diff --git a/src/Core/src/Handlers/CheckBox/CheckBoxHandler.iOS.cs b/src/Core/src/Handlers/CheckBox/CheckBoxHandler.iOS.cs new file mode 100644 index 000000000000..e8917bdf53f9 --- /dev/null +++ b/src/Core/src/Handlers/CheckBox/CheckBoxHandler.iOS.cs @@ -0,0 +1,74 @@ +using System; + +namespace Microsoft.Maui.Handlers +{ + public partial class CheckBoxHandler : AbstractViewHandler + { + protected virtual float MinimumSize => 44f; + + protected override NativeCheckBox CreateNativeView() + { + return new NativeCheckBox + { + MinimumViewSize = MinimumSize + }; + } + + protected override void ConnectHandler(NativeCheckBox nativeView) + { + base.ConnectHandler(nativeView); + + nativeView.CheckedChanged += OnCheckedChanged; + } + + protected override void DisconnectHandler(NativeCheckBox nativeView) + { + base.DisconnectHandler(nativeView); + + nativeView.CheckedChanged -= OnCheckedChanged; + } + + public override Size GetDesiredSize(double widthConstraint, double heightConstraint) + { + var size = base.GetDesiredSize(widthConstraint, heightConstraint); + + var set = false; + + var width = widthConstraint; + var height = heightConstraint; + + if (size.Width == 0) + { + if (widthConstraint <= 0 || double.IsInfinity(widthConstraint)) + { + width = MinimumSize; + set = true; + } + } + + if (size.Height == 0) + { + if (heightConstraint <= 0 || double.IsInfinity(heightConstraint)) + { + height = MinimumSize; + set = true; + } + } + + if (set) + { + size = new Size(width, height); + } + + return size; + } + + void OnCheckedChanged(object? sender, EventArgs e) + { + if (sender is NativeCheckBox nativeView && VirtualView != null) + { + VirtualView.IsChecked = nativeView.IsChecked; + } + } + } +} \ No newline at end of file diff --git a/src/Core/src/Hosting/AppHostBuilderExtensions.cs b/src/Core/src/Hosting/AppHostBuilderExtensions.cs index ce1a513d1c10..6ca5ac987185 100644 --- a/src/Core/src/Hosting/AppHostBuilderExtensions.cs +++ b/src/Core/src/Hosting/AppHostBuilderExtensions.cs @@ -37,6 +37,7 @@ public static IAppHostBuilder UseMauiHandlers(this IAppHostBuilder builder) builder.RegisterHandlers(new Dictionary { { typeof(IButton), typeof(ButtonHandler) }, + { typeof(ICheck), typeof(CheckBoxHandler) }, { typeof(IEditor), typeof(EditorHandler) }, { typeof(IEntry), typeof(EntryHandler) }, { typeof(ILayout), typeof(LayoutHandler) }, diff --git a/src/Core/src/Platform/Android/CheckBoxExtensions.cs b/src/Core/src/Platform/Android/CheckBoxExtensions.cs new file mode 100644 index 000000000000..a4d766974d58 --- /dev/null +++ b/src/Core/src/Platform/Android/CheckBoxExtensions.cs @@ -0,0 +1,57 @@ +using Android.Content.Res; +using Android.Graphics; +using AndroidX.AppCompat.Widget; +using AndroidX.Core.Widget; +using AAttribute = Android.Resource.Attribute; +using AColor = Android.Graphics.Color; +using XColor = Microsoft.Maui.Color; + +namespace Microsoft.Maui +{ + public static class CheckBoxExtensions + { + static readonly int[][] CheckedStates = new int[][] + { + new int[] { AAttribute.StateEnabled, AAttribute.StateChecked }, + new int[] { AAttribute.StateEnabled, -AAttribute.StateChecked }, + new int[] { -AAttribute.StateEnabled, AAttribute.StateChecked }, + new int[] { -AAttribute.StateEnabled, -AAttribute.StatePressed }, + }; + + public static void UpdateBackgroundColor(this AppCompatCheckBox nativeCheckBox, ICheck check) + { + if (check.BackgroundColor == XColor.Default) + nativeCheckBox.SetBackgroundColor(AColor.Transparent); + else + nativeCheckBox.SetBackgroundColor(check.BackgroundColor.ToNative()); + } + + public static void UpdateIsChecked(this AppCompatCheckBox nativeCheckBox, ICheck check) + { + nativeCheckBox.Checked = check.IsChecked; + } + + public static void UpdateColor(this AppCompatCheckBox nativeCheckBox, ICheck check) + { + // TODO: Delete when implementing the logic to set the system accent color. + XColor accent = XColor.FromHex("#ff33b5e5"); + + var tintColor = check.Color == XColor.Default ? accent.ToNative() : check.Color.ToNative(); + + var tintList = new ColorStateList( + CheckedStates, + new int[] + { + tintColor, + tintColor, + tintColor, + tintColor + }); + + var tintMode = PorterDuff.Mode.SrcIn; + + CompoundButtonCompat.SetButtonTintList(nativeCheckBox, tintList); + CompoundButtonCompat.SetButtonTintMode(nativeCheckBox, tintMode); + } + } +} \ No newline at end of file diff --git a/src/Core/src/Platform/Android/ViewExtensions.cs b/src/Core/src/Platform/Android/ViewExtensions.cs index c44009bb6a03..40744bf309d0 100644 --- a/src/Core/src/Platform/Android/ViewExtensions.cs +++ b/src/Core/src/Platform/Android/ViewExtensions.cs @@ -1,7 +1,3 @@ -using System; -using Android.Content.Res; -using Android.Graphics.Drawables; -using Microsoft.Maui; using AView = Android.Views.View; namespace Microsoft.Maui @@ -24,6 +20,16 @@ public static void UpdateBackgroundColor(this AView nativeView, IView view) nativeView?.SetBackgroundColor(backgroundColor.ToNative()); } + public static bool GetClipToOutline(this AView view) + { + return view.ClipToOutline; + } + + public static void SetClipToOutline(this AView view, bool value) + { + view.ClipToOutline = value; + } + public static void UpdateAutomationId(this AView nativeView, IView view) { if (AutomationTagId == DefaultAutomationTagId) diff --git a/src/Core/src/Platform/Standard/CheckBoxExtensions.cs b/src/Core/src/Platform/Standard/CheckBoxExtensions.cs new file mode 100644 index 000000000000..aee46706f1a5 --- /dev/null +++ b/src/Core/src/Platform/Standard/CheckBoxExtensions.cs @@ -0,0 +1,15 @@ +namespace Microsoft.Maui +{ + public static class CheckBoxExtensions + { + public static void UpdateIsChecked(this object nothing, ICheck check) + { + + } + + public static void UpdateColor(this object nothing, ICheck check) + { + + } + } +} \ No newline at end of file diff --git a/src/Core/src/Platform/iOS/CheckBoxExtensions.cs b/src/Core/src/Platform/iOS/CheckBoxExtensions.cs new file mode 100644 index 000000000000..6c58e26dfd5f --- /dev/null +++ b/src/Core/src/Platform/iOS/CheckBoxExtensions.cs @@ -0,0 +1,15 @@ +namespace Microsoft.Maui +{ + public static class CheckBoxExtensions + { + public static void UpdateIsChecked(this NativeCheckBox nativeCheckBox, ICheck check) + { + nativeCheckBox.IsChecked = check.IsChecked; + } + + public static void UpdateColor(this NativeCheckBox nativeCheckBox, ICheck check) + { + nativeCheckBox.CheckBoxTintColor = check.Color; + } + } +} \ No newline at end of file diff --git a/src/Core/src/Platform/iOS/NativeCheckBox.cs b/src/Core/src/Platform/iOS/NativeCheckBox.cs new file mode 100644 index 000000000000..4a5d8befef27 --- /dev/null +++ b/src/Core/src/Platform/iOS/NativeCheckBox.cs @@ -0,0 +1,264 @@ +using System; +using CoreGraphics; +using UIKit; + +namespace Microsoft.Maui +{ + public class NativeCheckBox : UIButton + { + // All these values were chosen to just match the android drawables that are used + const float DefaultSize = 18.0f; + const float LineWidth = 2.0f; + + static UIImage? Checked; + static UIImage? Unchecked; + + Color _tintColor; + bool _isChecked; + bool _isEnabled; + float _minimumViewSize; + bool _disposed; + + public EventHandler? CheckedChanged; + + internal float MinimumViewSize + { + get { return _minimumViewSize; } + set + { + _minimumViewSize = value; + var xOffset = (value - DefaultSize + LineWidth) / 4; + ContentEdgeInsets = new UIEdgeInsets(0, xOffset, 0, 0); + } + } + + public NativeCheckBox() + { + ContentMode = UIViewContentMode.Center; + ImageView.ContentMode = UIViewContentMode.ScaleAspectFit; + HorizontalAlignment = UIControlContentHorizontalAlignment.Left; + VerticalAlignment = UIControlContentVerticalAlignment.Center; + AdjustsImageWhenDisabled = false; + AdjustsImageWhenHighlighted = false; + + TouchUpInside += OnTouchUpInside; + } + + void OnTouchUpInside(object? sender, EventArgs e) + { + IsChecked = !IsChecked; + CheckedChanged?.Invoke(this, EventArgs.Empty); + } + + public bool IsChecked + { + get => _isChecked; + set + { + if (value == _isChecked) + return; + + _isChecked = value; + UpdateDisplay(); + } + } + + public bool IsEnabled + { + get => _isEnabled; + set + { + if (value == _isEnabled) + return; + + _isEnabled = value; + UserInteractionEnabled = IsEnabled; + UpdateDisplay(); + } + } + + public Color CheckBoxTintColor + { + get => _tintColor; + set + { + if (_tintColor == value) + return; + + _tintColor = value; + CheckBoxTintUIColor = CheckBoxTintColor.IsDefault ? null : CheckBoxTintColor.ToNative(); + } + } + + UIColor? _checkBoxTintUIColor; + UIColor? CheckBoxTintUIColor + { + get + { + return _checkBoxTintUIColor ?? UIColor.White; + } + set + { + if (value == _checkBoxTintUIColor) + return; + + _checkBoxTintUIColor = value; + ImageView.TintColor = value; + TintColor = value; + + if (Enabled) + SetNeedsDisplay(); + else + UpdateDisplay(); + } + } + + public override bool Enabled + { + get + { + return base.Enabled; + } + + set + { + bool changed = base.Enabled != value; + base.Enabled = value; + + if (changed) + UpdateDisplay(); + } + } + + protected virtual UIImage GetCheckBoximage() + { + // Ideally I would use the static images here but when disabled it always tints them grey + // and I don't know how to make it not tint them gray + if (!Enabled && CheckBoxTintColor != Color.Default) + { + if (IsChecked) + return CreateCheckBox(CreateCheckMark()).ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal); + + return CreateCheckBox(null).ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal); + } + + if (Checked == null) + Checked = CreateCheckBox(CreateCheckMark()).ImageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate); + + if (Unchecked == null) + Unchecked = CreateCheckBox(null).ImageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate); + + return IsChecked ? Checked : Unchecked; + } + + internal virtual UIBezierPath CreateBoxPath(CGRect backgroundRect) => UIBezierPath.FromOval(backgroundRect); + internal virtual UIBezierPath CreateCheckPath() => new UIBezierPath + { + LineWidth = (nfloat)0.077, + LineCapStyle = CGLineCap.Round, + LineJoinStyle = CGLineJoin.Round + }; + + internal virtual void DrawCheckMark(UIBezierPath path) + { + path.MoveTo(new CGPoint(0.72f, 0.22f)); + path.AddLineTo(new CGPoint(0.33f, 0.6f)); + path.AddLineTo(new CGPoint(0.15f, 0.42f)); + } + + internal virtual UIImage CreateCheckBox(UIImage? check) + { + UIGraphics.BeginImageContextWithOptions(new CGSize(DefaultSize, DefaultSize), false, 0); + var context = UIGraphics.GetCurrentContext(); + context.SaveState(); + + var checkedColor = CheckBoxTintUIColor; + + if (checkedColor != null) + { + checkedColor.SetFill(); + checkedColor.SetStroke(); + } + + var vPadding = LineWidth / 2; + var hPadding = LineWidth / 2; + var diameter = DefaultSize - LineWidth; + + var backgroundRect = new CGRect(hPadding, vPadding, diameter, diameter); + var boxPath = CreateBoxPath(backgroundRect); + boxPath.LineWidth = LineWidth; + boxPath.Stroke(); + + if (check != null) + { + boxPath.Fill(); + check.Draw(new CGPoint(0, 0), CGBlendMode.DestinationOut, 1); + } + + context.RestoreState(); + var img = UIGraphics.GetImageFromCurrentImageContext(); + UIGraphics.EndImageContext(); + + return img; + } + + internal UIImage CreateCheckMark() + { + UIGraphics.BeginImageContextWithOptions(new CGSize(DefaultSize, DefaultSize), false, 0); + var context = UIGraphics.GetCurrentContext(); + context.SaveState(); + + var vPadding = LineWidth / 2; + var hPadding = LineWidth / 2; + var diameter = DefaultSize - LineWidth; + + var checkPath = CreateCheckPath(); + + context.TranslateCTM(hPadding + (nfloat)(0.05 * diameter), vPadding + (nfloat)(0.1 * diameter)); + context.ScaleCTM(diameter, diameter); + DrawCheckMark(checkPath); + UIColor.White.SetStroke(); + checkPath.Stroke(); + + context.RestoreState(); + var img = UIGraphics.GetImageFromCurrentImageContext(); + UIGraphics.EndImageContext(); + + return img; + } + + public override CGSize SizeThatFits(CGSize size) + { + var result = base.SizeThatFits(size); + var height = Math.Max(MinimumViewSize, result.Height); + var width = Math.Max(MinimumViewSize, result.Width); + var final = Math.Min(width, height); + + return new CGSize(final, final); + } + + public override void LayoutSubviews() + { + base.LayoutSubviews(); + UpdateDisplay(); + } + + protected override void Dispose(bool disposing) + { + if (_disposed) + return; + + _disposed = true; + if (disposing) + TouchUpInside -= OnTouchUpInside; + + base.Dispose(disposing); + } + + void UpdateDisplay() + { + SetImage(GetCheckBoximage(), UIControlState.Normal); + SetNeedsDisplay(); + } + } +} \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Handlers/CheckBox/CheckBoxHandlerTests.Android.cs b/src/Core/tests/DeviceTests/Handlers/CheckBox/CheckBoxHandlerTests.Android.cs new file mode 100644 index 000000000000..a6c47ffb6cb1 --- /dev/null +++ b/src/Core/tests/DeviceTests/Handlers/CheckBox/CheckBoxHandlerTests.Android.cs @@ -0,0 +1,29 @@ +using System; +using System.Threading.Tasks; +using AndroidX.AppCompat.Widget; +using Microsoft.Maui.Handlers; + +namespace Microsoft.Maui.DeviceTests +{ + public partial class CheckBoxHandlerTests + { + AppCompatCheckBox GetNativeCheckBox(CheckBoxHandler checkBoxHandler) => + (AppCompatCheckBox)checkBoxHandler.View; + + bool GetNativeIsChecked(CheckBoxHandler checkBoxHandler) => + GetNativeCheckBox(checkBoxHandler).Checked; + + Task ValidateColor(ICheck checkBoxStub, Color color, Action action = null) => + ValidateHasColor(checkBoxStub, color, action); + + Task ValidateHasColor(ICheck checkBoxStub, Color color, Action action = null) + { + return InvokeOnMainThreadAsync(() => + { + var nativeSwitch = GetNativeCheckBox(CreateHandler(checkBoxStub)); + action?.Invoke(); + nativeSwitch.AssertContainsColor(color); + }); + } + } +} \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Handlers/CheckBox/CheckBoxHandlerTests.cs b/src/Core/tests/DeviceTests/Handlers/CheckBox/CheckBoxHandlerTests.cs new file mode 100644 index 000000000000..abd36f4f0a27 --- /dev/null +++ b/src/Core/tests/DeviceTests/Handlers/CheckBox/CheckBoxHandlerTests.cs @@ -0,0 +1,40 @@ +using System.Threading.Tasks; +using Microsoft.Maui.DeviceTests.Stubs; +using Microsoft.Maui.Handlers; +using Xunit; + +namespace Microsoft.Maui.DeviceTests +{ + [Category("CheckBoxHandler")] + public partial class CheckBoxHandlerTests : HandlerTestBase + { + public CheckBoxHandlerTests(HandlerTestFixture fixture) : base(fixture) + { + } + + [Theory(DisplayName = "IsChecked Initializes Correctly")] + [InlineData(true)] + [InlineData(false)] + public async Task IsCheckedInitializesCorrectly(bool isChecked) + { + var checkBoxStub = new CheckBoxStub() + { + IsChecked = isChecked + }; + + await ValidatePropertyInitValue(checkBoxStub, () => checkBoxStub.IsChecked, GetNativeIsChecked, checkBoxStub.IsChecked); + } + + [Fact(DisplayName = "Color Updates Correctly")] + public async Task ColorUpdatesCorrectly() + { + var checkBoxStub = new CheckBoxStub() + { + Color = Color.Red, + IsChecked = true + }; + + await ValidateColor(checkBoxStub, Color.Red, () => checkBoxStub.Color = Color.Red); + } + } +} \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Handlers/CheckBox/CheckBoxHandlerTests.iOS.cs b/src/Core/tests/DeviceTests/Handlers/CheckBox/CheckBoxHandlerTests.iOS.cs new file mode 100644 index 000000000000..f5aa9b6f7633 --- /dev/null +++ b/src/Core/tests/DeviceTests/Handlers/CheckBox/CheckBoxHandlerTests.iOS.cs @@ -0,0 +1,27 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Maui.Handlers; +using Xunit; + +namespace Microsoft.Maui.DeviceTests +{ + public partial class CheckBoxHandlerTests + { + NativeCheckBox GetNativeCheckBox(CheckBoxHandler checkBoxHandler) => + (NativeCheckBox)checkBoxHandler.View; + + bool GetNativeIsChecked(CheckBoxHandler checkBoxHandler) => + GetNativeCheckBox(checkBoxHandler).IsChecked; + + async Task ValidateColor(ICheck checkBoxStub, Color color, Action action = null) + { + var expected = await GetValueAsync(checkBoxStub, handler => + { + var native = GetNativeCheckBox(handler); + action?.Invoke(); + return native.CheckBoxTintColor; + }); + Assert.Equal(expected, color); + } + } +} \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Stubs/CheckBoxStub.cs b/src/Core/tests/DeviceTests/Stubs/CheckBoxStub.cs new file mode 100644 index 000000000000..26dff7d25c48 --- /dev/null +++ b/src/Core/tests/DeviceTests/Stubs/CheckBoxStub.cs @@ -0,0 +1,9 @@ +namespace Microsoft.Maui.DeviceTests.Stubs +{ + public partial class CheckBoxStub : StubBase, ICheck + { + public bool IsChecked { get; set; } + + public Color Color { get; set; } + } +} \ No newline at end of file