Jelajahi Sumber

Added MobileMenuButton component
Tweaked Shell Structure to handle Add / Cancel functionality
Improved Back Button Handling

Frank van den Bos 1 tahun lalu
induk
melakukan
c251023df9

+ 53 - 0
InABox.Mobile/InABox.Mobile.Shared/Components/MobileButton/TouchEffects.cs

@@ -0,0 +1,53 @@
+using System;
+using Xamarin.Forms;
+
+namespace InABox.Mobile
+{
+    
+    public enum TouchActionType
+    {
+        Entered,
+        Pressed,
+        Moved,
+        Released,
+        Exited,
+        Cancelled
+    }
+    
+    public class TouchActionEventArgs : EventArgs
+    {
+        public TouchActionEventArgs(long id, TouchActionType type, Point location, bool isInContact)
+        {
+            Id = id;
+            Type = type;
+            Location = location;
+            IsInContact = isInContact;
+        }
+
+        public long Id { private set; get; }
+
+        public TouchActionType Type { private set; get; }
+
+        public Point Location { private set; get; }
+
+        public bool IsInContact { private set; get; }
+    }
+    
+    public delegate void TouchActionEventHandler(object sender, TouchActionEventArgs args);
+    
+    public class TouchEffect : RoutingEffect
+    {
+        public event TouchActionEventHandler TouchAction;
+
+        public TouchEffect() : base("XamarinDocs.TouchEffect")
+        {
+        }
+
+        public bool Capture { set; get; }
+
+        public void OnTouchAction(Element element, TouchActionEventArgs args)
+        {
+            TouchAction?.Invoke(element, args);
+        }
+    }
+}

+ 10 - 0
InABox.Mobile/InABox.Mobile.Shared/Components/MobileMenuButton/MobileMenuButton.xaml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
+             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
+             xmlns:ui="clr-namespace:XF.Material.Forms.UI;assembly=XF.Material"
+             x:Class="InABox.Mobile.MobileMenuButton">
+    <ContentView.Content>
+        <ImageButton x:Name="_image" Source="{Binding Image}" Clicked="_image_OnClicked" />
+    </ContentView.Content>
+</ContentView>

+ 120 - 0
InABox.Mobile/InABox.Mobile.Shared/Components/MobileMenuButton/MobileMenuButton.xaml.cs

@@ -0,0 +1,120 @@
+using System;
+using Xamarin.Forms;
+using Xamarin.Forms.Xaml;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using Syncfusion.XForms.PopupLayout;
+using XF.Material.Forms.UI;
+
+namespace InABox.Mobile
+{
+    
+    [XamlCompilation(XamlCompilationOptions.Compile)]
+    public partial class MobileMenuButton
+    {
+        
+        private SfPopupLayout _popup;
+
+        private MobileMenuButtonMenu _menu;
+
+        public IList<MobileMenuButtonMenuItem> Items => _menu.Items;
+        
+        public RelativePosition Position { get; set; }
+        
+        public ImageSource Image
+        {
+            get => _image.Source;
+            set => _image.Source = value;
+        }
+        
+        public event MobileMenuButtonClickedEvent Clicked;
+        
+        public MobileMenuButton()
+        {
+            _menu = new MobileMenuButtonMenu();
+            InitializeComponent();
+            _popup = new SfPopupLayout();
+            Position = RelativePosition.AlignToLeftOf;
+        }
+        
+        private void _image_OnClicked(object sender, EventArgs e)
+        {
+            if (Items.Any())
+            {
+                _popup.PopupView.ContentTemplate = new DataTemplate(() => _menu);
+                _popup.PopupView.ShowFooter = false;
+                _popup.PopupView.ShowHeader = false;
+                _popup.PopupView.AutoSizeMode = AutoSizeMode.Both;
+                _popup.PopupView.PopupStyle.CornerRadius = 5;
+                _popup.Padding = new Thickness(5);
+                GetOffset(out double x, out double y);
+                _popup.ShowRelativeToView(this, Position, x, y);
+            }
+            else
+                Clicked?.Invoke(this, new MobileMenuButtonClickedEventArgs(null));
+        }
+
+        /// <summary>
+        /// Calculates the offest of the Menu to position it at the center of the Button
+        ///  Let's not presume that all the calculations are correct - its only been tested
+        /// against AlignToLeftOf (for top-right-hand side menu options 
+        /// </summary>
+        /// <param name="x"></param>
+        /// <param name="y"></param>
+        private void GetOffset(out double x, out double y)
+        {
+            x = 0F;
+            y = 0F;
+            // Displays the popup at the top of the given view.
+            if (Position == RelativePosition.AlignTop)
+            {
+                x = Width / 2F;
+                y = Height / 2F;
+            }
+            // Displays the popup to the left of the given view.</summary>
+            else if (Position == RelativePosition.AlignToLeftOf)
+            {
+                x = Width / 2F;
+                y = Height / 2F;
+            }
+            // Displays the popup to the right of the given view.</summary>
+            else if (Position == RelativePosition.AlignToRightOf)
+            {
+                x = 0F - (Width / 2F);
+                y = Height / 2F;
+            }
+            // Displays the popup at the bottom of the given view.</summary>
+            else if (Position == RelativePosition.AlignBottom)
+            {
+                x = Width / 2F;
+                y = 0F - (Height / 2F);
+            }
+            // Displays the popup at the top left position of the given view.
+            else if (Position == RelativePosition.AlignTopLeft)
+            {
+                x = Width / 2F;
+                y = Height / 2F;
+            }
+            // Displays the popup at the top right position of the given view.
+            else if (Position == RelativePosition.AlignTopRight)
+            {
+                x = 0F - (Width / 2F);
+                y = Height / 2F;
+            }
+            // Displays the popup at the bottom left position of the given view.
+            else if (Position == RelativePosition.AlignBottomLeft)
+            {
+                x = 0F - (Width / 2F);
+                y = Height / 2F;
+            }
+            // Displays the popup at the bottom right position of the given view.
+            else if (Position == RelativePosition.AlignBottomRight)
+            {
+                x = 0F - (Width / 2F);
+                y = 0F - (Height / 2F);
+            }
+        }
+    }
+    
+}

+ 16 - 0
InABox.Mobile/InABox.Mobile.Shared/Components/MobileMenuButton/MobileMenuButtonClickedEvent.cs

@@ -0,0 +1,16 @@
+using System;
+
+namespace InABox.Mobile
+{
+    public class MobileMenuButtonClickedEventArgs : EventArgs
+    {
+        public object Selected { get; private set; }
+
+        public MobileMenuButtonClickedEventArgs(object selected)
+        {
+            Selected = selected;
+        }
+    }
+    
+    public delegate void MobileMenuButtonClickedEvent(object sender, MobileMenuButtonClickedEventArgs args);
+}

+ 38 - 0
InABox.Mobile/InABox.Mobile.Shared/Components/MobileMenuButton/MobileMenuButtonMenu.xaml

@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
+             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
+             xmlns:mobile="clr-namespace:InABox.Mobile;assembly=InABox.Mobile.Shared"
+             x:Class="InABox.Mobile.MobileMenuButtonMenu" Padding="5">
+    <ContentView.BindingContext>
+        <mobile:MobileMenuButtonMenuViewModel x:Name="_viewModel"/>
+    </ContentView.BindingContext>
+    <ContentView.Content>
+        <StackLayout 
+            x:Name="_menu" 
+            Orientation="Vertical"
+            VerticalOptions="StartAndExpand" 
+            HorizontalOptions="StartAndExpand"
+            Margin="0,5,0,0"
+            Spacing="10"
+            BindableLayout.ItemsSource = "{Binding Items}"
+            BindableLayout.EmptyView="Nothing!">
+            <BindableLayout.ItemTemplate>
+                <DataTemplate x:DataType="mobile:MobileMenuButtonMenuItem">
+                    <Label Text="{Binding Text}" 
+                           VerticalOptions="CenterAndExpand" 
+                           HorizontalOptions="FillAndExpand" 
+                           HorizontalTextAlignment="Start"
+                           VerticalTextAlignment="Center"
+                           FontSize="Micro"
+                           Padding="2,5,2,2">
+                        <Label.GestureRecognizers>
+                            <TapGestureRecognizer Tapped="TapGestureRecognizer_OnTapped" />
+                        </Label.GestureRecognizers>    
+                    </Label>
+                </DataTemplate>
+            </BindableLayout.ItemTemplate>
+            
+        </StackLayout>
+    </ContentView.Content>
+</ContentView>

+ 54 - 0
InABox.Mobile/InABox.Mobile.Shared/Components/MobileMenuButton/MobileMenuButtonMenu.xaml.cs

@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using Xamarin.Forms;
+using Xamarin.Forms.Xaml;
+
+namespace InABox.Mobile
+{
+
+    
+    public class MobileMenuButtonMenuItem : BindableObject
+    {
+        public String Text { get; set; }
+        
+        public event EventHandler Clicked;
+
+        public void DoClicked()
+        {
+            Clicked?.Invoke(this, EventArgs.Empty);
+        }
+    }
+    
+    public class MobileMenuButtonMenuViewModel : BindableObject
+    {
+        public IList<MobileMenuButtonMenuItem> Items { get; private set; }
+        
+        public MobileMenuButtonMenuViewModel()
+        {
+            Items = new ObservableCollection<MobileMenuButtonMenuItem>();
+        }
+    }
+    
+    [XamlCompilation(XamlCompilationOptions.Compile)]
+    public partial class MobileMenuButtonMenu
+    {
+        public IList<MobileMenuButtonMenuItem> Items => _viewModel.Items;
+        
+        public MobileMenuButtonMenu()
+        {
+            InitializeComponent();
+        }
+
+        private void TapGestureRecognizer_OnTapped(object sender, EventArgs e)
+        {
+            if ((sender as Label)?.BindingContext is MobileMenuButtonMenuItem item)
+                item.DoClicked();
+        }
+        
+    }
+}

+ 48 - 9
InABox.Mobile/InABox.Mobile.Shared/Components/MobilePage/MobilePage.xaml.cs

@@ -1,11 +1,22 @@
 using System;
 using System.Collections.Generic;
+using System.Threading.Tasks;
+using Syncfusion.XForms.Core;
 using Syncfusion.XForms.PopupLayout;
 using Xamarin.Forms;
 using Xamarin.Forms.Xaml;
+using XF.Material.Forms.UI.Dialogs;
 
 namespace InABox.Mobile
 {
+
+    // public class BackButtonClickedEventArgs : CancelEventArgs
+    // {
+    //     
+    // }
+    //
+    // public delegate void BackButtonClickedEvent(object sender, BackButtonClickedEventArgs args);
+    
     [XamlCompilation(XamlCompilationOptions.Compile)]
     public partial class MobilePage 
     {
@@ -23,7 +34,7 @@ namespace InABox.Mobile
             set => _backButton.IsVisible = value;
         }
 
-        public event EventHandler BackButtonClicked;
+        //public event BackButtonClickedEvent BackButtonClicked;
         
         private SfPopupLayout _popup = new SfPopupLayout();
         
@@ -78,20 +89,48 @@ namespace InABox.Mobile
             //     _snackbar = null;
             // }
         }
-
-        private void _backButton_OnClicked(object sender, EventArgs e)
+        
+        private async void _backButton_OnClicked(object sender, EventArgs e)
         {
-            if (OnBackButtonPressed())
-            {
-                BackButtonClicked?.Invoke(this, EventArgs.Empty);
+
+            if (await OnBackButtonClicked())
                 Navigation.PopAsync();
-            }
-        }
 
-        protected override bool OnBackButtonPressed()
+            // if (OnBackButtonPressed())
+            // {
+            //     if (await ConfirmChanges())
+            //     var args = new BackButtonClickedEventArgs() { Cancel = false };
+            //     BackButtonClicked?.Invoke(this, args);
+            //     if (!args.Cancel)
+            //         
+            // }
+        }
+        
+        protected virtual async Task<bool> OnBackButtonClicked()
         {
             return true;
         }
+        
+        protected async Task<bool> ConfirmChanges(IShell item)
+        {
+            if (item.IsChanged())
+            {
+                var choice = await MaterialDialog.Instance.SelectActionAsync("Confirm Cancel?",
+                    new string[] { "Yes, Cancel", "Keep Editing", "Save Changes" });
+                if (choice == 0)
+                    item.Cancel();
+                else if (choice == 1)
+                    return false;
+                else if (choice == 2)
+                    item.Save("Saved on Mobile Device");
+                
+                Dispatcher.BeginInvokeOnMainThread(() => Navigation.PopAsync());
+                return false;
+            }
+            return true;
+        }
+
+
 
         protected void ShowPopup(Func<View> view, int height = 500, int width = 300, int padding = 10)
         {

+ 1 - 1
InABox.Mobile/InABox.Mobile.Shared/Converters/EmptyConverter.cs

@@ -18,7 +18,7 @@ namespace InABox.Mobile
     
     public class EmptyConverter : IValueConverter
     {
-        public EmptyConverterConvertingHandler Converting;
+        public event EmptyConverterConvertingHandler Converting;
         
         public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
         {

+ 1 - 1
InABox.Mobile/InABox.Mobile.Shared/DataModels/IModel.cs

@@ -31,6 +31,6 @@ namespace InABox.Mobile
         
         IModelHost Host { get; }
         ModelType Type { get; }
-        
+
     }
 }

+ 3 - 0
InABox.Mobile/InABox.Mobile.Shared/DataModels/IShell.cs

@@ -1,7 +1,10 @@
+using System;
+
 namespace InABox.Mobile
 {
     public interface IShell
     {
+        Guid ID { get; }
         IModel Parent { get; }
 
         bool IsChanged();

+ 3 - 0
InABox.Mobile/InABox.Mobile.Shared/DataModels/Lists/IListModel.cs

@@ -8,7 +8,10 @@ namespace InABox.Mobile
 
     public interface IListModel : IModel
     {
+        object CreateItem();
+        void CommitItem(object item);
         object AddItem();
+        void DeleteItem(object item);
         IEnumerable Items { get; }
         void Search();
     }

+ 35 - 3
InABox.Mobile/InABox.Mobile.Shared/DataModels/Lists/ListModel.cs

@@ -176,17 +176,49 @@ namespace InABox.Mobile
             return result;
         }
 
-        public TItem AddItem()
+        public TItem CreateItem()
         {
             CoreRow row = _table.NewRow();
-            _table.Rows.Add(row);
             var result = CreateItem<TItem>(row);
-            _allitems.Add(result);
+            return result;
+        }
+        
+        public void CommitItem(TItem item)
+        {
+            _table.Rows.Add(item.Row);
+            _allitems.Add(item);
             NotifyChanged();
+        }
+        
+        public TItem AddItem()
+        {
+            var result = CreateItem();
+            CommitItem(result);
             return result;
         }
+        
+        public void DeleteItem(TItem item)
+        {
+            _table.Rows.Remove(item.Row);
+            _allitems.Remove(item);
+            NotifyChanged();
+        }
 
+        object IListModel.CreateItem() => this.CreateItem();
+        
+        void IListModel.CommitItem(object item)
+        {
+            if (item is TItem titem)
+                CommitItem(titem);
+        }
+        
         object IListModel.AddItem() => this.AddItem();
+        
+        void IListModel.DeleteItem(object item)
+        {
+            if (item is TItem titem)
+                DeleteItem(titem);
+        }
 
         IEnumerator<TItem> IEnumerable<TItem>.GetEnumerator()
         {

+ 9 - 2
InABox.Mobile/InABox.Mobile.Shared/DataModels/Shell.cs

@@ -1,3 +1,4 @@
+using System;
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.Runtime.CompilerServices;
@@ -67,6 +68,8 @@ namespace InABox.Mobile
         public TParent Parent { get; set; }
 
         IModel IShell.Parent => this.Parent;
+
+        public Guid ID => Get<Guid>();
         
         #region Row Get/Set Caching
         
@@ -88,6 +91,7 @@ namespace InABox.Mobile
                 if (_columns == null)
                 {
                     _columns = new ShellColumns<TParent, TEntity>();
+                    _columns.Map(nameof(ID), x => x.ID);
                     ConfigureColumns(_columns);
                 }
                 return _columns;
@@ -105,8 +109,11 @@ namespace InABox.Mobile
                     CoreUtils.GetFullPropertyName(Columns[property], ".")
                 );
             }
-            var value = Row.Values[_columns.IndexOf(property)];
-            return value != null ? (T)CoreUtils.ChangeType(value, typeof(T)) : CoreUtils.GetDefault<T>();        
+
+            var col = _columns.IndexOf(property);
+            var value = Row.Get<T>(col, true); //() Row.Values[];
+            return value;
+            //return value != null ? (T)CoreUtils.ChangeType(value, typeof(T)) : CoreUtils.GetDefault<T>();        
         }
         
         protected virtual void Set<T>(T value, bool notify = true, [CallerMemberName] string property = null)