diff options
Diffstat (limited to 'Xamarin.Forms.Pages')
25 files changed, 1705 insertions, 0 deletions
diff --git a/Xamarin.Forms.Pages/BaseDataSource.cs b/Xamarin.Forms.Pages/BaseDataSource.cs new file mode 100644 index 00000000..31608b83 --- /dev/null +++ b/Xamarin.Forms.Pages/BaseDataSource.cs @@ -0,0 +1,113 @@ +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +namespace Xamarin.Forms.Pages +{ + public abstract class BaseDataSource : IDataSource, INotifyPropertyChanged + { + readonly DataSourceList _dataSourceList = new DataSourceList(); + bool _initialized; + bool _isLoading; + + public IReadOnlyList<IDataItem> Data + { + get + { + Initialize(); + return _dataSourceList; + } + } + + public bool IsLoading + { + get { return _isLoading; } + set + { + if (_isLoading == value) + return; + _isLoading = value; + OnPropertyChanged(); + } + } + +#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + public object this[string key] + { + get + { + Initialize(); + return GetValue(key); + } + set + { + Initialize(); + if (SetValue(key, value)) + OnKeyChanged(key); + } + } +#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + + IEnumerable<string> IDataSource.MaskedKeys => _dataSourceList.MaskedKeys; + + async void IDataSource.MaskKey(string key) + { + await Initialize(); + _dataSourceList.MaskKey(key); + } + + async void IDataSource.UnmaskKey(string key) + { + await Initialize(); + _dataSourceList.UnmaskKey(key); + } + + event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged + { + add { PropertyChanged += value; } + remove { PropertyChanged -= value; } + } + + protected abstract Task<IList<IDataItem>> GetRawData(); + + protected abstract object GetValue(string key); + + protected void OnPropertyChanged([CallerMemberName] string property = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property)); + } + + protected abstract bool SetValue(string key, object value); + + async Task Initialize() + { + // Do this lazy because GetRawData is virtual and calling it in the ctor is therefor unfriendly + if (_initialized) + return; + _initialized = true; + IList<IDataItem> rawData = await GetRawData(); + if (!(rawData is INotifyCollectionChanged)) + { + Log.Warning("Xamarin.Forms.Pages", "DataSource does not implement INotifyCollectionChanged, updates will not be reflected"); + rawData = rawData.ToList(); // Make a copy so we can be sure this list wont change out from under us + } + _dataSourceList.MainList = rawData; + + // Test if INPC("Item") is enough to trigger a full reset rather than triggering a new event for each key? + foreach (IDataItem dataItem in rawData) + { + OnKeyChanged(dataItem.Name); + } + } + + void OnKeyChanged(string key) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[{key}]")); + } + + event PropertyChangedEventHandler PropertyChanged; + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Pages/CardView.cs b/Xamarin.Forms.Pages/CardView.cs new file mode 100644 index 00000000..7aca6bde --- /dev/null +++ b/Xamarin.Forms.Pages/CardView.cs @@ -0,0 +1,37 @@ +using System; +using Xamarin.Forms; +using Xamarin.Forms.Pages; + +namespace Xamarin.Forms.Pages +{ + public class CardView : DataView + { + public static readonly BindableProperty TextProperty = + BindableProperty.Create(nameof(Text), typeof(string), typeof(CardView), null, BindingMode.OneWay); + + public string Text + { + get { return (string)GetValue(TextProperty); } + set { SetValue(TextProperty, value); } + } + + public static readonly BindableProperty DetailProperty = + BindableProperty.Create(nameof(Detail), typeof(string), typeof(CardView), null, BindingMode.OneWay); + + public string Detail + { + get { return (string)GetValue(DetailProperty); } + set { SetValue(DetailProperty, value); } + } + + public static readonly BindableProperty ImageSourceProperty = + BindableProperty.Create(nameof(ImageSource), typeof(ImageSource), typeof(CardView), null, BindingMode.OneWay); + + public ImageSource ImageSource + { + get { return (ImageSource)GetValue(ImageSourceProperty); } + set { SetValue(ImageSourceProperty, value); } + } + } +} + diff --git a/Xamarin.Forms.Pages/CompoundCollection.cs b/Xamarin.Forms.Pages/CompoundCollection.cs new file mode 100644 index 00000000..6dbf502b --- /dev/null +++ b/Xamarin.Forms.Pages/CompoundCollection.cs @@ -0,0 +1,223 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Linq; + +namespace Xamarin.Forms.Pages +{ + public class CompoundCollection : Element, IList, INotifyCollectionChanged + { + public static readonly BindableProperty MainListProperty = BindableProperty.Create(nameof(MainList), typeof(IReadOnlyList<object>), typeof(CompoundCollection), default(IReadOnlyList<object>), + propertyChanged: OnMainListPropertyChanged); + + readonly ObservableCollection<object> _appendList = new ObservableCollection<object>(); + + readonly ObservableCollection<object> _prependList = new ObservableCollection<object>(); + + public CompoundCollection() + { + _prependList.CollectionChanged += OnPrependCollectionChanged; + _appendList.CollectionChanged += OnAppendCollectionChanged; + } + + public IList AppendList => _appendList; + + public IReadOnlyList<object> MainList + { + get { return (IReadOnlyList<object>)GetValue(MainListProperty); } + set { SetValue(MainListProperty, value); } + } + + public IList PrependList => _prependList; + + public void CopyTo(Array array, int index) + { + throw new NotSupportedException(); + } + + public int Count => AppendList.Count + PrependList.Count + (MainList?.Count ?? 0); + + public bool IsSynchronized => false; + + public object SyncRoot => null; + + public IEnumerator GetEnumerator() + { + foreach (object item in PrependList) + yield return item; + foreach (object item in MainList) + yield return item; + foreach (object item in AppendList) + yield return item; + } + + public int Add(object value) + { + throw new NotSupportedException(); + } + + public void Clear() + { + throw new NotSupportedException(); + } + + public bool Contains(object value) + { + IReadOnlyList<object> mainList = MainList; + bool masterContains; + var masterList = mainList as IList; + if (masterList != null) + { + masterContains = masterList.Contains(value); + } + else + { + masterContains = mainList.Contains(value); + } + return masterContains || PrependList.Contains(value) || AppendList.Contains(value); + } + + public int IndexOf(object value) + { + int result; + result = PrependList.IndexOf(value); + if (result >= 0) + return result; + result = MainList.IndexOf(value); + if (result >= 0) + return result + PrependList.Count; + + result = AppendList.IndexOf(value); + if (result >= 0) + return result + PrependList.Count + MainList.Count; + return -1; + } + + public void Insert(int index, object value) + { + throw new NotSupportedException(); + } + + public bool IsFixedSize => false; + + public bool IsReadOnly => true; + + public object this[int index] + { + get + { + IReadOnlyList<object> mainList = MainList; + int prependSize = PrependList.Count; + if (index < prependSize) + return PrependList[index]; + index -= prependSize; + + if (mainList != null) + { + if (index < mainList.Count) + return mainList[index]; + index -= mainList.Count; + } + + if (index >= AppendList.Count) + throw new IndexOutOfRangeException(); + return AppendList[index]; + } + set { throw new NotSupportedException(); } + } + + public void Remove(object value) + { + throw new NotSupportedException(); + } + + public void RemoveAt(int index) + { + throw new NotSupportedException(); + } + + public event NotifyCollectionChangedEventHandler CollectionChanged; + + void OnAppendCollectionChanged(object sender, NotifyCollectionChangedEventArgs args) + { + int offset = _prependList.Count + (MainList?.Count ?? 0); + // here we just need to calculate the offset for the index, everything else is the same + switch (args.Action) + { + case NotifyCollectionChangedAction.Add: + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, args.NewItems, offset + args.NewStartingIndex)); + break; + case NotifyCollectionChangedAction.Move: + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, args.OldItems, offset + args.NewStartingIndex, offset + args.OldStartingIndex)); + break; + case NotifyCollectionChangedAction.Remove: + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, args.OldItems, offset + args.OldStartingIndex)); + break; + case NotifyCollectionChangedAction.Replace: + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, args.NewItems, args.OldItems, offset + args.OldStartingIndex)); + break; + case NotifyCollectionChangedAction.Reset: + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + void OnCollectionChanged(NotifyCollectionChangedEventArgs args) + { + CollectionChanged?.Invoke(this, args); + } + + void OnMainCollectionChanged(object sender, NotifyCollectionChangedEventArgs args) + { + // much complexity to be had here + switch (args.Action) + { + case NotifyCollectionChangedAction.Add: + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, args.NewItems, PublicIndexFromMainIndex(args.NewStartingIndex))); + break; + case NotifyCollectionChangedAction.Move: + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, args.OldItems, PublicIndexFromMainIndex(args.NewStartingIndex), + PublicIndexFromMainIndex(args.OldStartingIndex))); + break; + case NotifyCollectionChangedAction.Remove: + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, args.OldItems, PublicIndexFromMainIndex(args.OldStartingIndex))); + break; + case NotifyCollectionChangedAction.Replace: + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, args.NewItems, args.OldItems, PublicIndexFromMainIndex(args.OldStartingIndex))); + break; + case NotifyCollectionChangedAction.Reset: + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + static void OnMainListPropertyChanged(BindableObject bindable, object oldValue, object newValue) + { + var self = (CompoundCollection)bindable; + var observable = oldValue as INotifyCollectionChanged; + if (observable != null) + observable.CollectionChanged -= self.OnMainCollectionChanged; + observable = newValue as INotifyCollectionChanged; + if (observable != null) + observable.CollectionChanged += self.OnMainCollectionChanged; + self.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + + void OnPrependCollectionChanged(object sender, NotifyCollectionChangedEventArgs args) + { + // this can basically be a passthrough as prepend has no masking and identical indexing + OnCollectionChanged(args); + } + + int PublicIndexFromMainIndex(int index) + { + return PrependList.Count + index; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Pages/DataItem.cs b/Xamarin.Forms.Pages/DataItem.cs new file mode 100644 index 00000000..62af919c --- /dev/null +++ b/Xamarin.Forms.Pages/DataItem.cs @@ -0,0 +1,86 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace Xamarin.Forms.Pages +{ + public class DataItem : IDataItem, INotifyPropertyChanged + { + string _name; + object _val; + + public DataItem() + { + } + + public DataItem(string name, object value) + { + _name = name; + _val = value; + } + + public string Name + { + get { return _name; } + set + { + if (_name == value) + return; + _name = value; + OnPropertyChanged(); + } + } + + public object Value + { + get { return _val; } + set + { + if (_val == value) + return; + _val = value; + OnPropertyChanged(); + } + } + + public event PropertyChangedEventHandler PropertyChanged; + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + return false; + if (ReferenceEquals(this, obj)) + return true; + if (obj.GetType() != GetType()) + return false; + return Equals((DataItem)obj); + } + + public override int GetHashCode() + { + unchecked + { + return ((_name?.GetHashCode() ?? 0) * 397) ^ (_val?.GetHashCode() ?? 0); + } + } + + public static bool operator ==(DataItem left, DataItem right) + { + return Equals(left, right); + } + + public static bool operator !=(DataItem left, DataItem right) + { + return !Equals(left, right); + } + + protected bool Equals(DataItem other) + { + return string.Equals(_name, other._name) && Equals(_val, other._val); + } + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Pages/DataPage.cs b/Xamarin.Forms.Pages/DataPage.cs new file mode 100644 index 00000000..1ec28d5c --- /dev/null +++ b/Xamarin.Forms.Pages/DataPage.cs @@ -0,0 +1,76 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Xamarin.Forms.Pages +{ + public class DataPage : ContentPage, IDataSourceProvider + { + public static readonly BindableProperty DataProperty = BindableProperty.Create(nameof(Data), typeof(IEnumerable<IDataItem>), typeof(DataPage), default(IEnumerable<IDataItem>)); + + public static readonly BindableProperty DataSourceProperty = BindableProperty.Create(nameof(DataSource), typeof(IDataSource), typeof(DataPage), null, propertyChanged: OnDataSourceChanged); + + public static readonly BindableProperty DefaultItemTemplateProperty = BindableProperty.Create(nameof(DefaultItemTemplate), typeof(DataTemplate), typeof(DataPage), default(DataTemplate)); + + readonly HashSet<string> _maskedKeys = new HashSet<string>(); + + public DataPage() + { + SetBinding(DataProperty, new Binding("DataSource.Data", source: this)); + } + + public IEnumerable<IDataItem> Data + { + get { return (IEnumerable<IDataItem>)GetValue(DataProperty); } + set { SetValue(DataProperty, value); } + } + + public DataTemplate DefaultItemTemplate + { + get { return (DataTemplate)GetValue(DefaultItemTemplateProperty); } + set { SetValue(DefaultItemTemplateProperty, value); } + } + + public IDataSource DataSource + { + get { return (IDataSource)GetValue(DataSourceProperty); } + set { SetValue(DataSourceProperty, value); } + } + + void IDataSourceProvider.MaskKey(string key) + { + _maskedKeys.Add(key); + IDataSource dataSource = DataSource; + if (dataSource != null && !dataSource.MaskedKeys.Contains(key)) + { + dataSource.MaskKey(key); + } + } + + void IDataSourceProvider.UnmaskKey(string key) + { + _maskedKeys.Remove(key); + DataSource?.UnmaskKey(key); + } + + static void OnDataSourceChanged(BindableObject bindable, object oldValue, object newValue) + { + var dataView = (DataPage)bindable; + var dataSource = (IDataSource)newValue; + var oldSource = (IDataSource)oldValue; + + if (oldSource != null) + { + foreach (string key in dataView._maskedKeys) + oldSource.UnmaskKey(key); + } + + if (dataSource != null) + { + foreach (string key in dataView._maskedKeys) + { + dataSource.MaskKey(key); + } + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Pages/DataSourceBinding.cs b/Xamarin.Forms.Pages/DataSourceBinding.cs new file mode 100644 index 00000000..44af47b4 --- /dev/null +++ b/Xamarin.Forms.Pages/DataSourceBinding.cs @@ -0,0 +1,157 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; + +namespace Xamarin.Forms.Pages +{ + public class DataSourceBinding : BindingBase + { + internal const string SelfPath = "."; + IValueConverter _converter; + object _converterParameter; + WeakReference _dataSourceRef; + + BindingExpression _expression; + string _path; + + public DataSourceBinding() + { + } + + public DataSourceBinding(string path, BindingMode mode = BindingMode.Default, IValueConverter converter = null, object converterParameter = null, string stringFormat = null) + { + if (path == null) + throw new ArgumentNullException(nameof(path)); + if (string.IsNullOrWhiteSpace(path)) + throw new ArgumentException("path can not be an empty string", nameof(path)); + + AllowChaining = true; + Path = path; + Converter = converter; + ConverterParameter = converterParameter; + Mode = mode; + StringFormat = stringFormat; + } + + public IValueConverter Converter + { + get { return _converter; } + set + { + ThrowIfApplied(); + + _converter = value; + } + } + + public object ConverterParameter + { + get { return _converterParameter; } + set + { + ThrowIfApplied(); + + _converterParameter = value; + } + } + + public string Path + { + get { return _path; } + set + { + ThrowIfApplied(); + + _path = value; + _expression = GetBindingExpression($"DataSource[{value}]"); + } + } + + internal override void Apply(bool fromTarget) + { + base.Apply(fromTarget); + + if (_expression == null) + _expression = new BindingExpression(this, SelfPath); + + _expression.Apply(fromTarget); + } + + internal override async void Apply(object newContext, BindableObject bindObj, BindableProperty targetProperty) + { + var view = bindObj as VisualElement; + if (view == null) + throw new InvalidOperationException(); + + base.Apply(newContext, bindObj, targetProperty); + + Element dataSourceParent = await FindDataSourceParentAsync(view); + + var dataSourceProviderer = (IDataSourceProvider)dataSourceParent; + if (dataSourceProviderer != null) + _dataSourceRef = new WeakReference(dataSourceProviderer); + + dataSourceProviderer?.MaskKey(_path); + ApplyInner(dataSourceParent, bindObj, targetProperty); + } + + internal override BindingBase Clone() + { + return new DataSourceBinding(Path, Mode) { Converter = Converter, ConverterParameter = ConverterParameter, StringFormat = StringFormat }; + } + + internal override object GetSourceValue(object value, Type targetPropertyType) + { + if (Converter != null) + value = Converter.Convert(value, targetPropertyType, ConverterParameter, CultureInfo.CurrentUICulture); + + return base.GetSourceValue(value, targetPropertyType); + } + + internal override object GetTargetValue(object value, Type sourcePropertyType) + { + if (Converter != null) + value = Converter.ConvertBack(value, sourcePropertyType, ConverterParameter, CultureInfo.CurrentUICulture); + + return base.GetTargetValue(value, sourcePropertyType); + } + + internal override void Unapply() + { + base.Unapply(); + + if (_dataSourceRef != null && _dataSourceRef.IsAlive) + { + var dataSourceProviderer = (IDataSourceProvider)_dataSourceRef.Target; + dataSourceProviderer?.UnmaskKey(_path); + } + + _expression?.Unapply(); + } + + void ApplyInner(Element templatedParent, BindableObject bindableObject, BindableProperty targetProperty) + { + if (_expression == null && templatedParent != null) + _expression = new BindingExpression(this, SelfPath); + + _expression?.Apply(templatedParent, bindableObject, targetProperty); + } + + static async Task<Element> FindDataSourceParentAsync(Element element) + { + while (!Application.IsApplicationOrNull(element)) + { + if (element is IDataSourceProvider) + return element; + element = await TemplateUtilities.GetRealParentAsync(element); + } + + return null; + } + + BindingExpression GetBindingExpression(string path) + { + return new BindingExpression(this, !string.IsNullOrWhiteSpace(path) ? path : SelfPath); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Pages/DataSourceBindingExtension.cs b/Xamarin.Forms.Pages/DataSourceBindingExtension.cs new file mode 100644 index 00000000..279240e7 --- /dev/null +++ b/Xamarin.Forms.Pages/DataSourceBindingExtension.cs @@ -0,0 +1,35 @@ +using System; +using Xamarin.Forms.Xaml; + +namespace Xamarin.Forms.Pages +{ + [ContentProperty("Path")] + public sealed class DataSourceBindingExtension : IMarkupExtension<BindingBase> + { + public DataSourceBindingExtension() + { + Mode = BindingMode.Default; + Path = Binding.SelfPath; + } + + public IValueConverter Converter { get; set; } + + public object ConverterParameter { get; set; } + + public BindingMode Mode { get; set; } + + public string Path { get; set; } + + public string StringFormat { get; set; } + + object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider) + { + return (this as IMarkupExtension<BindingBase>).ProvideValue(serviceProvider); + } + + BindingBase IMarkupExtension<BindingBase>.ProvideValue(IServiceProvider serviceProvider) + { + return new DataSourceBinding(Path, Mode, Converter, ConverterParameter, StringFormat); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Pages/DataSourceList.cs b/Xamarin.Forms.Pages/DataSourceList.cs new file mode 100644 index 00000000..7f3a59c1 --- /dev/null +++ b/Xamarin.Forms.Pages/DataSourceList.cs @@ -0,0 +1,233 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; + +namespace Xamarin.Forms.Pages +{ + internal class DataSourceList : IList<IDataItem>, IReadOnlyList<IDataItem>, INotifyCollectionChanged + { + readonly List<int> _maskedIndexes = new List<int>(); // Indices + readonly HashSet<string> _maskedKeys = new HashSet<string>(); + IList<IDataItem> _mainList; + + public IList<IDataItem> MainList + { + get { return _mainList; } + set + { + var observable = _mainList as INotifyCollectionChanged; + if (observable != null) + observable.CollectionChanged -= OnMainCollectionChanged; + _mainList = value; + observable = _mainList as INotifyCollectionChanged; + if (observable != null) + observable.CollectionChanged += OnMainCollectionChanged; + _maskedIndexes.Clear(); + for (var i = 0; i < _mainList.Count; i++) + { + IDataItem data = _mainList[i]; + if (_maskedKeys.Contains(data.Name)) + _maskedIndexes.Add(i); + } + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + } + + public IEnumerable<string> MaskedKeys => _maskedKeys; + + public void Add(IDataItem item) + { + throw new NotSupportedException(); + } + + public void Clear() + { + throw new NotSupportedException(); + } + + public bool Contains(IDataItem item) + { + return MainList != null && !_maskedKeys.Contains(item.Name) && MainList.Contains(item); + } + + public void CopyTo(IDataItem[] array, int arrayIndex) + { + throw new NotSupportedException(); + } + + public int Count + { + get + { + if (MainList == null) + return 0; + var result = 0; + result += MainList.Count; + result -= _maskedIndexes.Count; + return result; + } + } + + public bool IsReadOnly => true; + + public bool Remove(IDataItem item) + { + throw new NotSupportedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public IEnumerator<IDataItem> GetEnumerator() + { + var index = 0; + if (MainList == null) + yield break; + foreach (IDataItem item in MainList) + { + if (!_maskedIndexes.Contains(index)) + yield return item; + index++; + } + } + + public int IndexOf(IDataItem item) + { + if (_maskedKeys.Contains(item.Name)) + return -1; + + if (MainList != null) + { + int result = MainList.IndexOf(item); + if (result >= 0) + return PublicIndexFromMainIndex(result); + } + return -1; + } + + public void Insert(int index, IDataItem item) + { + throw new NotSupportedException(); + } + + public IDataItem this[int index] + { + get + { + foreach (int i in _maskedIndexes) + { + if (i <= index) + index++; + } + if (_mainList == null) + throw new IndexOutOfRangeException(); + return _mainList[index]; + } + set { throw new NotSupportedException(); } + } + + public void RemoveAt(int index) + { + throw new NotSupportedException(); + } + + public event NotifyCollectionChangedEventHandler CollectionChanged; + + public void MaskKey(string key) + { + if (_maskedKeys.Contains(key) || _mainList == null) + return; + _maskedKeys.Add(key); + var index = 0; + foreach (IDataItem item in _mainList) + { + if (item.Name == key) + { + // We need to keep our indexes list sorted, so we insert everything pre-sorted + var added = false; + for (var i = 0; i < _maskedIndexes.Count; i++) + { + if (_maskedIndexes[i] > index) + { + _maskedIndexes.Insert(i, index); + added = true; + break; + } + } + if (!added) + _maskedIndexes.Add(index); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, PublicIndexFromMainIndex(index))); + break; + } + index++; + } + } + + public void UnmaskKey(string key) + { + _maskedKeys.Remove(key); + if (_mainList == null) + return; + var index = 0; + foreach (IDataItem item in _mainList) + { + if (item.Name == key) + { + bool removed = _maskedIndexes.Remove(index); + if (removed) + { + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, PublicIndexFromMainIndex(index))); + } + break; + } + index++; + } + } + + protected void OnCollectionChanged(NotifyCollectionChangedEventArgs args) + { + CollectionChanged?.Invoke(this, args); + } + + void OnMainCollectionChanged(object sender, NotifyCollectionChangedEventArgs args) + { + // much complexity to be had here + switch (args.Action) + { + case NotifyCollectionChangedAction.Add: + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, args.NewItems, PublicIndexFromMainIndex(args.NewStartingIndex))); + break; + case NotifyCollectionChangedAction.Move: + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, args.OldItems, PublicIndexFromMainIndex(args.NewStartingIndex), + PublicIndexFromMainIndex(args.OldStartingIndex))); + break; + case NotifyCollectionChangedAction.Remove: + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, args.OldItems, PublicIndexFromMainIndex(args.OldStartingIndex))); + break; + case NotifyCollectionChangedAction.Replace: + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, args.NewItems, args.OldItems, PublicIndexFromMainIndex(args.OldStartingIndex))); + break; + case NotifyCollectionChangedAction.Reset: + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + int PublicIndexFromMainIndex(int index) + { + var count = 0; + for (var x = 0; x < _maskedIndexes.Count; x++) + { + int i = _maskedIndexes[x]; + if (i < index) + count++; + } + return index - count; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Pages/DataView.cs b/Xamarin.Forms.Pages/DataView.cs new file mode 100644 index 00000000..983328ee --- /dev/null +++ b/Xamarin.Forms.Pages/DataView.cs @@ -0,0 +1,76 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Xamarin.Forms.Pages +{ + public class DataView : ContentView, IDataSourceProvider + { + public static readonly BindableProperty DataProperty = BindableProperty.Create(nameof(Data), typeof(IEnumerable<IDataItem>), typeof(DataView), default(IEnumerable<IDataItem>)); + + public static readonly BindableProperty DataSourceProperty = BindableProperty.Create(nameof(DataSource), typeof(IDataSource), typeof(DataView), null, propertyChanged: OnDataSourceChanged); + + public static readonly BindableProperty DefaultItemTemplateProperty = BindableProperty.Create(nameof(DefaultItemTemplate), typeof(DataTemplate), typeof(DataView), default(DataTemplate)); + + readonly HashSet<string> _maskedKeys = new HashSet<string>(); + + public DataView() + { + SetBinding(DataProperty, new Binding("DataSource.Data", source: this)); + } + + public IEnumerable<IDataItem> Data + { + get { return (IEnumerable<IDataItem>)GetValue(DataProperty); } + set { SetValue(DataProperty, value); } + } + + public DataTemplate DefaultItemTemplate + { + get { return (DataTemplate)GetValue(DefaultItemTemplateProperty); } + set { SetValue(DefaultItemTemplateProperty, value); } + } + + public IDataSource DataSource + { + get { return (IDataSource)GetValue(DataSourceProperty); } + set { SetValue(DataSourceProperty, value); } + } + + void IDataSourceProvider.MaskKey(string key) + { + _maskedKeys.Add(key); + IDataSource dataSource = DataSource; + if (dataSource != null && !dataSource.MaskedKeys.Contains(key)) + { + dataSource.MaskKey(key); + } + } + + void IDataSourceProvider.UnmaskKey(string key) + { + _maskedKeys.Remove(key); + DataSource?.UnmaskKey(key); + } + + static void OnDataSourceChanged(BindableObject bindable, object oldValue, object newValue) + { + var dataView = (DataView)bindable; + var dataSource = (IDataSource)newValue; + var oldSource = (IDataSource)oldValue; + + if (oldSource != null) + { + foreach (string key in dataView._maskedKeys) + oldSource.UnmaskKey(key); + } + + if (dataSource != null) + { + foreach (string key in dataView._maskedKeys) + { + dataSource.MaskKey(key); + } + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Pages/DirectoryPage.cs b/Xamarin.Forms.Pages/DirectoryPage.cs new file mode 100644 index 00000000..c85fae40 --- /dev/null +++ b/Xamarin.Forms.Pages/DirectoryPage.cs @@ -0,0 +1,13 @@ +namespace Xamarin.Forms.Pages +{ + public class DirectoryPage : DataPage + { + public static readonly BindableProperty IsGroupingEnabledProperty = BindableProperty.Create(nameof(IsGroupingEnabled), typeof(bool), typeof(DirectoryPage), default(bool)); + + public bool IsGroupingEnabled + { + get { return (bool)GetValue(IsGroupingEnabledProperty); } + set { SetValue(IsGroupingEnabledProperty, value); } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Pages/HeroImage.cs b/Xamarin.Forms.Pages/HeroImage.cs new file mode 100644 index 00000000..ce5a4c93 --- /dev/null +++ b/Xamarin.Forms.Pages/HeroImage.cs @@ -0,0 +1,37 @@ +namespace Xamarin.Forms.Pages +{ + public class HeroImage : DataView + { + public static readonly BindableProperty TextProperty = BindableProperty.Create(nameof(Text), typeof(string), typeof(HeroImage), null); + + public static readonly BindableProperty DetailProperty = BindableProperty.Create(nameof(Detail), typeof(string), typeof(HeroImage), null); + + public static readonly BindableProperty ImageSourceProperty = BindableProperty.Create(nameof(ImageSource), typeof(ImageSource), typeof(HeroImage), null); + + public static readonly BindableProperty AspectProperty = BindableProperty.Create(nameof(Aspect), typeof(Aspect), typeof(HeroImage), Aspect.AspectFit); + + public Aspect Aspect + { + get { return (Aspect)GetValue(AspectProperty); } + set { SetValue(AspectProperty, value); } + } + + public string Detail + { + get { return (string)GetValue(DetailProperty); } + set { SetValue(DetailProperty, value); } + } + + public ImageSource ImageSource + { + get { return (ImageSource)GetValue(ImageSourceProperty); } + set { SetValue(ImageSourceProperty, value); } + } + + public string Text + { + get { return (string)GetValue(TextProperty); } + set { SetValue(TextProperty, value); } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Pages/IDataItem.cs b/Xamarin.Forms.Pages/IDataItem.cs new file mode 100644 index 00000000..a594e8ee --- /dev/null +++ b/Xamarin.Forms.Pages/IDataItem.cs @@ -0,0 +1,9 @@ +namespace Xamarin.Forms.Pages +{ + public interface IDataItem + { + string Name { get; set; } + + object Value { get; set; } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Pages/IDataSource.cs b/Xamarin.Forms.Pages/IDataSource.cs new file mode 100644 index 00000000..a00e7f1c --- /dev/null +++ b/Xamarin.Forms.Pages/IDataSource.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace Xamarin.Forms.Pages +{ + public interface IDataSource + { + IReadOnlyList<IDataItem> Data { get; } + + bool IsLoading { get; } + + object this[string key] { get; set; } + + IEnumerable<string> MaskedKeys { get; } + + void MaskKey(string key); + void UnmaskKey(string key); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Pages/IDataSourceProvider.cs b/Xamarin.Forms.Pages/IDataSourceProvider.cs new file mode 100644 index 00000000..ae07c0f7 --- /dev/null +++ b/Xamarin.Forms.Pages/IDataSourceProvider.cs @@ -0,0 +1,11 @@ +namespace Xamarin.Forms.Pages +{ + public interface IDataSourceProvider + { + IDataSource DataSource { get; set; } + + void MaskKey(string key); + + void UnmaskKey(string key); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Pages/JsonDataSource.cs b/Xamarin.Forms.Pages/JsonDataSource.cs new file mode 100644 index 00000000..3be50e8a --- /dev/null +++ b/Xamarin.Forms.Pages/JsonDataSource.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading.Tasks; +using Newtonsoft.Json.Linq; + +namespace Xamarin.Forms.Pages +{ + public class JsonDataSource : BaseDataSource + { + readonly ObservableCollection<IDataItem> _dataItems = new ObservableCollection<IDataItem>(); + Task _currentParseTask; + bool _initialized; + JsonSource _source; + + public JsonDataSource() + { + } + + internal JsonDataSource(JToken rootToken) + { + ParseJsonToken(rootToken); + } + + [TypeConverter(typeof(JsonSourceConverter))] + public JsonSource Source + { + get { return _source; } + set + { + if (_source == value) + return; + _source = value; + + _dataItems.Clear(); + if (value != null && _initialized) + { + _currentParseTask = ParseJson(); + _currentParseTask.ContinueWith(t => { throw t.Exception; }, TaskContinuationOptions.OnlyOnFaulted); + } + } + } + + protected override async Task<IList<IDataItem>> GetRawData() + { + if (!_initialized) + { + Task task = _currentParseTask = ParseJson(); + await task; + } + else if (_currentParseTask != null && _currentParseTask.IsCompleted == false) + await _currentParseTask; + return _dataItems; + } + + protected override object GetValue(string key) + { + IDataItem target = _dataItems.FirstOrDefault(d => d.Name == key); + return target?.Value; + } + + protected override bool SetValue(string key, object value) + { + IDataItem target = _dataItems.FirstOrDefault(d => d.Name == key); + if (target == null) + { + _dataItems.Add(new DataItem(key, value)); + return true; + } + if (target.Value == value) + return false; + target.Value = value; + return true; + } + + object GetValueForJToken(JToken token) + { + switch (token.Type) + { + case JTokenType.Object: + case JTokenType.Array: + return new JsonDataSource(token); + case JTokenType.Constructor: + case JTokenType.Property: + case JTokenType.Comment: + throw new NotImplementedException(); + case JTokenType.Integer: + return (int)token; + case JTokenType.Float: + return (float)token; + case JTokenType.Raw: + case JTokenType.String: + return (string)token; + case JTokenType.Boolean: + return (bool)token; + case JTokenType.Date: + return (DateTime)token; + case JTokenType.Bytes: + return (byte[])token; + case JTokenType.Guid: + return (Guid)token; + case JTokenType.Uri: + return (Uri)token; + case JTokenType.TimeSpan: + return (TimeSpan)token; + default: + return null; + } + } + + async Task ParseJson() + { + _initialized = true; + + if (Source == null) + return; + + IsLoading = true; + string json = await Source.GetJson(); + JToken jToken = JToken.Parse(json); + ParseJsonToken(jToken); + IsLoading = false; + } + + void ParseJsonToken(JToken token) + { + var jArray = token as JArray; + var jObject = token as JObject; + if (jArray != null) + { + for (var i = 0; i < jArray.Count; i++) + { + JToken obj = jArray[i]; + _dataItems.Add(new DataItem(i.ToString(), GetValueForJToken(obj))); + } + } + else if (jObject != null) + { + foreach (KeyValuePair<string, JToken> kvp in jObject) + { + _dataItems.Add(new DataItem(kvp.Key, GetValueForJToken(kvp.Value))); + } + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Pages/JsonSource.cs b/Xamarin.Forms.Pages/JsonSource.cs new file mode 100644 index 00000000..46c0a95d --- /dev/null +++ b/Xamarin.Forms.Pages/JsonSource.cs @@ -0,0 +1,25 @@ +using System; +using System.Threading.Tasks; + +namespace Xamarin.Forms.Pages +{ + public abstract class JsonSource : Element + { + public static JsonSource FromString(string json) + { + return new StringJsonSource { Json = json }; + } + + public static JsonSource FromUri(Uri uri) + { + return new UriJsonSource { Uri = uri }; + } + + public abstract Task<string> GetJson(); + + public static implicit operator JsonSource(string json) + { + return FromString(json); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Pages/JsonSourceConverter.cs b/Xamarin.Forms.Pages/JsonSourceConverter.cs new file mode 100644 index 00000000..461c96d9 --- /dev/null +++ b/Xamarin.Forms.Pages/JsonSourceConverter.cs @@ -0,0 +1,22 @@ +using System; + +namespace Xamarin.Forms.Pages +{ + public class JsonSourceConverter : TypeConverter + { + public override object ConvertFromInvariantString(string value) + { + if (value != null) + { + value = value.Trim(); + Uri uri; + if (Uri.TryCreate(value, UriKind.Absolute, out uri) && uri.Scheme != "file") + return new UriJsonSource { Uri = uri }; + if (value.StartsWith("[") || value.StartsWith("{")) + return new StringJsonSource { Json = value }; + } + + throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(JsonSource))); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Pages/ListDataPage.cs b/Xamarin.Forms.Pages/ListDataPage.cs new file mode 100644 index 00000000..2a2585ec --- /dev/null +++ b/Xamarin.Forms.Pages/ListDataPage.cs @@ -0,0 +1,55 @@ +namespace Xamarin.Forms.Pages +{ + public class ListDataPageControl : ListView + { + public ListDataPageControl() + { + SetBinding(ItemTemplateProperty, new TemplateBinding(DataPage.DefaultItemTemplateProperty.PropertyName)); + SetBinding(SelectedItemProperty, new TemplateBinding(ListDataPage.SelectedItemProperty.PropertyName, BindingMode.TwoWay)); + SetBinding(ItemsSourceProperty, new TemplateBinding(DataPage.DataProperty.PropertyName)); + } + } + + public class ListDataPage : DataPage + { + public static readonly BindableProperty DetailTemplateProperty = BindableProperty.Create(nameof(DetailTemplate), typeof(DataTemplate), typeof(ListDataPage), null); + + public static readonly BindableProperty SelectedItemProperty = BindableProperty.Create(nameof(SelectedItem), typeof(object), typeof(ListDataPage), null, BindingMode.TwoWay, + propertyChanged: OnSelectedItemChanged); + + public DataTemplate DetailTemplate + { + get { return (DataTemplate)GetValue(DetailTemplateProperty); } + set { SetValue(DetailTemplateProperty, value); } + } + + public object SelectedItem + { + get { return GetValue(SelectedItemProperty); } + set { SetValue(SelectedItemProperty, value); } + } + + static async void OnSelectedItemChanged(BindableObject bindable, object oldValue, object newValue) + { + var self = (ListDataPage)bindable; + DataTemplate template = self.DetailTemplate; + if (newValue == null) + return; + + Page detailPage; + if (template == null) + { + detailPage = new DataPage(); + } + else + { + detailPage = (Page)template.CreateContent(newValue, self); + } + + detailPage.BindingContext = newValue; + await self.Navigation.PushAsync(detailPage); + + self.SelectedItem = null; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Pages/ListItemControl.cs b/Xamarin.Forms.Pages/ListItemControl.cs new file mode 100644 index 00000000..d5ba37a1 --- /dev/null +++ b/Xamarin.Forms.Pages/ListItemControl.cs @@ -0,0 +1,45 @@ +namespace Xamarin.Forms.Pages +{ + public class ListItemControl : DataView + { + public static readonly BindableProperty TitleProperty = BindableProperty.Create(nameof(Title), typeof(string), typeof(ListItemControl), default(string)); + + public static readonly BindableProperty DetailProperty = BindableProperty.Create(nameof(Detail), typeof(string), typeof(ListItemControl), default(string)); + + public static readonly BindableProperty ImageSourceProperty = BindableProperty.Create(nameof(ImageSource), typeof(ImageSource), typeof(ListItemControl), default(ImageSource)); + + public static readonly BindableProperty PlaceHolderImageSourceProperty = BindableProperty.Create(nameof(PlaceholderImageSource), typeof(ImageSource), typeof(ListItemControl), default(ImageSource)); + + public static readonly BindableProperty AspectProperty = BindableProperty.Create(nameof(Aspect), typeof(Aspect), typeof(ListItemControl), default(Aspect)); + + public Aspect Aspect + { + get { return (Aspect)GetValue(AspectProperty); } + set { SetValue(AspectProperty, value); } + } + + public string Detail + { + get { return (string)GetValue(DetailProperty); } + set { SetValue(DetailProperty, value); } + } + + public ImageSource ImageSource + { + get { return (ImageSource)GetValue(ImageSourceProperty); } + set { SetValue(ImageSourceProperty, value); } + } + + public ImageSource PlaceholderImageSource + { + get { return (ImageSource)GetValue(PlaceHolderImageSourceProperty); } + set { SetValue(PlaceHolderImageSourceProperty, value); } + } + + public string Title + { + get { return (string)GetValue(TitleProperty); } + set { SetValue(TitleProperty, value); } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Pages/PersonDetailPage.cs b/Xamarin.Forms.Pages/PersonDetailPage.cs new file mode 100644 index 00000000..9a58e106 --- /dev/null +++ b/Xamarin.Forms.Pages/PersonDetailPage.cs @@ -0,0 +1,90 @@ +namespace Xamarin.Forms.Pages +{ + public class PersonDetailPage : DataPage + { + public static readonly BindableProperty DisplayNameProperty = BindableProperty.Create(nameof(DisplayName), typeof(string), typeof(PersonDetailPage), default(string)); + + public static readonly BindableProperty PhoneNumberProperty = BindableProperty.Create(nameof(PhoneNumber), typeof(string), typeof(PersonDetailPage), default(string)); + + public static readonly BindableProperty ImageProperty = BindableProperty.Create(nameof(Image), typeof(ImageSource), typeof(PersonDetailPage), default(ImageSource)); + + public static readonly BindableProperty EmailProperty = BindableProperty.Create(nameof(Email), typeof(string), typeof(PersonDetailPage), default(string)); + + public static readonly BindableProperty AddressProperty = BindableProperty.Create(nameof(Address), typeof(string), typeof(PersonDetailPage), default(string)); + + public static readonly BindableProperty EmployerProperty = BindableProperty.Create(nameof(Employer), typeof(string), typeof(PersonDetailPage), default(string)); + + public static readonly BindableProperty TwitterProperty = BindableProperty.Create(nameof(Twitter), typeof(string), typeof(PersonDetailPage), default(string)); + + public static readonly BindableProperty FacebookProperty = BindableProperty.Create(nameof(Facebook), typeof(string), typeof(PersonDetailPage), default(string)); + + public static readonly BindableProperty WebsiteProperty = BindableProperty.Create(nameof(Website), typeof(string), typeof(PersonDetailPage), default(string)); + + public PersonDetailPage() + { + SetBinding(DisplayNameProperty, new DataSourceBinding(nameof(DisplayName))); + SetBinding(PhoneNumberProperty, new DataSourceBinding(nameof(PhoneNumber))); + SetBinding(ImageProperty, new DataSourceBinding(nameof(Image))); + SetBinding(EmailProperty, new DataSourceBinding(nameof(Email))); + SetBinding(AddressProperty, new DataSourceBinding(nameof(Address))); + SetBinding(EmployerProperty, new DataSourceBinding(nameof(Employer))); + SetBinding(TwitterProperty, new DataSourceBinding(nameof(Twitter))); + SetBinding(FacebookProperty, new DataSourceBinding(nameof(Facebook))); + SetBinding(WebsiteProperty, new DataSourceBinding(nameof(Website))); + } + + public string Address + { + get { return (string)GetValue(AddressProperty); } + set { SetValue(AddressProperty, value); } + } + + public string DisplayName + { + get { return (string)GetValue(DisplayNameProperty); } + set { SetValue(DisplayNameProperty, value); } + } + + public string Email + { + get { return (string)GetValue(EmailProperty); } + set { SetValue(EmailProperty, value); } + } + + public string Employer + { + get { return (string)GetValue(EmployerProperty); } + set { SetValue(EmployerProperty, value); } + } + + public string Facebook + { + get { return (string)GetValue(FacebookProperty); } + set { SetValue(FacebookProperty, value); } + } + + public ImageSource Image + { + get { return (ImageSource)GetValue(ImageProperty); } + set { SetValue(ImageProperty, value); } + } + + public string PhoneNumber + { + get { return (string)GetValue(PhoneNumberProperty); } + set { SetValue(PhoneNumberProperty, value); } + } + + public string Twitter + { + get { return (string)GetValue(TwitterProperty); } + set { SetValue(TwitterProperty, value); } + } + + public string Website + { + get { return (string)GetValue(WebsiteProperty); } + set { SetValue(WebsiteProperty, value); } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Pages/Properties/AssemblyInfo.cs b/Xamarin.Forms.Pages/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..a7150fb3 --- /dev/null +++ b/Xamarin.Forms.Pages/Properties/AssemblyInfo.cs @@ -0,0 +1,30 @@ +using System.Reflection; +using System.Resources; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. + +[assembly: AssemblyTitle("Xamarin.Forms.Pages")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Xamarin.Forms.Pages")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] + +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")]
\ No newline at end of file diff --git a/Xamarin.Forms.Pages/StringJsonSource.cs b/Xamarin.Forms.Pages/StringJsonSource.cs new file mode 100644 index 00000000..79e59e62 --- /dev/null +++ b/Xamarin.Forms.Pages/StringJsonSource.cs @@ -0,0 +1,20 @@ +using System.Threading.Tasks; + +namespace Xamarin.Forms.Pages +{ + public class StringJsonSource : JsonSource + { + public static readonly BindableProperty JsonProperty = BindableProperty.Create(nameof(Json), typeof(string), typeof(StringJsonSource), null); + + public string Json + { + get { return (string)GetValue(JsonProperty); } + set { SetValue(JsonProperty, value); } + } + + public override Task<string> GetJson() + { + return Task.FromResult(Json); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Pages/UriJsonSource.cs b/Xamarin.Forms.Pages/UriJsonSource.cs new file mode 100644 index 00000000..d732e25c --- /dev/null +++ b/Xamarin.Forms.Pages/UriJsonSource.cs @@ -0,0 +1,34 @@ +using System; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Xamarin.Forms.Pages +{ + public class UriJsonSource : JsonSource + { + public static readonly BindableProperty UriProperty = BindableProperty.Create(nameof(Uri), typeof(Uri), + typeof(UriJsonSource), null); + + public Uri Uri + { + get { return (Uri)GetValue(UriProperty); } + set { SetValue(UriProperty, value); } + } + + public override async Task<string> GetJson() + { + var webClient = new HttpClient(new ModernHttpClient.NativeMessageHandler()); + try + { + string json = await webClient.GetStringAsync(Uri); + return json; + } + catch + { + return null; + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Pages/Xamarin.Forms.Pages.csproj b/Xamarin.Forms.Pages/Xamarin.Forms.Pages.csproj new file mode 100644 index 00000000..15c8e1cd --- /dev/null +++ b/Xamarin.Forms.Pages/Xamarin.Forms.Pages.csproj @@ -0,0 +1,105 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> + <PropertyGroup> + <MinimumVisualStudioVersion>10.0</MinimumVisualStudioVersion> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{D6133DBD-6C60-4BD5-BEA2-07E0A3927C31}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>Xamarin.Forms.Pages</RootNamespace> + <AssemblyName>Xamarin.Forms.Pages</AssemblyName> + <DefaultLanguage>en-US</DefaultLanguage> + <FileAlignment>512</FileAlignment> + <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> + <TargetFrameworkProfile>Profile259</TargetFrameworkProfile> + <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <ItemGroup> + <!-- A reference to the entire .NET Framework is automatically included --> + <ProjectReference Include="..\Xamarin.Forms.Core\Xamarin.Forms.Core.csproj"> + <Project>{57b8b73d-c3b5-4c42-869e-7b2f17d354ac}</Project> + <Name>Xamarin.Forms.Core</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <Compile Include="BaseDataSource.cs" /> + <Compile Include="CardView.cs" /> + <Compile Include="CompoundCollection.cs" /> + <Compile Include="DataItem.cs" /> + <Compile Include="DataPage.cs" /> + <Compile Include="DataSourceBinding.cs" /> + <Compile Include="DataSourceBindingExtension.cs" /> + <Compile Include="DataSourceList.cs" /> + <Compile Include="DataView.cs" /> + <Compile Include="DirectoryPage.cs" /> + <Compile Include="HeroImage.cs" /> + <Compile Include="IDataItem.cs" /> + <Compile Include="IDataSource.cs" /> + <Compile Include="IDataSourceProvider.cs" /> + <Compile Include="JsonSource.cs" /> + <Compile Include="JsonDataSource.cs" /> + <Compile Include="JsonSourceConverter.cs" /> + <Compile Include="ListDataPage.cs" /> + <Compile Include="ListItemControl.cs" /> + <Compile Include="PersonDetailPage.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="StringJsonSource.cs" /> + <Compile Include="UriJsonSource.cs" /> + </ItemGroup> + <ItemGroup> + <Reference Include="ModernHttpClient, Version=2.4.2.0, Culture=neutral, processorArchitecture=MSIL"> + <HintPath>..\packages\modernhttpclient.2.4.2\lib\Portable-Net45+WinRT45+WP8+WPA81\ModernHttpClient.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Net.Http, Version=1.5.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\packages\Microsoft.Net.Http.2.2.29\lib\portable-net40+sl4+win8+wp71+wpa81\System.Net.Http.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Net.Http.Extensions, Version=1.5.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\packages\Microsoft.Net.Http.2.2.29\lib\portable-net40+sl4+win8+wp71+wpa81\System.Net.Http.Extensions.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="System.Net.Http.Primitives, Version=1.5.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>..\packages\Microsoft.Net.Http.2.2.29\lib\portable-net40+sl4+win8+wp71+wpa81\System.Net.Http.Primitives.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Newtonsoft.Json"> + <HintPath>..\packages\Newtonsoft.Json.8.0.3\lib\portable-net45+wp80+win8+wpa81+dnxcore50\Newtonsoft.Json.dll</HintPath> + </Reference> + </ItemGroup> + <ItemGroup> + <None Include="packages.config" /> + </ItemGroup> + <Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" /> + <Import Project="..\packages\Microsoft.Bcl.Build.1.0.14\tools\Microsoft.Bcl.Build.targets" Condition="Exists('..\packages\Microsoft.Bcl.Build.1.0.14\tools\Microsoft.Bcl.Build.targets')" /> + <Target Name="EnsureBclBuildImported" BeforeTargets="BeforeBuild" Condition="'$(BclBuildImported)' == ''"> + <Error Condition="!Exists('..\packages\Microsoft.Bcl.Build.1.0.14\tools\Microsoft.Bcl.Build.targets')" Text="This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=317567." HelpKeyword="BCLBUILD2001" /> + <Error Condition="Exists('..\packages\Microsoft.Bcl.Build.1.0.14\tools\Microsoft.Bcl.Build.targets')" Text="The build restored NuGet packages. Build the project again to include these packages in the build. For more information, see http://go.microsoft.com/fwlink/?LinkID=317568." HelpKeyword="BCLBUILD2002" /> + </Target> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project>
\ No newline at end of file diff --git a/Xamarin.Forms.Pages/packages.config b/Xamarin.Forms.Pages/packages.config new file mode 100644 index 00000000..1861a33d --- /dev/null +++ b/Xamarin.Forms.Pages/packages.config @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="Microsoft.Bcl" version="1.1.10" targetFramework="portable45-net45+win8+wp8+wpa81" /> + <package id="Microsoft.Bcl.Build" version="1.0.14" targetFramework="portable45-net45+win8+wp8+wpa81" /> + <package id="Microsoft.Net.Http" version="2.2.29" targetFramework="portable45-net45+win8+wp8+wpa81" /> + <package id="modernhttpclient" version="2.4.2" targetFramework="portable45-net45+win8+wp8+wpa81" /> + <package id="Newtonsoft.Json" version="8.0.3" targetFramework="portable-net45+win+wpa81+wp80+MonoTouch10+MonoAndroid10+xamarinmac20+xamarintvos10+xamarinwatchos10+xamarinios10" /> +</packages>
\ No newline at end of file |