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
Original file line number Diff line number Diff line change
Expand Up @@ -130,16 +130,40 @@

<!-- Text-only button for dialogs -->
<Style Selector="Button.text">
<Setter Property="Padding" Value="8,4"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="Margin" Value="0,2,0,0"/>
<Setter Property="MinWidth" Value="0"/>
<Setter Property="MinHeight" Value="0"/>
<Setter Property="FontWeight" Value="Normal"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Foreground" Value="{DynamicResource AccentBlue}"/>
</Style>

<Style Selector="Button.monospace">
<Setter Property="FontFamily" Value="{StaticResource MonospaceFontFamily}"/>
<Setter Property="FontSize" Value="12"/>
</Style>

<Style Selector="Button.text:pointerover">
<Setter Property="Foreground" Value="{DynamicResource HighlightGold}"/>
</Style>

<Style Selector="Button.link">
<Setter Property="Padding" Value="0"/>
<Setter Property="Margin" Value="0,2,0,0"/>
<Setter Property="MinWidth" Value="0"/>
<Setter Property="MinHeight" Value="0"/>
<Setter Property="FontWeight" Value="Normal"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Foreground" Value="{DynamicResource AccentBlue}"/>
</Style>

<Style Selector="Button.link:pointerover">
<Setter Property="Foreground" Value="{DynamicResource HighlightGold}"/>
</Style>

<!-- Small info icon buttons -->
<Style Selector="Button.info">
<Setter Property="FontSize" Value="10"/>
Expand Down Expand Up @@ -175,7 +199,7 @@
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Margin" Value="0,4,0,0"/>
<Setter Property="Margin" Value="0,8,0,0"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="Padding" Value="6,2"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<Setter Property="FontSize" Value="12"/>
<Setter Property="Padding" Value="8,6"/>
<Setter Property="MinHeight" Value="32"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="CornerRadius" Value="2"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Background" Value="{DynamicResource SecondaryBg}"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using System.Reflection;
using System.Reactive;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Highbyte.DotNet6502.App.Avalonia.Core.Services;
using ReactiveUI;
Expand All @@ -12,6 +14,8 @@ namespace Highbyte.DotNet6502.App.Avalonia.Core.ViewModels;
/// </summary>
public class AboutViewModel : ViewModelBase
{
private const string RepositoryUrl = "https://github.com/highbyte/dotnet-6502";

private readonly IAppUpdateService _updateService;

private string _statusText = string.Empty;
Expand All @@ -32,13 +36,12 @@ public AboutViewModel(IAppUpdateService updateService)
_updateService = updateService;
CurrentVersionDisplay = updateService.CurrentVersionDisplay;
IsUpdateCheckSupported = updateService.IsSupported;
CurrentReleaseNotesUrl = BuildCurrentReleaseNotesUrl(CurrentVersionDisplay);

UpdateNowCommand = ReactiveCommandHelper.CreateSafeCommand(UpdateNowAsync);
CloseCommand = ReactiveCommandHelper.CreateSafeCommand(() => RequestClose?.Invoke(this, EventArgs.Empty));

StatusText = IsUpdateCheckSupported
? "Checking for updates…"
: "Update checks aren't available for this build.";
StatusText = IsUpdateSectionVisible ? "Checking for updates…" : string.Empty;

// Opening the About dialog is an explicit user action, so always run a fresh check (force:true
// bypasses the daily cadence and the disabled-setting/CI gating). No separate "Check now" needed.
Expand All @@ -48,12 +51,23 @@ public AboutViewModel(IAppUpdateService updateService)

public string CurrentVersionDisplay { get; }
public bool IsUpdateCheckSupported { get; }
public bool IsUpdateSectionVisible => IsUpdateCheckSupported;
public string? CurrentReleaseNotesUrl { get; }
public bool HasCurrentReleaseNotes => !string.IsNullOrEmpty(CurrentReleaseNotesUrl);
public string Author => "Highbyte";
public string RepoUrl => RepositoryUrl;
public string OSVersion => RuntimeInformation.OSDescription;
public string DotNetVersion => RuntimeInformation.FrameworkDescription;
public string AvaloniaVersion => ReadAvaloniaVersion();

public ReactiveCommand<Unit, Unit> UpdateNowCommand { get; }
public ReactiveCommand<Unit, Unit> CloseCommand { get; }

/// <summary>True when a one-click update can be started (managed install with an available update).</summary>
public bool CanUpdateNow => IsUpdateCheckSupported && _isUpdateAvailable;
public bool ShowUpdateAvailableStatus => IsUpdateSectionVisible && _isUpdateAvailable;
public bool ShowPlainStatus => IsUpdateSectionVisible && !_isUpdateAvailable;
public bool ShowInlineReleaseNotes => ShowUpdateAvailableStatus && HasReleaseNotes;

public string StatusText
{
Expand All @@ -74,13 +88,20 @@ private set
{
this.RaiseAndSetIfChanged(ref _isUpdateAvailable, value);
this.RaisePropertyChanged(nameof(CanUpdateNow));
this.RaisePropertyChanged(nameof(ShowUpdateAvailableStatus));
this.RaisePropertyChanged(nameof(ShowPlainStatus));
this.RaisePropertyChanged(nameof(ShowInlineReleaseNotes));
}
}

public string? LatestVersionDisplay
{
get => _latestVersionDisplay;
private set => this.RaiseAndSetIfChanged(ref _latestVersionDisplay, value);
private set
{
this.RaiseAndSetIfChanged(ref _latestVersionDisplay, value);
this.RaisePropertyChanged(nameof(ShowUpdateAvailableStatus));
}
}

/// <summary>The <c>brew</c>/<c>scoop</c> command to run; non-null only when an update is available.</summary>
Expand All @@ -103,6 +124,7 @@ private set
{
this.RaiseAndSetIfChanged(ref _releaseNotesUrl, value);
this.RaisePropertyChanged(nameof(HasReleaseNotes));
this.RaisePropertyChanged(nameof(ShowInlineReleaseNotes));
}
}

Expand Down Expand Up @@ -147,7 +169,7 @@ private void ApplyStatus(AppUpdateStatus? status)
{
if (status is null)
{
StatusText = "Update checks aren't available for this build.";
StatusText = "Update check did not return any result.";
IsUpdateAvailable = false;
SuggestedCommand = null;
ReleaseNotesUrl = null;
Expand All @@ -166,4 +188,26 @@ private void ApplyStatus(AppUpdateStatus? status)
else
StatusText = $"You're on the latest version ({status.CurrentVersionDisplay}).";
}

private static string? BuildCurrentReleaseNotesUrl(string currentVersionDisplay)
{
if (!currentVersionDisplay.StartsWith("v", StringComparison.Ordinal))
return null;

return $"{RepositoryUrl}/releases/tag/{currentVersionDisplay}";
}

private static string ReadAvaloniaVersion()
{
try
{
var asm = typeof(global::Avalonia.Controls.Control).Assembly;
var infoAttr = asm.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
return infoAttr?.InformationalVersion ?? asm.GetName().Version?.ToString() ?? "Unknown";
}
catch
{
return "Unknown";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,134 @@
x:DataType="vm:AboutViewModel"
AutomationProperties.AutomationId="AboutDialog"
AutomationProperties.Name="About and updates"
Width="480"
Width="520"
MinWidth="360">

<Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto" Margin="16">
<Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto" Margin="16">

<!-- Title header -->
<Border Grid.Row="0" CornerRadius="4" Classes="panel-container" Margin="0,0,0,12">
<TextBlock Text="DotNet 6502 Emulator" Classes="h-center h2"/>
</Border>

<!-- Current version -->
<StackPanel Grid.Row="1" Orientation="Horizontal" Spacing="8" Margin="0,0,0,8">
<TextBlock Text="Version:" VerticalAlignment="Center"/>
<TextBlock Text="{Binding CurrentVersionDisplay}" Classes="monospace" VerticalAlignment="Center"/>
<StackPanel Grid.Row="1" Orientation="Horizontal" Spacing="8" Margin="0,0,0,12">
<TextBlock Text="Current version:" VerticalAlignment="Center"/>
<TextBlock Text="{Binding CurrentVersionDisplay}"
Classes="monospace"
VerticalAlignment="Center"
IsVisible="{Binding !HasCurrentReleaseNotes}"/>
<Button Content="{Binding CurrentVersionDisplay}"
VerticalAlignment="Center"
Classes="text monospace"
IsVisible="{Binding HasCurrentReleaseNotes}"
Click="OnCurrentReleaseNotesClick"/>
</StackPanel>

<Border Grid.Row="2" Classes="section-container" Margin="0,0,0,12">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>

<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="70" MaxWidth="110" />
<ColumnDefinition Width="*" MinWidth="180"/>
</Grid.ColumnDefinitions>

<Border Grid.Row="0" Grid.Column="0" Classes="table-cell">
<TextBlock Text="By" Classes="small bold" TextWrapping="Wrap" />
</Border>

<Border Grid.Row="0" Grid.Column="1" Classes="table-cell-last-column">
<SelectableTextBlock Text="{Binding Author}" Classes="small" />
</Border>

<Border Grid.Row="1" Grid.Column="0" Classes="table-cell">
<TextBlock Text="Repo" Classes="small bold" TextWrapping="Wrap" />
</Border>

<Border Grid.Row="1" Grid.Column="1" Classes="table-cell-last-column">
<Grid ColumnDefinitions="*,Auto">
<SelectableTextBlock Grid.Column="0" Text="{Binding RepoUrl}" Classes="small" />
<Button Grid.Column="1"
Content="i"
AutomationProperties.AutomationId="AboutRepoLinkButton"
AutomationProperties.Name="Open repository in browser"
Classes="info"
Click="OnRepositoryClick" />
</Grid>
</Border>

<Border Grid.Row="2" Grid.Column="0" Classes="table-cell">
<TextBlock Text="OS" Classes="small bold" TextWrapping="Wrap" />
</Border>

<Border Grid.Row="2" Grid.Column="1" Classes="table-cell-last-column">
<SelectableTextBlock Text="{Binding OSVersion}" Classes="small" />
</Border>

<Border Grid.Row="3" Grid.Column="0" Classes="table-cell">
<TextBlock Text=".NET" Classes="small bold" TextWrapping="Wrap" />
</Border>

<Border Grid.Row="3" Grid.Column="1" Classes="table-cell-last-column">
<SelectableTextBlock Text="{Binding DotNetVersion}" Classes="small" />
</Border>

<Border Grid.Row="4" Grid.Column="0" Classes="table-cell-last-row">
<TextBlock Text="Avalonia" Classes="small bold" TextWrapping="Wrap" />
</Border>

<Border Grid.Row="4" Grid.Column="1" Classes="table-cell-last">
<SelectableTextBlock Text="{Binding AvaloniaVersion}" Classes="small" />
</Border>
</Grid>
</Border>

<!-- Update status line -->
<TextBlock Grid.Row="2"
<StackPanel Grid.Row="3"
Orientation="Horizontal"
Spacing="4"
IsVisible="{Binding ShowUpdateAvailableStatus}"
Margin="0,0,0,8">
<TextBlock Text="Update available:"
VerticalAlignment="Center"/>
<TextBlock Text="{Binding CurrentVersionDisplay}"
Classes="monospace"
VerticalAlignment="Center"/>
<TextBlock Text="→"
VerticalAlignment="Center"/>
<TextBlock Text="{Binding LatestVersionDisplay}"
Classes="monospace"
VerticalAlignment="Center"/>
<Button Content="What's new"
VerticalAlignment="Center"
Margin="8,2,0,0"
Classes="text"
IsVisible="{Binding ShowInlineReleaseNotes}"
Click="OnReleaseNotesClick"/>
</StackPanel>

<TextBlock Grid.Row="3"
Text="{Binding StatusText}"
IsVisible="{Binding ShowPlainStatus}"
TextWrapping="Wrap"
Margin="0,0,0,8"/>

<!-- Indeterminate progress while checking -->
<ProgressBar Grid.Row="3"
<ProgressBar Grid.Row="4"
IsIndeterminate="True"
IsVisible="{Binding IsChecking}"
Height="4"
Margin="0,0,0,8"/>

<!-- Suggested upgrade command (managed + update available) -->
<Grid Grid.Row="4"
<Grid Grid.Row="5"
ColumnDefinitions="*,Auto"
IsVisible="{Binding HasSuggestedCommand}"
Margin="0,0,0,12">
Expand All @@ -54,13 +151,13 @@
<!-- Action buttons: secondary "What's new" link on the left; dismiss + the positive primary
action ("Update now", rightmost, green) on the right. Close stays the Enter-default so an
accidental Enter can't trigger the quit-and-relaunch update. -->
<Grid Grid.Row="5" ColumnDefinitions="*,Auto">
<Grid Grid.Row="6" ColumnDefinitions="*,Auto">
<Button Grid.Column="0"
x:Name="ReleaseNotesButton"
Content="What's new"
HorizontalAlignment="Left"
Classes="text"
IsVisible="{Binding HasReleaseNotes}"
IsVisible="{Binding !ShowInlineReleaseNotes}"
Click="OnReleaseNotesClick"/>

<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="8">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,16 @@ private void OnCopyCommandClick(object? sender, RoutedEventArgs e)
}

private void OnReleaseNotesClick(object? sender, RoutedEventArgs e)
=> LaunchUri((DataContext as AboutViewModel)?.ReleaseNotesUrl);

private void OnCurrentReleaseNotesClick(object? sender, RoutedEventArgs e)
=> LaunchUri((DataContext as AboutViewModel)?.CurrentReleaseNotesUrl);

private void OnRepositoryClick(object? sender, RoutedEventArgs e)
=> LaunchUri((DataContext as AboutViewModel)?.RepoUrl);

private void LaunchUri(string? url)
{
var url = (DataContext as AboutViewModel)?.ReleaseNotesUrl;
if (string.IsNullOrEmpty(url) || !Uri.TryCreate(url, UriKind.Absolute, out var uri))
return;
var launcher = TopLevel.GetTopLevel(this)?.Launcher;
Expand Down
Loading
Loading