From e3ab9fcc3ff19d03a69f91acbe1fb8803c7f86cb Mon Sep 17 00:00:00 2001 From: Lucas Zimerman Fraulob Date: Tue, 9 Mar 2021 23:36:52 -0300 Subject: [PATCH 1/2] WIP - Multi image picker performance page. --- .../Interfaces/IImageResizer.cs | 9 +++ .../Sample.Xamarin.Core.csproj | 3 + .../ViewModels/ImageSelectorPageViewModel.cs | 70 +++++++++++++++++++ .../ViewModels/MainPageViewModel.cs | 8 +++ .../Views/ImageSelectorPage.xaml | 34 +++++++++ .../Views/ImageSelectorPage.xaml.cs | 20 ++++++ .../Sample.Xamarin.Core/Views/MainPage.xaml | 15 ++++ .../Sample.Xamarin.Droid.csproj | 1 + .../Tools/ImageResizer.cs | 53 ++++++++++++++ 9 files changed, 213 insertions(+) create mode 100644 Samples/Sample.Xamarin.Core/Interfaces/IImageResizer.cs create mode 100644 Samples/Sample.Xamarin.Core/ViewModels/ImageSelectorPageViewModel.cs create mode 100644 Samples/Sample.Xamarin.Core/Views/ImageSelectorPage.xaml create mode 100644 Samples/Sample.Xamarin.Core/Views/ImageSelectorPage.xaml.cs create mode 100644 Samples/Sample.Xamarin.Droid/Tools/ImageResizer.cs diff --git a/Samples/Sample.Xamarin.Core/Interfaces/IImageResizer.cs b/Samples/Sample.Xamarin.Core/Interfaces/IImageResizer.cs new file mode 100644 index 0000000..ac674c1 --- /dev/null +++ b/Samples/Sample.Xamarin.Core/Interfaces/IImageResizer.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Sample.Xamarin.Core.Interfaces +{ + public interface IImageResizer + { + Task ResizeImage(string imagePath, int maxSize, string filename); + } +} diff --git a/Samples/Sample.Xamarin.Core/Sample.Xamarin.Core.csproj b/Samples/Sample.Xamarin.Core/Sample.Xamarin.Core.csproj index dfce5ed..2db5391 100644 --- a/Samples/Sample.Xamarin.Core/Sample.Xamarin.Core.csproj +++ b/Samples/Sample.Xamarin.Core/Sample.Xamarin.Core.csproj @@ -65,6 +65,9 @@ MSBuild:UpdateDesignTimeXaml + + MSBuild:UpdateDesignTimeXaml + MSBuild:UpdateDesignTimeXaml diff --git a/Samples/Sample.Xamarin.Core/ViewModels/ImageSelectorPageViewModel.cs b/Samples/Sample.Xamarin.Core/ViewModels/ImageSelectorPageViewModel.cs new file mode 100644 index 0000000..da2e5b1 --- /dev/null +++ b/Samples/Sample.Xamarin.Core/ViewModels/ImageSelectorPageViewModel.cs @@ -0,0 +1,70 @@ +using Sample.Xamarin.Core.Interfaces; +using Sentry; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Xamarin.Forms; + +namespace Sample.Xamarin.Core.ViewModels +{ + public class ImageSelectorPageViewModel : NavigationService + { + public bool TaskCompleted { get; set; } + public int MaxThreads { get; set; } + public int MinThreads { get; set; } + private int _selectedMaxThreads; + public int SelectedMaxThreads + { + get => _selectedMaxThreads; + set + { + _selectedMaxThreads = value; + OnPropertyChanged(); + } + } + public ObservableCollection Images { get; set; } + + private List _paths; + + private readonly IImageResizer _imageResizer; + + public ImageSelectorPageViewModel() + { + _imageResizer = DependencyService.Get(); + + MaxThreads = Environment.ProcessorCount; + SelectedMaxThreads = 1; + MinThreads = 1; + _paths = new List(); + _ = _paths; + } + + public async Task LoadImagesFromPath(List paths) + { + Images = new ObservableCollection(); + var throttler = new SemaphoreSlim(initialCount: MaxThreads); + var tasks = paths.Select(async item => + { + try + { + await throttler.WaitAsync(); + var resizedPath = await _imageResizer.ResizeImage(item, 1250, item + "_R"); + Images.Add(ImageSource.FromFile(resizedPath)); + } + catch (Exception ex) + { + SentrySdk.CaptureException(ex); + } + finally + { + throttler.Release(); + } + }); + await Task.WhenAll(tasks); + TaskCompleted = true; + } + } +} diff --git a/Samples/Sample.Xamarin.Core/ViewModels/MainPageViewModel.cs b/Samples/Sample.Xamarin.Core/ViewModels/MainPageViewModel.cs index 03525d2..8196d93 100644 --- a/Samples/Sample.Xamarin.Core/ViewModels/MainPageViewModel.cs +++ b/Samples/Sample.Xamarin.Core/ViewModels/MainPageViewModel.cs @@ -20,6 +20,7 @@ public class MainPageViewModel : NavigationService public Command PopupCmd { get; } public Command BrokenViewCmd { get; } public Command NativeCrashCmd { get; } + public Command ImageSelectorCmd { get; } public MainPageViewModel() { DiscoCmd = new Command(GotoDisco); @@ -29,6 +30,8 @@ public MainPageViewModel() BrokenViewCmd = new Command(GotoBrokenView); FeedbackCmd = new Command(ShowFeedback); NativeCrashCmd = new Command(NativeCrash); + ImageSelectorCmd = new Command(ImageSelector); + _nativeCrashService = DependencyService.Get(); } @@ -85,5 +88,10 @@ public MainPageViewModel() _nativeCrashService?.BrokenNativeCallback(); DisplayAlert("Whoops", "A native crash sample wasn't implemented for this platform, mind opening a pull request? :)", "Yes"); }; + + private Action ImageSelector => () => + { + NavigateTo(new ImageSelectorPage()); + }; } } diff --git a/Samples/Sample.Xamarin.Core/Views/ImageSelectorPage.xaml b/Samples/Sample.Xamarin.Core/Views/ImageSelectorPage.xaml new file mode 100644 index 0000000..6c511c7 --- /dev/null +++ b/Samples/Sample.Xamarin.Core/Views/ImageSelectorPage.xaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/Samples/Sample.Xamarin.Core/Views/ImageSelectorPage.xaml.cs b/Samples/Sample.Xamarin.Core/Views/ImageSelectorPage.xaml.cs new file mode 100644 index 0000000..dcd6e1e --- /dev/null +++ b/Samples/Sample.Xamarin.Core/Views/ImageSelectorPage.xaml.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Xamarin.Forms; +using Xamarin.Forms.Xaml; + +namespace Sample.Xamarin.Core.Views +{ + [XamlCompilation(XamlCompilationOptions.Compile)] + public partial class ImageSelectorPage : ContentPage + { + public ImageSelectorPage() + { + InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/Samples/Sample.Xamarin.Core/Views/MainPage.xaml b/Samples/Sample.Xamarin.Core/Views/MainPage.xaml index 88f7d2a..2f718f4 100644 --- a/Samples/Sample.Xamarin.Core/Views/MainPage.xaml +++ b/Samples/Sample.Xamarin.Core/Views/MainPage.xaml @@ -138,6 +138,21 @@ HorizontalOptions="Center"/> + + + + + + + + diff --git a/Samples/Sample.Xamarin.Droid/Sample.Xamarin.Droid.csproj b/Samples/Sample.Xamarin.Droid/Sample.Xamarin.Droid.csproj index 9c6cdc0..3e2a2d6 100644 --- a/Samples/Sample.Xamarin.Droid/Sample.Xamarin.Droid.csproj +++ b/Samples/Sample.Xamarin.Droid/Sample.Xamarin.Droid.csproj @@ -75,6 +75,7 @@ + diff --git a/Samples/Sample.Xamarin.Droid/Tools/ImageResizer.cs b/Samples/Sample.Xamarin.Droid/Tools/ImageResizer.cs new file mode 100644 index 0000000..14df54f --- /dev/null +++ b/Samples/Sample.Xamarin.Droid/Tools/ImageResizer.cs @@ -0,0 +1,53 @@ +using Android.Graphics; +using Sample.Xamarin.Core.Interfaces; +using System.Threading.Tasks; +using Xamarin.Essentials; + +namespace Sample.Xamarin.Droid.Tools +{ + internal class ImageResizer : IImageResizer + { + public async Task ResizeImage(string imagePath, int maxSize, string filename) + => await Task.Run(() => + { + var option = new BitmapFactory.Options() + { + InSampleSize = 1 + }; + + var originalImage = BitmapFactory.DecodeFile(imagePath, option); + var scaledSize = GetMeasure(originalImage.Width, originalImage.Height, maxSize); + var resizedImage = Bitmap.CreateScaledBitmap(originalImage, scaledSize.Item1, scaledSize.Item2, false); + + var path = System.IO.Path.Combine(FileSystem.CacheDirectory, filename); + using (var f = System.IO.File.Create(path)) + { + resizedImage.Compress(Bitmap.CompressFormat.Jpeg, 35, f); + } + if (resizedImage != originalImage) + { + originalImage.Recycle(); + } + resizedImage.Recycle(); + return path; + }); + + private (int, int) GetMeasure(int width, int height, int newMax) + { + double ratio; + if (width > newMax) + { + ratio = (double)newMax / width; + height = (int)(height * ratio); + width = (int)(width * ratio); + } + if (height > newMax) + { + ratio = (double)newMax / height; + width = (int)(width * ratio); + height = (int)(height * ratio); + } + return (width, height); + } + } +} \ No newline at end of file From 25bddc44fa24c3484d21d8cef3b19a79140c1ab7 Mon Sep 17 00:00:00 2001 From: Lucas Zimerman Fraulob Date: Tue, 16 Mar 2021 09:45:00 -0300 Subject: [PATCH 2/2] WIP --- .../CustomControls/CustomWebView.cs | 13 ++++ .../Renderer/CustomWebViewRenderer.cs | 67 +++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 Samples/Sample.Xamarin.Core/CustomControls/CustomWebView.cs create mode 100644 Samples/Sample.Xamarin.Droid/Renderer/CustomWebViewRenderer.cs diff --git a/Samples/Sample.Xamarin.Core/CustomControls/CustomWebView.cs b/Samples/Sample.Xamarin.Core/CustomControls/CustomWebView.cs new file mode 100644 index 0000000..968464d --- /dev/null +++ b/Samples/Sample.Xamarin.Core/CustomControls/CustomWebView.cs @@ -0,0 +1,13 @@ +using Xamarin.Forms; + +namespace Sample.Xamarin.Core.CustomControls +{ + /// + /// Sadly this extra class is required since you can't scale things properly + /// with the default WebView. + /// Custom render, here we go :D + /// + public class CustomWebView : WebView + { + } +} diff --git a/Samples/Sample.Xamarin.Droid/Renderer/CustomWebViewRenderer.cs b/Samples/Sample.Xamarin.Droid/Renderer/CustomWebViewRenderer.cs new file mode 100644 index 0000000..747b28e --- /dev/null +++ b/Samples/Sample.Xamarin.Droid/Renderer/CustomWebViewRenderer.cs @@ -0,0 +1,67 @@ +using Android.App; +using Android.Content; +using Android.OS; +using Android.Runtime; +using Android.Views; +using Android.Widget; +using Sample.Xamarin.Core.CustomControls; +using Sample.Xamarin.Droid.Renderer; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Threading.Tasks; +using Xamarin.Forms; +using Xamarin.Forms.Platform.Android; +using static Android.Webkit.WebSettings; + +[assembly: ExportRenderer(typeof(CustomWebView), typeof(CustomWebViewRenderer))] +namespace Sample.Xamarin.Droid.Renderer +{ + public class CustomWebViewRenderer : WebViewRenderer + { + public CustomWebViewRenderer(Context context) : base(context) + { + + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + Control.SetWebViewClient(new ExtendedWebViewClient(Element as CustomWebView)); + + } + + class ExtendedWebViewClient : Android.Webkit.WebViewClient + { + private CustomWebView _xwebView { get; set; } + public ExtendedWebViewClient(CustomWebView webView) + { + _xwebView = webView; + } + + async public override void OnPageFinished(Android.Webkit.WebView view, string url) + { + if (_xwebView != null) + { + _xwebView.HeightRequest = 0d; + await Task.Delay(100); + var result = await _xwebView.EvaluateJavaScriptAsync("(function(){return document.body.scrollHeight;})()"); + Console.WriteLine(result); + var newResult = result; + for (int i = 0; i < 1000 && result == newResult; i++) + { + await Task.Delay(100); + newResult = await _xwebView.EvaluateJavaScriptAsync("(function(){return document.body.scrollHeight;})()"); + Console.WriteLine(newResult); + } + _xwebView.HeightRequest = Convert.ToDouble(newResult); + view.SetInitialScale(40); + + } + + base.OnPageFinished(view, url); + } + } + } +}