Ver código fonte

avalonia: Added DFEmbeddedImageFieldControl; added ImageViewer; added DigitalFormDocumentFactory

Kenric Nugteren 2 meses atrás
pai
commit
6dcb9fd284

+ 64 - 0
PRS.Avalonia/PRS.Avalonia/Components/FormsEditor/Fields/DFEmbeddedImageFieldControl.cs

@@ -0,0 +1,64 @@
+using Avalonia.Controls;
+using Avalonia.Media;
+using InABox.Avalonia;
+using InABox.Avalonia.Platform;
+using InABox.Core;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PRS.Avalonia.DigitalForms;
+
+class DFEmbeddedImageFieldControl : DFEmbeddedMediaFieldControl<DFLayoutEmbeddedImage, DFLayoutEmbeddedImageProperties, Guid>
+{
+    protected override bool DisableLibrary => Field.Properties.DisableLibrary;
+
+    protected override bool IsVideo => false;
+
+    public override Guid GetValue() => _value.ID;
+
+    public override void SetValue(Guid value)
+    {
+        SetSerializedValue(new() { ID = value });
+    }
+
+    protected override Control Create()
+    {
+        var control = base.Create();
+
+        AddButton(Images.rotate, RotateImage);
+
+        return control;
+    }
+
+    private void RotateImage()
+    {
+        if(_value.Thumbnail != null)
+        {
+            _value.Thumbnail = PlatformTools.ImageTools.RotateImage(_value.Thumbnail, 90F, 100);
+        }
+        if(_value.Data != null)
+        {
+            _value.Data = PlatformTools.ImageTools.RotateImage(_value.Data, 90F, 100);
+        }
+
+        UpdateUI();
+    }
+
+    protected override Task<MobileDocument> CaptureMedia()
+    {
+        return MobileDocument.From(App.TopLevel, PhotoUtils.CreateCameraOptions());
+    }
+
+    protected override byte[] CreateThumbnail(byte[] data, int maxWidth = 256, int maxHeight = 256)
+    {
+        return PlatformTools.ImageTools.CreateThumbnail(data, maxWidth, maxHeight);
+    }
+
+    protected override Task<MobileDocument> SelectMedia()
+    {
+        return MobileDocument.From(App.TopLevel, PhotoUtils.CreatePhotoLibraryOptions());
+    }
+}

+ 221 - 0
PRS.Avalonia/PRS.Avalonia/Components/FormsEditor/Fields/DFEmbeddedMediaFieldControl.cs

@@ -0,0 +1,221 @@
+using Avalonia.Controls;
+using Avalonia.Layout;
+using Avalonia.Media;
+using Avalonia.Threading;
+using CommunityToolkit.Mvvm.Input;
+using InABox.Avalonia;
+using InABox.Core;
+using PRS.Avalonia.Components;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PRS.Avalonia.DigitalForms;
+
+abstract partial class DFEmbeddedMediaFieldControl<TField, TProperties, TValue> : DigitalFormFieldControl<TField, TProperties, TValue, DFLayoutEmbeddedMediaValue>
+    where TField : DFLayoutField<TProperties>, new()
+    where TProperties : DFLayoutFieldProperties<TValue, DFLayoutEmbeddedMediaValue>, new()
+{
+    private Button Button = null!;
+    private Image Image = null!;
+    private StackPanel StackPanel = null!;
+
+    private ColumnDefinition ButtonColumn = null!;
+
+    private bool _isEmpty = true;
+
+    protected DFLayoutEmbeddedMediaValue _value = new();
+    protected abstract bool DisableLibrary { get; }
+    protected abstract bool IsVideo { get; }
+
+    protected override Control Create()
+    {
+        var grid = new Grid();
+        grid.Height = 250;
+
+        grid.AddColumn(GridUnitType.Star);
+        ButtonColumn = grid.AddColumn(GridUnitType.Auto);
+
+        grid.AddRow(GridUnitType.Auto);
+        grid.AddRow(GridUnitType.Auto);
+        grid.AddRow(GridUnitType.Star);
+        grid.AddRow(GridUnitType.Auto);
+
+        Button = new Button
+        {
+            Padding = new(5),
+            Command = ImageClickedCommand
+        };
+        Button.Classes.Add("Standard");
+
+        Image = new Image
+        {
+        };
+        Button.Content = Image;
+
+        var cameraButton = new Button
+        {
+            Content = new Image
+            {
+                Source = IsVideo ? Images.camcorder : Images.camera,
+                Width = 25,
+                Height = 25,
+            },
+            Command = CameraClickedCommand
+        };
+        cameraButton.Classes.Add("Standard");
+
+        if (!DisableLibrary)
+        {
+            var libraryButton = new Button
+            {
+                Content = new Image
+                {
+                    Source = IsVideo ? Images.videolibrary : Images.photolibrary,
+                    Width = 25,
+                    Height = 25,
+                },
+                Command = LibraryClickedCommand
+            };
+            libraryButton.Classes.Add("Standard");
+            grid.AddChild(libraryButton, 1, 1);
+        }
+
+        StackPanel = new StackPanel { Orientation = Orientation.Vertical };
+
+        grid.AddChild(Button, 0, 0, rowSpan: 4);
+        grid.AddChild(cameraButton, 0, 1);
+        grid.AddChild(StackPanel, 3, 1);
+
+        return grid;
+    }
+
+    protected void AddButton(IImage? image, Action action)
+    {
+        var button = new Button
+        {
+            Content = new Image
+            {
+                Source = image,
+                Width = 25,
+                Height = 25,
+            },
+            CommandParameter = action,
+            Command = ButtonClickedCommand
+        };
+        button.Classes.Add("Standard");
+        StackPanel.Children.Add(button);
+    }
+
+    [RelayCommand]
+    private void ButtonClicked(Action action)
+    {
+        action();
+    }
+
+    protected abstract Task<MobileDocument> CaptureMedia();
+
+    protected abstract Task<MobileDocument> SelectMedia();
+
+    [RelayCommand]
+    private void ImageClicked()
+    {
+        if(_value.Thumbnail is null || _value.Thumbnail.Length == 0) return;
+
+        if(_value.Data is not null && _value.Data.Length > 0)
+        {
+            Navigation.Navigate<ImageViewerViewModel>(model =>
+            {
+                model.Data = _value.Data;
+                model.DeleteCommand = DeleteCommand;
+            });
+        }
+        else if(_value.ID != Guid.Empty)
+        {
+            DigitalFormDocumentFactory.LoadDocument(
+                _value.ID,
+                data =>
+                {
+                    _value.Data = data;
+                    Navigation.Navigate<ImageViewerViewModel>(model =>
+                    {
+                        model.Data = _value.Data;
+                        model.DeleteCommand = DeleteCommand;
+                    });
+                });
+        }
+    }
+
+    [RelayCommand]
+    private void Delete()
+    {
+        if (!_isEmpty)
+        {
+            SetSerializedValue(new DFLayoutEmbeddedMediaValue());
+            ChangeField();
+        }
+    }
+
+    [RelayCommand]
+    private async Task CameraClicked()
+    {
+        var document = await CaptureMedia();
+        if(document.Data is not null && document.Data.Length > 0)
+        {
+            SetSerializedValue(new() { Data = document.Data });
+            ChangeField();
+        }
+    }
+
+    [RelayCommand]
+    private async Task LibraryClicked()
+    {
+        var document = await SelectMedia();
+        if(document.Data is not null && document.Data.Length > 0)
+        {
+            SetSerializedValue(new() { Data = document.Data });
+            ChangeField();
+        }
+    }
+
+    public override void SetBackground(IBrush brush, bool defaultColour)
+    {
+        Button.Background = brush;
+    }
+
+    public override void SetSerializedValue(DFLayoutEmbeddedMediaValue value)
+    {
+        _value = value;
+        _value.Thumbnail = (_value.Data is not null && _value.Data.Length > 0) ? CreateThumbnail(_value.Data) : null;
+
+        UpdateUI();
+    }
+
+    protected void UpdateUI()
+    {
+        var data = (!IsVideo && _value.Data is not null && _value.Data.Length > 0) ? _value.Data : _value.Thumbnail;
+        var image = ImageUtils.ImageFromBytes(data, false);
+
+        Image.Source = image;
+        _isEmpty = image is null;
+    }
+
+    public override DFLayoutEmbeddedMediaValue GetSerializedValue()
+    {
+        if (_value.Data is not null && _value.Data.Length != 0 && _value.ID == Guid.Empty)
+            _value.ID = DigitalFormDocumentFactory.SaveDocument(_value.Data);
+        return _value;
+    }
+
+    protected abstract byte[] CreateThumbnail(byte[] data, int maxWidth = 256, int maxHeight = 256);
+
+    protected override bool IsEmpty() => _isEmpty;
+
+    public override void SetEnabled(bool enabled)
+    {
+        ButtonColumn.Width = enabled ? GridLength.Auto : new(0);
+        Button.IsEnabled = enabled;
+    }
+}

+ 21 - 0
PRS.Avalonia/PRS.Avalonia/Components/FormsEditor/MobileDigitalFormDocumentHandler.cs

@@ -0,0 +1,21 @@
+using InABox.Avalonia;
+using InABox.Core;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PRS.Avalonia.Components.FormsEditor
+{
+    public class MobileDigitalFormDocumentHandler : DigitalFormDocumentHandler
+    {
+        protected override string CachePath => CoreRepository.CacheFolder();
+        protected override bool IsConnected => ViewModelBase.DataAccess.Status == ConnectionStatus.Connected;
+
+        public MobileDigitalFormDocumentHandler([NotNull] Action<bool> status) : base(status)
+        {
+        }
+    }
+}

+ 13 - 0
PRS.Avalonia/PRS.Avalonia/Components/ImageViewer/ImageViewerView.axaml

@@ -0,0 +1,13 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             xmlns:components="using:PRS.Avalonia.Components"
+             xmlns:converters="using:InABox.Avalonia.Converters"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="PRS.Avalonia.Components.ImageViewerView"
+			 x:DataType="components:ImageViewerViewModel">
+	<Border Classes="Standard">
+	    <Image Source="{Binding Data,Converter={x:Static converters:ByteArrayToImageSourceConverter.Instance}}"/>
+	</Border>
+</UserControl>

+ 13 - 0
PRS.Avalonia/PRS.Avalonia/Components/ImageViewer/ImageViewerView.axaml.cs

@@ -0,0 +1,13 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace PRS.Avalonia.Components;
+
+public partial class ImageViewerView : UserControl
+{
+    public ImageViewerView()
+    {
+        InitializeComponent();
+    }
+}

+ 35 - 0
PRS.Avalonia/PRS.Avalonia/Components/ImageViewer/ImageViewerViewModel.cs

@@ -0,0 +1,35 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using InABox.Avalonia;
+using InABox.Avalonia.Components;
+using PRS.Avalonia.Modules;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PRS.Avalonia.Components;
+
+internal partial class ImageViewerViewModel : ModuleViewModel
+{
+    public override string Title => "View Document";
+
+    [ObservableProperty]
+    private byte[] _data = null!;
+
+    [ObservableProperty]
+    private IRelayCommand? _deleteCommand;
+
+    public ImageViewerViewModel()
+    {
+        PrimaryMenu.Items.Add(new AvaloniaMenuItem(Images.cross, Delete));
+    }
+
+    private Task<bool> Delete()
+    {
+        DeleteCommand?.Execute(null);
+        Navigation.Back();
+        return Task.FromResult(true);
+    }
+}

+ 8 - 1
PRS.Avalonia/PRS.Avalonia/DataAccessLayer.cs

@@ -3,6 +3,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
 using InABox.Avalonia;
 using InABox.Clients;
 using InABox.Core;
+using PRS.Avalonia.Components.FormsEditor;
 
 namespace PRS.Avalonia;
 
@@ -23,6 +24,12 @@ public class DataAccessLayer : ObservableObject, IModelHost
         _transport = new TransportLayer();
         _transport.Connected += (sender, args) => DoConnected();
         _transport.Disconnected += (sender, args) => DoDisconnected();
+
+        DigitalFormDocumentFactory.Init<MobileDigitalFormDocumentHandler>(new(b =>
+        {
+            IsBackgroundUpdateStatusActive = b;
+            BackgroundUpdateStatusChanged?.Invoke(this, EventArgs.Empty);
+        }));
     }
 
     public ConnectionStatus Status
@@ -31,7 +38,7 @@ public class DataAccessLayer : ObservableObject, IModelHost
         private set => SetProperty(ref _status, value);
     }
 
-    public bool IsBackgroundUpdateStatusActive { get; }
+    public bool IsBackgroundUpdateStatusActive { get; private set; }
 
     public event BackgroundUpdateStatusEvent? BackgroundUpdateStatusChanged;
 

+ 5 - 0
PRS.Avalonia/PRS.Avalonia/Images/Images.cs

@@ -33,12 +33,14 @@ public static class Images
     public static SvgImage? badge => LoadSVG("/Images/badge.svg");
     public static SvgImage? barcode => LoadSVG("/Images/barcode.svg");
     public static SvgImage? books => LoadSVG("/Images/books.svg");
+    public static SvgImage? camcorder => LoadSVG("/Images/camcorder.svg");
     public static SvgImage? camera => LoadSVG("/Images/camera.svg");
     public static SvgImage? circle_gray => LoadSVG("/Images/circle_gray.svg");
     public static SvgImage? circle_green => LoadSVG("/Images/circle_green.svg");
     public static SvgImage? circle_red => LoadSVG("/Images/circle_red.svg");
     public static SvgImage? clock => LoadSVG("/Images/clock.svg");
     public static SvgImage? construction => LoadSVG("/Images/construction.svg");
+    public static SvgImage? cross => LoadSVG("/Images/cross.svg");
     public static SvgImage? delivery => LoadSVG("/Images/delivery.svg");
     public static SvgImage? digitalform => LoadSVG("/Images/digitalform.svg");
     public static SvgImage? drawing => LoadSVG("/Images/drawing.svg");
@@ -53,8 +55,10 @@ public static class Images
     public static SvgImage? notification => LoadSVG("/Images/notification.svg");
     public static SvgImage? person => LoadSVG("/Images/person.svg");
     public static SvgImage? phone => LoadSVG("/Images/phone.svg");
+    public static SvgImage? photolibrary => LoadSVG("/Images/photolibrary.svg");
     public static SvgImage? plus => LoadSVG("/Images/plus.svg");
     public static SvgImage? refresh => LoadSVG("/Images/refresh.svg");
+    public static SvgImage? rotate => LoadSVG("/Images/rotate.svg");
     public static SvgImage? save => LoadSVG("/Images/save.svg");
     public static SvgImage? schedule => LoadSVG("/Images/schedule.svg");
     public static SvgImage? shoppingcart => LoadSVG("/Images/shoppingcart.svg");
@@ -67,6 +71,7 @@ public static class Images
     public static SvgImage? tick => LoadSVG("/Images/tick.svg");
     public static SvgImage? trolley => LoadSVG("/Images/trolley.svg");
     public static SvgImage? version => LoadSVG("/Images/version.svg");
+    public static SvgImage? videolibrary => LoadSVG("/Images/videolibrary.svg");
     public static SvgImage? warehouse => LoadSVG("/Images/warehouse.svg");
 
 }

+ 1 - 0
PRS.Avalonia/PRS.Avalonia/RepositoryLayer.cs

@@ -5,6 +5,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
 using InABox.Avalonia;
 using InABox.Clients;
 using InABox.Core;
+using PRS.Avalonia.Components.FormsEditor;
 
 namespace PRS.Avalonia;