Skip to content

Commit

Permalink
Check to see if Image Should Reload When Attached (#24023)
Browse files Browse the repository at this point in the history
* Check to see if Image Should Reload When Attached

* - cleanup

* Update ImageSourcePartLoader.cs

* - fix images
  • Loading branch information
PureWeen authored Aug 6, 2024
1 parent 6bfd7e1 commit b0da2a0
Show file tree
Hide file tree
Showing 12 changed files with 245 additions and 1 deletion.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue14471.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8" ?>
<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Maui.Controls.Sample.Issues.Issue14471"
xmlns:ns="clr-namespace:Maui.Controls.Sample.Issues">
<TabBar>
<ShellContent Route="tab1" Title="tab1" ContentTemplate="{DataTemplate ns:Issue14471Tab1Content}"/>
<ShellContent Route="tab2" Title="tab2" ContentTemplate="{DataTemplate ns:Issue14471Tab2Content}"/>
</TabBar>
</Shell>
55 changes: 55 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue14471.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
namespace Maui.Controls.Sample.Issues
{
[XamlCompilation(XamlCompilationOptions.Compile)]
[Issue(IssueTracker.Github, 14471, "Image can disappear when going back to the page", PlatformAffected.Android)]
public partial class Issue14471 : Shell
{
public Issue14471()
{
InitializeComponent();
}
}

public class Issue14471Tab1Content : ContentPage
{

public Issue14471Tab1Content()
{
var image = new Image() { HeightRequest = 100, AutomationId = "image" };
var imageButton = new ImageButton() { HeightRequest = 100, AutomationId = "imageButton" };
Content = new VerticalStackLayout()
{
new Button()
{
AutomationId = "switchToTab2Button",
Text = "Switch to tab 2",
Command = new Command(async () => await Shell.Current.GoToAsync("//tab2"))
},
image,
imageButton
};
LoadImage((source) => image.Source = source);
LoadImage((source) => imageButton.Source = source);
}

async void LoadImage(Action<ImageSource> setImageSource)
{
await Task.Delay(1);
var imageBytes = Convert.FromBase64String("iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAB+FBMVEUAAAA/mUPidDHiLi5Cn0XkNTPmeUrkdUg/m0Q0pEfcpSbwaVdKskg+lUP4zA/iLi3msSHkOjVAmETdJSjtYFE/lkPnRj3sWUs8kkLeqCVIq0fxvhXqUkbVmSjwa1n1yBLepyX1xxP0xRXqUkboST9KukpHpUbuvRrzrhF/ljbwaljuZFM4jELaoSdLtElJrUj1xxP6zwzfqSU4i0HYnydMtUlIqUfywxb60AxZqEXaoifgMCXptR9MtklHpEY2iUHWnSjvvRr70QujkC+pUC/90glMuEnlOjVMt0j70QriLS1LtEnnRj3qUUXfIidOjsxAhcZFo0bjNDH0xxNLr0dIrUdmntVTkMoyfL8jcLBRuErhJyrgKyb4zA/5zg3tYFBBmUTmQTnhMinruBzvvhnxwxZ/st+Ktt5zp9hqota2vtK6y9FemNBblc9HiMiTtMbFtsM6gcPV2r6dwroseLrMrbQrdLGdyKoobKbo3Zh+ynrgVllZulTsXE3rV0pIqUf42UVUo0JyjEHoS0HmsiHRGR/lmRz/1hjqnxjvpRWfwtOhusaz0LRGf7FEfbDVmqHXlJeW0pbXq5bec3fX0nTnzmuJuWvhoFFhm0FtrziBsjaAaDCYWC+uSi6jQS3FsSfLJiTirCOkuCG1KiG+wSC+GBvgyhTszQ64Z77KAAAARXRSTlMAIQRDLyUgCwsE6ebm5ubg2dLR0byXl4FDQzU1NDEuLSUgC+vr6urq6ubb29vb2tra2tG8vLu7u7uXl5eXgYGBgYGBLiUALabIAAABsElEQVQoz12S9VPjQBxHt8VaOA6HE+AOzv1wd7pJk5I2adpCC7RUcHd3d3fXf5PvLkxheD++z+yb7GSRlwD/+Hj/APQCZWxM5M+goF+RMbHK594v+tPoiN1uHxkt+xzt9+R9wnRTZZQpXQ0T5uP1IQxToyOAZiQu5HEpjeA4SWIoksRxNiGC1tRZJ4LNxgHgnU5nJZBDvuDdl8lzQRBsQ+s9PZt7s7Pz8wsL39/DkIfZ4xlB2Gqsq62ta9oxVlVrNZpihFRpGO9fzQw1ms0NDWZz07iGkJmIFH8xxkc3a/WWlubmFkv9AB2SEpDvKxbjidN2faseaNV3zoHXvv7wMODJdkOHAegweAfFPx4G67KluxzottCU9n8CUqXzcIQdXOytAHqXxomvykhEKN9EFutG22p//0rbNvHVxiJywa8yS2KDfV1dfbu31H8jF1RHiTKtWYeHxUvq3bn0pyjCRaiRU6aDO+gb3aEfEeVNsDgm8zzLy9egPa7Qt8TSJdwhjplk06HH43ZNJ3s91KKCHQ5x4sw1fRGYDZ0n1L4FKb9/BP5JLYxToheoFCVxz57PPS8UhhEpLBVeAAAAAElFTkSuQmCC");
setImageSource.Invoke(ImageSource.FromStream(() => new MemoryStream(imageBytes)));
}
}

public class Issue14471Tab2Content : ContentPage
{
public Issue14471Tab2Content()
{
Content = new Button()
{
AutomationId = "switchToTab1Button",
Text = "Switch to tab 1",
Command = new Command(async () => await Shell.Current.GoToAsync("//tab1"))
};
}
}
}
22 changes: 22 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue6625.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Maui.Controls.Sample.Issues.Issue6625">
<ContentPage.Content>
<Grid x:Name="Container"
RowDefinitions="Auto,*">
<Label x:Name="Label"
Grid.Row="0"
LineBreakMode="CharacterWrap"
HorizontalOptions="Center"
VerticalOptions="Start"
Padding="8,16"/>
<Image x:Name="Image"
Grid.Row="1"
BackgroundColor="WhiteSmoke"
Aspect="AspectFit"
VerticalOptions="Center"
HorizontalOptions="Center" />
</Grid>
</ContentPage.Content>
</ContentPage>
51 changes: 51 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue6625.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System;
using System.Collections;
using System.Linq;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Xaml;
using Microsoft.Maui.Platform;

namespace Maui.Controls.Sample.Issues
{
[Issue(IssueTracker.Github, 6625, "Changing image source on Android causes flicker between images", PlatformAffected.Android)]
public partial class Issue6625 : ContentPage
{
readonly ImageSource[] imageSources =
[
"https://64.media.tumblr.com/14cb5aa197e5d9ca6479d955f68344f0/cb28a32e384437f2-07/s540x810/005508be5849eff8fda5b0ebda23f9fcbede164e.jpg",
"https://64.media.tumblr.com/d66b1709639c23315d26c0a4e977c399/1fb3e31c5e63625d-81/s540x810/2e9a893ec8c1f65a4b9fb8f1264b4fb76914ced8.jpg",
null,
"https://64.media.tumblr.com/e7db300b8248c0a7f4c66884032c9414/8f89498ebe784d1c-4e/s540x810/90835c041547bf2936ee249c5c5e1b4eddd589a3.jpg",
"https://64.media.tumblr.com/fd901060692d2c9ca6f05ddc58e28e5d/2db73c9c5730d87c-e2/s500x750/092c9dc3cff5150acf24bf22a9e61b9719d9ccd6.jpg",
"https://64.media.tumblr.com/c011aa547a45ac6fe47c46beeb290596/eafc76ded66f16f9-dc/s500x750/5ed923ed086296a2bd41cab3106079eb7addc2d0.jpg",
"https://this.is.a.broken.url",
new FontImageSource { FontFamily = "FA", Glyph = "\uf111", Size = 200, Color = Colors.Blue },
new FontImageSource { FontFamily = "FA", Glyph = "\uf192", Size = 200, Color = Colors.Blue },
new FontImageSource { FontFamily = "FA", Glyph = "\uf111", Size = 200, Color = Colors.Blue },
new FontImageSource { FontFamily = "FA", Glyph = "\uf192", Size = 200, Color = Colors.Blue }
];

int imageNo = -1;

public Issue6625()
{
InitializeComponent();
Container.GestureRecognizers.Add(new TapGestureRecognizer { Command = new Command(NextImage) });
NextImage();
}

void NextImage()
{
imageNo = (imageNo + 1) % imageSources.Length;

ImageSource imageSource = imageSources[imageNo];
Label.Text = imageSource switch
{
UriImageSource uri => uri.Uri.ToString(),
FontImageSource font => $"Glyph: {Convert.ToBase64String(font.Glyph.Select(c => (byte)c).ToArray())}",
_ => imageSource?.GetType().Name ?? "null"
};
Image.Source = imageSource;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#if !MACCATALYST // VerifyScreenshot() is not supported on MacCatalyst
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues;

public class Issue14471 : _IssuesUITest
{
public Issue14471(TestDevice device) : base(device){ }

public override string Issue => "Image can disappear when going back to the page";

[Test]
[Category(UITestCategories.Image)]
public void ImageDoesntDisappearWhenNavigatingBack()
{
App.WaitForElement("image");
App.Click("switchToTab2Button");
App.WaitForElement("switchToTab1Button");
App.Click("switchToTab1Button");
App.WaitForElement("image");

// The test passes if image is loaded
VerifyScreenshot();
}
}
#endif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
48 changes: 47 additions & 1 deletion src/Core/src/Handlers/Image/ImageHandler.Android.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
using System.Threading.Tasks;
using Android.Graphics.Drawables;
using Android.Views;
using Android.Widget;
using AndroidX.AppCompat.Widget;
using Google.Android.Material.Button;

namespace Microsoft.Maui.Handlers
{
public partial class ImageHandler : ViewHandler<IImage, ImageView>
{

protected override ImageView CreatePlatformView()
{
var imageView = new AppCompatImageView(Context);
Expand All @@ -21,9 +22,15 @@ protected override ImageView CreatePlatformView()
return imageView;
}

protected override void ConnectHandler(ImageView platformView)
{
platformView.ViewAttachedToWindow += OnPlatformViewAttachedToWindow;
}

protected override void DisconnectHandler(ImageView platformView)
{
base.DisconnectHandler(platformView);
platformView.ViewAttachedToWindow -= OnPlatformViewAttachedToWindow;
SourceLoader.Reset();
}

Expand Down Expand Up @@ -54,10 +61,17 @@ await handler
.SourceLoader
.UpdateImageSourceAsync();


// This indicates that the image has finished loading
// So, now if the attached event fires again then we need to see if Glide has cleared the image out
handler.SourceLoader.CheckForImageLoadedOnAttached = true;

// Because this resolves from a task we should validate that the
// handler hasn't been disconnected
if (handler.IsConnected())
{
handler.UpdateValue(nameof(IImage.IsAnimationPlaying));
}
}

public override void PlatformArrange(Graphics.Rect frame)
Expand All @@ -79,12 +93,44 @@ public override void PlatformArrange(Graphics.Rect frame)
base.PlatformArrange(frame);
}

internal static void OnPlatformViewAttachedToWindow(IImageHandler imageHandler)
{

// Glide will automatically clear out the image if the Fragment or Activity is destroyed
// So we want to reload the image here if it's supposed to have an image
if (imageHandler.SourceLoader.CheckForImageLoadedOnAttached &&
imageHandler.PlatformView.Drawable is null &&
imageHandler.VirtualView.Source is not null)
{
imageHandler.SourceLoader.CheckForImageLoadedOnAttached = false;
imageHandler.UpdateValue(nameof(IImage.Source));
}
}

void OnPlatformViewAttachedToWindow(object? sender, View.ViewAttachedToWindowEventArgs e)
{
if (sender is not View platformView)
{
return;
}

if (!this.IsConnected())
{
platformView.ViewAttachedToWindow -= OnPlatformViewAttachedToWindow;
return;
}

OnPlatformViewAttachedToWindow(this);
}

partial class ImageImageSourcePartSetter
{
public override void SetImageSource(Drawable? platformImage)
{
if (Handler?.PlatformView is not ImageView image)
{
return;
}

image.SetImageDrawable(platformImage);
}
Expand Down
18 changes: 18 additions & 0 deletions src/Core/src/Handlers/ImageButton/ImageButtonHandler.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ protected override void DisconnectHandler(ShapeableImageView platformView)
platformView.FocusChange -= OnFocusChange;
platformView.Click -= OnClick;
platformView.Touch -= OnTouch;
platformView.ViewAttachedToWindow -= OnPlatformViewAttachedToWindow;

base.DisconnectHandler(platformView);

Expand All @@ -34,6 +35,7 @@ protected override void ConnectHandler(ShapeableImageView platformView)
platformView.FocusChange += OnFocusChange;
platformView.Click += OnClick;
platformView.Touch += OnTouch;
platformView.ViewAttachedToWindow += OnPlatformViewAttachedToWindow;

base.ConnectHandler(platformView);
}
Expand Down Expand Up @@ -90,6 +92,22 @@ void OnClick(object? sender, EventArgs e)
VirtualView?.Clicked();
}

void OnPlatformViewAttachedToWindow(object? sender, View.ViewAttachedToWindowEventArgs e)
{
if (sender is not View platformView)
{
return;
}

if (!this.IsConnected())
{
platformView.ViewAttachedToWindow -= OnPlatformViewAttachedToWindow;
return;
}

ImageHandler.OnPlatformViewAttachedToWindow(this);
}

partial class ImageButtonImageSourcePartSetter
{
public override void SetImageSource(Drawable? platformImage)
Expand Down
13 changes: 13 additions & 0 deletions src/Core/src/Platform/ImageSourcePartLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ public partial class ImageSourcePartLoader
IImageSourceServiceProvider? _imageSourceServiceProvider;
#endif

#if ANDROID
// This is a temporary workaround for Android so that images don't just keep vanishing
// We will have a better fix in the next release that's better integrated with Glide
internal bool CheckForImageLoadedOnAttached { get; set; }
#endif

readonly IImageSourcePartSetter _setter;

internal ImageSourceServiceResultManager SourceManager { get; } = new ImageSourceServiceResultManager();
Expand Down Expand Up @@ -77,10 +83,17 @@ public async Task UpdateImageSourceAsync()
#else
await Task.CompletedTask;
#endif

#if ANDROID
CheckForImageLoadedOnAttached = true;
#endif
}
else
{
Setter.SetImageSource(null);
#if ANDROID
CheckForImageLoadedOnAttached = false;
#endif
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ Microsoft.Maui.SoftInputExtensions
override Microsoft.Maui.FontSize.Equals(object? obj) -> bool
override Microsoft.Maui.FontSize.GetHashCode() -> int
override Microsoft.Maui.Handlers.EditorHandler.PlatformArrange(Microsoft.Maui.Graphics.Rect frame) -> void
override Microsoft.Maui.Handlers.ImageHandler.ConnectHandler(Android.Widget.ImageView! platformView) -> void
override Microsoft.Maui.Handlers.RadioButtonHandler.PlatformArrange(Microsoft.Maui.Graphics.Rect frame) -> void
override Microsoft.Maui.Handlers.ShapeViewHandler.GetDesiredSize(double widthConstraint, double heightConstraint) -> Microsoft.Maui.Graphics.Size
override Microsoft.Maui.Layouts.FlexBasis.Equals(object? obj) -> bool
Expand Down

0 comments on commit b0da2a0

Please sign in to comment.