Prechádzať zdrojové kódy

Merge remote-tracking branch 'refs/remotes/origin/2016' into app-service-helpers

# Conflicts:

#	HandsOnLab/Finish/DevDaysSpeakers/DevDaysSpeakers/ViewModel/SpeakersViewModel.cs
James Montemagno 9 rokov pred
rodič
commit
d7adf8a309
30 zmenil súbory, kde vykonal 779 pridanie a 357 odobranie
  1. 5 0
      Demos/README.md
  2. 38 0
      HandsOnLab/Finish/DevDaysSpeakers/DevDaysSpeakers.UWP/App.xaml
  3. 1 1
      HandsOnLab/Finish/DevDaysSpeakers/DevDaysSpeakers/View/SpeakersPage.xaml
  4. 2 24
      HandsOnLab/Finish/DevDaysSpeakers/DevDaysSpeakers/View/SpeakersPage.xaml.cs
  5. 22 6
      HandsOnLab/Finish/DevDaysSpeakers/DevDaysSpeakers/ViewModel/SpeakersViewModel.cs
  6. 553 1
      HandsOnLab/README.md
  7. 4 1
      HandsOnLab/Start/DevDaysSpeakers.sln
  8. 2 2
      HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers.Droid/DevDaysSpeakers.Droid.csproj
  9. 1 1
      HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers.Droid/MainActivity.cs
  10. 4 3
      HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers.Droid/Properties/AndroidManifest.xml
  11. 4 0
      HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers.Droid/app.config
  12. 1 1
      HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers.Droid/packages.config
  13. 38 0
      HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers.UWP/App.xaml
  14. 1 0
      HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers.UWP/project.json
  15. 27 26
      HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers.UWP/project.lock.json
  16. 2 2
      HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers.iOS/DevDaysSpeakers.iOS.csproj
  17. 2 4
      HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers.iOS/Entitlements.plist
  18. 48 49
      HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers.iOS/Info.plist
  19. 4 0
      HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers.iOS/app.config
  20. 1 1
      HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers.iOS/packages.config
  21. 0 4
      HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers/App.cs
  22. 7 3
      HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers/DevDaysSpeakers.csproj
  23. 1 6
      HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers/Model/Speaker.cs
  24. 1 13
      HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers/View/DetailsPage.xaml
  25. 5 18
      HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers/View/DetailsPage.xaml.cs
  26. 0 16
      HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers/View/SpeakersPage.xaml
  27. 2 40
      HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers/View/SpeakersPage.xaml.cs
  28. 0 65
      HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers/ViewModel/AzureStore.cs
  29. 1 69
      HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers/ViewModel/SpeakersViewModel.cs
  30. 2 1
      HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers/packages.config

+ 5 - 0
Demos/README.md

@@ -0,0 +1,5 @@
+## Xamrin Dev Days Demos
+
+Feel free to browse through the demo apps that were presenter here at Xamarin Dev Days!
+
+If you have any trouble please up an GitHub issue.

+ 38 - 0
HandsOnLab/Finish/DevDaysSpeakers/DevDaysSpeakers.UWP/App.xaml

@@ -4,5 +4,43 @@
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     xmlns:local="using:DevDaysSpeakers.UWP"
     RequestedTheme="Light">
+    <Application.Resources>
+        <DataTemplate x:Key="ImageCell">
+            <Grid Padding="10">
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="Auto" />
+                    <ColumnDefinition />
+                </Grid.ColumnDefinitions>
 
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto" />
+                    <RowDefinition Height="Auto" />
+                </Grid.RowDefinitions>
+
+                <Image Grid.Column="0" Grid.RowSpan="2"
+                       MaxHeight="66"
+                       MaxWidth="66"
+				DataContext="{Binding ImageSource, Converter={StaticResource ImageConverter}}"
+				Source="{Binding Value}"
+				VerticalAlignment="Center" />
+
+                <TextBlock Grid.Column="1" Grid.Row="0"
+                Margin="10,0,0,0"
+				Text="{Binding Text}"
+				Style="{ThemeResource BaseTextBlockStyle}"
+				Visibility="{Binding Text,RelativeSource={RelativeSource Self}, Converter={StaticResource CollapseWhenEmpty}}"
+				Foreground="{Binding TextColor, Converter={StaticResource ColorConverter}, ConverterParameter=DefaultTextForegroundThemeBrush}" />
+
+                <TextBlock Grid.Column="1" Grid.Row="1"
+                Margin="10,0,0,0"
+				Text="{Binding Detail}"
+				Style="{ThemeResource BodyTextBlockStyle}"
+				Visibility="{Binding Text,RelativeSource={RelativeSource Self}, Converter={StaticResource CollapseWhenEmpty}}"
+				Foreground="{Binding DetailColor, Converter={StaticResource ColorConverter}, ConverterParameter=DefaultTextForegroundThemeBrush}"
+				x:Name="detail" />
+            </Grid>
+        </DataTemplate>
+
+
+    </Application.Resources>
 </Application>

+ 1 - 1
HandsOnLab/Finish/DevDaysSpeakers/DevDaysSpeakers/View/SpeakersPage.xaml

@@ -5,7 +5,7 @@
              Title="Speakers">
 
   <StackLayout Spacing="0">
-    <Button Text="Sync Speakers" x:Name="ButtonSpeakers"/>
+    <Button Text="Sync Speakers" Command="{Binding GetSpeakersCommand}"/>
     
     <ActivityIndicator IsRunning="{Binding IsBusy}" IsVisible="{Binding IsBusy}"/>
 

+ 2 - 24
HandsOnLab/Finish/DevDaysSpeakers/DevDaysSpeakers/View/SpeakersPage.xaml.cs

@@ -23,8 +23,7 @@ namespace DevDaysSpeakers.View
             vm = new SpeakersViewModel(client);
 
             BindingContext = vm;
-
-            ButtonSpeakers.Clicked += ButtonSpeakers_Clicked;
+            
 
             ListViewSpeakers.ItemSelected += ListViewSpeakers_ItemSelected;
 
@@ -42,27 +41,6 @@ namespace DevDaysSpeakers.View
 
         }
 
-        private async void ButtonSpeakers_Clicked(object sender, EventArgs e)
-        {
-            Exception ex = null;
-
-            try
-            {
-                ButtonSpeakers.IsEnabled = false;
-
-                await vm.GetSpeakers();
-            }
-            catch(Exception error)
-            {
-                ex = error;
-            }
-            finally
-            {
-                ButtonSpeakers.IsEnabled = true;
-            }
-
-            if (ex != null)
-                await DisplayAlert("Error!", ex.Message, "OK");
-        }
+       
     }
 }

+ 22 - 6
HandsOnLab/Finish/DevDaysSpeakers/DevDaysSpeakers/ViewModel/SpeakersViewModel.cs

@@ -6,24 +6,32 @@ using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
 
+using Xamarin.Forms;
 using DevDaysSpeakers.Model;
 using System.Net.Http;
 using Newtonsoft.Json;
 using System.Collections.ObjectModel;
 using AppServiceHelpers.Abstractions;
 using AppServiceHelpers;
+using System.Runtime.CompilerServices;
 
 namespace DevDaysSpeakers.ViewModel
 {
     public class SpeakersViewModel : INotifyPropertyChanged
     {
         public ObservableCollection<Speaker> Speakers { get; set; }
+        public Command GetSpeakersCommand { get; set; }
 
         ITableDataStore<Speaker> table;
         public SpeakersViewModel(IEasyMobileServiceClient client)
         {
-            Speakers = new ObservableCollection<Speaker>();
             table = client.Table<Speaker>();
+
+            Speakers = new ObservableCollection<Speaker>();
+            GetSpeakersCommand = new Command(
+                async () => await GetSpeakers(),
+                () => !IsBusy);
+
         }
 
         bool busy;
@@ -34,15 +42,20 @@ namespace DevDaysSpeakers.ViewModel
             set
             {
                 busy = value;
-                OnPropertyChanged("IsBusy");
+                OnPropertyChanged();
+
+                GetSpeakersCommand.ChangeCanExecute();
             }
         }
 
-        public async Task GetSpeakers()
+
+
+        async Task GetSpeakers()
         {
             if (IsBusy)
                 return;
 
+            Exception error = null;
             try
             {
                 IsBusy = true;
@@ -53,20 +66,23 @@ namespace DevDaysSpeakers.ViewModel
                 foreach (var item in items)
                     Speakers.Add(item);
             }
-            catch(Exception ex)
+            catch (Exception ex)
             {
                 Debug.WriteLine("Error: " + ex);
-                throw;
+                error = ex;
             }
             finally
             {
                 IsBusy = false;
             }
+
+            if (error != null)
+                await Application.Current.MainPage.DisplayAlert("Error!", error.Message, "OK");
         }
 
         public event PropertyChangedEventHandler PropertyChanged;
 
-        void OnPropertyChanged(string name)
+        void OnPropertyChanged([CallerMemberName] string name = null)
         {
             var changed = PropertyChanged;
 

+ 553 - 1
HandsOnLab/README.md

@@ -1,3 +1,555 @@
 ## Xamrin Dev Days Hands On Lab
 
-Coming soon.
+Today, we will be building a cloud connected [Xamarin.Forms](http://xamarin.com/forms) application that will display a list of speakers at Xamarin Dev Days and show their details. We will start by building some business logic backend that pulls down json from a RESTful endpoint and then we will connect it to an Azure Mobile App backend in just a few lines of code.
+
+
+### Get Started
+
+Open **Start/DevDaysSpeakers.sln**
+
+This solution contains 4 projects
+
+* DevDaysSpeakers (Portable) - Portable Class Library that will have all shared code (model, views, and view models).
+* DevDaysSpeakers.Droid - Xamarin.Android application
+* DevDaysSpeakers.iOS - Xamarin.iOS application
+* DevDaysSpeakers.UWP - Windows 10 UWP application (can only be run from VS 2015 on Windows 10)
+
+![Solution](http://content.screencast.com/users/JamesMontemagno/folders/Jing/media/44f4caa9-efb9-4405-95d4-7341608e1c0a/Portable.png)
+
+The **DevDaysSpeakers (Portable)** also has blank code files and XAML pages that we will use during the Hands on Lab.
+
+#### NuGet Restore
+
+All projects have the required NuGet packages already installed, so there will be no need to install additional packages during the Hands on Lab. The first thing that we must do is Restore all of the NuGet packages from the internet.
+
+This can be done by **Right-clicking** on the **Solution** and clicking on **Restore NuGet packages...**
+
+![Restore NuGets](http://content.screencast.com/users/JamesMontemagno/folders/Jing/media/a31a6bff-b45d-4c60-a602-1359f984e80b/2016-07-11_1328.png)
+
+### Model
+
+We will be pulling down information about spearks. Open the **DevDaysSpeakers/Model/Speaker.cs** file and add the following properties inside of the **Speaker** class:
+
+```csharp
+public string Id { get; set; }
+public string Name { get; set; }
+public string Description { get; set; }
+public string Website { get; set; }
+public string Title { get; set; }
+public string Avatar { get; set; }
+```
+
+### View Model
+
+The **SpeakersViewModel.cs** will provide all of the functionality for how our main Xamarin.Forms view will display data. It will consist of a list of speakers and a method that can be called to get the speakers from the server. It will also contain a boolean flag that will indicate if the we are getting data in a background task.
+
+#### Implementing INotifyPropertyChanged
+
+*INotifyPropertyChanged* is the key data binding in MVVM Frameworks. It is an interface that provides a contract that the user interface can subscribe to for notifications when the code in the code behind changes.
+
+Update:
+
+```csharp
+public class SpeakersViewModel
+{
+
+}
+```
+
+to
+
+```csharp
+public class SpeakersViewModel : INotifyPropertyChanged
+{
+
+}
+```
+
+Simply right click and tap **Implement Interface**, which will add the following line of code:
+
+```csharp
+public event PropertyChangedEventHandler PropertyChanged;
+```
+
+This is the method that we will invoke whenever a property changes. This means we need a helper method to implement called **OnPropertyChanged**:
+
+##### C# 6 (Visual Studio 2015 or Xamarin Studio on Mac)
+```csharp
+void OnPropertyChanged([CallerMemberName] string name = null) =>
+    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
+```
+
+
+##### C# 5 (Visual Studio 2012 or 2013)
+```csharp
+void OnPropertyChanged([CallerMemberName] string name = null)
+{
+    var changed = PropertyChanged;
+
+            if (changed == null)
+                return;
+
+            changed.Invoke(this, new PropertyChangedEventArgs(name));
+}
+```
+
+Now, we can call **OnPropertyChanged();** whenever a property updates. Let's create our first bindable property now.
+
+#### IsBusy
+
+In the class we will want to create a backing field and auto-properties for getting and setting a boolean.
+
+First, create the backing field:
+
+```csharp
+bool busy;
+```
+
+Next, let's create the auto-property:
+
+```csharp
+public bool IsBusy
+{
+    get { return busy; }
+    set
+    {
+        busy = value;
+        OnPropertyChanged();
+    }
+}
+```
+
+Notice that we call **OnPropertyChanged();** so Xamarin.Forms can be notified automatically.
+
+#### ObservableCollection of Speaker
+
+We will use an ObservableCollection<Speaker> that will be cleared and then loaded with speakers. We will use an **ObservableCollection** because it has built in support for **CollectionChanged** events when we Add or Remove from it. This is very nice so we don't have to call **OnPropertyChanged** each time.
+
+In the class above the constructor simply create an autoproperty:
+
+```csharp
+public ObservableCollection<Speaker> Speakers { get; set; }
+```
+
+Inside of the constructor create a new instance of the ObservableCollection so the constructor looks like:
+
+```csharp
+public SpeakersViewModel()
+{
+    Speakers = new ObservableCollection<Speaker>();
+}
+```
+
+#### GetSpeakers Method
+
+We are now set to create our method called GetSpeakers, which will call to get all of the speaker data from the internet. We will first implement this with a simply HTTP request, but later on update it to grab and sync the data from Azure!
+
+First, create a method called **GetSpeakers** which is of type *async Task* (it is a Task because it is using Async methods):
+
+```csharp
+async Task GetSpeakers()
+{
+
+}
+```
+The following code will be written INSIDE of this method:
+
+First is to check if we are already grabbind data:
+
+```csharp
+async Task GetSpeakers()
+{
+    if(IsBusy)
+        return;
+}
+```
+
+Next we will create some scaffolding for try/catch/finally blocks:
+
+```csharp
+async Task GetSpeakers()
+{
+    if (IsBusy)
+        return;
+
+    Exception error = null;
+    try
+    {
+        IsBusy = true;
+
+    }
+    catch (Exception ex)
+    {
+        error = ex;
+    }
+    finally
+    {
+       IsBusy = false;
+    }
+
+}
+```
+
+Notice, that we set IsBusy to true and then false when we start to call to the server and when we finish.
+
+Now, we will use *HttpClient* to grab a the json from the server inside of the **try** block.
+
+ ```csharp
+using(var client = new HttpClient())
+{
+    //grab json from server
+    var json = await client.GetStringAsync("http://demo4404797.mockable.io/speakers");
+} 
+```
+
+Still inside of the **using**, we will Deserialize the json and turn it into a list of Speakers with Json.NET:
+
+```csharp
+var items = JsonConvert.DeserializeObject<List<Speaker>>(json);
+```
+
+Still inside of the **using**, we can will clear the speakers and then load them into the ObservableCollection:
+
+```csharp
+Speakers.Clear();
+foreach (var item in items)
+    Speakers.Add(item);
+```
+If anything goes wrong the **catch** will save out the exception and AFTER the finally block we can pop up an alert:
+
+```csharp
+if (error != null)
+    await Application.Current.MainPage.DisplayAlert("Error!", error.Message, "OK");
+```
+
+The completed code should look like:
+
+```csharp
+async Task GetSpeakers()
+{
+    if (IsBusy)
+        return;
+
+    Exception error = null;
+    try
+    {
+        IsBusy = true;
+        
+        using(var client = new HttpClient())
+        {
+            //grab json from server
+            var json = await client.GetStringAsync("http://demo4404797.mockable.io/speakers");
+            
+            //Deserialize json
+            var items = JsonConvert.DeserializeObject<List<Speaker>>(json);
+            
+            //Load speakers into list
+            Speakers.Clear();
+            foreach (var item in items)
+                Speakers.Add(item);
+        } 
+    }
+    catch (Exception ex)
+    {
+        Debug.WriteLine("Error: " + ex);
+        error = ex;
+    }
+    finally
+    {
+        IsBusy = false;
+    }
+
+    if (error != null)
+        await Application.Current.MainPage.DisplayAlert("Error!", error.Message, "OK");
+}
+```
+
+Our main method for gettering data is now complete!
+
+#### GetSpeakers Command
+
+Intead of invoking this method directly, we will expose it with a **Command**. A Command has an interface that knows what method to invoke and has an optional way of describing if the Command is enabled.
+
+Where we created our ObservableCollection<Speaker> Speakers {get;set;} create a new Command called **GetSpeakersCommand**:
+
+```csharp
+public Command GetSpeakersCommand { get; set; }
+```
+
+Inside of the **SpeakersViewModel()** constructor we can create the GetSpeakersCommand and assign it a method to use. We can also pass in an enabled flag leveraging our IsBusy:
+
+```csharp
+GetSpeakersCommand = new Command(
+                async () => await GetSpeakers(),
+                () => !IsBusy);
+```
+
+The only modification that we will have to make is when we set the IsBusy property, as we will want to re-evaluate the enabled function that we created. In the **set** of **IsBusy** simply invoke the **ChangeCanExecute** method on the **GetSpeakersCommand** such as:
+
+```csharp
+set
+{
+    busy = value;
+    OnPropertyChanged();
+    //Update the can execute
+    GetSpeakersCommand.ChangeCanExecute();
+}
+```
+
+## The User Interface!!!
+It is now finally time to build out our first Xamarin.Forms user interface in the *View/SpeakersPage.xaml**
+
+### SpeakersPage.xaml
+
+For the first page of the app we will add a few controls onto the page that are stacked vertically. We can use a StackLayout to do this. Inbetween the ContentPage add the following:
+
+```xml
+ <StackLayout Spacing="0">
+
+  </StackLayout>
+```
+
+This will be the base where all of the child controls will go and will have no space inbetween them.
+
+Next, let's add a Button that has a binding to the **GetSpeakersCommand** that we created:
+
+```xml
+<Button Text="Sync Speakers" Command="{Binding GetSpeakersCommand}"/>
+```
+
+Under the button we can display a loading bar when we are gathering data form the server. We can use an ActivityIndicator to do this and bind to the IsBusy property we created:
+
+```xml
+<ActivityIndicator IsRunning="{Binding IsBusy}" IsVisible="{Binding IsBusy}"/>
+```
+
+We will use a ListView that binds to the Speakers collection to display all of the items. We can use a special property called *x:Name=""* to name any control:
+
+```xml
+<ListView x:Name="ListViewSpeakers"
+              ItemsSource="{Binding Speakers}">
+        <!--Add ItemTemplate Here-->
+</ListView>
+```
+
+We still need to describe what each item looks like, and to do so, we can use an ItemTemplate that has a DataTemplate with a specific View inside of it. Xamarin.Forms contains a few default Cells that we can use, and we will use the **ImageCell** that has an image and two rows of text
+
+Replace <!--Add ItemTemplate Here--> with: 
+
+```xml
+<ListView.ItemTemplate>
+    <DataTemplate>
+        <ImageCell Text="{Binding Name}"
+                    Detail="{Binding Title}"
+                    ImageSource="{Binding Avatar}"/>
+    </DataTemplate>
+</ListView.ItemTemplate>
+```
+Xamarin.Forms will automatically download, cache, and display the image from the server.
+
+### Validate App.cs
+
+Open the App.cs file and you will see the entry point for the application, which is the constructor for App(). It simply creates the new SpeakersPage, and then wraps it in a navigation page to get a nice title bar.
+
+### Run the App!
+
+Now, you can set the iOS, Android, or UWP (Windows/VS2015 only) as the start project and start debugging.
+
+![Startup project](http://content.screencast.com/users/JamesMontemagno/folders/Jing/media/020972ff-2a81-48f1-bbc7-1e4b89794369/2016-07-11_1442.png)
+
+#### iOS
+If you are on a PC then you will need to be connected to a macOS device with Xamarin installed to run and debug the app.
+
+If connected, you will see a Green connection status. Select **iPhoneSimulator as your target, and then select the Simulator to debug on.
+
+![iOS Setup](http://content.screencast.com/users/JamesMontemagno/folders/Jing/media/a6b32d62-cd3d-41ea-bd16-1bcc1fbe1f9d/2016-07-11_1445.png)
+
+#### Android
+
+Simply set the DevDaysSpeakers.Droid as the startup project and select a simulator to run on.
+
+#### Windows 10
+
+Simply set the DevDaysSpeakers.UWP as the startup project and select debug to **Local Machine**.
+
+## Details
+
+Now, let's do some navigation and display some Details. Let's upen up the code behind for **SpeakersPage.xaml** called **SpeakersPage.xaml.cs**.
+
+### ItemSelected Event
+
+In the code behind you will find the setup for the SpeakersViewModel. Under **BindingContext = vm;**, let's add an event to the **ListViewSpeakers** to get notified when an item is selected
+
+```csharp
+ListViewSpeakers.ItemSelected += ListViewSpeakers_ItemSelected;
+```
+
+Let's create and fill in this method and navigate to the DetailsPage.
+
+```csharp
+private async void ListViewSpeakers_ItemSelected(object sender, SelectedItemChangedEventArgs e)
+{
+    var speaker = e.SelectedItem as Speaker;
+    if (speaker == null)
+        return;
+
+    await Navigation.PushAsync(new DetailsPage(speaker));
+
+    ListViewSpeakers.SelectedItem = null;
+}
+```
+
+In the above code we check to see if the selected item is not null and then use the built in **Navigation** API to push a new page and then deselect the item.
+
+### DetailsPage.xaml
+
+Let's now fill in the Details page. Similar to the SpeakersPage, we will use a StackLayout, but we will wrap it in a ScrollView, incase we have long text.
+
+```xml
+  <ScrollView Padding="10">
+    <StackLayout Spacing="10">
+     <!-- Detail controls here -->
+    </StackLayout>    
+  </ScrollView>
+```
+
+Now, let's add controls and bindings for the properties in the Speaker object:
+
+```xml
+<Image Source="{Binding Avatar}" HeightRequest="200" WidthRequest="200"/>
+      
+<Label Text="{Binding Name}" FontSize="24"/>
+<Label Text="{Binding Title}" TextColor="Purple"/>
+<Label Text="{Binding Description}"/>
+```
+
+Now, for fun, let's add two buttons that we will add click events to in the code behind:
+
+```xml
+<Button Text="Speak" x:Name="ButtonSpeak"/>
+<Button Text="Go to Website" x:Name="ButtonWebsite"/>
+```
+
+### Text to Speech
+
+If we open up **DetailsPage.xaml.cs** we can now add a few more click handlers. Let's start with ButtonSpeak, where we will use the [Text To Speech Plugin](https://github.com/jamesmontemagno/TextToSpeechPlugin) to read back the speaker's description.
+
+In the constructor, add a click handler below the BindingContext:
+
+```csharp
+ButtonSpeak.Clicked += ButtonSpeak_Clicked;
+```
+
+Then we can add the click handler and call the cross platform API for text to speech:
+
+```csharp
+private void ButtonSpeak_Clicked(object sender, EventArgs e)
+{
+    CrossTextToSpeech.Current.Speak(this.speaker.Description);
+}
+```
+
+### Open Website
+Xamarin.Forms itself has some nice APIs built write in for cross platform functionality, such as opening a URL in the default browser.
+
+Let's add another click handler, but this time for ButtonWebsite:
+
+```csharp
+ButtonWebsite.Clicked += ButtonWebsite_Clicked;
+```
+
+Then, we can use the Device keyword to call the OpenUri method:
+
+```csharp
+private void ButtonWebsite_Clicked(object sender, EventArgs e)
+{
+    if (speaker.Website.StartsWith("http"))
+        Device.OpenUri(new Uri(speaker.Website));
+}
+```
+
+### Compile & Run
+Now, we should be all set to compile, and run just like before!
+
+## Connect to Azure Mobile Apps
+
+//Coming soon
+
+## Bonus
+
+Take Dev Days father with the need challenges!
+
+### Challenge 1: Cognitive Services
+For fun, you can add the [Cognitive Serivce Emotion API](https://www.microsoft.com/cognitive-services/en-us/emotion-api) and add another Button to the detail page to analyze the speakers face for happiness level. 
+
+Go to: http://microsoft.com/cognitive and create a new account and an API key for the Emotion service.
+
+Follow these steps:
+
+1.) Add **Microsoft.ProjectOxford.Emotion** nuget package to all projects
+
+2.) Add a new class called EmotionService and add the following code:
+
+```csharp
+public class EmotionService
+{
+    private static async Task<Emotion[]> GetHappinessAsync(string url)
+    {
+        var client = new HttpClient();
+        var stream = await client.GetStreamAsync(url);
+        var emotionClient = new EmotionServiceClient(CognitiveServicesKeys.Emotion);
+
+        var emotionResults = await emotionClient.RecognizeAsync(stream);
+
+        if (emotionResults == null || emotionResults.Count() == 0)
+        {
+            throw new Exception("Can't detect face");
+        }
+
+        return emotionResults;
+    }
+
+    //Average happiness calculation in case of multiple people
+    public static async Task<float> GetAverageHappinessScoreAsync(string url)
+    {
+        Emotion[] emotionResults = await GetHappinessAsync(url);
+
+        float score = 0;
+        foreach (var emotionResult in emotionResults)
+        {
+            score = score + emotionResult.Scores.Happiness;
+        }
+
+        return score / emotionResults.Count();
+    }
+
+    public static string GetHappinessMessage(float score)
+    {
+        score = score * 100;
+        double result = Math.Round(score, 2);
+
+        if (score >= 50)
+            return result + " % :-)";
+        else
+            return result + "% :-(";
+    }
+}
+```
+
+3.) Now add a new button to the Details Page and expose it with **x:Name="ButtonAnalyze**
+
+4.) Add a new click handler and add the async keyword to it.
+
+5.) Call 
+```csharp
+var level = await EmotionService.GetAverageHappinessScoreAsync(this.speaker.Avatar);
+```
+
+6.) Then display a pop up alert:
+```csharp
+await DisplayAlert("Happiness Level", level, "OK");
+```
+
+### Challenge 2: Edit Speaker Details
+
+//coming soon

+ 4 - 1
HandsOnLab/Start/DevDaysSpeakers.sln

@@ -127,7 +127,8 @@ Global
 		{367B4F1E-C7D4-432C-B767-59121E12C2BF}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator
 		{367B4F1E-C7D4-432C-B767-59121E12C2BF}.AppStore|x64.ActiveCfg = AppStore|iPhone
 		{367B4F1E-C7D4-432C-B767-59121E12C2BF}.AppStore|x86.ActiveCfg = AppStore|iPhone
-		{367B4F1E-C7D4-432C-B767-59121E12C2BF}.Debug|Any CPU.ActiveCfg = Debug|iPhone
+		{367B4F1E-C7D4-432C-B767-59121E12C2BF}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator
+		{367B4F1E-C7D4-432C-B767-59121E12C2BF}.Debug|Any CPU.Build.0 = Debug|iPhoneSimulator
 		{367B4F1E-C7D4-432C-B767-59121E12C2BF}.Debug|ARM.ActiveCfg = Debug|iPhone
 		{367B4F1E-C7D4-432C-B767-59121E12C2BF}.Debug|iPhone.ActiveCfg = Debug|iPhone
 		{367B4F1E-C7D4-432C-B767-59121E12C2BF}.Debug|iPhone.Build.0 = Debug|iPhone
@@ -186,6 +187,8 @@ Global
 		{5A77AD80-10E0-4093-9332-ECFB62D3342A}.Debug|ARM.Build.0 = Debug|ARM
 		{5A77AD80-10E0-4093-9332-ECFB62D3342A}.Debug|ARM.Deploy.0 = Debug|ARM
 		{5A77AD80-10E0-4093-9332-ECFB62D3342A}.Debug|iPhone.ActiveCfg = Debug|x86
+		{5A77AD80-10E0-4093-9332-ECFB62D3342A}.Debug|iPhone.Build.0 = Debug|x86
+		{5A77AD80-10E0-4093-9332-ECFB62D3342A}.Debug|iPhone.Deploy.0 = Debug|x86
 		{5A77AD80-10E0-4093-9332-ECFB62D3342A}.Debug|iPhoneSimulator.ActiveCfg = Debug|x86
 		{5A77AD80-10E0-4093-9332-ECFB62D3342A}.Debug|iPhoneSimulator.Build.0 = Debug|x86
 		{5A77AD80-10E0-4093-9332-ECFB62D3342A}.Debug|iPhoneSimulator.Deploy.0 = Debug|x86

+ 2 - 2
HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers.Droid/DevDaysSpeakers.Droid.csproj

@@ -66,8 +66,8 @@
     </Reference>
     <Reference Include="Mono.Android" />
     <Reference Include="mscorlib" />
-    <Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
-      <HintPath>..\..\packages\Newtonsoft.Json.6.0.4\lib\portable-net45+wp80+win8+wpa81\Newtonsoft.Json.dll</HintPath>
+    <Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Newtonsoft.Json.9.0.1\lib\portable-net45+wp80+win8+wpa81\Newtonsoft.Json.dll</HintPath>
       <Private>True</Private>
     </Reference>
     <Reference Include="Plugin.TextToSpeech, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">

+ 1 - 1
HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers.Droid/MainActivity.cs

@@ -9,7 +9,7 @@ using Android.OS;
 
 namespace DevDaysSpeakers.Droid
 {
-    [Activity(Label = "DevDaysSpeakers", Icon = "@drawable/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
+    [Activity(Label = "Dev Days Speakers", Icon = "@drawable/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
     public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
     {
         protected override void OnCreate(Bundle bundle)

+ 4 - 3
HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers.Droid/Properties/AndroidManifest.xml

@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="auto" package="com.sample.devdaysspeakers" android:versionCode="1" android:versionName="1">
 	<uses-sdk android:minSdkVersion="15" />
-	<application android:label="$safeprojectname$"></application>
-</manifest>
+	<uses-permission android:name="android.permission.INTERNET" />
+	<application android:label="Dev Days Speakers" android:icon="@drawable/icon"></application>
+</manifest>

+ 4 - 0
HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers.Droid/app.config

@@ -14,6 +14,10 @@
         <assemblyIdentity name="System.Net.Http" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
         <bindingRedirect oldVersion="0.0.0.0-1.5.0.0" newVersion="1.5.0.0" />
       </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0" />
+      </dependentAssembly>
     </assemblyBinding>
   </runtime>
 </configuration>

+ 1 - 1
HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers.Droid/packages.config

@@ -5,7 +5,7 @@
   <package id="Microsoft.Bcl" version="1.1.10" targetFramework="monoandroid60" />
   <package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="monoandroid60" />
   <package id="Microsoft.Net.Http" version="2.2.29" targetFramework="monoandroid60" />
-  <package id="Newtonsoft.Json" version="6.0.4" targetFramework="monoandroid60" />
+  <package id="Newtonsoft.Json" version="9.0.1" targetFramework="monoandroid60" />
   <package id="SQLitePCL" version="3.8.7.2" targetFramework="monoandroid60" />
   <package id="Xam.Plugins.TextToSpeech" version="2.0.0" targetFramework="monoandroid60" />
   <package id="Xamarin.Android.Support.Animated.Vector.Drawable" version="23.3.0" targetFramework="monoandroid60" />

+ 38 - 0
HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers.UWP/App.xaml

@@ -4,5 +4,43 @@
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     xmlns:local="using:DevDaysSpeakers.UWP"
     RequestedTheme="Light">
+<Application.Resources>
+        <DataTemplate x:Key="ImageCell">
+            <Grid Padding="10">
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="Auto" />
+                    <ColumnDefinition />
+                </Grid.ColumnDefinitions>
 
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto" />
+                    <RowDefinition Height="Auto" />
+                </Grid.RowDefinitions>
+
+                <Image Grid.Column="0" Grid.RowSpan="2"
+                       MaxHeight="66"
+                       MaxWidth="66"
+				DataContext="{Binding ImageSource, Converter={StaticResource ImageConverter}}"
+				Source="{Binding Value}"
+				VerticalAlignment="Center" />
+
+                <TextBlock Grid.Column="1" Grid.Row="0"
+                Margin="10,0,0,0"
+				Text="{Binding Text}"
+				Style="{ThemeResource BaseTextBlockStyle}"
+				Visibility="{Binding Text,RelativeSource={RelativeSource Self}, Converter={StaticResource CollapseWhenEmpty}}"
+				Foreground="{Binding TextColor, Converter={StaticResource ColorConverter}, ConverterParameter=DefaultTextForegroundThemeBrush}" />
+
+                <TextBlock Grid.Column="1" Grid.Row="1"
+                Margin="10,0,0,0"
+				Text="{Binding Detail}"
+				Style="{ThemeResource BodyTextBlockStyle}"
+				Visibility="{Binding Text,RelativeSource={RelativeSource Self}, Converter={StaticResource CollapseWhenEmpty}}"
+				Foreground="{Binding DetailColor, Converter={StaticResource ColorConverter}, ConverterParameter=DefaultTextForegroundThemeBrush}"
+				x:Name="detail" />
+            </Grid>
+        </DataTemplate>
+
+
+    </Application.Resources>
 </Application>

+ 1 - 0
HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers.UWP/project.json

@@ -6,6 +6,7 @@
     "Microsoft.Bcl.Build": "1.0.21",
     "Microsoft.Net.Http": "2.2.29",
     "Microsoft.NETCore.UniversalWindowsPlatform": "5.0.0",
+    "Newtonsoft.Json": "9.0.1",
     "SQLitePCL": "3.8.7.2",
     "Xam.Plugins.TextToSpeech": "2.0.0",
     "Xamarin.Forms": "2.3.0.107"

+ 27 - 26
HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers.UWP/project.lock.json

@@ -313,12 +313,12 @@
           "lib/dotnet/Microsoft.Win32.Primitives.dll": {}
         }
       },
-      "Newtonsoft.Json/6.0.4": {
+      "Newtonsoft.Json/9.0.1": {
         "compile": {
-          "lib/netcore45/Newtonsoft.Json.dll": {}
+          "lib/portable-net45+wp80+win8+wpa81/Newtonsoft.Json.dll": {}
         },
         "runtime": {
-          "lib/netcore45/Newtonsoft.Json.dll": {}
+          "lib/portable-net45+wp80+win8+wpa81/Newtonsoft.Json.dll": {}
         }
       },
       "SQLitePCL/3.8.7.2": {
@@ -2191,12 +2191,12 @@
           "lib/dotnet/Microsoft.Win32.Primitives.dll": {}
         }
       },
-      "Newtonsoft.Json/6.0.4": {
+      "Newtonsoft.Json/9.0.1": {
         "compile": {
-          "lib/netcore45/Newtonsoft.Json.dll": {}
+          "lib/portable-net45+wp80+win8+wpa81/Newtonsoft.Json.dll": {}
         },
         "runtime": {
-          "lib/netcore45/Newtonsoft.Json.dll": {}
+          "lib/portable-net45+wp80+win8+wpa81/Newtonsoft.Json.dll": {}
         }
       },
       "SQLitePCL/3.8.7.2": {
@@ -3915,12 +3915,12 @@
           "lib/dotnet/Microsoft.Win32.Primitives.dll": {}
         }
       },
-      "Newtonsoft.Json/6.0.4": {
+      "Newtonsoft.Json/9.0.1": {
         "compile": {
-          "lib/netcore45/Newtonsoft.Json.dll": {}
+          "lib/portable-net45+wp80+win8+wpa81/Newtonsoft.Json.dll": {}
         },
         "runtime": {
-          "lib/netcore45/Newtonsoft.Json.dll": {}
+          "lib/portable-net45+wp80+win8+wpa81/Newtonsoft.Json.dll": {}
         }
       },
       "SQLitePCL/3.8.7.2": {
@@ -5637,12 +5637,12 @@
           "lib/dotnet/Microsoft.Win32.Primitives.dll": {}
         }
       },
-      "Newtonsoft.Json/6.0.4": {
+      "Newtonsoft.Json/9.0.1": {
         "compile": {
-          "lib/netcore45/Newtonsoft.Json.dll": {}
+          "lib/portable-net45+wp80+win8+wpa81/Newtonsoft.Json.dll": {}
         },
         "runtime": {
-          "lib/netcore45/Newtonsoft.Json.dll": {}
+          "lib/portable-net45+wp80+win8+wpa81/Newtonsoft.Json.dll": {}
         }
       },
       "SQLitePCL/3.8.7.2": {
@@ -7361,12 +7361,12 @@
           "lib/dotnet/Microsoft.Win32.Primitives.dll": {}
         }
       },
-      "Newtonsoft.Json/6.0.4": {
+      "Newtonsoft.Json/9.0.1": {
         "compile": {
-          "lib/netcore45/Newtonsoft.Json.dll": {}
+          "lib/portable-net45+wp80+win8+wpa81/Newtonsoft.Json.dll": {}
         },
         "runtime": {
-          "lib/netcore45/Newtonsoft.Json.dll": {}
+          "lib/portable-net45+wp80+win8+wpa81/Newtonsoft.Json.dll": {}
         }
       },
       "SQLitePCL/3.8.7.2": {
@@ -9083,12 +9083,12 @@
           "lib/dotnet/Microsoft.Win32.Primitives.dll": {}
         }
       },
-      "Newtonsoft.Json/6.0.4": {
+      "Newtonsoft.Json/9.0.1": {
         "compile": {
-          "lib/netcore45/Newtonsoft.Json.dll": {}
+          "lib/portable-net45+wp80+win8+wpa81/Newtonsoft.Json.dll": {}
         },
         "runtime": {
-          "lib/netcore45/Newtonsoft.Json.dll": {}
+          "lib/portable-net45+wp80+win8+wpa81/Newtonsoft.Json.dll": {}
         }
       },
       "SQLitePCL/3.8.7.2": {
@@ -10807,12 +10807,12 @@
           "lib/dotnet/Microsoft.Win32.Primitives.dll": {}
         }
       },
-      "Newtonsoft.Json/6.0.4": {
+      "Newtonsoft.Json/9.0.1": {
         "compile": {
-          "lib/netcore45/Newtonsoft.Json.dll": {}
+          "lib/portable-net45+wp80+win8+wpa81/Newtonsoft.Json.dll": {}
         },
         "runtime": {
-          "lib/netcore45/Newtonsoft.Json.dll": {}
+          "lib/portable-net45+wp80+win8+wpa81/Newtonsoft.Json.dll": {}
         }
       },
       "SQLitePCL/3.8.7.2": {
@@ -13058,11 +13058,11 @@
         "ref/xamarinmac20/_._"
       ]
     },
-    "Newtonsoft.Json/6.0.4": {
-      "sha512": "FyQLmEpjsCrEP+znauLDGAi+h6i9YnaMkITlfIoiM4RYyX3nki306bTHsr/0okiIvIc7BJhQTbOAIZVocccFUw==",
+    "Newtonsoft.Json/9.0.1": {
+      "sha512": "U82mHQSKaIk+lpSVCbWYKNavmNH1i5xrExDEquU1i6I5pV6UMOqRnJRSlKO3cMPfcpp0RgDY+8jUXHdQ4IfXvw==",
       "type": "package",
       "files": [
-        "Newtonsoft.Json.6.0.4.nupkg.sha512",
+        "Newtonsoft.Json.9.0.1.nupkg.sha512",
         "Newtonsoft.Json.nuspec",
         "lib/net20/Newtonsoft.Json.dll",
         "lib/net20/Newtonsoft.Json.xml",
@@ -13072,8 +13072,8 @@
         "lib/net40/Newtonsoft.Json.xml",
         "lib/net45/Newtonsoft.Json.dll",
         "lib/net45/Newtonsoft.Json.xml",
-        "lib/netcore45/Newtonsoft.Json.dll",
-        "lib/netcore45/Newtonsoft.Json.xml",
+        "lib/netstandard1.0/Newtonsoft.Json.dll",
+        "lib/netstandard1.0/Newtonsoft.Json.xml",
         "lib/portable-net40+sl5+wp80+win8+wpa81/Newtonsoft.Json.dll",
         "lib/portable-net40+sl5+wp80+win8+wpa81/Newtonsoft.Json.xml",
         "lib/portable-net45+wp80+win8+wpa81/Newtonsoft.Json.dll",
@@ -15928,6 +15928,7 @@
       "Microsoft.Bcl.Build >= 1.0.21",
       "Microsoft.NETCore.UniversalWindowsPlatform >= 5.0.0",
       "Microsoft.Net.Http >= 2.2.29",
+      "Newtonsoft.Json >= 9.0.1",
       "SQLitePCL >= 3.8.7.2",
       "Xam.Plugins.TextToSpeech >= 2.0.0",
       "Xamarin.Forms >= 2.3.0.107"

+ 2 - 2
HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers.iOS/DevDaysSpeakers.iOS.csproj

@@ -129,8 +129,8 @@
       <HintPath>..\..\packages\Microsoft.Azure.Mobile.Client.SQLiteStore.2.1.0\lib\portable-win+net45+wp8+wpa81+monotouch+monoandroid\Microsoft.WindowsAzure.Mobile.SQLiteStore.dll</HintPath>
       <Private>True</Private>
     </Reference>
-    <Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
-      <HintPath>..\..\packages\Newtonsoft.Json.6.0.4\lib\portable-net45+wp80+win8+wpa81\Newtonsoft.Json.dll</HintPath>
+    <Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Newtonsoft.Json.9.0.1\lib\portable-net45+wp80+win8+wpa81\Newtonsoft.Json.dll</HintPath>
       <Private>True</Private>
     </Reference>
     <Reference Include="Plugin.TextToSpeech, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">

+ 2 - 4
HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers.iOS/Entitlements.plist

@@ -1,7 +1,5 @@
-<?xml version="1.0" encoding="UTF-8" ?>
+<?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
-<dict>
-</dict>
+<dict/>
 </plist>
-

+ 48 - 49
HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers.iOS/Info.plist

@@ -1,52 +1,51 @@
-<?xml version="1.0" encoding="UTF-8"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
-  <dict>
-    <key>UIDeviceFamily</key>
-    <array>
-      <integer>1</integer>
-      <integer>2</integer>
-    </array>
-    <key>UISupportedInterfaceOrientations</key>
-    <array>
-      <string>UIInterfaceOrientationPortrait</string>
-      <string>UIInterfaceOrientationLandscapeLeft</string>
-      <string>UIInterfaceOrientationLandscapeRight</string>
-    </array>
-    <key>UISupportedInterfaceOrientations~ipad</key>
-    <array>
-      <string>UIInterfaceOrientationPortrait</string>
-      <string>UIInterfaceOrientationPortraitUpsideDown</string>
-      <string>UIInterfaceOrientationLandscapeLeft</string>
-      <string>UIInterfaceOrientationLandscapeRight</string>
-    </array>
-    <key>MinimumOSVersion</key>
-    <string>6.0</string>
-    <key>CFBundleDisplayName</key>
-    <string>DevDaysSpeakers</string>
-    <key>CFBundleIdentifier</key>
-    <string>com.yourcompany.DevDaysSpeakers</string>
-    <key>CFBundleVersion</key>
-    <string>1.0</string>
-    <key>CFBundleIconFiles</key>
-    <array>
-      <string>Icon-60@2x</string>
-      <string>Icon-60@3x</string>
-      <string>Icon-76</string>
-      <string>Icon-76@2x</string>
-      <string>Default</string>
-      <string>Default@2x</string>
-      <string>Default-568h@2x</string>
-      <string>Default-Portrait</string>
-      <string>Default-Portrait@2x</string>
-      <string>Icon-Small-40</string>
-      <string>Icon-Small-40@2x</string>
-      <string>Icon-Small-40@3x</string>
-      <string>Icon-Small</string>
-      <string>Icon-Small@2x</string>
-      <string>Icon-Small@3x</string>
-    </array>
-    <key>UILaunchStoryboardName</key>
-    <string>LaunchScreen</string>
-  </dict>
+<dict>
+	<key>UIDeviceFamily</key>
+	<array>
+		<integer>1</integer>
+		<integer>2</integer>
+	</array>
+	<key>UISupportedInterfaceOrientations</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+	<key>UISupportedInterfaceOrientations~ipad</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationPortraitUpsideDown</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+	<key>MinimumOSVersion</key>
+	<string>8.0</string>
+	<key>CFBundleDisplayName</key>
+	<string>DevDaysSpeakers</string>
+	<key>CFBundleIdentifier</key>
+	<string>com.yourcompany.DevDaysSpeakers</string>
+	<key>CFBundleVersion</key>
+	<string>1.0</string>
+	<key>CFBundleIconFiles</key>
+	<array>
+		<string>Icon-60@2x.png</string>
+		<string>Icon-76.png</string>
+		<string>Icon-76@2x.png</string>
+		<string>Default.png</string>
+		<string>Default@2x.png</string>
+		<string>Default-568h@2x.png</string>
+		<string>Default-Portrait.png</string>
+		<string>Default-Portrait@2x.png</string>
+		<string>Icon-Small-40.png</string>
+		<string>Icon-Small-40@2x.png</string>
+		<string>Icon-Small.png</string>
+		<string>Icon-Small@2x.png</string>
+	</array>
+	<key>UILaunchStoryboardName</key>
+	<string>LaunchScreen</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1</string>
+</dict>
 </plist>

+ 4 - 0
HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers.iOS/app.config

@@ -14,6 +14,10 @@
         <assemblyIdentity name="System.Net.Http" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
         <bindingRedirect oldVersion="0.0.0.0-1.5.0.0" newVersion="1.5.0.0" />
       </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0" />
+      </dependentAssembly>
     </assemblyBinding>
   </runtime>
 </configuration>

+ 1 - 1
HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers.iOS/packages.config

@@ -5,7 +5,7 @@
   <package id="Microsoft.Bcl" version="1.1.10" targetFramework="xamarinios10" />
   <package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="xamarinios10" />
   <package id="Microsoft.Net.Http" version="2.2.29" targetFramework="xamarinios10" />
-  <package id="Newtonsoft.Json" version="6.0.4" targetFramework="xamarinios10" />
+  <package id="Newtonsoft.Json" version="9.0.1" targetFramework="xamarinios10" />
   <package id="SQLitePCL" version="3.8.7.2" targetFramework="xamarinios10" />
   <package id="Xam.Plugins.TextToSpeech" version="2.0.0" targetFramework="xamarinios10" />
   <package id="Xamarin.Forms" version="2.3.0.107" targetFramework="xamarinios10" />

+ 0 - 4
HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers/App.cs

@@ -1,8 +1,4 @@
 using DevDaysSpeakers.View;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
 
 using Xamarin.Forms;
 

+ 7 - 3
HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers/DevDaysSpeakers.csproj

@@ -38,7 +38,6 @@
     <Compile Include="App.cs" />
     <Compile Include="Model\Speaker.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
-    <Compile Include="ViewModel\AzureStore.cs" />
     <Compile Include="ViewModel\SpeakersViewModel.cs" />
     <Compile Include="View\DetailsPage.xaml.cs">
       <DependentUpon>DetailsPage.xaml</DependentUpon>
@@ -48,6 +47,10 @@
     </Compile>
   </ItemGroup>
   <ItemGroup>
+    <Reference Include="Azure.Mobile.Forms, Version=1.0.6025.25770, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\AppService.Helpers.Forms.1.0.1\lib\Azure.Mobile.Forms.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
     <Reference Include="Microsoft.WindowsAzure.Mobile, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
       <HintPath>..\..\packages\Microsoft.Azure.Mobile.Client.2.1.0\lib\portable-win+net45+wp8+wpa81+monotouch+monoandroid\Microsoft.WindowsAzure.Mobile.dll</HintPath>
       <Private>True</Private>
@@ -56,8 +59,8 @@
       <HintPath>..\..\packages\Microsoft.Azure.Mobile.Client.SQLiteStore.2.1.0\lib\portable-win+net45+wp8+wpa81+monotouch+monoandroid\Microsoft.WindowsAzure.Mobile.SQLiteStore.dll</HintPath>
       <Private>True</Private>
     </Reference>
-    <Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
-      <HintPath>..\..\packages\Newtonsoft.Json.6.0.4\lib\portable-net45+wp80+win8+wpa81\Newtonsoft.Json.dll</HintPath>
+    <Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Newtonsoft.Json.9.0.1\lib\portable-net45+wp80+win8+wpa81\Newtonsoft.Json.dll</HintPath>
       <Private>True</Private>
     </Reference>
     <Reference Include="Plugin.TextToSpeech, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
@@ -114,6 +117,7 @@
       <SubType>Designer</SubType>
     </EmbeddedResource>
   </ItemGroup>
+  <ItemGroup />
   <Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
   <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
     <PropertyGroup>

+ 1 - 6
HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers/Model/Speaker.cs

@@ -8,11 +8,6 @@ namespace DevDaysSpeakers.Model
 {
     public class Speaker
     {
-        public string Id { get; set; }
-        public string Name { get; set; }
-        public string Description { get; set; }
-        public string Website { get; set; }
-        public string Title { get; set; }
-        public string Avatar { get; set; }
+        
     }
 }

+ 1 - 13
HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers/View/DetailsPage.xaml

@@ -2,18 +2,6 @@
 <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
              xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
              x:Class="DevDaysSpeakers.View.DetailsPage"
-             Title="{Binding Name}">
+             Title="Details">
 
-  <ScrollView Padding="10">
-    <StackLayout Spacing="10">
-      <Image Source="{Binding Avatar}" HeightRequest="200" WidthRequest="200"/>
-      
-      <Label Text="{Binding Name}" FontSize="24"/>
-      <Label Text="{Binding Title}" TextColor="Purple"/>
-      <Label Text="{Binding Description}"/>
-
-      <Button Text="Speak" x:Name="ButtonSpeak"/>
-      <Button Text="Go to Website" x:Name="ButtonWebsite"/>
-    </StackLayout>    
-  </ScrollView>
 </ContentPage>

+ 5 - 18
HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers/View/DetailsPage.xaml.cs

@@ -14,27 +14,14 @@ namespace DevDaysSpeakers.View
     public partial class DetailsPage : ContentPage
     {
         Speaker speaker;
-        public DetailsPage(Speaker item)
+        public DetailsPage(Speaker speaker)
         {
             InitializeComponent();
-            this.speaker = item;
-
+            
+            //Set local instance of speaker and set BindingContext
+            this.speaker = speaker;
             BindingContext = this.speaker;
-
-            ButtonSpeak.Clicked += ButtonSpeak_Clicked;
-
-            ButtonWebsite.Clicked += ButtonWebsite_Clicked;
-        }
-
-        private void ButtonWebsite_Clicked(object sender, EventArgs e)
-        {
-            if (speaker.Website.StartsWith("http"))
-                Device.OpenUri(new Uri(speaker.Website));
-        }
-
-        private void ButtonSpeak_Clicked(object sender, EventArgs e)
-        {
-            CrossTextToSpeech.Current.Speak(this.speaker.Description);
         }
+        
     }
 }

+ 0 - 16
HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers/View/SpeakersPage.xaml

@@ -4,20 +4,4 @@
              x:Class="DevDaysSpeakers.View.SpeakersPage"
              Title="Speakers">
 
-  <StackLayout Spacing="0">
-    <Button Text="Sync Speakers" x:Name="ButtonSpeakers"/>
-    
-    <ActivityIndicator IsRunning="{Binding IsBusy}" IsVisible="{Binding IsBusy}"/>
-
-    <ListView x:Name="ListViewSpeakers"
-              ItemsSource="{Binding Speakers}">
-      <ListView.ItemTemplate>
-        <DataTemplate>
-          <ImageCell Text="{Binding Name}"
-                     Detail="{Binding Title}"
-                     ImageSource="{Binding Avatar}"/>
-        </DataTemplate>
-      </ListView.ItemTemplate>
-    </ListView>
-  </StackLayout>
 </ContentPage>

+ 2 - 40
HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers/View/SpeakersPage.xaml.cs

@@ -19,49 +19,11 @@ namespace DevDaysSpeakers.View
         {
             InitializeComponent();
 
+            //Create the view model and set as binding context
             vm = new SpeakersViewModel();
-
             BindingContext = vm;
-
-            ButtonSpeakers.Clicked += ButtonSpeakers_Clicked;
-
-            ListViewSpeakers.ItemSelected += ListViewSpeakers_ItemSelected;
-
         }
 
-        private async void ListViewSpeakers_ItemSelected(object sender, SelectedItemChangedEventArgs e)
-        {
-            var speaker = e.SelectedItem as Speaker;
-            if (speaker == null)
-                return;
-
-            await Navigation.PushAsync(new DetailsPage(speaker));
-
-            ListViewSpeakers.SelectedItem = null;
-
-        }
-
-        private async void ButtonSpeakers_Clicked(object sender, EventArgs e)
-        {
-            Exception ex = null;
-
-            try
-            {
-                ButtonSpeakers.IsEnabled = false;
-
-                await vm.GetSpeakers();
-            }
-            catch(Exception error)
-            {
-                ex = error;
-            }
-            finally
-            {
-                ButtonSpeakers.IsEnabled = true;
-            }
-
-            if (ex != null)
-                await DisplayAlert("Error!", ex.Message, "OK");
-        }
+       
     }
 }

+ 0 - 65
HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers/ViewModel/AzureStore.cs

@@ -1,65 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-using Microsoft.WindowsAzure.MobileServices;
-using Microsoft.WindowsAzure.MobileServices.Sync;
-using Microsoft.WindowsAzure.MobileServices.SQLiteStore;
-using DevDaysSpeakers.Model;
-using System.Diagnostics;
-
-namespace DevDaysSpeakers.ViewModel
-{
-    public class AzureStore
-    {
-        static AzureStore current;
-        public static AzureStore Current
-        {
-            get
-            {
-                if (current == null)
-                    current = new AzureStore();
-
-                return current;
-            }
-        }
-
-        MobileServiceClient MobileService { get; set; }
-        IMobileServiceSyncTable<Speaker> speakerTable;
-
-        public async Task Initialize()
-        {
-            if (MobileService != null)
-                return;
-
-            MobileService = new MobileServiceClient("https://montemagnospeakers.azurewebsites.net");
-
-            var store = new MobileServiceSQLiteStore("speakers.db");
-
-            store.DefineTable<Speaker>();
-
-            await MobileService.SyncContext.InitializeAsync(store);
-
-            speakerTable = MobileService.GetSyncTable<Speaker>();
-        }
-
-        public async Task<List<Speaker>> GetSpeakers()
-        {
-            await Initialize();
-
-            try
-            {
-                await speakerTable.PullAsync("allSpeakers", speakerTable.CreateQuery());
-            }
-            catch (Exception ex)
-            {
-                Debug.WriteLine(ex);
-            }
-
-            return await speakerTable.ToListAsync();
-        }
-
-    }
-}

+ 1 - 69
HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers/ViewModel/SpeakersViewModel.cs

@@ -1,80 +1,12 @@
 using System;
 using System.Collections.Generic;
-using System.ComponentModel;
-using System.Diagnostics;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
 
-using DevDaysSpeakers.Model;
-using System.Net.Http;
-using Newtonsoft.Json;
-using System.Collections.ObjectModel;
-
 namespace DevDaysSpeakers.ViewModel
 {
-    public class SpeakersViewModel : INotifyPropertyChanged
+    public class SpeakersViewModel
     {
-
-        public ObservableCollection<Speaker> Speakers { get; set; }
-        public SpeakersViewModel()
-        {
-            Speakers = new ObservableCollection<Speaker>();
-        }
-
-        bool busy;
-
-        public bool IsBusy
-        {
-            get { return busy; }
-            set
-            {
-                busy = value;
-                OnPropertyChanged("IsBusy");
-            }
-        }
-
-        public async Task GetSpeakers()
-        {
-            if (IsBusy)
-                return;
-
-            try
-            {
-                IsBusy = true;
-
-
-                var items = await AzureStore.Current.GetSpeakers();
-
-                Speakers.Clear();
-                foreach (var item in items)
-                    Speakers.Add(item);
-                
-            }
-            catch(Exception ex)
-            {
-                Debug.WriteLine("Error: " + ex);
-                throw;
-            }
-            finally
-            {
-                IsBusy = false;
-            }
-        }
-
-
-
-
-        public event PropertyChangedEventHandler PropertyChanged;
-
-        void OnPropertyChanged(string name)
-        {
-            var changed = PropertyChanged;
-
-            if (changed == null)
-                return;
-
-            changed.Invoke(this, new PropertyChangedEventArgs(name));
-        }
     }
 }

+ 2 - 1
HandsOnLab/Start/DevDaysSpeakers/DevDaysSpeakers/packages.config

@@ -1,11 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
+  <package id="AppService.Helpers.Forms" version="1.0.1" targetFramework="portable45-net45+win8+wp8+wpa81" />
   <package id="Microsoft.Azure.Mobile.Client" version="2.1.0" targetFramework="portable45-net45+win8+wp8+wpa81" />
   <package id="Microsoft.Azure.Mobile.Client.SQLiteStore" version="2.1.0" targetFramework="portable45-net45+win8+wp8+wpa81" />
   <package id="Microsoft.Bcl" version="1.1.10" targetFramework="portable45-net45+win8+wp8+wpa81" />
   <package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="portable45-net45+win8+wp8+wpa81" />
   <package id="Microsoft.Net.Http" version="2.2.29" targetFramework="portable45-net45+win8+wp8+wpa81" />
-  <package id="Newtonsoft.Json" version="6.0.4" targetFramework="portable45-net45+win8+wp8+wpa81" />
+  <package id="Newtonsoft.Json" version="9.0.1" targetFramework="portable45-net45+win8+wp8+wpa81" />
   <package id="SQLitePCL" version="3.8.7.2" targetFramework="portable45-net45+win8+wp8+wpa81" />
   <package id="Xam.Plugins.TextToSpeech" version="2.0.0" targetFramework="portable45-net45+win8+wp8+wpa81" />
   <package id="Xamarin.Forms" version="2.3.0.107" targetFramework="portable45-net45+win8+wp8+wpa81" />