Skip to content

Commit b1aa7d5

Browse files
authored
Merge pull request block-core#644
* Wire backend in Invest V2 * Fix typo in method names and update wallet context method in CreatePr…
1 parent 15b16ee commit b1aa7d5

Some content is hidden

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

41 files changed

+577
-431
lines changed
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
using Angor.Sdk.Wallet.Domain;
2+
using CSharpFunctionalExtensions;
23

34
namespace AngorApp.Model.Amounts;
45

5-
public class AmountUI(long sats, string symbol = "BTC") : IAmountUI
6+
public class AmountUI(long sats, string symbol = "BTC") : ValueObject, IAmountUI
67
{
78
public long Sats { get; } = sats;
89
public string Symbol { get; } = symbol;
910

1011
public static AmountUI FromBtc(int btc) => new(btc * 100_000_000);
1112
public static AmountUI FromBtc(decimal btc) => new((long)(btc * 100_000_000));
1213
public static AmountUI FromBtc(double btc) => new((long)(btc * 100_000_000));
14+
protected override IEnumerable<object> GetEqualityComponents()
15+
{
16+
yield return Sats;
17+
}
1318
}

src/Angor/Avalonia/AngorApp.Model/Contracts/Wallet/IWallet.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ public interface IWallet
1919
public IEnhancedCommand<Result<string>> GetReceiveAddress { get; }
2020
public Task<Result<string>> GenerateReceiveAddress();
2121
public IEnhancedCommand<Result> GetTestCoins { get; }
22+
public DateTimeOffset CreatedOn { get; }
2223
}

src/Angor/Avalonia/AngorApp.Model/Wallet/Simple/SimpleWallet.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ private RefreshableCollection<IBroadcastedTransaction, string> CreateTransaction
9494
public IEnhancedCommand<Result<IEnumerable<IBroadcastedTransaction>>> Load { get; }
9595
public ReadOnlyObservableCollection<IBroadcastedTransaction> History { get; }
9696
public IEnhancedCommand<Result> GetTestCoins { get; }
97+
public DateTimeOffset CreatedOn { get; } = DateTimeOffset.MinValue;
9798

9899
public Result IsAddressValid(string address)
99100
{

src/Angor/Avalonia/AngorApp/Composition/Registrations/ViewModels/ViewModels.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public static IServiceCollection AddViewModels(this IServiceCollection services)
4040
.AddTransient<IFounderSectionViewModel, FounderSectionViewModel>()
4141
.AddTransient<ISettingsSectionViewModel, SettingsSectionViewModel>()
4242
.AddScoped<IPenaltiesViewModel, PenaltiesViewModel>()
43-
.AddScoped<IInvestViewModel, InvestViewModel>()
43+
.AddScoped<Func<IFullProject, IInvestViewModel>>(provider => proj => ActivatorUtilities.CreateInstance<InvestViewModel>(provider, proj))
4444
.AddScoped<IPaymentSelectorViewModel, PaymentSelectorViewModel>()
4545
.AddScoped<IRecoverViewModel, RecoverViewModel>()
4646
.AddSingleton<IShellViewModel, ShellViewModel>();

src/Angor/Avalonia/AngorApp/UI/Flows/CreateProject/CreateProjectFlow.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ ILogger logger
3232
{
3333
public Task<Result<Maybe<string>>> CreateProject()
3434
{
35-
return from wallet in walletContext.GetDefaultWallet()
35+
return from wallet in walletContext.Require()
3636
from seed in GetProjectSeed(wallet.Id)
3737
from creationResult in Create(wallet.Id, seed)
3838
select creationResult;
@@ -43,7 +43,7 @@ private async Task<Result<Maybe<string>>> Create(WalletId walletId, ProjectSeedD
4343
SlimWizard<string> rootWizard = WizardBuilder
4444
.StartWith(() => new WelcomeViewModel()).NextCommand(model => model.Start)
4545
.Then(_ => new ProjectTypeViewModel())
46-
.NextCommand<Unit, ProjectTypeViewModel, string>(vm => CreateProjectOftype(
46+
.NextCommand<Unit, ProjectTypeViewModel, string>(vm => CreateProjectOfType(
4747
vm,
4848
walletId,
4949
seed))
@@ -53,7 +53,7 @@ private async Task<Result<Maybe<string>>> Create(WalletId walletId, ProjectSeedD
5353
return await rootWizard.Navigate(navigator);
5454
}
5555

56-
private IEnhancedCommand<Result<string>> CreateProjectOftype(
56+
private IEnhancedCommand<Result<string>> CreateProjectOfType(
5757
ProjectTypeViewModel vm,
5858
WalletId walletId,
5959
ProjectSeedDto seed

src/Angor/Avalonia/AngorApp/UI/Flows/Invest/Amount/AmountViewModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public AmountViewModel(IWallet wallet, FullProject project)
4141

4242
stageBreakdownsHelper = this.WhenAnyValue(model => model.Amount)
4343
.WhereNotNull()
44-
.Select(investAmount => project.Stages.Select(stage => new Breakdown(stage.Index, new AmountUI(investAmount!.Value), stage.RatioOfTotal, stage.ReleaseDate)))
44+
.Select(investAmount => project.Stages.Select(stage => new Breakdown(new AmountUI(investAmount!.Value), stage.RatioOfTotal, stage.ReleaseDate)))
4545
.ToProperty(this, x => x.StageBreakdowns);
4646

4747
requiresFounderApprovalHelper = this.WhenAnyValue(model => model.Amount)

src/Angor/Avalonia/AngorApp/UI/Flows/Invest/Amount/AmountViewModelSample.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ public class AmountViewModelSample : IAmountViewModel
88

99
public IEnumerable<Breakdown> StageBreakdowns { get; } = new List<Breakdown>
1010
{
11-
new(1, new AmountUI(120), 0.2m, DateTime.Now),
12-
new(2, new AmountUI(120), 0.4m, DateTime.Now.AddMonths(1)),
13-
new(3, new AmountUI(120), 0.6m, DateTime.Now.AddMonths(2).AddDays(5)),
11+
new(new AmountUI(120), 0.2m, DateTime.Now),
12+
new(new AmountUI(120), 0.4m, DateTime.Now.AddMonths(1)),
13+
new(new AmountUI(120), 0.6m, DateTime.Now.AddMonths(2).AddDays(5)),
1414
};
1515

1616
public IObservable<bool> IsValid { get; } = Observable.Return(true);
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
namespace AngorApp.UI.Flows.Invest.Amount;
22

3-
public record Breakdown(int Index, IAmountUI InvestAmount, decimal RatioOfTotal, DateTimeOffset ReleaseDate)
3+
public record Breakdown(IAmountUI InvestAmount, decimal RatioOfTotal, DateTimeOffset ReleaseDate)
44
{
55
public IAmountUI Amount => new AmountUI((long)(InvestAmount.Sats * RatioOfTotal));
66
}

src/Angor/Avalonia/AngorApp/UI/Flows/InvestV2/Footer/FooterView.axaml

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,38 +3,37 @@
33
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
44
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
55
xmlns:l="clr-namespace:AngorApp.UI.Flows.InvestV2.Footer"
6-
xmlns:angorApp="clr-namespace:AngorApp"
76
xmlns:investV2="clr-namespace:AngorApp.UI.Flows.InvestV2"
8-
mc:Ignorable="d" d:DesignWidth="800"
7+
mc:Ignorable="d" d:DesignWidth="800"
98
x:Class="AngorApp.UI.Flows.InvestV2.Footer.FooterView"
109
x:DataType="l:IFooterViewModel">
11-
10+
1211
<Design.DataContext>
1312
<investV2:InvestViewModelSample />
1413
</Design.DataContext>
15-
14+
1615
<UserControl.Styles>
1716
<Style Selector="Border.Shadow">
1817
<Setter Property="BoxShadow" Value="{StaticResource CardShadow}" />
1918
</Style>
2019
<Style Selector="Border">
2120
<Setter Property="CornerRadius" Value="10" />
2221
<Setter Property="BorderThickness" Value="1" />
23-
<Setter Property="Background" Value="White"></Setter>
24-
<Setter Property="Padding" Value="10"></Setter>
22+
<Setter Property="Background" Value="White" />
23+
<Setter Property="Padding" Value="10" />
2524
<Setter Property="BoxShadow" Value="{StaticResource SectionShadow}" />
2625
</Style>
2726
</UserControl.Styles>
28-
27+
2928
<StackPanel Spacing="30" Margin="10" HorizontalAlignment="Right" Orientation="Horizontal">
3029
<TextBlock DockPanel.Dock="Right" VerticalAlignment="Center">
3130
<Run>Total (incl. fees):</Run>
32-
<Run Foreground="Black" FontWeight="Bold" Text="{Binding AmountToInvest.DecimalString}" />
31+
<Run Foreground="Black" FontWeight="Bold" Text="{Binding AmountToInvest.Value.DecimalString}" />
3332
</TextBlock>
3433
<TextBlock DockPanel.Dock="Right" VerticalAlignment="Center">
35-
<Run>Total Raised:</Run>
36-
<Run Foreground="Black" FontWeight="Bold" Text="{Binding NumberOfReleases, StringFormat='{}{0} releases'}" />
34+
<Run>Stages:</Run>
35+
<Run Foreground="Black" FontWeight="Bold" Text="{Binding StageCount, StringFormat='{}{0} releases'}" />
3736
</TextBlock>
38-
<EnhancedButton Command="{Binding Invest}" DockPanel.Dock="Right" Classes="Outline" Icon="{Icon fa-chart-line}" Content="Invest Now" />
37+
<EnhancedButton Command="{Binding Invest}" DockPanel.Dock="Right" Classes="Outline" Icon="{Icon fa-chart-line}" Content="Invest" />
3938
</StackPanel>
4039
</UserControl>
Lines changed: 84 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,91 @@
1-
using Zafiro.Avalonia.Dialogs;
2-
1+
using System.Linq;
2+
using Angor.Sdk.Funding.Investor;
3+
using AngorApp.UI.Flows.InvestV2.Invoice;
34
using AngorApp.UI.Flows.InvestV2.PaymentSelector;
45
using AngorApp.UI.Shell;
6+
using Reactive.Bindings;
7+
using Zafiro.Avalonia.Dialogs;
8+
using ReactiveCommand = ReactiveUI.ReactiveCommand;
59

6-
namespace AngorApp.UI.Flows.InvestV2.Footer;
7-
8-
public class FooterViewModel : IFooterViewModel
10+
namespace AngorApp.UI.Flows.InvestV2.Footer
911
{
10-
private readonly UIServices uiServices;
11-
public IAmountUI AmountToInvest { get; } = AmountUI.FromBtc(0.4m);
12-
public int NumberOfReleases { get; } = 1;
13-
public IEnhancedCommand<bool> Invest { get; }
14-
15-
public FooterViewModel(UIServices uiServices, IShellViewModel shell)
12+
public class FooterViewModel : IFooterViewModel
1613
{
17-
this.uiServices = uiServices;
18-
Invest = ReactiveCommand.CreateFromTask(() => uiServices.Dialog.Show(new PaymentSelectorViewModel(uiServices, shell), "Select Wallet", (model, closeable) => model.Options(closeable))).Enhance();
14+
private readonly IFullProject fullProject;
15+
private readonly IInvestmentAppService investmentAppService;
16+
17+
public FooterViewModel(
18+
IFullProject fullProject,
19+
IObservable<IAmountUI> amountToInvest,
20+
UIServices uiServices,
21+
IInvestmentAppService investmentAppService,
22+
IShellViewModel shell,
23+
IWalletContext walletContext
24+
)
25+
{
26+
this.fullProject = fullProject;
27+
this.investmentAppService = investmentAppService;
28+
AmountToInvest = new ReadOnlyReactiveProperty<IAmountUI>(amountToInvest);
29+
Invest = ReactiveCommand.CreateFromTask(
30+
() => InvestFlow(uiServices, shell, walletContext),
31+
AmountToInvest.Select(IsValidInvest)).Enhance();
32+
}
33+
34+
public IReadOnlyReactiveProperty<IAmountUI> AmountToInvest { get; }
35+
public IEnhancedCommand Invest { get; }
36+
public IAmountUI TotalRaised => fullProject.RaisedAmount;
37+
public int StageCount => fullProject.Stages.Count();
38+
39+
private static bool IsValidInvest(IAmountUI ui)
40+
{
41+
return ui.Sats >= 10000;
42+
}
43+
44+
private async Task<Result<Maybe<string>>> InvestFlow(
45+
UIServices uiServices,
46+
IShellViewModel shell,
47+
IWalletContext walletContext
48+
)
49+
{
50+
if (walletContext.Wallets.Count == 0)
51+
{
52+
return await walletContext.GetOrCreate().Map(wallet => uiServices.Dialog.ShowAndGetResult(
53+
new InvoiceViewModel(wallet),
54+
"Select Wallet",
55+
model => model.IsValid,
56+
_ => ""));
57+
}
58+
59+
IWallet wallet = ChooseWallet(walletContext);
60+
61+
if (HasEnoughBalance(wallet))
62+
{
63+
bool show = await uiServices.Dialog.Show(new InvoiceViewModel(wallet), "Select Wallet", _ => []);
64+
return Maybe.From(show ? "" : null);
65+
}
66+
67+
return await uiServices.Dialog.ShowAndGetResult(
68+
new PaymentSelectorViewModel(fullProject.ProjectId, uiServices, shell, investmentAppService, walletContext, AmountToInvest.Value, wallet),
69+
"Select Wallet",
70+
(model, closeable) => model.Options(closeable),
71+
_ => "");
72+
}
73+
74+
private bool HasEnoughBalance(IWallet wallet)
75+
{
76+
return wallet.Balance.Sats < AmountToInvest.Value.Sats;
77+
}
78+
79+
private IWallet ChooseWallet(IWalletContext walletContext)
80+
{
81+
Maybe<IWallet> enoughBalance = walletContext.Wallets
82+
.Where(w => w.Balance.Sats >= AmountToInvest.Value.Sats)
83+
.OrderByDescending(w => w.CreatedOn)
84+
.TryFirst();
85+
86+
IWallet last = walletContext.Wallets.OrderByDescending(w => w.CreatedOn).Last();
87+
88+
return enoughBalance.GetValueOrDefault(last);
89+
}
1990
}
2091
}

0 commit comments

Comments
 (0)