using System; using System.Collections.Generic; using System.ComponentModel; using System.Runtime.CompilerServices; using InABox.Clients; using InABox.Core; using Xamarin.Forms; namespace InABox.Mobile { public abstract class Shell : BindableObject, INotifyPropertyChanged, IShell, IShell where TParent : ICoreRepository where TEntity : Entity, IPersistent, IRemotable, new() { #region INotifyPropertyChanged public new event PropertyChangedEventHandler PropertyChanged; protected void DoPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } protected bool SetProperty(ref T field, T value, [CallerMemberName] string propertyName = null) { if (EqualityComparer.Default.Equals(field, value)) return false; field = value; DoPropertyChanged(propertyName); return true; } #endregion private TEntity _entity; private TEntity CheckEntity() { _entity ??= Row.ToObject(); return _entity; } public TEntity Entity => CheckEntity(); protected virtual void RowChanged() { } public bool IsChanged() => _entity?.IsChanged() ?? false; public virtual void Save(string auditmessage) { if (_entity != null) new Client().Save(_entity, auditmessage); } public void Cancel() { _entity?.CancelChanges(); _entity = null; } private CoreRow _row; public CoreRow Row { get => _row; set { _row = value; RowChanged(); } } public TParent Parent { get; set; } ICoreRepository IShell.Parent => this.Parent; public Guid ID => Get(); #region Row Get/Set Caching // We do this for three reasons: // 1. Rather than define properties in once class and columns in another, // we can define and link properties and columns in the one class, // using a _static_ constructor, which reduces complexity // 2. By caching based on the property name, we eliminate the need to convert // expressions to strings (expensive), while still retaining type-safety // 3. Using the Get/Set helper functions reduces code complexity when defining // shell properties, and distinguishes between data and calculated properties private static ShellColumns _columns; public ShellColumns Columns { get { if (_columns == null) { _columns = new ShellColumns(); _columns.Map(nameof(ID), x => x.ID); ConfigureColumns(_columns); } return _columns; } } protected abstract void ConfigureColumns(ShellColumns columns); protected virtual T Get([CallerMemberName] string property = null) { if (_entity != null) { return (T)CoreUtils.GetPropertyValue( _entity, CoreUtils.GetFullPropertyName(Columns[property], ".") ); } if (_row != null) { var col = _columns.IndexOf(property); var value = Row.Get(col); //() Row.Values[]; return value; } return CoreUtils.GetDefault(); //return value != null ? (T)CoreUtils.ChangeType(value, typeof(T)) : CoreUtils.GetDefault(); } protected virtual void Set(T value, bool notify = true, [CallerMemberName] string property = null) { CheckEntity(); CoreUtils.SetPropertyValue( _entity, CoreUtils.GetFullPropertyName(Columns[property], "."), value ); //Row.Values[Columns.IndexOf(property)] = value; if (notify) DoPropertyChanged(property); } #endregion } }