Skip to content

Commit 1536b0e

Browse files
authored
Fixes exceptions when using Maybe.Value in ViewModels (#257)
* Fix usage of Maybe in ViewModels * Fix usage of Maybe in ProjectLookup
1 parent 631bea9 commit 1536b0e

File tree

9 files changed

+80
-38
lines changed

9 files changed

+80
-38
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using CSharpFunctionalExtensions;
2+
3+
namespace AngorApp.Core;
4+
5+
public class SafeMaybe<T>(Maybe<T> maybe)
6+
{
7+
public Maybe<T> Maybe { get; } = maybe;
8+
public T? Value => Maybe.GetValueOrDefault();
9+
public bool HasValue => Maybe.HasValue;
10+
public bool HasNoValue => !Maybe.HasValue;
11+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using CSharpFunctionalExtensions;
2+
3+
namespace AngorApp.Core;
4+
5+
public static class SafeMaybeExtensions
6+
{
7+
public static SafeMaybe<T> AsSafeMaybe<T>(this Maybe<T> maybe) =>
8+
new SafeMaybe<T>(maybe);
9+
10+
public static SafeMaybe<T> AsSafeMaybe<T>(this T? obj) where T : class =>
11+
obj.AsMaybe().AsSafeMaybe();
12+
}

src/Angor/Avalonia/AngorApp/Sections/Browse/ProjectLookup/IProjectLookupViewModel.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Windows.Input;
2+
using AngorApp.Core;
23
using CSharpFunctionalExtensions;
34

45
namespace AngorApp.Sections.Browse.ProjectLookup;
@@ -9,7 +10,7 @@ public interface IProjectLookupViewModel
910
public IProjectViewModel SelectedProject { get; set; }
1011
public IObservable<bool> IsBusy { get; }
1112

12-
ReactiveCommand<string, Maybe<IList<IProjectViewModel>>> Lookup { get; }
13-
Maybe<IList<IProjectViewModel>> LookupResults { get; }
13+
ReactiveCommand<string, SafeMaybe<IList<IProjectViewModel>>> Lookup { get; }
14+
SafeMaybe<IList<IProjectViewModel>> LookupResults { get; }
1415
public ICommand GoToSelectedProject { get; }
1516
}

src/Angor/Avalonia/AngorApp/Sections/Browse/ProjectLookup/ProjectLookupViewModel.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Reactive.Linq;
44
using System.Windows.Input;
55
using Angor.UI.Model;
6+
using AngorApp.Core;
67
using AngorApp.Services;
78
using CSharpFunctionalExtensions;
89
using ReactiveUI.SourceGenerators;
@@ -16,7 +17,7 @@ public partial class ProjectLookupViewModel : ReactiveObject, IProjectLookupView
1617
{
1718
[ObservableAsProperty] private ICommand? goToSelectedProject;
1819

19-
[ObservableAsProperty] private Maybe<IList<IProjectViewModel>> lookupResults;
20+
[ObservableAsProperty] private SafeMaybe<IList<IProjectViewModel>> lookupResults;
2021

2122
[Reactive] private string? projectId;
2223
[Reactive] private IProjectViewModel? selectedProject;
@@ -27,7 +28,9 @@ public ProjectLookupViewModel(
2728
INavigator navigator,
2829
UIServices uiServices)
2930
{
30-
Lookup = ReactiveCommand.CreateFromTask<string, Maybe<IList<IProjectViewModel>>>(
31+
lookupResults = new SafeMaybe<IList<IProjectViewModel>>(Maybe<IList<IProjectViewModel>>.None);
32+
33+
Lookup = ReactiveCommand.CreateFromTask<string, SafeMaybe<IList<IProjectViewModel>>>(
3134
async pid =>
3235
{
3336
var maybeProject = await projectService.FindById(pid);
@@ -37,7 +40,7 @@ public ProjectLookupViewModel(
3740
{
3841
var vm = new ProjectViewModel(walletProvider, project, navigator, uiServices);
3942
return new List<IProjectViewModel> { vm };
40-
});
43+
}).AsSafeMaybe();
4144
}
4245
);
4346

@@ -51,12 +54,12 @@ public ProjectLookupViewModel(
5154
.Do(pid => Log.Debug("Search for ProjectId {ProjectId}", pid))
5255
.InvokeCommand(Lookup!);
5356

54-
this.WhenAnyValue(x => x.LookupResults).Values().Do(x => SelectedProject = x.FirstOrDefault()).Subscribe();
57+
this.WhenAnyValue(x => x.LookupResults).Select(x => x.Maybe).Values().Do(x => SelectedProject = x.FirstOrDefault()).Subscribe();
5558

5659
goToSelectedProjectHelper = this.WhenAnyValue(x => x.SelectedProject!.GoToDetails).ToProperty(this, x => x.GoToSelectedProject);
5760
}
5861

59-
public ReactiveCommand<string, Maybe<IList<IProjectViewModel>>> Lookup { get; }
62+
public ReactiveCommand<string, SafeMaybe<IList<IProjectViewModel>>> Lookup { get; }
6063

6164
public IObservable<bool> IsBusy { get; }
6265
}

src/Angor/Avalonia/AngorApp/Sections/Browse/ProjectLookup/ProjectLookupViewModelDesign.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Linq;
22
using System.Reactive.Linq;
33
using System.Windows.Input;
4+
using AngorApp.Core;
45
using CSharpFunctionalExtensions;
56
using ReactiveUI.SourceGenerators;
67

@@ -10,21 +11,21 @@ public partial class ProjectLookupViewModelDesign : ReactiveObject, IProjectLook
1011
{
1112
private bool hasResults;
1213

13-
[Reactive] private Maybe<IList<IProjectViewModel>> lookupResults;
14+
[Reactive] private SafeMaybe<IList<IProjectViewModel>> lookupResults;
1415

1516
public bool HasResults
1617
{
1718
get => hasResults;
1819
set
1920
{
20-
var asMaybe = SampleData.GetProjects().Select(project => (IProjectViewModel)new ProjectViewModelDesign(project)).ToList().AsMaybe<IList<IProjectViewModel>>();
21-
LookupResults = hasResults ? asMaybe : Maybe<IList<IProjectViewModel>>.None;
21+
var asMaybe = SampleData.GetProjects().Select(project => (IProjectViewModel)new ProjectViewModelDesign(project)).ToList().AsSafeMaybe<IList<IProjectViewModel>>();
22+
LookupResults = hasResults ? asMaybe : new SafeMaybe<IList<IProjectViewModel>>(Maybe<IList<IProjectViewModel>>.None);
2223
hasResults = value;
2324
}
2425
}
2526

2627
public IObservable<bool> IsBusy { get; set; } = Observable.Return(false);
27-
public ReactiveCommand<string, Maybe<IList<IProjectViewModel>>> Lookup { get; }
28+
public ReactiveCommand<string, SafeMaybe<IList<IProjectViewModel>>> Lookup { get; }
2829
public ICommand GoToSelectedProject { get; }
2930

3031
public string? ProjectId { get; set; }

src/Angor/Avalonia/AngorApp/Sections/Wallet/CreateAndRecover/Steps/SeedWordsGeneration/GeneratedWords.axaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
33
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
44
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
5-
xmlns:generic="clr-namespace:System.Collections.Generic;assembly=System.Collections"
65
xmlns:f="clr-namespace:Zafiro.Avalonia.Misc;assembly=Zafiro.Avalonia"
76
xmlns:converters="clr-namespace:Zafiro.Avalonia.Converters;assembly=Zafiro.Avalonia"
87
xmlns:seedWordsGeneration="clr-namespace:AngorApp.Sections.Wallet.CreateAndRecover.Steps.SeedWordsGeneration"
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
using Angor.UI.Model;
2-
using CSharpFunctionalExtensions;
2+
using AngorApp.Core;
33
using Zafiro.Avalonia.Controls.Wizards.Builder;
44

55
namespace AngorApp.Sections.Wallet.CreateAndRecover.Steps.SeedWordsGeneration;
66

77
public interface ISeedWordsViewModel : IStep
88
{
9-
Maybe<SeedWords> Words { get; }
10-
ReactiveCommand<Unit, Maybe<SeedWords>> GenerateWords { get; }
9+
SafeMaybe<SeedWords> Words { get; }
10+
ReactiveCommand<Unit, SafeMaybe<SeedWords>> GenerateWords { get; }
1111
bool AreWordsWrittenDown { get; set; }
1212
}

src/Angor/Avalonia/AngorApp/Sections/Wallet/CreateAndRecover/Steps/SeedWordsGeneration/SeedWordsViewModel.cs

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,44 @@
11
using System.Reactive.Linq;
22
using System.Threading.Tasks;
33
using Angor.UI.Model;
4-
using AngorApp.Sections.Browse;
4+
using AngorApp.Core;
55
using AngorApp.Services;
66
using CSharpFunctionalExtensions;
7-
using CSharpFunctionalExtensions.ValueTasks;
87
using ReactiveUI.SourceGenerators;
98
using ReactiveUI.Validation.Helpers;
109
using Zafiro.Avalonia.Dialogs;
11-
using Zafiro.CSharpFunctionalExtensions;
10+
using SampleData = AngorApp.Sections.Browse.SampleData;
1211

1312
namespace AngorApp.Sections.Wallet.CreateAndRecover.Steps.SeedWordsGeneration;
1413

1514
public partial class SeedWordsViewModel : ReactiveValidationObject, ISeedWordsViewModel
1615
{
1716
public SeedWordsViewModel(UIServices uiServices)
1817
{
19-
GenerateWords = ReactiveCommand.CreateFromTask(() =>
18+
// Start without seed words
19+
words = new SafeMaybe<SeedWords>(Maybe<SeedWords>.None);
20+
21+
GenerateWords = ReactiveCommand.CreateFromTask(async () =>
2022
{
21-
return words.Match(
22-
async wordList =>
23-
{
24-
var dialogResult = await ShowConfirmation(uiServices);
25-
return dialogResult.Match(confirmed => confirmed ? CreateNewWords() : wordList, () => wordList).AsMaybe();
26-
},
27-
() => Task.FromResult(CreateNewWords().AsMaybe())
28-
);
23+
if (words.HasValue)
24+
{
25+
// Existing words? ask for others
26+
var dialogResult = await ShowConfirmation(uiServices);
27+
var newWords = dialogResult.Match(
28+
confirmed => confirmed ? CreateNewWords() : words.Value,
29+
() => words.Value);
30+
return newWords.AsSafeMaybe();
31+
}
32+
else
33+
{
34+
// No seedwords? Create them
35+
return CreateNewWords().AsSafeMaybe();
36+
}
2937
});
3038

31-
GenerateWords.Values().Do(_ => AreWordsWrittenDown = false).Subscribe();
39+
GenerateWords
40+
.Do(_ => AreWordsWrittenDown = false)
41+
.Subscribe();
3242

3343
wordsHelper = GenerateWords.ToProperty(this, x => x.Words);
3444
}
@@ -40,16 +50,20 @@ private static SeedWords CreateNewWords()
4050

4151
private static Task<Maybe<bool>> ShowConfirmation(UIServices uiServices)
4252
{
43-
return uiServices.Dialog.ShowConfirmation("Do you want to generate new seed words?",
53+
return uiServices.Dialog.ShowConfirmation(
54+
"Do you want to generate new seed words?",
4455
"You will see a different set of seed words. Make sure not to mix them with the ones you just saw and write down only the new ones.");
4556
}
4657

47-
public ReactiveCommand<Unit,Maybe<SeedWords>> GenerateWords { get; }
48-
58+
public ReactiveCommand<Unit, SafeMaybe<SeedWords>> GenerateWords { get; }
59+
4960
[Reactive] private bool areWordsWrittenDown;
5061

51-
[ObservableAsProperty] private Maybe<SeedWords> words;
52-
public IObservable<bool> IsValid => this.WhenAnyValue<SeedWordsViewModel, bool, bool, Maybe<SeedWords>>(x => x.AreWordsWrittenDown, x => x.Words, (written, maybeWords) => written && maybeWords.HasValue);
62+
[ObservableAsProperty] private SafeMaybe<SeedWords> words;
63+
64+
public IObservable<bool> IsValid => this.WhenAnyValue(x => x.AreWordsWrittenDown, x => x.Words,
65+
(written, safeWords) => written && safeWords.HasValue);
66+
5367
public IObservable<bool> IsBusy => Observable.Return(false);
5468
public bool AutoAdvance => false;
5569

src/Angor/Avalonia/AngorApp/Sections/Wallet/CreateAndRecover/Steps/SeedWordsGeneration/SeedWordsViewModelDesign.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
using System.Reactive.Linq;
22
using Angor.UI.Model;
3-
using AngorApp.Sections.Browse;
3+
using AngorApp.Core;
44
using CSharpFunctionalExtensions;
55
using ReactiveUI.SourceGenerators;
6+
using SampleData = AngorApp.Sections.Browse.SampleData;
67

78
namespace AngorApp.Sections.Wallet.CreateAndRecover.Steps.SeedWordsGeneration;
89

@@ -13,7 +14,7 @@ public partial class SeedWordsViewModelDesign : ReactiveObject, ISeedWordsViewMo
1314
public IObservable<bool> IsBusy { get; } = Observable.Return(false);
1415
public bool AutoAdvance => false;
1516

16-
[Reactive] private Maybe<SeedWords> words = Maybe<SeedWords>.None;
17+
[Reactive] private SafeMaybe<SeedWords> words = new(Maybe<SeedWords>.None);
1718

1819
public bool HasWords
1920
{
@@ -25,11 +26,11 @@ public bool HasWords
2526

2627
if (value)
2728
{
28-
Words = SampleData.Seedwords.AsMaybe();
29+
Words = SampleData.Seedwords.AsSafeMaybe();
2930
}
3031
else
3132
{
32-
Words = Maybe<SeedWords>.None;
33+
Words = Maybe<SeedWords>.None.AsSafeMaybe();
3334
}
3435

3536
hasWords = value;
@@ -38,6 +39,6 @@ public bool HasWords
3839
}
3940
}
4041

41-
public ReactiveCommand<Unit, Maybe<SeedWords>> GenerateWords { get; }
42+
public ReactiveCommand<Unit, SafeMaybe<SeedWords>> GenerateWords { get; }
4243
[Reactive] private bool areWordsWrittenDown;
4344
}

0 commit comments

Comments
 (0)