Skip to content
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
3 changes: 2 additions & 1 deletion src/design/App.Android/MainActivity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ namespace App.Android;
[Activity(
Label = "Angor",
Theme = "@style/MyTheme.NoActionBar",
Icon = "@drawable/icon",
Icon = "@mipmap/icon",
RoundIcon = "@mipmap/icon_round",
MainLauncher = true,
ScreenOrientation = ScreenOrientation.Portrait,
WindowSoftInputMode = SoftInput.AdjustNothing,
Expand Down
31 changes: 31 additions & 0 deletions src/design/App.Android/Resources/drawable/icon_foreground.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">

<!-- Angor "A" mark centered in the 66dp adaptive-icon safe zone.
Source artwork viewport is 120x129; scaled 0.42 and centered. -->
<group
android:scaleX="0.34"
android:scaleY="0.34"
android:translateX="33.6"
android:translateY="32.1">

<!-- Main triangular shape with inner cutout -->
<path
android:pathData="M57.69,3.69c1.11,-1.24,2.34,-1.56,3.54,-0.78c0.63,0.43,1.27,1.03,1.65,1.7c0.98,1.7,1.9,3.44,2.75,5.22c1.27,2.59,2.47,5.25,3.7,7.88c1.87,4.01,3.73,8.03,5.6,12.04c1.46,3.12,2.94,6.21,4.4,9.34c1.29,2.77,2.58,5.54,3.89,8.31c1.33,2.88,2.69,5.72,4.02,8.59c1.3,2.73,2.56,5.5,3.86,8.24c1.64,3.55,3.32,7.03,4.9,10.62c0.47,0.99,0.79,2.09,1.08,3.2c0.57,2.45,-0.19,4.51,-1.87,5.93c-1.52,1.28,-3.13,2.31,-4.75,3.34c-2.85,1.81,-5.72,3.55,-8.57,5.29c-3.01,1.85,-6.01,3.66,-9.02,5.5c-2.75,1.7,-5.51,3.41,-8.29,5.04c-1.52,0.92,-3.1,1.49,-3.96,1.49c-2.75,-0.04,-4.46,-0.85,-6.07,-1.85c-3.13,-1.88,-6.23,-3.8,-9.33,-5.72c-5.17,-3.17,-10.33,-6.35,-15.47,-9.55c-1.36,-0.82,-2.72,-1.63,-3.99,-2.59c-0.82,-0.6,-1.58,-1.42,-2.21,-2.27c-1.2,-1.63,-1.3,-3.59,-0.6,-5.47c0.98,-2.63,2.15,-5.18,3.32,-7.7c1.74,-3.83,3.54,-7.6,5.35,-11.4c2.12,-4.54,4.27,-9.09,6.42,-13.67c2.15,-4.58,4.3,-9.23,6.49,-13.85c1.83,-3.98,3.73,-7.95,5.6,-11.93c1.49,-3.16,2.94,-6.39,4.46,-9.55c0.89,-1.88,1.71,-3.87,3.1,-5.4zM59.78,44.48c-4.46,9.48,-8.92,18.99,-13.38,28.55c1.2,0.75,2.34,1.49,3.48,2.17c3.1,1.92,6.2,3.8,9.3,5.72c0.51,0.32,0.95,0.32,1.46,0.04c2.82,-1.74,5.63,-3.48,8.48,-5.22c1.41,-0.88,2.84,-1.76,4.27,-2.66c-0.85,-2.41,-13.12,-28.28,-13.6,-28.58z"
android:fillColor="#0A0A0A"/>

<!-- Bottom left triangle -->
<path
android:pathData="M12.68,98.95c1.65,-2.79,4.18,-3.57,6.81,-2.12c2.57,1.45,5.1,2.93,7.51,4.66c2.44,1.76,3.36,4.34,2.16,7.52c-1.08,2.86,-2.41,5.58,-3.71,8.33c-0.82,1.73,-1.68,3.39,-2.63,5.01c-1.39,2.37,-3.42,3.64,-5.86,3.85c-1.81,0.18,-3.58,0.21,-5.83,0.32c-1.58,-0.11,-3.65,-0.18,-5.64,-0.46c-1.36,-0.21,-2.6,-0.92,-3.04,-2.58c-0.19,-0.78,-0.16,-1.66,-0.03,-2.47c0.29,-1.91,1.21,-3.53,2,-5.22c1.62,-3.5,3.27,-6.99,4.94,-10.45c1.05,-2.15,2.09,-4.34,3.33,-6.39z"
android:fillColor="#0A0A0A"/>

<!-- Bottom right triangle -->
<path
android:pathData="M101.22,96.52c2.4,-0.92,4.32,-0.28,5.83,2.05c1.1,1.69,2.02,3.56,2.9,5.4c1.23,2.47,2.4,4.98,3.56,7.52c1.26,2.72,2.52,5.47,3.72,8.26c0.28,0.67,0.41,1.52,0.44,2.26c0.13,1.98,-0.94,3.49,-2.77,3.92c-1.04,0.25,-2.14,0.35,-3.21,0.42c-1.1,0.07,-2.24,0.04,-3.34,0.04v0.14c-1.96,-0.14,-3.94,-0.21,-5.87,-0.46c-2.59,-0.28,-4.51,-1.91,-5.77,-4.41c-1.99,-3.81,-3.82,-7.73,-5.61,-11.65c-0.44,-0.99,-0.66,-2.15,-0.76,-3.25c-0.22,-2.43,0.92,-4.13,2.59,-5.33c1.7,-1.23,3.47,-2.33,5.24,-3.42c0.98,-0.57,1.99,-1.09,3.06,-1.48z"
android:fillColor="#0A0A0A"/>
</group>
</vector>
6 changes: 6 additions & 0 deletions src/design/App.Android/Resources/mipmap-anydpi-v26/icon.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/icon_background" />
<foreground android:drawable="@drawable/icon_foreground" />
<monochrome android:drawable="@drawable/icon_foreground" />
</adaptive-icon>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/icon_background" />
<foreground android:drawable="@drawable/icon_foreground" />
<monochrome android:drawable="@drawable/icon_foreground" />
</adaptive-icon>
Binary file added src/design/App.Android/Resources/mipmap/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions src/design/App.Android/Resources/values/colors.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@
<color name="splash_background">#FFFFFF</color>
<!-- System bar color follows the current theme background -->
<color name="system_bars">@color/splash_background</color>
<!-- Adaptive launcher icon background (Angor green) -->
<color name="icon_background">#5FAF78</color>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public void ManageProjectContentView_stays_responsive_after_detach_and_reattach(
}

[AvaloniaFact]
public void MyProjectsView_sidebar_strips_desktop_chrome_on_mobile()
public void MyProjectsView_sidebar_hidden_on_mobile()
{
LayoutModeService.Instance.UpdateWidth(1280);

Expand All @@ -96,13 +96,13 @@ public void MyProjectsView_sidebar_strips_desktop_chrome_on_mobile()
sidebar.Should().NotBeNull();

SetWidth(window, 390);
// On mobile the sidebar hero card must not render as an empty Surface box:
// background/border stripped, hero elements hidden, mobile actions shown.
sidebar!.Background.Should().BeNull("mobile sidebar must not render an empty background card");
view.FindControl<Grid>("SidebarStats")!.IsVisible.Should().BeFalse();
// On mobile the sidebar is removed entirely — no empty box; the content
// column shows the mobile action buttons instead.
sidebar!.IsVisible.Should().BeFalse("mobile must not render the sidebar at all");
view.FindControl<StackPanel>("MobileActionPanel")!.IsVisible.Should().BeTrue();

SetWidth(window, 1280);
sidebar.IsVisible.Should().BeTrue("desktop sidebar must come back");
sidebar.Background.Should().NotBeNull("desktop sidebar chrome must come back");
view.FindControl<Grid>("SidebarStats")!.IsVisible.Should().BeTrue();
}
Expand Down
4 changes: 3 additions & 1 deletion src/design/App/UI/Sections/Funders/FundersView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,7 @@
<StackPanel Orientation="Horizontal" Spacing="6"
IsVisible="{Binding IsAmountPending}">
<i:Icon Value="fa-solid fa-arrows-rotate"
Classes="Spin"
FontSize="12"
Foreground="{DynamicResource TextMuted}"
VerticalAlignment="Center" />
Expand Down Expand Up @@ -749,6 +750,7 @@
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="6"
IsVisible="{Binding IsAmountPending}">
<i:Icon Value="fa-solid fa-arrows-rotate"
Classes="Spin"
FontSize="12"
Foreground="{DynamicResource TextMuted}"
VerticalAlignment="Center" />
Expand Down Expand Up @@ -820,7 +822,7 @@
IsVisible="{Binding IsExpanded}"
BorderThickness="0,1,0,0"
BorderBrush="{DynamicResource Stroke}"
Padding="0,16,0,0">
Padding="0,16,0,16">
<StackPanel Spacing="8">
<!-- Vue: text-sm font-medium text-gray-400/text-gray-600 -->
<TextBlock Text="Investor npub"
Expand Down
53 changes: 27 additions & 26 deletions src/design/App/UI/Sections/MyProjects/MyProjectsView.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,9 @@ private void ApplyResponsiveLayout(LayoutMode mode)
var cols = _projectListGrid.ColumnDefinitions;
var rows = _projectListGrid.RowDefinitions;

// Hide sidebar hero content on compact — Vue mobile shows only action buttons
// Compact hides the sidebar entirely — Vue mobile shows only the action buttons
// (MobileActionPanel lives in the content column, not the sidebar).
_myProjectsSidebar.IsVisible = !isCompact;
if (_sidebarLogo != null) _sidebarLogo.IsVisible = !isCompact;
if (_sidebarTitle != null) _sidebarTitle.IsVisible = !isCompact;
if (_sidebarSubtitle != null) _sidebarSubtitle.IsVisible = !isCompact;
Expand All @@ -141,15 +143,7 @@ private void ApplyResponsiveLayout(LayoutMode mode)

Grid.SetColumn(_myProjectsSidebar, 0);
Grid.SetRow(_myProjectsSidebar, 0);
_myProjectsSidebar.Margin = new Avalonia.Thickness(0, 0, 0, 16);
_myProjectsSidebar.VerticalAlignment = Avalonia.Layout.VerticalAlignment.Top;
// Keep the same card inset as the funded projects sidebar; only strip
// desktop chrome so compact actions do not sit flush against the edge.
_myProjectsSidebar.Background = null;
_myProjectsSidebar.BorderThickness = new Avalonia.Thickness(0);
_myProjectsSidebar.BoxShadow = new Avalonia.Media.BoxShadows(default);
// Explicit (not ClearValue) so the literal XAML padding isn't reset to 0.
_myProjectsSidebar.Padding = new Avalonia.Thickness(24);
_myProjectsSidebar.Margin = new Avalonia.Thickness(0);

Grid.SetColumn(_myProjectsContent, 0);
Grid.SetRow(_myProjectsContent, 1);
Expand All @@ -168,22 +162,7 @@ private void ApplyResponsiveLayout(LayoutMode mode)
Grid.SetRow(_myProjectsSidebar, 0);
_myProjectsSidebar.Margin = new Avalonia.Thickness(0, 0, columnGap, 0);
_myProjectsSidebar.VerticalAlignment = Avalonia.Layout.VerticalAlignment.Stretch;
// Restore card styling. ClearValue does NOT work here: the XAML
// DynamicResource assignment is itself a local value, so clearing it
// removes the binding permanently (sidebar stayed chrome-less after
// mobile → desktop resize). Resolve the theme resources explicitly.
if (this.TryFindResource("Surface", ActualThemeVariant, out var surfaceRes))
{
_myProjectsSidebar.Background = surfaceRes switch
{
Avalonia.Media.IBrush brush => brush,
Avalonia.Media.Color color => new Avalonia.Media.Immutable.ImmutableSolidColorBrush(color),
_ => _myProjectsSidebar.Background,
};
}
if (this.TryFindResource("ItemShadow", ActualThemeVariant, out var shadowRes) && shadowRes is Avalonia.Media.BoxShadows shadow)
_myProjectsSidebar.BoxShadow = shadow;
_myProjectsSidebar.BorderThickness = new Avalonia.Thickness(1);
RestoreSidebarChrome();
_myProjectsSidebar.Padding = new Avalonia.Thickness(24);

Grid.SetColumn(_myProjectsContent, 1);
Expand All @@ -195,6 +174,28 @@ private void ApplyResponsiveLayout(LayoutMode mode)
}
}

/// <summary>
/// Restore card styling on the sidebar Border. ClearValue does NOT work here: the
/// XAML DynamicResource assignment is itself a local value, so clearing it removes
/// the binding permanently. Resolve the theme resources explicitly instead.
/// </summary>
private void RestoreSidebarChrome()
{
if (_myProjectsSidebar == null) return;
if (this.TryFindResource("Surface", ActualThemeVariant, out var surfaceRes))
{
_myProjectsSidebar.Background = surfaceRes switch
{
Avalonia.Media.IBrush brush => brush,
Avalonia.Media.Color color => new Avalonia.Media.Immutable.ImmutableSolidColorBrush(color),
_ => _myProjectsSidebar.Background,
};
}
if (this.TryFindResource("ItemShadow", ActualThemeVariant, out var shadowRes) && shadowRes is Avalonia.Media.BoxShadows shadow)
_myProjectsSidebar.BoxShadow = shadow;
_myProjectsSidebar.BorderThickness = new Avalonia.Thickness(1);
}

/// <summary>
/// Mobile perf: force a measure pass on the hidden ManageProjectView so its
/// template, resources, and layout are realized before the first drill-down.
Expand Down
1 change: 1 addition & 0 deletions src/design/App/UI/Shared/Controls/ProjectCard.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@
DockPanel.Dock="Left"
IsVisible="False">
<i:Icon Value="fa-solid fa-arrows-rotate"
Classes="Spin"
FontSize="11"
Foreground="{DynamicResource TextMuted}"
VerticalAlignment="Center" />
Expand Down
18 changes: 18 additions & 0 deletions src/design/App/UI/Shared/SectionPanel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,24 @@ public void AddSection(string key, Control view)
{
if (_sections.ContainsKey(key)) return;

// Android activity relaunch (config change outside ConfigurationChanges)
// rebuilds ShellView + SectionPanel, but the section views come from the
// ViewModel's cache and may still be parented to the previous (dead)
// SectionPanel. Detach from the old parent first or Children.Add throws
// "already has a visual parent" and crashes the app on relaunch.
switch (view.Parent)
{
case Panel oldPanel when oldPanel != this:
oldPanel.Children.Remove(view);
break;
case ContentControl oldContent:
oldContent.Content = null;
break;
case Decorator oldDecorator:
oldDecorator.Child = null;
break;
}

view.IsVisible = false;
_sections[key] = view;

Expand Down
21 changes: 21 additions & 0 deletions src/design/App/UI/Shell/ShellViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,27 @@ public PrototypeSettings(IStore store, ILogger<PrototypeSettings> logger)
? result.Value.IsDarkTheme
: null;
}

// Icon-bake workaround: Optris i:Icon snapshots its Foreground brush
// into the rendered image at construction — before the theme
// dictionaries are reachable it bakes the black fallback. A
// RequestedThemeVariant change re-fires every DynamicResource
// foreground and re-bakes all icons. Settings-load with a theme
// override triggers that flip via IsDarkTheme; first launch (no
// settings) never does, leaving black icons in dark mode. Force
// one flip once startup theming has settled.
if ((!result.IsSuccess || !result.Value.HasThemeOverride) && Application.Current != null)
{
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
{
if (Application.Current is not { } app) return;
var osIsDark = app.ActualThemeVariant == Avalonia.Styling.ThemeVariant.Dark;
app.RequestedThemeVariant = osIsDark
? Avalonia.Styling.ThemeVariant.Light
: Avalonia.Styling.ThemeVariant.Dark;
app.RequestedThemeVariant = Avalonia.Styling.ThemeVariant.Default;
}, Avalonia.Threading.DispatcherPriority.Loaded);
}
});
});

Expand Down
17 changes: 17 additions & 0 deletions src/design/App/UI/Themes/V2/Styles/Icons.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,23 @@
<Setter Property="Foreground" Value="{DynamicResource TextMutedBrush}" />
</Style>

<!-- Spinning icon for syncing/loading affordances: infinite 360° rotation -->
<Style Selector="i|Icon.Spin">
<Setter Property="RenderTransform">
<RotateTransform />
</Setter>
<Style.Animations>
<Animation Duration="0:0:1.2" IterationCount="Infinite">
<KeyFrame Cue="0%">
<Setter Property="RotateTransform.Angle" Value="0" />
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="RotateTransform.Angle" Value="360" />
</KeyFrame>
</Animation>
</Style.Animations>
</Style>

<!-- Sizes -->
<Style Selector="i|Icon.Icon-S">
<Setter Property="FontSize" Value="14" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,19 @@ public class ProjectStatsHandler(IProjectInvestmentsService projectInvestmentsSe
{
public async Task<Result<ProjectStatisticsDto>> Handle(ProjectStatsRequest request, CancellationToken cancellationToken)
{
var stagesInformation = await projectInvestmentsService.ScanFullInvestments(request.ProjectId.Value);
// Run the investment scan and the project fetch concurrently — the scan
// does not depend on the project result and both hit the network.
var stagesInformationTask = projectInvestmentsService.ScanFullInvestments(request.ProjectId.Value);
var projectTask = projectService.GetAsync(request.ProjectId);

var stagesInformation = await stagesInformationTask;

if (stagesInformation.IsFailure)
{
return Result.Failure<ProjectStatisticsDto>(stagesInformation.Error);
}

var project = await projectService.GetAsync(request.ProjectId);
var project = await projectTask;
return Result.Try(() => CalculateTotalValues(stagesInformation.Value.ToList(), project.Value.ToProjectInfo()));
}

Expand Down
Loading