Skip to content
This repository has been archived by the owner on Jul 19, 2024. It is now read-only.

App Center Integration #234

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<ContentDialog
x:Class="HoloLensCommander.MobileCenterAppsDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:HoloLensCommander"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="Mobile Center Apps"
PrimaryButtonText="Close"
PrimaryButtonClick="ContentDialog_PrimaryButtonClick"
MaxHeight="800">

<StackPanel>
<Canvas
Width="450" Height="40">
<TextBlock
x:Name="UserDisplayNameLabel"
Text="Display Name"
FontSize="16"
Canvas.Top="10"/>
<TextBlock
x:Name="UserDisplayName"
Text="{Binding Path=DeviceAddress}"
ToolTipService.ToolTip="The address of the target device"
FontSize="16"
Width="347"
Canvas.Left="103" Canvas.Top="10"/>
</Canvas>
<Canvas
Width="450" Height="440">
<TextBlock
x:Name="UserEmailLabel"
Text="Email"
FontSize="16"/>
<ListBox

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to make this multi-select?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@davidkline-ms Yes, we could make it multi-select to allow for multiple downloads at once. That would require some additional steps for making the download call async and queuing up concurrent downloads, correct?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are probably right. Unless the AppCenter has an endpoint for "install these". Probably something best left for the next rev :)

Copy link
Contributor Author

@awolowiecki720 awolowiecki720 Jan 25, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To my knowledge there is not. I am using the download URL and then caching that downloaded data. There is "install" api other than to install to a mobile device for testing.
imgo

x:Name="MobileCenterAppsList"
ItemsSource="{Binding MobileCenterApps}"
SelectedItem="{Binding SelectedMobileCenterApp, Mode=TwoWay}"
ToolTipService.ToolTip="Mobile Center apps from your organization"
Width="395" Height= "400"
BorderThickness="1"
ScrollViewer.HorizontalScrollMode="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
Canvas.Top="26"/>
<Button
x:Name="downloadSelectedApp"
Content="&#xE896;"
Command="{Binding RefreshInstalledAppsCommand}"
ToolTipService.ToolTip="Download selected app from the Mobile Center"
FontFamily="Segoe MDL2 Assets" FontSize="24"
Width="50" Height="40"
Canvas.Left="400" Canvas.Top="25"/>
</Canvas>
</StackPanel>
</ContentDialog>
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using Windows.Web.Http;
using Windows.Web.Http.Headers;
using Windows.Web.Http.Filters;

// The Content Dialog item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238

namespace HoloLensCommander
{
public sealed partial class MobileCenterAppsDialog : ContentDialog
{


public MobileCenterAppsDialog()
{
jsonParse();
this.InitializeComponent();
}

private void ContentDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
//close the dialog
}

private void ContentDialog_SecondaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no secondary button in the XAML, no need to have this method.

{
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Newtonsoft.Json;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, this project's only dependency is on WindowsDevicePortalWrapper. Is the json data from the app center complex enough to warrant using the Netwtonsoft component? If not, I would prefer we not take an additional dependency.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can help with the deserialization code if we can avoid the component,.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@davidkline-ms Yes, there are only 2 instances of deserializing the JSON response data, and it’s a relatively simple object (~15 fields total, but I only use 4). Help with this would be awesome so that we do not add more dependencies.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool. Since you only use the four, we should be able to only define the subset when configuring the object for the built in json serializer

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@davidkline-ms what's the easiest way to write our own json parser for these objects? Are you thinking we parse the strings ourselves? Or are there built in functions I am not aware of?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here is a sample class definition from the WindowsDevicePortalWrapper project:

     using System.Runtime.Serialization;

    [DataContract]
    public class BatteryState
    {
        [DataMember(Name = "AcOnline")]
        public bool IsOnAcPower { get; private set; }

        [DataMember(Name = "BatteryPresent")]
        public bool IsBatteryPresent { get; private set; }

        [DataMember(Name = "Charging")]
        public bool IsCharging { get; private set; }

        [DataMember(Name = "DefaultAlert1")]
        public int DefaultAlert1 { get; private set; }

        [DataMember(Name = "DefaultAlert2")]
        public int DefaultAlert2 { get; private set; }

        [DataMember(Name = "EstimatedTime")]
        public uint EstimatedTimeRaw { get; private set; }

        [DataMember(Name = "MaximumCapacity")]
        public int MaximumCapacity { get; private set; }

        [DataMember(Name = "RemainingCapacity")]
        public int RemainingCapacity { get; private set; }
    }

Copy link

@david-c-kline david-c-kline Jan 25, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the BatteryState example, if all you cared about was whether or not a battery existed and if the system was plugged in, you could include only the first two members of the class.

using System.ComponentModel;
using System.Net;

namespace HoloLensCommander
{
public partial class MobileCenterAppsDialogViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

public MobileCenterAppsDialogViewModel (AppResponse[] appResponseList)
{
for (int i = 0; i < appResponseList.Length; i++)
{
this.MobileCenterApps.Add(appResponseList[i].name);
}

}
private void NotifyPropertyChanged(string propertyName)
{
this.PropertyChanged?.Invoke(
this,
new PropertyChangedEventArgs(propertyName));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.Collections.Generic;

namespace HoloLensCommander
{
public partial class MobileCenterAppsDialogViewModel
{
private List<string> mobileCenterApps = new List<string>();
public List<string> MobileCenterApps
{
get
{
return this.mobileCenterApps;
}
set
{
if (this.mobileCenterApps != value)
{
this.mobileCenterApps = value;
this.NotifyPropertyChanged(nameof(MobileCenterApps));
}
}
}
}
}
11 changes: 11 additions & 0 deletions HoloLensCommander/HoloLensCommander/HoloLensCommander.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,11 @@
<Compile Include="Dialogs\ManageAppsDialogViewModelProperties.cs" />
<Compile Include="Dialogs\MixedRealityViewDialogViewModel.cs" />
<Compile Include="Dialogs\MixedRealityViewDialogViewModelProperties.cs" />
<Compile Include="Dialogs\MobileCenterAppsDialog.xaml.cs">
<DependentUpon>MobileCenterAppsDialog.xaml</DependentUpon>
</Compile>
<Compile Include="Dialogs\MobileCenterAppsDialogViewModel.cs" />
<Compile Include="Dialogs\MobileCenterAppsDialogViewModelProperties.cs" />
<Compile Include="Dialogs\SetAPItokenDialog.xaml.cs">
<DependentUpon>SetAPItokenDialog.xaml</DependentUpon>
</Compile>
Expand Down Expand Up @@ -170,6 +175,7 @@
<DependentUpon>SettingsDialog.xaml</DependentUpon>
</Compile>
<Compile Include="Utilities\AppInstallFiles.cs" />
<Compile Include="Utilities\AppResponse.cs" />
<Compile Include="Utilities\ConnectionInformation.cs" />
<Compile Include="Utilities\ConnectOptions.cs" />
<Compile Include="Utilities\DeviceMonitorControlComparer.cs" />
Expand All @@ -181,6 +187,7 @@
<Compile Include="Utilities\UserInformation.cs" />
<Compile Include="Utilities\UserToken.cs" />
<Compile Include="Utilities\Utilities.cs" />
<Compile Include="Utilities\WebRequest.cs" />
</ItemGroup>
<ItemGroup>
<AppxManifest Include="Package.appxmanifest">
Expand Down Expand Up @@ -218,6 +225,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Dialogs\MobileCenterAppsDialog.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Dialogs\SetAPItokenDialog.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
Expand Down
7 changes: 7 additions & 0 deletions HoloLensCommander/HoloLensCommander/MainPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,13 @@
FontFamily="Segoe MDL2 Assets"
Width="50" Height="40"
Canvas.Top="5" Canvas.Left="675"/>
<Button
Content="&#xE71D;"
ToolTipService.ToolTip="Display Mobile Center app list"
Command="{Binding Path=ShowMobileCenterAppsCommand}"
FontFamily="Segoe MDL2 Assets"
Width="50" Height="40"
Canvas.Top="5" Canvas.Left="730"/>
</Canvas>
<ScrollViewer Grid.Column="1" Grid.Row="1"
VerticalScrollBarVisibility="Auto" VerticalScrollMode="Auto">
Expand Down
6 changes: 6 additions & 0 deletions HoloLensCommander/HoloLensCommander/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,12 @@ await this.ConnectToDeviceAsync(
this.SelectAllDevices();
});

this.ShowMobileCenterAppsCommand = new Command(
async (parameter) =>
{
await this.ShowMobileCenterApps();
});

this.ShowSetAPItokenCommand = new Command(
async (parameter) =>
{
Expand Down
16 changes: 16 additions & 0 deletions HoloLensCommander/HoloLensCommander/MainWindowViewModelCommands.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
Expand Down Expand Up @@ -586,6 +587,21 @@ private async Task ShowSetCredentials()
}
}

public ICommand ShowMobileCenterAppsCommand
{ get; private set; }

private async Task ShowMobileCenterApps()
{
AppCenterWebRequest appCenterWebRequest = new AppCenterWebRequest();
string requestResponseJson = await appCenterWebRequest.HttpWebRequest();

AppResponse[] appResponse;
appResponse = JsonConvert.DeserializeObject<AppResponse[]>(requestResponseJson);

ContentDialog mobileCenterAppsDialog = new MobileCenterAppsDialog(appResponse);
await mobileCenterAppsDialog.ShowAsync();
}

public ICommand ShowSetAPItokenCommand
{ get; private set; }

Expand Down
16 changes: 16 additions & 0 deletions HoloLensCommander/HoloLensCommander/Utilities/AppResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace HoloLensCommander
{
public class AppResponse

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on this class definition, we can easily eliminate the newtonsoft dependency.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@davidkline-ms Sounds good; How do you recommend deserializing without newtonsoft?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Over in the WindowsDevicePortalWrapper project (https://github.com/Microsoft/WindowsDevicePortalWrapper), take a look at how the OperatingSystemInformation object is defined (https://github.com/Microsoft/WindowsDevicePortalWrapper/blob/master/WindowsDevicePortalWrapper/WindowsDevicePortalWrapper.Shared/Core/OsInformation.cs).

The ResponseHelpers.cs file (https://github.com/Microsoft/WindowsDevicePortalWrapper/blob/master/WindowsDevicePortalWrapper/WindowsDevicePortalWrapper.Shared/HttpRest/ResponseHelpers.cs) shows how we validate the data format and read the contents.

As mentioned before, i am happy to help with this as i put together the original version of the WDPW json code.

{
public string display_name;
public string name;
public string app_secret;
public string id;
}
}
40 changes: 40 additions & 0 deletions HoloLensCommander/HoloLensCommander/Utilities/WebRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using System.Threading.Tasks;
using Windows.Web.Http;
using Windows.Web.Http.Headers;

namespace HoloLensCommander
{
public class AppCenterWebRequest
{
public async Task<string> HttpWebRequest()
{
HttpClient httpClient = new HttpClient();

var request = new HttpRequestMessage();

request.Headers.Accept.Add(new HttpMediaTypeWithQualityHeaderValue("application/json"));
request.Headers.Append("X-API-Token", "9b8e356d29f80de93d88fac0252e48f8fbe22e96");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may want to read this from settings (with a const variable that defines the currently known value) just in case it changes when the API / endpoint is revised,

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@davidkline-ms I intend to save/read the token from localcache. What are best practices for defining API endpoints in the app and reading them from settings?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see below


Uri uri = new Uri("https://api.appcenter.ms/v0.1/apps");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may want to read this from settings. I expect it will change given that it is a v0.1 endpoint.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@davidkline-ms Same as above, I am curious about best practices for saving API endpoints in a way that they can be easily maintained/updated.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way the app currently handles this is to create a static readonly or const variable that stores the default value (ex: https://api.appcenter.ms/v0.1/apps). If the user enters a different value into the settings dialog, the value is written into the cache. on startup, if there is a value in the cache it is used, otherwise use the default

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, so saving the apis in a config file and loading it on startup could work here. If the user needs to change the endpoints, they could open the config file in the app's cache and make changes. It seems weird to expose request header names and endpoints through the UI.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that it is a bit odd to expose that info in the UI. Maybe that should be something we just document in the issues section to update the code / config file when the endpoint changes.

request.RequestUri = uri;


HttpResponseMessage httpResponseMessage = new HttpResponseMessage();
string httpResponseBody = "";

try
{
httpResponseMessage = await httpClient.SendRequestAsync(request);
httpResponseMessage.EnsureSuccessStatusCode();
httpResponseBody = await httpResponseMessage.Content.ReadAsStringAsync();
}
catch (Exception ex)
{
httpResponseBody = "Error: " + ex.HResult.ToString("X") + " Message: " + ex.Message;
}

return httpResponseBody;
}
}
}
1 change: 1 addition & 0 deletions HoloLensCommander/HoloLensCommander/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"dependencies": {
"Microsoft.NETCore.UniversalWindowsPlatform": "5.1.0",
"WindowsDevicePortalWrapper": "0.9.4"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be 0.9.5.1 to take advantage of updates and fixes (looks like I may have missed that in a checkin last month-ish)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@davidkline-ms Sounds good. Is there a cadence for updating dependency versions?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I typically try to update whenever the wrapper issues a new version. There may be a delay if the changes do not directly impact the Commander app.

"Newtonsoft.Json": "10.0.3",
},
"frameworks": {
"uap10.0": {}
Expand Down