diff --git a/src/Compatibility/Core/src/Android/Renderers/ActivityIndicatorRenderer.cs b/src/Compatibility/Core/src/Android/Renderers/ActivityIndicatorRenderer.cs index a87b3c6e19eb..4731921bc4e9 100644 --- a/src/Compatibility/Core/src/Android/Renderers/ActivityIndicatorRenderer.cs +++ b/src/Compatibility/Core/src/Android/Renderers/ActivityIndicatorRenderer.cs @@ -12,6 +12,7 @@ public ActivityIndicatorRenderer(Context context) : base(context) AutoPackage = false; } + [PortHandler] protected override AProgressBar CreateNativeControl() { return new AProgressBar(Context) { Indeterminate = true }; @@ -42,6 +43,7 @@ protected override void OnElementPropertyChanged(object sender, PropertyChangedE UpdateColor(); } + [PortHandler] void UpdateColor() { if (Element == null || Control == null) @@ -55,6 +57,7 @@ void UpdateColor() Control.IndeterminateDrawable?.ClearColorFilter(); } + [PortHandler] void UpdateVisibility() { if (Element == null || Control == null) diff --git a/src/Compatibility/Core/src/iOS/Renderers/ActivityIndicatorRenderer.cs b/src/Compatibility/Core/src/iOS/Renderers/ActivityIndicatorRenderer.cs index 1129d5e455da..c97990a5eb45 100644 --- a/src/Compatibility/Core/src/iOS/Renderers/ActivityIndicatorRenderer.cs +++ b/src/Compatibility/Core/src/iOS/Renderers/ActivityIndicatorRenderer.cs @@ -5,6 +5,7 @@ namespace Microsoft.Maui.Controls.Compatibility.Platform.iOS { + [PortHandler] public sealed class UIActivityIndicatorViewDelegate : UIActivityIndicatorView { ActivityIndicator _element; @@ -40,6 +41,7 @@ public ActivityIndicatorRenderer() } + [PortHandler] protected override void OnElementChanged(ElementChangedEventArgs e) { if (e.NewElement != null) @@ -69,11 +71,13 @@ protected override void OnElementPropertyChanged(object sender, PropertyChangedE UpdateIsRunning(); } + [PortHandler] void UpdateColor() { Control.Color = Element.Color == Color.Default ? null : Element.Color.ToUIColor(); } + [PortHandler] void UpdateIsRunning() { if (Control?.Superview == null) diff --git a/src/Controls/samples/Controls.Sample/Pages/MainPage.cs b/src/Controls/samples/Controls.Sample/Pages/MainPage.cs index fc02ad767d46..e7c2e9886da3 100644 --- a/src/Controls/samples/Controls.Sample/Pages/MainPage.cs +++ b/src/Controls/samples/Controls.Sample/Pages/MainPage.cs @@ -64,6 +64,8 @@ void SetupMauiLayout() var underlineLabel = new Label { Text = "underline", TextDecorations = TextDecorations.Underline }; verticalStack.Add(underlineLabel); + verticalStack.Add(new ActivityIndicator()); + verticalStack.Add(new ActivityIndicator { Color = Color.Red, IsRunning = true }); var button = new Button() { Text = _viewModel.Text, WidthRequest = 200 }; var button2 = new Button() diff --git a/src/Controls/src/Core/ActivityIndicator.cs b/src/Controls/src/Core/ActivityIndicator.cs index 6a8b37c8d7ee..fa5ae9247705 100644 --- a/src/Controls/src/Core/ActivityIndicator.cs +++ b/src/Controls/src/Core/ActivityIndicator.cs @@ -2,7 +2,7 @@ namespace Microsoft.Maui.Controls { - public class ActivityIndicator : View, IColorElement, IElementConfiguration + public partial class ActivityIndicator : View, IColorElement, IElementConfiguration { public static readonly BindableProperty IsRunningProperty = BindableProperty.Create("IsRunning", typeof(bool), typeof(ActivityIndicator), default(bool)); diff --git a/src/Controls/src/Core/HandlerImpl/ActivityIndicator.Impl.cs b/src/Controls/src/Core/HandlerImpl/ActivityIndicator.Impl.cs new file mode 100644 index 000000000000..ab5a58035b9d --- /dev/null +++ b/src/Controls/src/Core/HandlerImpl/ActivityIndicator.Impl.cs @@ -0,0 +1,7 @@ +namespace Microsoft.Maui.Controls +{ + public partial class ActivityIndicator : IActivityIndicator + { + + } +} \ No newline at end of file diff --git a/src/Core/src/Core/IActivityIndicator.cs b/src/Core/src/Core/IActivityIndicator.cs new file mode 100644 index 000000000000..5405d9da7cdf --- /dev/null +++ b/src/Core/src/Core/IActivityIndicator.cs @@ -0,0 +1,20 @@ +namespace Microsoft.Maui +{ + /// + /// Represents a View that displays an animation to show that the application is engaged + /// in a lengthy activity. + /// + public interface IActivityIndicator : IView + { + /// + /// Gets a value that indicates whether the ActivityIndicator should be visible and animating, + /// or hidden. + /// + bool IsRunning { get; } + + /// + /// Gets a Color value that defines the display color. + /// + Color Color { get; } + } +} \ No newline at end of file diff --git a/src/Core/src/Handlers/ActivityIndicator/ActivityIndicatorHandler.Android.cs b/src/Core/src/Handlers/ActivityIndicator/ActivityIndicatorHandler.Android.cs new file mode 100644 index 000000000000..e8641ed95174 --- /dev/null +++ b/src/Core/src/Handlers/ActivityIndicator/ActivityIndicatorHandler.Android.cs @@ -0,0 +1,9 @@ +using Android.Widget; + +namespace Microsoft.Maui.Handlers +{ + public partial class ActivityIndicatorHandler : AbstractViewHandler + { + protected override ProgressBar CreateNativeView() => new ProgressBar(Context) { Indeterminate = true }; + } +} \ No newline at end of file diff --git a/src/Core/src/Handlers/ActivityIndicator/ActivityIndicatorHandler.Standard.cs b/src/Core/src/Handlers/ActivityIndicator/ActivityIndicatorHandler.Standard.cs new file mode 100644 index 000000000000..e3bcf0a074b4 --- /dev/null +++ b/src/Core/src/Handlers/ActivityIndicator/ActivityIndicatorHandler.Standard.cs @@ -0,0 +1,9 @@ +using System; + +namespace Microsoft.Maui.Handlers +{ + public partial class ActivityIndicatorHandler : AbstractViewHandler + { + protected override object CreateNativeView() => throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/Core/src/Handlers/ActivityIndicator/ActivityIndicatorHandler.cs b/src/Core/src/Handlers/ActivityIndicator/ActivityIndicatorHandler.cs new file mode 100644 index 000000000000..89380c4153c1 --- /dev/null +++ b/src/Core/src/Handlers/ActivityIndicator/ActivityIndicatorHandler.cs @@ -0,0 +1,31 @@ +namespace Microsoft.Maui.Handlers +{ + public partial class ActivityIndicatorHandler + { + public static PropertyMapper ActivityIndicatorMapper = new PropertyMapper(ViewHandler.ViewMapper) + { + [nameof(IActivityIndicator.IsRunning)] = MapIsRunning, + [nameof(IActivityIndicator.Color)] = MapColor + }; + + public static void MapIsRunning(ActivityIndicatorHandler handler, IActivityIndicator activityIndicator) + { + handler.TypedNativeView?.UpdateIsRunning(activityIndicator); + } + + public static void MapColor(ActivityIndicatorHandler handler, IActivityIndicator activityIndicator) + { + handler.TypedNativeView?.UpdateColor(activityIndicator); + } + + public ActivityIndicatorHandler() : base(ActivityIndicatorMapper) + { + + } + + public ActivityIndicatorHandler(PropertyMapper mapper) : base(mapper ?? ActivityIndicatorMapper) + { + + } + } +} \ No newline at end of file diff --git a/src/Core/src/Handlers/ActivityIndicator/ActivityIndicatorHandler.iOS.cs b/src/Core/src/Handlers/ActivityIndicator/ActivityIndicatorHandler.iOS.cs new file mode 100644 index 000000000000..6949aa9c5df6 --- /dev/null +++ b/src/Core/src/Handlers/ActivityIndicator/ActivityIndicatorHandler.iOS.cs @@ -0,0 +1,13 @@ +using CoreGraphics; +using UIKit; + +namespace Microsoft.Maui.Handlers +{ + public partial class ActivityIndicatorHandler : AbstractViewHandler + { + protected override NativeActivityIndicator CreateNativeView() => new NativeActivityIndicator(CGRect.Empty, VirtualView) + { + ActivityIndicatorViewStyle = UIActivityIndicatorViewStyle.Gray + }; + } +} \ No newline at end of file diff --git a/src/Core/src/Hosting/AppHostBuilderExtensions.cs b/src/Core/src/Hosting/AppHostBuilderExtensions.cs index 80048436d7d6..15e21dd85b9b 100644 --- a/src/Core/src/Hosting/AppHostBuilderExtensions.cs +++ b/src/Core/src/Hosting/AppHostBuilderExtensions.cs @@ -36,6 +36,7 @@ public static IAppHostBuilder UseMauiHandlers(this IAppHostBuilder builder) { builder.RegisterHandlers(new Dictionary { + { typeof(IActivityIndicator), typeof(ActivityIndicatorHandler) }, { typeof(IButton), typeof(ButtonHandler) }, { typeof(ICheckBox), typeof(CheckBoxHandler) }, { typeof(IEditor), typeof(EditorHandler) }, diff --git a/src/Core/src/Platform/Android/ActivityIndicatorExtensions.cs b/src/Core/src/Platform/Android/ActivityIndicatorExtensions.cs new file mode 100644 index 000000000000..f36afb093a29 --- /dev/null +++ b/src/Core/src/Platform/Android/ActivityIndicatorExtensions.cs @@ -0,0 +1,21 @@ +using Android.Views; +using Android.Widget; + +namespace Microsoft.Maui +{ + public static class ActivityIndicatorExtensions + { + public static void UpdateIsRunning(this ProgressBar progressBar, IActivityIndicator activityIndicator) => + progressBar.Visibility = activityIndicator.IsRunning ? ViewStates.Visible : ViewStates.Invisible; + + public static void UpdateColor(this ProgressBar progressBar, IActivityIndicator activityIndicator) + { + Color color = activityIndicator.Color; + + if (!color.IsDefault) + progressBar.IndeterminateDrawable?.SetColorFilter(color.ToNative(), FilterMode.SrcIn); + else + progressBar.IndeterminateDrawable?.ClearColorFilter(); + } + } +} \ No newline at end of file diff --git a/src/Core/src/Platform/Standard/ActivityIndicatorExtensions.cs b/src/Core/src/Platform/Standard/ActivityIndicatorExtensions.cs new file mode 100644 index 000000000000..89c86ca030b2 --- /dev/null +++ b/src/Core/src/Platform/Standard/ActivityIndicatorExtensions.cs @@ -0,0 +1,15 @@ +namespace Microsoft.Maui +{ + public static class ActivityIndicatorExtensions + { + public static void UpdateIsRunning(this object nothing, IActivityIndicator activityIndicator) + { + + } + + public static void UpdateColor(this object nothing, IActivityIndicator activityIndicator) + { + + } + } +} diff --git a/src/Core/src/Platform/iOS/ActivityIndicatorExtensions.cs b/src/Core/src/Platform/iOS/ActivityIndicatorExtensions.cs new file mode 100644 index 000000000000..a2adce6cdaac --- /dev/null +++ b/src/Core/src/Platform/iOS/ActivityIndicatorExtensions.cs @@ -0,0 +1,18 @@ +namespace Microsoft.Maui +{ + public static class ActivityIndicatorExtensions + { + public static void UpdateIsRunning(this NativeActivityIndicator activityIndicatorView, IActivityIndicator activityIndicator) + { + if (activityIndicator.IsRunning) + activityIndicatorView.StartAnimating(); + else + activityIndicatorView.StopAnimating(); + } + + public static void UpdateColor(this NativeActivityIndicator activityIndicatorView, IActivityIndicator activityIndicator) + { + activityIndicatorView.Color = activityIndicator.Color == Color.Default ? null : activityIndicator.Color.ToNative(); + } + } +} \ No newline at end of file diff --git a/src/Core/src/Platform/iOS/NativeActivityIndicator.cs b/src/Core/src/Platform/iOS/NativeActivityIndicator.cs new file mode 100644 index 000000000000..59517d559db0 --- /dev/null +++ b/src/Core/src/Platform/iOS/NativeActivityIndicator.cs @@ -0,0 +1,35 @@ +using CoreGraphics; +using UIKit; + +namespace Microsoft.Maui +{ + public class NativeActivityIndicator : UIActivityIndicatorView + { + IActivityIndicator? _virtualView; + + public NativeActivityIndicator(CGRect rect, IActivityIndicator? virtualView) : base(rect) + => _virtualView = virtualView; + + public override void Draw(CGRect rect) + { + base.Draw(rect); + + if (_virtualView?.IsRunning == true) + StartAnimating(); + } + + public override void LayoutSubviews() + { + base.LayoutSubviews(); + + if (_virtualView?.IsRunning == true) + StartAnimating(); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + _virtualView = null; + } + } +} \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Handlers/ActivityIndicator/ActivityIndicatorHandlerTests.Android.cs b/src/Core/tests/DeviceTests/Handlers/ActivityIndicator/ActivityIndicatorHandlerTests.Android.cs new file mode 100644 index 000000000000..49ed3540e89e --- /dev/null +++ b/src/Core/tests/DeviceTests/Handlers/ActivityIndicator/ActivityIndicatorHandlerTests.Android.cs @@ -0,0 +1,30 @@ +using System; +using System.Threading.Tasks; +using Android.Views; +using Android.Widget; +using Microsoft.Maui.Handlers; + +namespace Microsoft.Maui.DeviceTests +{ + public partial class ActivityIndicatorHandlerTests + { + ProgressBar GetNativeActivityIndicator(ActivityIndicatorHandler activityIndicatorHandler) => + (ProgressBar)activityIndicatorHandler.View; + + bool GetNativeIsRunning(ActivityIndicatorHandler activityIndicatorHandler) => + GetNativeActivityIndicator(activityIndicatorHandler).Visibility == ViewStates.Visible; + + Task ValidateColor(IActivityIndicator activityIndicator, Color color, Action action = null) => + ValidateHasColor(activityIndicator, color, action); + + Task ValidateHasColor(IActivityIndicator activityIndicator, Color color, Action action = null) + { + return InvokeOnMainThreadAsync(() => + { + var nativeActivityIndicator = GetNativeActivityIndicator(CreateHandler(activityIndicator)); + action?.Invoke(); + nativeActivityIndicator.AssertContainsColor(color); + }); + } + } +} \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Handlers/ActivityIndicator/ActivityIndicatorHandlerTests.cs b/src/Core/tests/DeviceTests/Handlers/ActivityIndicator/ActivityIndicatorHandlerTests.cs new file mode 100644 index 000000000000..7c4172fafdb6 --- /dev/null +++ b/src/Core/tests/DeviceTests/Handlers/ActivityIndicator/ActivityIndicatorHandlerTests.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("ActivityIndicatorHandler")] + public partial class ActivityIndicatorHandlerTests : HandlerTestBase + { + public ActivityIndicatorHandlerTests(HandlerTestFixture fixture) : base(fixture) + { + } + + [Theory(DisplayName = "IsRunning Initializes Correctly")] + [InlineData(true)] + [InlineData(false)] + public async Task IsRunningInitializesCorrectly(bool isRunning) + { + var activityIndicator = new ActivityIndicatorStub() + { + IsRunning = isRunning + }; + + await ValidatePropertyInitValue(activityIndicator, () => activityIndicator.IsRunning, GetNativeIsRunning, activityIndicator.IsRunning); + } + + [Fact(DisplayName = "BackgroundColor Updates Correctly")] + public async Task BackgroundColorUpdatesCorrectly() + { + var activityIndicator = new ActivityIndicatorStub() + { + BackgroundColor = Color.Yellow, + IsRunning = true + }; + + await ValidateColor(activityIndicator, Color.Yellow, () => activityIndicator.BackgroundColor = Color.Yellow); + } + } +} \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Handlers/ActivityIndicator/ActivityIndicatorHandlerTests.iOS.cs b/src/Core/tests/DeviceTests/Handlers/ActivityIndicator/ActivityIndicatorHandlerTests.iOS.cs new file mode 100644 index 000000000000..c479279edf76 --- /dev/null +++ b/src/Core/tests/DeviceTests/Handlers/ActivityIndicator/ActivityIndicatorHandlerTests.iOS.cs @@ -0,0 +1,28 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Maui.Handlers; +using UIKit; +using Xunit; + +namespace Microsoft.Maui.DeviceTests +{ + public partial class ActivityIndicatorHandlerTests + { + UIActivityIndicatorView GetNativeActivityIndicator(ActivityIndicatorHandler activityIndicatorHandler) => + (UIActivityIndicatorView)activityIndicatorHandler.View; + + bool GetNativeIsRunning(ActivityIndicatorHandler activityIndicatorHandler) => + GetNativeActivityIndicator(activityIndicatorHandler).IsAnimating; + + async Task ValidateColor(IActivityIndicator activityIndicator, Color color, Action action = null) + { + var expected = await GetValueAsync(activityIndicator, handler => + { + var native = GetNativeActivityIndicator(handler); + action?.Invoke(); + return native.BackgroundColor.ToColor(); + }); + Assert.Equal(expected, color); + } + } +} \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Stubs/ActivityIndicatorStub.cs b/src/Core/tests/DeviceTests/Stubs/ActivityIndicatorStub.cs new file mode 100644 index 000000000000..8abdb87d1fdb --- /dev/null +++ b/src/Core/tests/DeviceTests/Stubs/ActivityIndicatorStub.cs @@ -0,0 +1,9 @@ +namespace Microsoft.Maui.DeviceTests.Stubs +{ + public class ActivityIndicatorStub : StubBase, IActivityIndicator + { + public bool IsRunning { get; set; } + + public Color Color { get; set; } + } +} \ No newline at end of file