xamarin / Essentials

Xamarin.Essentials is no longer supported. Migrate your apps to .NET MAUI, which includes Maui.Essentials.
https://aka.ms/xamarin-upgrade
Other
1.52k stars 505 forks source link

[Feature Request]: Native Image Viewer #680

Closed azimuthdeveloper closed 5 years ago

azimuthdeveloper commented 5 years ago

There's no "native image viewer" for Xamarin Forms, so every time someone wants to pinch and zoom an image on their Xamarin Forms app, they have to rely on XF gestures to achieve this. I've personally never found a perfect solution for this, all available code shudders on zoom, or allows the user to zoom off the image.

When I say "native image viewer" I mean:

As I used to be an Ionic developer I know that they had a native image viewer component that would render images for pinching and zooming on a platform specific basis.

In my mind, this kind of functionality fits the bill for Xamarin.Essentials as viewing images, and pinching and zooming them, is something that I think would be used fairly broadly. I have some code in an application that currently does this, and I'm currently trying to put it into a nuget package (using @jamesmontemagno's template package).

Existing Issues

Shared Code

The interface on the shared code would probably just be as simple as this:

    public interface IPhotoViewer
    {
        void ShowImage(string uri);
        void HideImage();
    }

Android implementation

Package references

 <PackageReference Include="Xamarin.Android.PhotoView">
      <Version>2.1.4</Version>
    </PackageReference>
    <PackageReference Include="Plugin.CurrentActivity">
      <Version>2.1.0.4</Version>
    </PackageReference>

Platform Specific Code _AndroidPhotoView.cs

 public class Android_PhotoViewer : IPhotoViewer
    {

        public void ShowImage(string uri)
        {

            var intent = new Intent(CrossCurrentActivity.Current.AppContext, typeof(MyImageView));
            intent.PutExtra("uri", uri);
            CrossCurrentActivity.Current.AppContext.StartActivity(intent);
        }

        public void HideImage()
        {
            throw new NotImplementedException();
        }

    }

    [Activity]
    public class MyImageView : FragmentActivity
    {
        private PhotoView _photoView;
        private string _uri;
        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);

            SetContentView(Resource.Layout.activity_immersive);

            _uri = Intent.GetStringExtra("uri");
            _photoView = FindViewById<PhotoView>(Resource.Id.photo_view);
            var fab = FindViewById<FloatingActionButton>(Resource.Id.floatingBack);
            fab.Click += Fab_Click;
            ShowImage(_uri);
        }

        void Fab_Click(object sender, EventArgs e)
        {
            this.Finish();
        }

        public void ShowImage(string uri)
        {
            var bitmap = BitmapFactory.DecodeFile(uri);
            _photoView.SetImageBitmap(bitmap);
            FullScreen();
        }

        public void FullScreen()
        {
            var uiOptions = (SystemUiFlags)(int)Window.DecorView.SystemUiVisibility;
            var newUiOptions = uiOptions;
            newUiOptions ^= SystemUiFlags.Fullscreen;
            Window.RequestFeature(WindowFeatures.NoTitle);
            Window.DecorView.SystemUiVisibility = (StatusBarVisibility)(int)newUiOptions;
        }

    }

_layout/activityimerrsive.xaml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/root"
    android:theme="@style/Theme.AppCompat.NoActionBar"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <com.github.chrisbanes.photoview.PhotoView
        android:id="@+id/photo_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    <android.support.design.widget.FloatingActionButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="right|bottom"
        android:layout_margin="16dp"
        android:src="@drawable/back_arrow" <!-- loading an image here, probably should use an inbuilt platform icon or something -->
        android:id="@+id/floatingBack" />

</FrameLayout>

iOS Implementation

Package References

    <PackageReference Include="Ricardo.MWPhotoBrowser.iOS">
      <Version>2.1.4.1</Version>
    </PackageReference>

Platform Specific Code

_iOSPhotoViewer.cs

public class MyPhotoViewer : MWPhotoBrowserDelegate
    {
        protected string _uri;

        public MyPhotoViewer(string uri)
        {
            _uri = uri;
        }

        protected List<MWPhoto> _photos = new List<MWPhoto>();

        public void ShowImage()
        {

                var image = UIImage.FromFile(_uri);
            MWPhoto mp = MWPhoto.FromImage(image);

            _photos.Add(mp);
            MWPhotoBrowser browser = new MWPhotoBrowser(this);
            browser.DisplayActionButton = false;
            UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(new UINavigationController(browser), true, null);
        }

        public void HideImage()
        {
            UIApplication.SharedApplication.KeyWindow.RootViewController.DismissViewController(true, null);
        }

        public override MWPhoto GetPhoto(MWPhotoBrowser photoBrowser, nuint index) => _photos[(int)index];

        public override nuint NumberOfPhotosInPhotoBrowser(MWPhotoBrowser photoBrowser) => (nuint)_photos.Count;
    }

    public class PhotoViewerImplementation : IPhotoViewer
    {
        protected static MyPhotoViewer _mainViewer;

        public void ShowImage(string uri)
        {
            _mainViewer = new MyPhotoViewer(uri);
            _mainViewer.ShowImage();
        }

        public void HideImage()
        {
            _mainViewer.HideImage();
        }

    }

This is my first submission like this, and I'd like to bring this functionality into XF Essentials through a pull request long term so everyone can use it. I have no prior experience in doing this so if this is wrong or wonky in any way, or if my code is garbage, then please forgive me in advance :)

aherrick commented 5 years ago

I'm currently using this to handle my Images in Forms.

https://github.com/stormlion227/PhotoBrowser.Forms

Not sure if this belong in Essentials honestly...

pictos commented 5 years ago

My 2 cents... I think this PR, is better in Xamarin.Forms. And this is a good one

azimuthdeveloper commented 5 years ago

@pictos I reckon that'd be fairly unlikely. Especially if things like FFImage are still third party to the XF codebase.

pictos commented 5 years ago

@lewcianci one question, in iOS, can we do this without the Ricardo nuget? Because the Xamarin.Essentials don't allow external references.

azimuthdeveloper commented 5 years ago

@pictos We could do that. I'd have to carve out an iOS specific implementation. That's probably a better idea anyway (the existing implementation has allowance for multiple images, something that is not taken advantage of in the current setup). I'll have to see if there are any equivalent native Xamarin packages for iOS for pinch/pan zooming.

pictos commented 5 years ago

But first, wait for the staff responsible for the XE to talk about whether the idea is viable ...

jamesmontemagno commented 5 years ago

Thanks for the write up and suggestion. One of our guiding principles is not to create or maintain custom user interface with Xamarin.Essentials or take dependencies on additional libraries unless 100% necessary. For user interface this would be better suited for Xamarin.Forms as mentioned earlier.