From 5907152c50ee2c658b266f2804e6b383bb15a6f1 Mon Sep 17 00:00:00 2001 From: Jason Smith Date: Sun, 24 Apr 2016 12:25:26 -0400 Subject: Evolve feature branch (#117) * Initial import of evolve features * [Android] Add Xamarin.Forms.Platform.Android.AppLinks project * [iOS] Fix issues with c# 6 features on iOS AppLinks * Added naive stanza to update-docs-windows.bat to produce Pages docs. Not tested. (#69) * Update packages * Add AppLinks android nuspec and fix linker issues * Fix build * Fix nusepc * Fix nuspec * Update android support nugets to 23.2.1 * Update Xamarin.UITest * Add CardView * [iOS] Fix app link for CoreSpotlight * [Android] Update AppLinks android support libs * Add Newtonsoft.Json dependency to nuspec * Fix NRE when setting ControlTemplate to null * Move to ModernHttpClient for download * Try fix build * Preserve android app links * Fix margin issue * General coding and simple fixes --- Xamarin.Forms.Pages/BaseDataSource.cs | 113 +++++++++++ Xamarin.Forms.Pages/CardView.cs | 37 ++++ Xamarin.Forms.Pages/CompoundCollection.cs | 223 +++++++++++++++++++++ Xamarin.Forms.Pages/DataItem.cs | 86 ++++++++ Xamarin.Forms.Pages/DataPage.cs | 76 +++++++ Xamarin.Forms.Pages/DataSourceBinding.cs | 157 +++++++++++++++ Xamarin.Forms.Pages/DataSourceBindingExtension.cs | 35 ++++ Xamarin.Forms.Pages/DataSourceList.cs | 233 ++++++++++++++++++++++ Xamarin.Forms.Pages/DataView.cs | 76 +++++++ Xamarin.Forms.Pages/DirectoryPage.cs | 13 ++ Xamarin.Forms.Pages/HeroImage.cs | 37 ++++ Xamarin.Forms.Pages/IDataItem.cs | 9 + Xamarin.Forms.Pages/IDataSource.cs | 18 ++ Xamarin.Forms.Pages/IDataSourceProvider.cs | 11 + Xamarin.Forms.Pages/JsonDataSource.cs | 147 ++++++++++++++ Xamarin.Forms.Pages/JsonSource.cs | 25 +++ Xamarin.Forms.Pages/JsonSourceConverter.cs | 22 ++ Xamarin.Forms.Pages/ListDataPage.cs | 55 +++++ Xamarin.Forms.Pages/ListItemControl.cs | 45 +++++ Xamarin.Forms.Pages/PersonDetailPage.cs | 90 +++++++++ Xamarin.Forms.Pages/Properties/AssemblyInfo.cs | 30 +++ Xamarin.Forms.Pages/StringJsonSource.cs | 20 ++ Xamarin.Forms.Pages/UriJsonSource.cs | 34 ++++ Xamarin.Forms.Pages/Xamarin.Forms.Pages.csproj | 105 ++++++++++ Xamarin.Forms.Pages/packages.config | 8 + 25 files changed, 1705 insertions(+) create mode 100644 Xamarin.Forms.Pages/BaseDataSource.cs create mode 100644 Xamarin.Forms.Pages/CardView.cs create mode 100644 Xamarin.Forms.Pages/CompoundCollection.cs create mode 100644 Xamarin.Forms.Pages/DataItem.cs create mode 100644 Xamarin.Forms.Pages/DataPage.cs create mode 100644 Xamarin.Forms.Pages/DataSourceBinding.cs create mode 100644 Xamarin.Forms.Pages/DataSourceBindingExtension.cs create mode 100644 Xamarin.Forms.Pages/DataSourceList.cs create mode 100644 Xamarin.Forms.Pages/DataView.cs create mode 100644 Xamarin.Forms.Pages/DirectoryPage.cs create mode 100644 Xamarin.Forms.Pages/HeroImage.cs create mode 100644 Xamarin.Forms.Pages/IDataItem.cs create mode 100644 Xamarin.Forms.Pages/IDataSource.cs create mode 100644 Xamarin.Forms.Pages/IDataSourceProvider.cs create mode 100644 Xamarin.Forms.Pages/JsonDataSource.cs create mode 100644 Xamarin.Forms.Pages/JsonSource.cs create mode 100644 Xamarin.Forms.Pages/JsonSourceConverter.cs create mode 100644 Xamarin.Forms.Pages/ListDataPage.cs create mode 100644 Xamarin.Forms.Pages/ListItemControl.cs create mode 100644 Xamarin.Forms.Pages/PersonDetailPage.cs create mode 100644 Xamarin.Forms.Pages/Properties/AssemblyInfo.cs create mode 100644 Xamarin.Forms.Pages/StringJsonSource.cs create mode 100644 Xamarin.Forms.Pages/UriJsonSource.cs create mode 100644 Xamarin.Forms.Pages/Xamarin.Forms.Pages.csproj create mode 100644 Xamarin.Forms.Pages/packages.config (limited to 'Xamarin.Forms.Pages') 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 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 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> 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 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), typeof(CompoundCollection), default(IReadOnlyList), + propertyChanged: OnMainListPropertyChanged); + + readonly ObservableCollection _appendList = new ObservableCollection(); + + readonly ObservableCollection _prependList = new ObservableCollection(); + + public CompoundCollection() + { + _prependList.CollectionChanged += OnPrependCollectionChanged; + _appendList.CollectionChanged += OnAppendCollectionChanged; + } + + public IList AppendList => _appendList; + + public IReadOnlyList MainList + { + get { return (IReadOnlyList)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 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 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), typeof(DataPage), default(IEnumerable)); + + 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 _maskedKeys = new HashSet(); + + public DataPage() + { + SetBinding(DataProperty, new Binding("DataSource.Data", source: this)); + } + + public IEnumerable Data + { + get { return (IEnumerable)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 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 + { + 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).ProvideValue(serviceProvider); + } + + BindingBase IMarkupExtension.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, IReadOnlyList, INotifyCollectionChanged + { + readonly List _maskedIndexes = new List(); // Indices + readonly HashSet _maskedKeys = new HashSet(); + IList _mainList; + + public IList 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 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 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), typeof(DataView), default(IEnumerable)); + + 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 _maskedKeys = new HashSet(); + + public DataView() + { + SetBinding(DataProperty, new Binding("DataSource.Data", source: this)); + } + + public IEnumerable Data + { + get { return (IEnumerable)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 Data { get; } + + bool IsLoading { get; } + + object this[string key] { get; set; } + + IEnumerable 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 _dataItems = new ObservableCollection(); + 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> 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 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 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 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 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 @@ + + + + + 10.0 + Debug + AnyCPU + {D6133DBD-6C60-4BD5-BEA2-07E0A3927C31} + Library + Properties + Xamarin.Forms.Pages + Xamarin.Forms.Pages + en-US + 512 + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Profile259 + v4.5 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + {57b8b73d-c3b5-4c42-869e-7b2f17d354ac} + Xamarin.Forms.Core + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ..\packages\modernhttpclient.2.4.2\lib\Portable-Net45+WinRT45+WP8+WPA81\ModernHttpClient.dll + True + + + ..\packages\Microsoft.Net.Http.2.2.29\lib\portable-net40+sl4+win8+wp71+wpa81\System.Net.Http.dll + True + + + ..\packages\Microsoft.Net.Http.2.2.29\lib\portable-net40+sl4+win8+wp71+wpa81\System.Net.Http.Extensions.dll + True + + + ..\packages\Microsoft.Net.Http.2.2.29\lib\portable-net40+sl4+win8+wp71+wpa81\System.Net.Http.Primitives.dll + True + + + ..\packages\Newtonsoft.Json.8.0.3\lib\portable-net45+wp80+win8+wpa81+dnxcore50\Newtonsoft.Json.dll + + + + + + + + + + + + + \ 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 @@ + + + + + + + + \ No newline at end of file -- cgit v1.2.3