Skip to content

Commit 75e56fe

Browse files
committed
Better reporting for Credentials dialog
1 parent 5f8c909 commit 75e56fe

7 files changed

Lines changed: 138 additions & 72 deletions

File tree

src/Platforms/SecureFolderFS.Maui/Popups/CredentialsPopup.xaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@
119119
<Grid.RowDefinitions>
120120
<RowDefinition Height="Auto" />
121121
<RowDefinition />
122+
<RowDefinition Height="Auto" />
122123
</Grid.RowDefinitions>
123124

124125
<Label
@@ -139,6 +140,29 @@
139140
SelectionTemplate="{StaticResource SelectionTemplate}" />
140141
</uc:ContentPresentation.TemplateSelector>
141142
</uc:ContentPresentation>
143+
144+
<!-- Status notification -->
145+
<Border
146+
Grid.Row="2"
147+
Padding="16,12"
148+
Background="{StaticResource ThemeSecondaryColorBrush}"
149+
IsVisible="{Binding ViewModel.StatusInfoBar.IsOpen, Mode=OneWay, Source={x:Reference ThisPopup}}"
150+
StrokeThickness="0">
151+
<Border.StrokeShape>
152+
<RoundRectangle CornerRadius="12" />
153+
</Border.StrokeShape>
154+
<VerticalStackLayout Spacing="2">
155+
<Label
156+
FontAttributes="Bold"
157+
FontSize="14"
158+
Text="{Binding ViewModel.StatusInfoBar.Title, Mode=OneWay, Source={x:Reference ThisPopup}}" />
159+
<Label
160+
FontSize="13"
161+
LineBreakMode="WordWrap"
162+
Opacity="0.7"
163+
Text="{Binding ViewModel.StatusInfoBar.Message, Mode=OneWay, Source={x:Reference ThisPopup}}" />
164+
</VerticalStackLayout>
165+
</Border>
142166
</Grid>
143167
</Border>
144168
</tv:Popup>

src/Platforms/SecureFolderFS.Maui/Popups/CredentialsPopup.xaml.cs

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -110,13 +110,10 @@ private async void ResetViewButton_Click(object? sender, EventArgs e)
110110
button.IsEnabled = false;
111111
await Task.Delay(100);
112112

113-
await credentialsResetViewModel.ConfirmAsync(default);
114-
await HideAsync();
115-
}
116-
catch (Exception ex)
117-
{
118-
// TODO: Report to user
119-
_ = ex;
113+
var result = await credentialsResetViewModel.ConfirmAsync(default);
114+
ViewModel?.Report(result);
115+
if (result.Successful)
116+
await HideAsync();
120117
}
121118
finally
122119
{
@@ -137,13 +134,10 @@ private async void ConfirmationViewButton_Click(object? sender, EventArgs e)
137134
button.IsEnabled = false;
138135
await Task.Delay(100);
139136

140-
await credentialsConfirmation.ConfirmAsync(default);
141-
await HideAsync();
142-
}
143-
catch (Exception ex)
144-
{
145-
// TODO: Report to user
146-
_ = ex;
137+
var result = await credentialsConfirmation.ConfirmAsync();
138+
ViewModel?.Report(result);
139+
if (result.Successful)
140+
await HideAsync();
147141
}
148142
finally
149143
{

src/Platforms/SecureFolderFS.Uno/Dialogs/CredentialsDialog.xaml

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -219,22 +219,33 @@
219219
Click="GoBack_Click" />
220220
</ContentDialog.Title>
221221

222-
<ContentControl
223-
HorizontalContentAlignment="Stretch"
224-
VerticalContentAlignment="Stretch"
225-
Content="{x:Bind ViewModel.SelectedViewModel, Mode=OneWay}"
226-
IsTabStop="False">
227-
<ContentControl.ContentTemplateSelector>
228-
<ts:CredentialsTemplateSelector
229-
ConfirmationTemplate="{StaticResource ConfirmationTemplate}"
230-
LoginTemplate="{StaticResource LoginTemplate}"
231-
ResetTemplate="{StaticResource ResetTemplate}"
232-
SelectionTemplate="{StaticResource SelectionTemplate}" />
233-
</ContentControl.ContentTemplateSelector>
234-
<ContentControl.ContentTransitions>
235-
<TransitionCollection>
236-
<PopupThemeTransition />
237-
</TransitionCollection>
238-
</ContentControl.ContentTransitions>
239-
</ContentControl>
222+
<StackPanel Spacing="8">
223+
<ContentControl
224+
HorizontalContentAlignment="Stretch"
225+
VerticalContentAlignment="Stretch"
226+
Content="{x:Bind ViewModel.SelectedViewModel, Mode=OneWay}"
227+
IsTabStop="False">
228+
<ContentControl.ContentTemplateSelector>
229+
<ts:CredentialsTemplateSelector
230+
ConfirmationTemplate="{StaticResource ConfirmationTemplate}"
231+
LoginTemplate="{StaticResource LoginTemplate}"
232+
ResetTemplate="{StaticResource ResetTemplate}"
233+
SelectionTemplate="{StaticResource SelectionTemplate}" />
234+
</ContentControl.ContentTemplateSelector>
235+
<ContentControl.ContentTransitions>
236+
<TransitionCollection>
237+
<PopupThemeTransition />
238+
</TransitionCollection>
239+
</ContentControl.ContentTransitions>
240+
</ContentControl>
241+
242+
<!-- Status notification -->
243+
<InfoBar
244+
Title="{x:Bind ViewModel.StatusInfoBar.Title, Mode=OneWay}"
245+
HorizontalAlignment="Stretch"
246+
IsClosable="{x:Bind ViewModel.StatusInfoBar.IsCloseable, Mode=OneWay}"
247+
IsOpen="{x:Bind ViewModel.StatusInfoBar.IsOpen, Mode=TwoWay}"
248+
Message="{x:Bind ViewModel.StatusInfoBar.Message, Mode=OneWay}"
249+
Severity="{x:Bind ViewModel.StatusInfoBar.Severity, Mode=OneWay, Converter={StaticResource GenericEnumConverter}}" />
250+
</StackPanel>
240251
</ContentDialog>

src/Platforms/SecureFolderFS.Uno/Dialogs/CredentialsDialog.xaml.cs

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -66,32 +66,20 @@ private async void ContentDialog_PrimaryButtonClick(ContentDialog sender, Conten
6666

6767
case CredentialsResetViewModel credentialsReset:
6868
{
69-
try
70-
{
71-
await credentialsReset.ConfirmAsync(default);
69+
var result = await credentialsReset.ConfirmAsync(default);
70+
ViewModel.Report(result);
71+
if (result.Successful)
7272
await HideAsync();
73-
}
74-
catch (Exception ex)
75-
{
76-
// TODO: Report to user
77-
_ = ex;
78-
}
7973

8074
break;
8175
}
8276

8377
case CredentialsConfirmationViewModel credentialsConfirmation:
8478
{
85-
try
86-
{
87-
await credentialsConfirmation.ConfirmAsync(default);
79+
var result = await credentialsConfirmation.ConfirmAsync();
80+
ViewModel.Report(result);
81+
if (result.Successful)
8882
await HideAsync();
89-
}
90-
catch (Exception ex)
91-
{
92-
// TODO: Report to user
93-
_ = ex;
94-
}
9583

9684
break;
9785
}

src/Sdk/SecureFolderFS.Sdk/ViewModels/Views/Credentials/CredentialsConfirmationViewModel.cs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System.Threading;
55
using System.Threading.Tasks;
66
using CommunityToolkit.Mvvm.ComponentModel;
7-
using CommunityToolkit.Mvvm.Input;
87
using OwlCore.Storage;
98
using SecureFolderFS.Sdk.AppModels;
109
using SecureFolderFS.Sdk.Attributes;
@@ -47,13 +46,26 @@ public CredentialsConfirmationViewModel(IFolder vaultFolder, RegisterViewModel r
4746
RegisterViewModel.CredentialsProvided += RegisterViewModel_CredentialsProvided;
4847
}
4948

50-
[RelayCommand]
51-
public async Task ConfirmAsync(CancellationToken cancellationToken)
49+
/// <summary>
50+
/// Confirms the credentials by performing either a removal or modification operation, depending on the current context.
51+
/// </summary>
52+
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that cancels this action.</param>
53+
/// <returns>A <see cref="Task"/> that represents the asynchronous operation. Value is a result of type <see cref="IResult"/> indicating the success or failure of the operation.</returns>
54+
public async Task<IResult> ConfirmAsync(CancellationToken cancellationToken = default)
5255
{
53-
if (IsRemoving)
54-
await RemoveAsync(cancellationToken);
55-
else
56-
await ModifyAsync(cancellationToken);
56+
try
57+
{
58+
if (IsRemoving)
59+
await RemoveAsync(cancellationToken);
60+
else
61+
await ModifyAsync(cancellationToken);
62+
63+
return Result.Success;
64+
}
65+
catch (Exception ex)
66+
{
67+
return Result.Failure(ex);
68+
}
5769
}
5870

5971
private async Task ModifyAsync(CancellationToken cancellationToken)

src/Sdk/SecureFolderFS.Sdk/ViewModels/Views/Credentials/CredentialsResetViewModel.cs

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System.Threading;
66
using System.Threading.Tasks;
77
using CommunityToolkit.Mvvm.ComponentModel;
8-
using CommunityToolkit.Mvvm.Input;
98
using OwlCore.Storage;
109
using SecureFolderFS.Sdk.AppModels;
1110
using SecureFolderFS.Sdk.Attributes;
@@ -55,22 +54,35 @@ public async Task InitAsync(CancellationToken cancellationToken = default)
5554
RegisterViewModel.CurrentViewModel = AuthenticationOptions.FirstOrDefault();
5655
}
5756

58-
[RelayCommand]
59-
public async Task ConfirmAsync(CancellationToken cancellationToken)
57+
/// <summary>
58+
/// Confirms the credentials reset process and applies the updated authentication configuration for the vault.
59+
/// </summary>
60+
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that cancels this action.</param>
61+
/// <returns>A <see cref="Task"/> that represents the asynchronous operation. Value is an <see cref="IResult"/> indicating the success or failure of the confirmation process.</returns>
62+
public async Task<IResult> ConfirmAsync(CancellationToken cancellationToken)
6063
{
61-
ArgumentNullException.ThrowIfNull(RegisterViewModel.CurrentViewModel);
62-
RegisterViewModel.ConfirmCredentialsCommand.Execute(null);
63-
64-
var key = await _credentialsTcs.Task;
65-
var configuredOptions = await VaultService.GetVaultOptionsAsync(_vaultFolder, cancellationToken);
66-
var newOptions = configuredOptions with
64+
try
6765
{
68-
UnlockProcedure = new AuthenticationMethod([ RegisterViewModel.CurrentViewModel.Id ], null)
69-
};
66+
ArgumentNullException.ThrowIfNull(RegisterViewModel.CurrentViewModel);
67+
RegisterViewModel.ConfirmCredentialsCommand.Execute(null);
68+
69+
var key = await _credentialsTcs.Task;
70+
var configuredOptions = await VaultService.GetVaultOptionsAsync(_vaultFolder, cancellationToken);
71+
var newOptions = configuredOptions with
72+
{
73+
UnlockProcedure = new AuthenticationMethod([ RegisterViewModel.CurrentViewModel.Id ], null)
74+
};
7075

71-
await VaultManagerService.ModifyAuthenticationAsync(_vaultFolder, _unlockContract, key, newOptions, cancellationToken);
72-
if (!string.IsNullOrEmpty(configuredOptions.VaultId))
73-
PersistedCredentialsModel.Instance.Remove(configuredOptions.VaultId);
76+
await VaultManagerService.ModifyAuthenticationAsync(_vaultFolder, _unlockContract, key, newOptions, cancellationToken);
77+
if (!string.IsNullOrEmpty(configuredOptions.VaultId))
78+
PersistedCredentialsModel.Instance.Remove(configuredOptions.VaultId);
79+
80+
return Result.Success;
81+
}
82+
catch (Exception ex)
83+
{
84+
return Result.Failure(ex);
85+
}
7486
}
7587

7688
private void RegisterViewModel_CredentialsProvided(object? sender, CredentialsProvidedEventArgs e)

src/Sdk/SecureFolderFS.Sdk/ViewModels/Views/Overlays/CredentialsOverlayViewModel.cs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ namespace SecureFolderFS.Sdk.ViewModels.Views.Overlays
2121
{
2222
[Bindable(true)]
2323
[Inject<IVaultService>, Inject<IVaultCredentialsService>]
24-
public sealed partial class CredentialsOverlayViewModel : OverlayViewModel, IAsyncInitialize, IDisposable
24+
public sealed partial class CredentialsOverlayViewModel : OverlayViewModel, IAsyncInitialize, IProgress<IResult>, IDisposable
2525
{
2626
private readonly IFolder _vaultFolder;
2727
private readonly KeySequence _keySequence;
@@ -31,6 +31,7 @@ public sealed partial class CredentialsOverlayViewModel : OverlayViewModel, IAsy
3131
[ObservableProperty] private RegisterViewModel _RegisterViewModel;
3232
[ObservableProperty] private CredentialsSelectionViewModel _SelectionViewModel;
3333
[ObservableProperty] private INotifyPropertyChanged? _SelectedViewModel;
34+
[ObservableProperty] private InfoBarViewModel _StatusInfoBar = new();
3435

3536
public CredentialsOverlayViewModel(IFolder vaultFolder, string? vaultName, AuthenticationStage authenticationStage)
3637
{
@@ -47,10 +48,33 @@ public CredentialsOverlayViewModel(IFolder vaultFolder, string? vaultName, Authe
4748
PrimaryText = "Continue".ToLocalized();
4849

4950
LoginViewModel.VaultUnlocked += LoginViewModel_VaultUnlocked;
51+
LoginViewModel.StateChanged += LoginViewModel_StateChanged;
5052
RegisterViewModel.PropertyChanged += RegisterViewModel_PropertyChanged;
5153
SelectionViewModel.ConfirmationRequested += SelectionViewModel_ConfirmationRequested;
5254
}
5355

56+
/// <inheritdoc/>
57+
public void Report(IResult result)
58+
{
59+
if (result.Successful)
60+
{
61+
StatusInfoBar.IsOpen = false;
62+
return;
63+
}
64+
65+
StatusInfoBar.Title = "CredentialsChangeFailed".ToLocalized();
66+
StatusInfoBar.Message = result.GetMessage("UnknownError".ToLocalized());
67+
StatusInfoBar.Severity = Severity.Critical;
68+
StatusInfoBar.IsCloseable = true;
69+
StatusInfoBar.IsOpen = true;
70+
}
71+
72+
private void LoginViewModel_StateChanged(object? sender, EventArgs e)
73+
{
74+
if (e is ErrorReportedEventArgs args)
75+
Report(args.Result);
76+
}
77+
5478
/// <inheritdoc/>
5579
public async Task InitAsync(CancellationToken cancellationToken = default)
5680
{
@@ -118,6 +142,7 @@ public void Dispose()
118142
RegisterViewModel.PropertyChanged -= RegisterViewModel_PropertyChanged;
119143
SelectionViewModel.ConfirmationRequested -= SelectionViewModel_ConfirmationRequested;
120144
LoginViewModel.VaultUnlocked -= LoginViewModel_VaultUnlocked;
145+
LoginViewModel.StateChanged -= LoginViewModel_StateChanged;
121146
(SelectedViewModel as IDisposable)?.Dispose();
122147
SelectionViewModel.Dispose();
123148
LoginViewModel.Dispose();

0 commit comments

Comments
 (0)