diff --git a/src/design/App/UI/Sections/Portfolio/InvestmentDetailView.axaml b/src/design/App/UI/Sections/Portfolio/InvestmentDetailView.axaml index 392456128..17db4e7a4 100644 --- a/src/design/App/UI/Sections/Portfolio/InvestmentDetailView.axaml +++ b/src/design/App/UI/Sections/Portfolio/InvestmentDetailView.axaml @@ -541,91 +541,91 @@ - - + + IsVisible="{Binding !IsRefreshing}" + HorizontalAlignment="Center" /> + + + + + + + + + + + + + + IsVisible="{Binding !IsCancelling}" + HorizontalAlignment="Center" /> + + + + + + + + + + + + + diff --git a/src/design/App/UI/Sections/Portfolio/InvestmentDetailView.axaml.cs b/src/design/App/UI/Sections/Portfolio/InvestmentDetailView.axaml.cs index 2ee03ad75..53f4e0ce1 100644 --- a/src/design/App/UI/Sections/Portfolio/InvestmentDetailView.axaml.cs +++ b/src/design/App/UI/Sections/Portfolio/InvestmentDetailView.axaml.cs @@ -320,61 +320,61 @@ private async Task ConfirmInvestmentAsync() investVm.IsProcessing = false; } - /// - /// Cancel a pending investment request (Gap 2: CancelInvestmentRequest). - /// Available at Step 1 (PendingFounderSignatures) and Step 2 (FounderSignaturesReceived). - /// - private async Task CancelInvestmentAsync() - { - if (DataContext is not InvestmentViewModel investVm) return; - if (investVm.IsProcessing) return; - - investVm.IsProcessing = true; - - var portfolioVm = App.Services.GetService(); - if (portfolioVm != null) - { - await portfolioVm.CancelInvestmentAsync(investVm); - } - - // Always reset IsProcessing — even on success the VM may still be observed by tests - // or pending bindings. On success the detail is already closed (SelectedInvestment=null). - investVm.IsProcessing = false; - } - - /// - /// Refresh the current investment's data from the SDK, including approval status changes. - /// Reloads all investments to pick up founder approval, then re-selects this investment - /// and refreshes its recovery status. - /// - private async Task RefreshInvestmentAsync() - { - if (DataContext is not InvestmentViewModel investVm) return; - if (investVm.IsProcessing) return; - - investVm.IsProcessing = true; - - var portfolioVm = App.Services.GetService(); - if (portfolioVm != null) - { - var projectId = investVm.ProjectIdentifier; - - // Reload all investments from SDK to pick up approval status changes (#7) - await portfolioVm.LoadInvestmentsFromSdkAsync(); - - // Re-select the same investment (LoadInvestmentsFromSdkAsync recreates VMs) - var refreshed = portfolioVm.Investments.FirstOrDefault(i => i.ProjectIdentifier == projectId); - if (refreshed != null) - { - portfolioVm.OpenInvestmentDetail(refreshed); - // OpenInvestmentDetail already triggers LoadRecoveryStatusAsync - } - } - - // Note: investVm may be stale now (replaced by refreshed VM), but IsProcessing - // is set on the old VM which is no longer displayed — this is fine. - investVm.IsProcessing = false; - } + /// + /// Cancel a pending investment request (Gap 2: CancelInvestmentRequest). + /// Available at Step 1 (PendingFounderSignatures) and Step 2 (FounderSignaturesReceived). + /// + private async Task CancelInvestmentAsync() + { + if (DataContext is not InvestmentViewModel investVm) return; + if (investVm.IsCancelling) return; + + investVm.IsCancelling = true; + + var portfolioVm = App.Services.GetService(); + if (portfolioVm != null) + { + await portfolioVm.CancelInvestmentAsync(investVm); + } + + // Always reset IsCancelling — even on success the VM may still be observed by tests + // or pending bindings. On success the detail is already closed (SelectedInvestment=null). + investVm.IsCancelling = false; + } + + /// + /// Refresh the current investment's data from the SDK, including approval status changes. + /// Reloads all investments to pick up founder approval, then re-selects this investment + /// and refreshes its recovery status. + /// + private async Task RefreshInvestmentAsync() + { + if (DataContext is not InvestmentViewModel investVm) return; + if (investVm.IsRefreshing) return; + + investVm.IsRefreshing = true; + + var portfolioVm = App.Services.GetService(); + if (portfolioVm != null) + { + var projectId = investVm.ProjectIdentifier; + + // Reload all investments from SDK to pick up approval status changes (#7) + await portfolioVm.LoadInvestmentsFromSdkAsync(); + + // Re-select the same investment (LoadInvestmentsFromSdkAsync recreates VMs) + var refreshed = portfolioVm.Investments.FirstOrDefault(i => i.ProjectIdentifier == projectId); + if (refreshed != null) + { + portfolioVm.OpenInvestmentDetail(refreshed); + // OpenInvestmentDetail already triggers LoadRecoveryStatusAsync + } + } + + // Note: investVm may be stale now (replaced by refreshed VM), but IsRefreshing + // is set on the old VM which is no longer displayed — this is fine. + investVm.IsRefreshing = false; + } /// /// Open the investment transaction in the system browser via the indexer explorer. diff --git a/src/design/App/UI/Sections/Portfolio/PortfolioViewModel.cs b/src/design/App/UI/Sections/Portfolio/PortfolioViewModel.cs index 293c51950..a5b7b33a9 100644 --- a/src/design/App/UI/Sections/Portfolio/PortfolioViewModel.cs +++ b/src/design/App/UI/Sections/Portfolio/PortfolioViewModel.cs @@ -385,6 +385,20 @@ public bool IsProcessing set { if (_isProcessing == value) return; _isProcessing = value; OnPropertyChanged(); } } + private bool _isCancelling; + public bool IsCancelling + { + get => _isCancelling; + set { if (_isCancelling == value) return; _isCancelling = value; OnPropertyChanged(); } + } + + private bool _isRefreshing; + public bool IsRefreshing + { + get => _isRefreshing; + set { if (_isRefreshing == value) return; _isRefreshing = value; OnPropertyChanged(); } + } + private string? _errorMessage; /// Error message shown when a recovery operation fails. public string? ErrorMessage