Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Loading