diff --git a/src/design/App.Android/MainActivity.cs b/src/design/App.Android/MainActivity.cs index fbb97ff1f..a8355c477 100644 --- a/src/design/App.Android/MainActivity.cs +++ b/src/design/App.Android/MainActivity.cs @@ -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, diff --git a/src/design/App.Android/Resources/drawable/icon_foreground.xml b/src/design/App.Android/Resources/drawable/icon_foreground.xml new file mode 100644 index 000000000..2b06cbae5 --- /dev/null +++ b/src/design/App.Android/Resources/drawable/icon_foreground.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + diff --git a/src/design/App.Android/Resources/mipmap-anydpi-v26/icon.xml b/src/design/App.Android/Resources/mipmap-anydpi-v26/icon.xml new file mode 100644 index 000000000..4186747fc --- /dev/null +++ b/src/design/App.Android/Resources/mipmap-anydpi-v26/icon.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/design/App.Android/Resources/mipmap-anydpi-v26/icon_round.xml b/src/design/App.Android/Resources/mipmap-anydpi-v26/icon_round.xml new file mode 100644 index 000000000..4186747fc --- /dev/null +++ b/src/design/App.Android/Resources/mipmap-anydpi-v26/icon_round.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/design/App.Android/Resources/mipmap/icon.png b/src/design/App.Android/Resources/mipmap/icon.png new file mode 100644 index 000000000..fb866e30e Binary files /dev/null and b/src/design/App.Android/Resources/mipmap/icon.png differ diff --git a/src/design/App.Android/Resources/values/colors.xml b/src/design/App.Android/Resources/values/colors.xml index 6ae9cea61..dc04eab73 100644 --- a/src/design/App.Android/Resources/values/colors.xml +++ b/src/design/App.Android/Resources/values/colors.xml @@ -3,4 +3,6 @@ #FFFFFF @color/splash_background + + #5FAF78 diff --git a/src/design/App.Test.Integration/LayoutRegression/ResponsivenessRegressionTests.cs b/src/design/App.Test.Integration/LayoutRegression/ResponsivenessRegressionTests.cs index b6dcddaea..ca8098dea 100644 --- a/src/design/App.Test.Integration/LayoutRegression/ResponsivenessRegressionTests.cs +++ b/src/design/App.Test.Integration/LayoutRegression/ResponsivenessRegressionTests.cs @@ -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); @@ -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("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("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("SidebarStats")!.IsVisible.Should().BeTrue(); } diff --git a/src/design/App/UI/Sections/Funders/FundersView.axaml b/src/design/App/UI/Sections/Funders/FundersView.axaml index b48ccab18..16f8448e5 100644 --- a/src/design/App/UI/Sections/Funders/FundersView.axaml +++ b/src/design/App/UI/Sections/Funders/FundersView.axaml @@ -706,6 +706,7 @@ @@ -749,6 +750,7 @@ @@ -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"> 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); @@ -195,6 +174,28 @@ private void ApplyResponsiveLayout(LayoutMode mode) } } + /// + /// 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. + /// + 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); + } + /// /// Mobile perf: force a measure pass on the hidden ManageProjectView so its /// template, resources, and layout are realized before the first drill-down. diff --git a/src/design/App/UI/Shared/Controls/ProjectCard.axaml b/src/design/App/UI/Shared/Controls/ProjectCard.axaml index 5b95028e9..f363b600c 100644 --- a/src/design/App/UI/Shared/Controls/ProjectCard.axaml +++ b/src/design/App/UI/Shared/Controls/ProjectCard.axaml @@ -174,6 +174,7 @@ DockPanel.Dock="Left" IsVisible="False"> diff --git a/src/design/App/UI/Shared/SectionPanel.cs b/src/design/App/UI/Shared/SectionPanel.cs index 6d9eee67a..96bd40929 100644 --- a/src/design/App/UI/Shared/SectionPanel.cs +++ b/src/design/App/UI/Shared/SectionPanel.cs @@ -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; diff --git a/src/design/App/UI/Shell/ShellViewModel.cs b/src/design/App/UI/Shell/ShellViewModel.cs index 60f3f0a20..01c640f61 100644 --- a/src/design/App/UI/Shell/ShellViewModel.cs +++ b/src/design/App/UI/Shell/ShellViewModel.cs @@ -243,6 +243,27 @@ public PrototypeSettings(IStore store, ILogger 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); + } }); }); diff --git a/src/design/App/UI/Themes/V2/Styles/Icons.axaml b/src/design/App/UI/Themes/V2/Styles/Icons.axaml index 24bef893f..af67c30f4 100644 --- a/src/design/App/UI/Themes/V2/Styles/Icons.axaml +++ b/src/design/App/UI/Themes/V2/Styles/Icons.axaml @@ -10,6 +10,23 @@ + + +