8000 Improve "Invest in project" for Avalonia App by SuperJMN · Pull Request #442 · block-core/angor · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Improve "Invest in project" for Avalonia App #442

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Jun 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
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
8000
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public async Task Create_investment_draft()
var projectId = new ProjectId("angor1qkmmqqktfhe79wxp20555cdp5gfardr4s26wr00");

// Act
var result = await sut.CreateInvestmentDraft(Guid.Empty, projectId, new Amount(12345));
var result = await sut.CreateInvestmentDraft(Guid.Empty, projectId, new Amount(12345), new DomainFeerate(123));

// Assert
Assert.True(result.IsSuccess, result.IsFailure ? result.Error : string.Empty);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace Angor.Contexts.Funding.Investor;

public interface IInvestmentAppService
{
Task<Result<CreateInvestment.Draft>> CreateInvestmentDraft(Guid sourceWalletId, ProjectId projectId, Amount amount);
Task<Result<CreateInvestment.Draft>> CreateInvestmentDraft(Guid sourceWalletId, ProjectId projectId, Amount amount, DomainFeerate feerate);
Task<Result<Guid>> Invest(Guid sourceWalletId, ProjectId projectId, CreateInvestment.Draft draft);
Task<Result<IEnumerable<Investment>>> GetInvestments(Guid walletId, ProjectId projectId);
Task<Result> ApproveInvestment(Guid walletId, ProjectId projectId, Investment investment);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ namespace Angor.Contexts.Funding.Investor;

public class InvestmentAppService(IInvestmentRepository investmentRepository, IMediator mediator) : IInvestmentAppService
{
public Task<Result<CreateInvestment.Draft>> CreateInvestmentDraft(Guid sourceWalletId, ProjectId projectId, Amount amount)
public Task<Result<CreateInvestment.Draft>> CreateInvestmentDraft(Guid sourceWalletId, ProjectId projectId, Amount amount, DomainFeerate feerate)
{
return mediator.Send(new CreateInvestment.CreateInvestmentTransactionRequest(sourceWalletId, projectId, amount));
return mediator.Send(new CreateInvestment.CreateInvestmentTransactionRequest(sourceWalletId, projectId, amount, feerate));
}

public Task<Result<Guid>> Invest(Guid sourceWalletId, ProjectId projectId, CreateInvestment.Draft draft)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,23 @@ public class CreateInvestmentTransactionRequest : IRequest<Result<Draft>>
public Guid WalletId { get; }
public ProjectId ProjectId { get; }
public Amount Amount { get; }
public DomainFeerate Feerate { get; }

public CreateInvestmentTransactionRequest(Guid walletId, ProjectId projectId, Amount amount)
public CreateInvestmentTransactionRequest(Guid walletId, ProjectId projectId, Amount amount, DomainFeerate feerate)
{
WalletId = walletId;
ProjectId = projectId;
Amount = amount;
Feerate = feerate;
}
}

public record Draft(string InvestorKey, string SignedTxHex, string TransactionId, Amount TotalFee);

public record Draft(string InvestorKey, string SignedTxHex, string TransactionId, Amount TransactionFee)
{
public Amount MinerFee { get; set; } = new Amount(-1);
public Amount AngorFee { get; set; } = new Amount(-1);
}

public class CreateInvestmentTransactionHandler(
IProjectRepository projectRepository,
IInvestorTransactionActions investorTransactionActions,
Expand All @@ -40,6 +46,10 @@ public async Task<Result<Draft>> Handle(CreateInvestmentTransactionRequest trans
{
try
{
// TODO: Don't forget to use the feerate to create the Draft. It's not used currently.
// feerate is the feerate that the user selected as "transaction speed" in the UI.
var feerate = transactionRequest.Feerate;

// Get the project and investor key
var projectResult = await projectRepository.Get(transactionRequest.ProjectId);
if (projectResult.IsFailure)
Expand Down Expand Up @@ -77,7 +87,12 @@ public async Task<Result<Draft>> Handle(CreateInvestmentTransactionRequest trans
return new Draft(investorKey,
signedTxHex,
signedTxResult.Value.Transaction.GetHash().ToString(),
new Amount(totalFee));
new Amount(totalFee))
{
// TODO: Please, fill this with the correct values
// MinerFee = new Amount(1234),
// AngorFee = new Amount(5678),
};
}
catch (Exception ex)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
namespace Angor.Contexts.Funding.Projects.Domain;

public record class ModelFeeRate;
public record DomainFeerate(long SatsPerVByte);
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,13 @@ public static Project ToProject(this ProjectData data)
project.Stages = data.ProjectInfo.Stages
.Select((stage, index) =>
{
var stageAmountToRelease = (long)stage.AmountToRelease * 1_0000_0000;
var stageRatio = (double)stage.AmountToRelease / 100;
return new Stage
{
ReleaseDate = stage.ReleaseDate,
Amount = stageAmountToRelease,
Amount = (long)stage.AmountToRelease * 1_0000_0000,
Index = index + 1,
RatioOfTotal = (double)stageAmountToRelease / data.ProjectInfo.TargetAmount,
RatioOfTotal = stageRatio,
};
})
.ToList();
Expand Down
1 change: 0 additions & 1 deletion src/Angor/Avalonia/AngorApp/AngorApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
<PackageReference Include="Avalonia.Svg" />
<PackageReference Include="Avalonia.Themes.Fluent" />
<PackageReference Include="Avalonia.Fonts.Inter" />
<PackageReference Include="Avalonia.Xaml.Behaviors" />
<PackageReference Include="AvaloniaUI.DiagnosticsSupport" />
<PackageReference Include="CSharpFunctionalExtensions" />
<PackageReference Include="Humanizer.Core" />
Expand Down
10 changes: 0 additions & 10 deletions src/Angor/Avalonia/AngorApp/App.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@
<StyleInclude Source="avares://Zafiro.Avalonia.Dialogs/Styles.axaml" />
<StyleInclude Source="avares://AsyncImageLoader.Avalonia/AdvancedImage.axaml" />
<StyleInclude Source="UI/Controls/Pane.axaml" />
<StyleInclude Source="UI/Controls/Dialog.axaml" />
<StyleInclude Source="UI/Controls/TextBox.axaml" />
<StyleInclude Source="UI/Controls/AmountControl.axaml" />
<StyleInclude Source="UI/Controls/List.axaml" />
Expand All @@ -81,15 +80,6 @@
<ResourceDictionary>

<IconDataTemplate x:Key="IconDataTemplate" Converter="{x:Static f:AngorIconConverter.Instance}" />

<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Light">
<ImageBrush x:Key="AppBackground" Source="avares://AngorApp/Assets/darkbg.jpg" />
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<ImageBrush Stretch="UniformToFill" x:Key="AppBackground" Source="avares://AngorApp/Assets/darkbg.jpg" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source="avares://FluentAvalonia/Styling/ControlThemes/FAControls/ProgressRingStyles.axaml" />
<ResourceInclude Source="avares://FluentAvalonia/Styling/ControlThemes/FAControls/NumberBoxStyles.axaml" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Angor.Shared;
using AngorApp.UI.Services;
using Avalonia.Controls.Notifications;
using Avalonia.Controls.Primitives;
using Microsoft.Extensions.DependencyInjection;
using Zafiro.Avalonia.Dialogs;
using Zafiro.Avalonia.Services;
Expand All @@ -23,7 +24,11 @@ public static IServiceCollection Register(this IServiceCollection services, Cont

return services
.AddSingleton<ILauncherService>(_ => new LauncherService(topLevel!.Launcher))
.AddSingleton(DialogService.Create())
.AddSingleton<IDialog>(new AdornerDialog(() =>
{
var adornerLayer = AdornerLayer.GetAdornerLayer(parent);
return adornerLayer!;
}))
.AddSingleton<IActiveWallet, ActiveWallet>()
.AddSingleton(sp => new ShellProperties("Angor", o =>
{
Expand Down
38 changes: 19 additions & 19 deletions src/Angor/Avalonia/AngorApp/Features/Invest/Amount/AmountView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:behaviors="clr-namespace:Zafiro.Avalonia.Behaviors;assembly=Zafiro.Avalonia"
xmlns:avalonia="https://github.com/projektanker/icons.avalonia"
xmlns:controls="clr-namespace:AngorApp.UI.Controls"
xmlns:amount1="clr-namespace:AngorApp.Features.Invest.Amount"
xmlns:c="clr-namespace:AngorApp.Features.Invest.Amount"
mc:Ignorable="d"
d:DesignWidth="500"
x:Class="AngorApp.Features.Invest.Amount.AmountView" x:DataType="amount1:IAmountViewModel">
x:Class="AngorApp.Features.Invest.Amount.AmountView" x:DataType="c:IAmountViewModel">

<Design.DataContext>
<amount1:AmountViewModelDesign />
<c:AmountViewModelDesign />
</Design.DataContext>

<UserControl.Styles>
Expand All @@ -21,26 +20,27 @@
</UserControl.Styles>

<Panel>
<StackPanel Spacing="20">
<StackPanel Spacing="10" Grid.IsSharedSizeScope="True">

<StackPanel Spacing="10" Grid.IsSharedSizeScope="True">
<HeaderedContainer>
<HeaderedContainer.Header>
<TextBlock Padding="10 0">
Enter the amount you want to invest
</TextBlock>
</HeaderedContainer.Header>
<controls:AmountControl Satoshis="{Binding Amount}">
<Interaction.Behaviors>
<behaviors:UntouchedClassBehavior />
</Interaction.Behaviors>
</controls:AmountControl>
<controls:IconButton HorizontalAlignment="Right" Content="Stages breakdown"
IsVisible="{Binding !!Amount}">
<controls:IconButton.Icon>
<avalonia:Icon Value="fa-info" />
</controls:IconButton.Icon>
<controls:IconButton.Flyout>
<Flyout Placement="BottomEdgeAlignedLeft">
<amount1:StagesBreakdown />
</Flyout>
</controls:IconButton.Flyout>
</controls:IconButton>
</StackPanel>
</HeaderedContainer>
<HeaderedContainer IsVisible="{Binding IsValid^}" >
<HeaderedContainer.Header>
<TextBlock Padding="10 0">
Stage breakdown
</TextBlock>
</HeaderedContainer.Header>
<c:StagesBreakdown />
</HeaderedContainer>
</StackPanel>
</Panel>
</UserControl>
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ public partial class AmountViewModel : ReactiveValidationObject, IAmountViewMode

public AmountViewModel(IWallet wallet, IProject project)
{
Project = project;

this.ValidationRule(x => x.Amount, x => x is null or > 0, _ => "Amount must be greater than zero");
this.ValidationRule(x => x.Amount, x => x is not null, _ => "Please, specify an amount");

Expand All @@ -25,10 +23,9 @@ public AmountViewModel(IWallet wallet, IProject project)

stageBreakdownsHelper = this.WhenAnyValue(model => model.Amount)
.WhereNotNull()
.Select(l => project.Stages.Select(stage => new Breakdown(stage.Index, l!.Value, stage.RatioOfTotal, stage.ReleaseDate)))
.Select(investAmount => project.Stages.Select(stage => new Breakdown(stage.Index, new AmountUI(investAmount!.Value), stage.RatioOfTotal, stage.ReleaseDate)))
.ToProperty(this, x => x.StageBreakdowns);
}

public IProject Project { get; }
public IObservable<bool> IsValid => this.IsValid();
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
using System.Windows.Input;
using AngorApp.Sections.Browse;

namespace AngorApp.Features.Invest.Amount;

public class AmountViewModelDesign : IAmountViewModel
{
public ICommand Next { get; } = ReactiveCommand.Create(() => { });
public long? Amount { get; set; } = 20000;
public decimal? AmountInBtc { get; set; }
public IProject Project { get; } = new ProjectDesign();

public IEnumerable<Breakdown> StageBreakdowns { get; } = new List<Breakdown>
{
new Breakdown(1, 120, 0.2, DateTime.Now),
new Breakdown(1, 120, 0.2, DateTime.Now),
new Breakdown(1, 120, 0.2, DateTime.Now),
new(1, new AmountUI(120), 0.2, DateTime.Now),
new(2, new AmountUI(120), 0.4, DateTime.Now.AddMonths(1)),
new(3, new AmountUI(120), 0.6, DateTime.Now.AddMonths(2).AddDays(5)),
};

public IObservable<bool> IsValid => Observable.Return(true);
public IObservable<bool> IsBusy => Observable.Return(false);
public bool AutoAdvance => false;

public IObservable<bool> IsValid { get; } = Observable.Return(true);
}
11 changes: 2 additions & 9 deletions src/Angor/Avalonia/AngorApp/Features/Invest/Amount/Breakdown.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
namespace AngorApp.Features.Invest.Amount;

public record Breakdown(
int Index,
long Amount,
double RatioOfTotal,
DateTime ReleaseDate)
public record Breakdown(int Index, IAmountUI InvestAmount, double RatioOfTotal, DateTimeOffset ReleaseDate)
{
public long InvestmentSats => (long)(Amount * RatioOfTotal);

public string Description =>
$"Stage {Index}: invest {InvestmentSats} sats that will be released on {ReleaseDate:d}";
public IAmountUI Amount => new AmountUI((long)(InvestAmount.Sats * RatioOfTotal));
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ namespace AngorApp.Features.Invest.Amount;
public interface IAmountViewModel
{
public long? Amount { get; set; }
IProject Project { get; }
IEnumerable<Breakdown> StageBreakdowns { get; }
IObservable<bool> IsValid { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,57 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:AngorApp.UI.Controls"
xmlns:amount1="clr-namespace:AngorApp.Features.Invest.Amount"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="AngorApp.Features.Invest.Amount.StagesBreakdown" x:DataType="amount1:IAmountViewModel">
xmlns:a="clr-namespace:AngorApp.Features.Invest.Amount"
xmlns:ui="clr-namespace:Zafiro.UI;assembly=Zafiro.UI"
mc:Ignorable="d" d:DesignWidth="500"
x:Class="AngorApp.Features.Invest.Amount.StagesBreakdown" x:DataType="a:IAmountViewModel">


<Design.DataContext>
<amount1:AmountViewModelDesign />
<a:AmountViewModelDesign />
</Design.DataContext>

<UserControl.Styles>
<Style Selector="ItemsControl">
<Setter Property="ItemsPanel">
<ItemsPanelTemplate>
<UniformGrid Rows="1" />
</ItemsPanelTemplate>
</Setter>
</Style>
<Style Selector="ItemsControl:overflow">
<Setter Property="ItemsPanel">
<ItemsPanelTemplate>
<UniformGrid Columns="1" />
</ItemsPanelTemplate>
</Setter>
</Style>
</UserControl.Styles>

<ItemsControl ItemsSource="{Binding StageBreakdowns}">
<Interaction.Behaviors>
<OverflowBehavior />
</Interaction.Behaviors>
<ItemsControl.ItemTemplate>
<DataTemplate>
<controls:SectionItem Margin="6" Padding="10">
<TextBlock DockPanel.Dock="Top" Text="{Binding Description}" />
</controls:SectionItem>
<StackPanel>
<!-- <Card Header="{Binding Index, StringFormat='Stage {0}'}" -->
<!-- HeaderEndContent="{Binding ReleaseDate, Converter={x:Static controls:AngorConverters.HumanizeDate}}" -->
<!-- Content="{Binding Amount.SatsString}"> -->
<!-- </Card> -->
<Card Padding="10 5 10 10" HeaderPadding="5 5 5 0" HeaderStartContent="{Binding Index, StringFormat='Stage {0}'}" HeaderEndContent="{Binding Amount.DecimalString}">
<Card.HeaderEndContentTemplate>
<DataTemplate DataType="x:Object">
<Border Padding="5" CornerRadius="10" Background="{DynamicResource SystemChromeHighColor}">
<ContentPresenter Content="{Binding}" FontWeight="Bold" FontSize="10" />
</Border>
</DataTemplate>
</Card.HeaderEndContentTemplate>
<StackPanel Spacing="4">
<EdgePanel StartContent="{Binding ReleaseDate, StringFormat='Release: {0:d}'}" EndContent="{Binding ReleaseDate, Converter={x:Static controls:AngorConverters.HumanizeDate}}" />
</StackPanel>
</Card>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Expand Down
Loading
0