Skip to content
This repository was archived by the owner on Jun 21, 2023. It is now read-only.

Commit 5106ff5

Browse files
committed
Rebase of MVVM refactor.
The MVVM refactor (#1346) was originally based on the SSO feature branch and had a _lot_ of WIP etc commits. Rebased it off of #1353 and squashed into a single commit.
1 parent c747b45 commit 5106ff5

File tree

190 files changed

+3722
-5185
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

190 files changed

+3722
-5185
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Dialog Views with Connections
2+
3+
Some dialog views need a connection to operate - if there is no connection, a login dialog should be shown: for example, clicking Create Gist without a connection will first prompt the user to log in.
4+
5+
Achieving this is simple, first make your view model interface implement `IConnectionInitializedViewModel` and do any initialization that requires a connection in the `InitializeAsync` method in your view model:
6+
7+
```csharp
8+
public Task InitializeAsync(IConnection connection)
9+
{
10+
// .. at this point, you're guaranteed to have a connection.
11+
}
12+
```
13+
14+
To show the dialog, call `IShowDialogService.ShowWithFirstConnection` instead of `Show`:
15+
16+
```csharp
17+
public async Task ShowExampleDialog()
18+
{
19+
var viewModel = serviceProvider.ExportProvider.GetExportedValue<IExampleDialogViewModel>();
20+
await showDialog.ShowWithFirstConnection(viewModel);
21+
}
22+
```
23+
24+
`ShowFirstConnection` first checks if there are any logged in connections. If there are, the first logged in connection will be passed to `InitializeAsync` and the view shown immediately. If there are no logged in connections, the login view will first be shown. Once the user has successfully logged in, the new connection will be passed to `InitalizeAsync` and the view shown.
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# Implementing a Dialog View
2+
3+
GitHub for Visual Studio has a common dialog which is used to show the login, clone, create repository etc. operations. To add a new view to the dialog and show the dialog with this view, you need to do the following:
4+
5+
## Create a View Model and Interface
6+
7+
> TODO: `NewViewModelBase` will become simply `ViewModelBase` once the MVVM refactor is completed.
8+
9+
- Create an interface for the view model that implements `IDialogContentViewModel` in `GitHub.Exports.Reactive\ViewModels\Dialog`
10+
- Create a view model that inherits from `NewViewModelBase` and implements the interface in `GitHub.App\ViewModels\Dialog`
11+
- Export the view model with the interface as the contract and add a `[PartCreationPolicy(CreationPolicy.NonShared)]` attribute
12+
13+
A minimal example that just exposes a command that will dismiss the dialog:
14+
15+
```csharp
16+
using System;
17+
using ReactiveUI;
18+
19+
namespace GitHub.ViewModels.Dialog
20+
{
21+
public interface IExampleDialogViewModel : IDialogContentViewModel
22+
{
23+
ReactiveCommand<object> Dismiss { get; }
24+
}
25+
}
26+
```
27+
28+
```csharp
29+
using System;
30+
using System.ComponentModel.Composition;
31+
using ReactiveUI;
32+
33+
namespace GitHub.ViewModels.Dialog
34+
{
35+
[Export(typeof(IExampleDialogViewModel))]
36+
[PartCreationPolicy(CreationPolicy.NonShared)]
37+
public class ExampleDialogViewModel : NewViewModelBase, IExampleDialogViewModel
38+
{
39+
[ImportingConstructor]
40+
public ExampleDialogViewModel()
41+
{
42+
Dismiss = ReactiveCommand.Create();
43+
}
44+
45+
public string Title => "Example Dialog";
46+
47+
public ReactiveCommand<object> Dismiss { get; }
48+
49+
public IObservable<object> Done => Dismiss;
50+
}
51+
}
52+
```
53+
54+
## Create a View
55+
56+
> TODO: Decide if `GitHub.VisualStudio\Views` is the best place for views
57+
58+
- Create a WPF `UserControl` under `GitHub.VisualStudio\Views\Dialog`
59+
- Add an `ExportViewFor` attribute with the type of the view model interface
60+
- Add a `PartCreationPolicy(CreationPolicy.NonShared)]` attribute
61+
62+
Continuing the example above:
63+
64+
```xml
65+
<UserControl x:Class="GitHub.VisualStudio.Views.Dialog.ExampleDialogView"
66+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
67+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
68+
<Button Command="{Binding Dismiss}" HorizontalAlignment="Center" VerticalAlignment="Center">
69+
Dismiss
70+
</Button>
71+
</UserControl>
72+
```
73+
74+
```csharp
75+
using System.ComponentModel.Composition;
76+
using System.Windows.Controls;
77+
using GitHub.Exports;
78+
using GitHub.ViewModels.Dialog;
79+
80+
namespace GitHub.VisualStudio.Views.Dialog
81+
{
82+
[ExportViewFor(typeof(IExampleDialogViewModel))]
83+
[PartCreationPolicy(CreationPolicy.NonShared)]
84+
public partial class ExampleDialogView : UserControl
85+
{
86+
public ExampleDialogView()
87+
{
88+
InitializeComponent();
89+
}
90+
}
91+
}
92+
```
93+
94+
## Show the Dialog!
95+
96+
To show the dialog you will need an instance of the `IShowDialog` service. Once you have that, simply call the `Show` method with an instance of your view model.
97+
98+
```csharp
99+
var viewModel = new ExampleDialogViewModel();
100+
showDialog.Show(viewModel)
101+
```
102+
103+
## Optional: Add a method to `DialogService`
104+
105+
Creating a view model like this may be the right thing to do, but it's not very reusable or testable. If you want your dialog to be easy reusable, add a method to `DialogService`:
106+
107+
```csharp
108+
public async Task ShowExampleDialog()
109+
{
110+
var viewModel = factory.CreateViewModel<IExampleDialogViewModel>();
111+
await showDialog.Show(viewModel);
112+
}
113+
```
114+
115+
Obviously, add this method to `IDialogService` too.
116+
117+
Note that these methods are `async` - this means that if you need to do asynchronous initialization of your view model, you can do it here before calling `showDialog`.
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Multi-paged Dialogs
2+
3+
Some dialogs will be multi-paged - for example the login dialog has a credentials page and a 2Fa page that is shown if two-factor authorization is required.
4+
5+
## The View Model
6+
7+
To help implement view models for a multi-page dialog there is a useful base class called `PagedDialogViewModelBase`. The typical way of implementing this is as follows:
8+
9+
- Define each page of the dialog as you would [implement a single dialog view model](implementing-a-dialog-view.md)
10+
- Implement a "container" view model for the dialog that inherits from `PagedDialogViewModel`
11+
- Import each page into the container view model
12+
- Add logic to switch between pages by setting the `PagedDialogViewModelBase.Content` property
13+
- Add a `Done` observable
14+
15+
Here's a simple example of a container dialog that has two pages. The pages are switched using `ReactiveCommand`s:
16+
17+
```csharp
18+
using System;
19+
using System.ComponentModel.Composition;
20+
21+
namespace GitHub.ViewModels.Dialog
22+
{
23+
[Export(typeof(IExamplePagedDialogViewModel))]
24+
[PartCreationPolicy(CreationPolicy.NonShared)]
25+
public class ExamplePagedDialogViewModel : PagedDialogViewModelBase,
26+
IExamplePagedDialogViewModel
27+
{
28+
[ImportingConstructor]
29+
public ExamplePagedDialogViewModel(
30+
IPage1ViewModel page1,
31+
IPage2ViewModel page2)
32+
{
33+
Content = page1;
34+
page1.Next.Subscribe(_ => Content = page2);
35+
page2.Previous.Subscribe(_ => Content = page1);
36+
Done = Observable.Merge(page2.Done, page2.Done);
37+
}
38+
39+
public override IObservable<object> Done { get; }
40+
}
41+
}
42+
```
43+
44+
## The View
45+
46+
The view in this case is very simple: it just needs to display the `Content` property of the container view model:
47+
48+
```xml
49+
<UserControl x:Class="GitHub.VisualStudio.Views.Dialog.ExamplePagedDialogView"
50+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
51+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
52+
Content="{Binding Content}">
53+
</UserControl>
54+
```
55+
56+
```csharp
57+
using System;
58+
using System.ComponentModel.Composition;
59+
using System.Windows.Controls;
60+
using GitHub.Exports;
61+
using GitHub.ViewModels.Dialog;
62+
63+
namespace GitHub.VisualStudio.Views.Dialog
64+
{
65+
[ExportViewFor(typeof(IExamplePagedDialogViewModel))]
66+
[PartCreationPolicy(CreationPolicy.NonShared)]
67+
public partial class ExamplePagedDialogView : UserControl
68+
{
69+
public NewLoginView()
70+
{
71+
InitializeComponent();
72+
}
73+
}
74+
}
75+
```
76+

documentation/UIController.md

Lines changed: 0 additions & 59 deletions
This file was deleted.

src/GitHub.App/Authentication/TwoFactorChallengeHandler.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using GitHub.Api;
99
using GitHub.Helpers;
1010
using GitHub.Extensions;
11+
using GitHub.ViewModels.Dialog;
1112

1213
namespace GitHub.Authentication
1314
{
@@ -16,11 +17,11 @@ namespace GitHub.Authentication
1617
[PartCreationPolicy(CreationPolicy.Shared)]
1718
public class TwoFactorChallengeHandler : ReactiveObject, IDelegatingTwoFactorChallengeHandler
1819
{
19-
ITwoFactorDialogViewModel twoFactorDialog;
20+
ILogin2FaViewModel twoFactorDialog;
2021
public IViewModel CurrentViewModel
2122
{
2223
get { return twoFactorDialog; }
23-
private set { this.RaiseAndSetIfChanged(ref twoFactorDialog, (ITwoFactorDialogViewModel)value); }
24+
private set { this.RaiseAndSetIfChanged(ref twoFactorDialog, (ILogin2FaViewModel)value); }
2425
}
2526

2627
public void SetViewModel(IViewModel vm)
@@ -50,7 +51,7 @@ public async Task<TwoFactorChallengeResult> HandleTwoFactorException(TwoFactorAu
5051
public async Task ChallengeFailed(Exception exception)
5152
{
5253
await ThreadingHelper.SwitchToMainThreadAsync();
53-
await twoFactorDialog.Cancel.ExecuteAsync(null);
54+
twoFactorDialog.Cancel();
5455
}
5556
}
5657
}

src/GitHub.App/Authentication/TwoFactorRequiredUserError.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,18 @@ namespace GitHub.Authentication
88
public class TwoFactorRequiredUserError : UserError
99
{
1010
public TwoFactorRequiredUserError(TwoFactorAuthorizationException exception)
11+
: this(exception, exception.TwoFactorType)
12+
{
13+
}
14+
15+
public TwoFactorRequiredUserError(
16+
TwoFactorAuthorizationException exception,
17+
TwoFactorType twoFactorType)
1118
: base(exception.Message, innerException: exception)
1219
{
1320
Guard.ArgumentNotNull(exception, nameof(exception));
1421

15-
TwoFactorType = exception.TwoFactorType;
22+
TwoFactorType = twoFactorType;
1623
RetryFailed = exception is TwoFactorChallengeFailedException;
1724
}
1825

0 commit comments

Comments
 (0)