summaryrefslogtreecommitdiff
path: root/Xamarin.Forms.Pages
diff options
context:
space:
mode:
authorJason Smith <jason.smith@xamarin.com>2016-04-24 12:25:26 -0400
committerRui Marinho <me@ruimarinho.net>2016-04-24 12:25:26 -0400
commit5907152c50ee2c658b266f2804e6b383bb15a6f1 (patch)
tree9beb907623359723456c5c03b08922bebc4f41f3 /Xamarin.Forms.Pages
parentfeac1ba3ed6df5e27b3fa2076bd15c190cbacd1c (diff)
downloadxamarin-forms-5907152c50ee2c658b266f2804e6b383bb15a6f1.tar.gz
xamarin-forms-5907152c50ee2c658b266f2804e6b383bb15a6f1.tar.bz2
xamarin-forms-5907152c50ee2c658b266f2804e6b383bb15a6f1.zip
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
Diffstat (limited to 'Xamarin.Forms.Pages')
-rw-r--r--Xamarin.Forms.Pages/BaseDataSource.cs113
-rw-r--r--Xamarin.Forms.Pages/CardView.cs37
-rw-r--r--Xamarin.Forms.Pages/CompoundCollection.cs223
-rw-r--r--Xamarin.Forms.Pages/DataItem.cs86
-rw-r--r--Xamarin.Forms.Pages/DataPage.cs76
-rw-r--r--Xamarin.Forms.Pages/DataSourceBinding.cs157
-rw-r--r--Xamarin.Forms.Pages/DataSourceBindingExtension.cs35
-rw-r--r--Xamarin.Forms.Pages/DataSourceList.cs233
-rw-r--r--Xamarin.Forms.Pages/DataView.cs76
-rw-r--r--Xamarin.Forms.Pages/DirectoryPage.cs13
-rw-r--r--Xamarin.Forms.Pages/HeroImage.cs37
-rw-r--r--Xamarin.Forms.Pages/IDataItem.cs9
-rw-r--r--Xamarin.Forms.Pages/IDataSource.cs18
-rw-r--r--Xamarin.Forms.Pages/IDataSourceProvider.cs11
-rw-r--r--Xamarin.Forms.Pages/JsonDataSource.cs147
-rw-r--r--Xamarin.Forms.Pages/JsonSource.cs25
-rw-r--r--Xamarin.Forms.Pages/JsonSourceConverter.cs22
-rw-r--r--Xamarin.Forms.Pages/ListDataPage.cs55
-rw-r--r--Xamarin.Forms.Pages/ListItemControl.cs45
-rw-r--r--Xamarin.Forms.Pages/PersonDetailPage.cs90
-rw-r--r--Xamarin.Forms.Pages/Properties/AssemblyInfo.cs30
-rw-r--r--Xamarin.Forms.Pages/StringJsonSource.cs20
-rw-r--r--Xamarin.Forms.Pages/UriJsonSource.cs34
-rw-r--r--Xamarin.Forms.Pages/Xamarin.Forms.Pages.csproj105
-rw-r--r--Xamarin.Forms.Pages/packages.config8
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