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
197 changes: 197 additions & 0 deletions Components/Modals/UpdateModalComponent.axaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:Quell.ViewModels"
xmlns:ui="using:FluentAvalonia.UI.Controls"
mc:Ignorable="d" d:DesignWidth="1000" d:DesignHeight="700"
x:Class="Quell.Components.UpdateModalComponent"
x:DataType="vm:UpdateModalViewModel"
IsVisible="{Binding IsOpen}">

<!-- Reusable Software Update & GitHub Release Notification Modal Dialog -->
<Grid>
<!-- Backdrop (Click to dismiss) -->
<Border Classes="modal-backdrop" Cursor="Hand" Focusable="False" FocusAdorner="{x:Null}" Background="{DynamicResource AppModalOverlayBrush}" PointerPressed="OnBackdropPointerPressed" />

<!-- Centered Dialog Window Card -->
<Border Classes="modal-dialog-card"
Background="{DynamicResource AppWorkspaceBrush}"
BorderBrush="{DynamicResource AppBorderBrush}"
BorderThickness="1"
CornerRadius="8"
ClipToBounds="True"
Focusable="False" FocusAdorner="{x:Null}"
BoxShadow="0 10 25 0 #33000000"
Width="520"
HorizontalAlignment="Center"
VerticalAlignment="Center">

<Grid RowDefinitions="Auto, *, Auto">

<!-- 1. Header -->
<Border Grid.Row="0"
Background="{DynamicResource AppSurfaceBrush}"
BorderBrush="{DynamicResource AppBorderBrush}"
BorderThickness="0,0,0,1"
CornerRadius="8,8,0,0"
Padding="16,12">
<Grid ColumnDefinitions="Auto, *, Auto">
<Border Grid.Column="0"
Background="{DynamicResource AppAccentBrush}"
CornerRadius="6"
Width="28"
Height="28"
Margin="0,0,10,0"
VerticalAlignment="Center">
<ui:FASymbolIcon Symbol="Download" FontSize="13" Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>

<StackPanel Grid.Column="1" VerticalAlignment="Center">
<TextBlock Text="New Version Available"
Foreground="{DynamicResource AppTextPrimaryBrush}"
FontSize="13"
FontWeight="Bold"/>
<TextBlock Text="A new release of Quell Studio is ready on GitHub"
Foreground="{DynamicResource AppTextMutedBrush}"
FontSize="11"/>
</StackPanel>

<!-- Dismiss (X) Button -->
<Button Grid.Column="2"
Classes="classic-tool-btn"
Command="{Binding DismissCommand}"
Padding="4"
Width="26" Height="26"
ToolTip.Tip="Remind me later (Dismiss)">
<ui:FASymbolIcon Symbol="Dismiss" FontSize="10" Foreground="{DynamicResource AppTextSecondaryBrush}"/>
</Button>
</Grid>
</Border>

<!-- 2. Body -->
<StackPanel Grid.Row="1" Spacing="14" Margin="18,16">

<!-- Version Comparison Badges -->
<Grid ColumnDefinitions="*, Auto, *">
<!-- Current Installed Version -->
<Border Grid.Column="0"
Background="{DynamicResource AppSurfaceBrush}"
BorderBrush="{DynamicResource AppBorderBrush}"
BorderThickness="1"
CornerRadius="6"
Padding="12,8">
<StackPanel Spacing="3">
<TextBlock Text="CURRENT VERSION" FontSize="9" FontWeight="Bold" Foreground="{DynamicResource AppTextMutedBrush}"/>
<TextBlock Text="{Binding CurrentVersion}" FontSize="13" FontWeight="SemiBold" Foreground="{DynamicResource AppTextSecondaryBrush}"/>
</StackPanel>
</Border>

<!-- Arrow Indicator -->
<Border Grid.Column="1" Margin="10,0" VerticalAlignment="Center">
<ui:FASymbolIcon Symbol="ChevronRight" FontSize="14" Foreground="{DynamicResource AppTextMutedBrush}"/>
</Border>

<!-- Latest Release Version -->
<Border Grid.Column="2"
Background="{DynamicResource AppAccentBrush}"
CornerRadius="6"
Padding="12,8">
<StackPanel Spacing="3">
<Grid ColumnDefinitions="*, Auto">
<TextBlock Grid.Column="0" Text="LATEST RELEASE" FontSize="9" FontWeight="Bold" Foreground="#CCFFFFFF"/>
<Border Grid.Column="1" Background="#22C55E" CornerRadius="3" Padding="4,1" VerticalAlignment="Center">
<TextBlock Text="UPDATE" FontSize="8" FontWeight="Bold" Foreground="White"/>
</Border>
</Grid>
<TextBlock Text="{Binding LatestVersion}" FontSize="13" FontWeight="Bold" Foreground="White"/>
</StackPanel>
</Border>
</Grid>

<!-- Release Title -->
<TextBlock Text="{Binding ReleaseTitle}"
FontSize="12"
FontWeight="Bold"
Foreground="{DynamicResource AppTextPrimaryBrush}"
TextTrimming="CharacterEllipsis"/>

<!-- Release Highlights / Notes Box -->
<Border Background="{DynamicResource AppSurfaceMutedBrush}"
BorderBrush="{DynamicResource AppBorderBrush}"
BorderThickness="1"
CornerRadius="6"
Padding="10"
MaxHeight="140">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<TextBlock Text="{Binding ReleaseNotes}"
FontSize="11"
Foreground="{DynamicResource AppTextSecondaryBrush}"
LineHeight="16"
TextWrapping="Wrap"/>
</ScrollViewer>
</Border>

<!-- Optional Update Notice -->
<Border Background="{DynamicResource AppSurfaceBrush}"
BorderBrush="{DynamicResource AppBorderBrush}"
BorderThickness="1"
CornerRadius="6"
Padding="10,8">
<Grid ColumnDefinitions="Auto, *">
<ui:FASymbolIcon Grid.Column="0" Symbol="Help" FontSize="13" Foreground="{DynamicResource AppAccentBrush}" Margin="0,0,8,0" VerticalAlignment="Center"/>
<TextBlock Grid.Column="1"
Text="Updating is optional. You can download the new version now or continue working and update at any time from 'Get Updates'."
FontSize="10.5"
Foreground="{DynamicResource AppTextSecondaryBrush}"
TextWrapping="Wrap"
VerticalAlignment="Center"/>
</Grid>
</Border>
</StackPanel>

<!-- 3. Footer Actions -->
<Border Grid.Row="2"
Background="{DynamicResource AppSurfaceBrush}"
BorderBrush="{DynamicResource AppBorderBrush}"
BorderThickness="0,1,0,0"
CornerRadius="0,0,8,8"
Padding="16,12">
<Grid ColumnDefinitions="*, Auto, Auto">
<TextBlock Grid.Column="0"
Text="{Binding PublishedAt}"
FontSize="10"
Foreground="{DynamicResource AppTextMutedBrush}"
VerticalAlignment="Center"/>

<!-- Secondary: Remind Me Later -->
<Button Grid.Column="1"
Classes="classic-tool-btn"
Command="{Binding DismissCommand}"
Content="Remind Me Later"
FontSize="11"
Padding="12,6"
Margin="0,0,8,0"
Cursor="Hand"/>

<!-- Primary: Download & Install / View Release -->
<Button Grid.Column="2"
Background="{DynamicResource AppAccentBrush}"
Foreground="White"
Command="{Binding OpenReleaseUrlCommand}"
FontSize="11"
FontWeight="SemiBold"
Padding="14,6"
CornerRadius="4"
Cursor="Hand">
<StackPanel Orientation="Horizontal" Spacing="6" VerticalAlignment="Center">
<ui:FASymbolIcon Symbol="Download" FontSize="11" Foreground="White" VerticalAlignment="Center"/>
<TextBlock Text="Download Update" Foreground="White" VerticalAlignment="Center"/>
</StackPanel>
</Button>
</Grid>
</Border>
</Grid>
</Border>
</Grid>
</UserControl>
21 changes: 21 additions & 0 deletions Components/Modals/UpdateModalComponent.axaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Avalonia.Controls;
using Avalonia.Input;
using Quell.ViewModels;

namespace Quell.Components;

public partial class UpdateModalComponent : UserControl
{
public UpdateModalComponent()
{
InitializeComponent();
}

private void OnBackdropPointerPressed(object? sender, PointerPressedEventArgs e)
{
if (DataContext is UpdateModalViewModel vm)
{
vm.DismissCommand.Execute(null);
}
}
}
28 changes: 25 additions & 3 deletions Components/Navigation/StatusBarComponent.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:Quell.ViewModels"
mc:Ignorable="d" d:DesignWidth="1000" d:DesignHeight="24"
x:Class="Quell.Components.StatusBarComponent">
x:Class="Quell.Components.StatusBarComponent"
x:DataType="vm:MainViewModel">

<!-- Reusable Desktop Status Bar Component -->
<Border Background="{DynamicResource AppSurfaceBrush}"
Expand All @@ -21,8 +23,28 @@
VerticalAlignment="Center"/>
</StackPanel>

<!-- Right: Document & Server Metadata -->
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="16" VerticalAlignment="Center">
<!-- Right: Document & Server Metadata + Corner Update Badge -->
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="14" VerticalAlignment="Center">

<!-- Corner "Get Updates" Pill Button in Status Bar -->
<Button Classes="classic-tool-btn"
IsVisible="{Binding UpdateModal.IsCornerBadgeVisible}"
Command="{Binding OpenUpdateModalCommand}"
Padding="6,2"
Height="20"
CornerRadius="4"
Background="{DynamicResource AppSurfaceMutedBrush}"
BorderBrush="{DynamicResource AppAccentBrush}"
BorderThickness="1"
Cursor="Hand"
ToolTip.Tip="A new version of Quell Studio is ready. Click to update.">
<StackPanel Orientation="Horizontal" Spacing="5" VerticalAlignment="Center">
<Ellipse Width="6" Height="6" Fill="#10B981" VerticalAlignment="Center"/>
<TextBlock Text="Get Updates" FontSize="10" FontWeight="Bold" Foreground="{DynamicResource AppTextPrimaryBrush}" VerticalAlignment="Center"/>
<TextBlock Text="{Binding UpdateModal.LatestVersion}" FontSize="9.5" Foreground="{DynamicResource AppAccentBrush}" FontWeight="SemiBold" VerticalAlignment="Center"/>
</StackPanel>
</Button>

<TextBlock Text="Ln 1, Col 1"
Foreground="{DynamicResource AppTextMutedBrush}"
FontSize="11"
Expand Down
17 changes: 17 additions & 0 deletions Models/UpdateReleaseModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;

namespace Quell.Models;

/// <summary>
/// Model representing a GitHub release payload and version metadata.
/// </summary>
public class UpdateReleaseModel
{
public string TagName { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string Body { get; set; } = string.Empty;
public string HtmlUrl { get; set; } = string.Empty;
public string PublishedAt { get; set; } = string.Empty;
public Version? ParsedVersion { get; set; }
public bool IsNewer { get; set; }
}
60 changes: 60 additions & 0 deletions Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1807,6 +1807,66 @@ await Quell.Services.DatabaseStorageService.Instance.UpdateConnectionAsync(
// Restore defaults
await Quell.Services.Terminal.TerminalSettingsService.Instance.SaveSettingsAsync(new Quell.Models.TerminalSettingsModel());

// 23. Test Application Versioning Architecture, GitHub Releases Checker & Corner "Get Updates" Box
Console.WriteLine("\n[Action] Verifying Semantic Versioning, GitHub Releases Service & Corner 'Get Updates' Notification...");

// 23.1 Test Version Parsing & SemVer Comparisons
bool p1 = Quell.Services.AppUpdateService.TryParseVersion("v1.2.0", out var v1);
bool p2 = Quell.Services.AppUpdateService.TryParseVersion("1.0.0-rc1", out var v2);
bool p3 = Quell.Services.AppUpdateService.TryParseVersion("v0.9.8", out var v3);
if (!p1 || v1 == null || v1 != new Version(1, 2, 0)) throw new Exception("Version parsing for v1.2.0 failed!");
if (!p2 || v2 == null || v2 != new Version(1, 0, 0)) throw new Exception("Version parsing for 1.0.0-rc1 failed!");
if (!p3 || v3 == null || v3 >= Quell.Services.AppUpdateService.CurrentVersion) throw new Exception("SemVer comparison failed: v0.9.8 should be less than current version!");
if (v1 <= Quell.Services.AppUpdateService.CurrentVersion) throw new Exception("SemVer comparison failed: v1.2.0 should be greater than current version!");
Console.WriteLine($"[Success] Semantic version parsing & comparison verified (Current: {Quell.Services.AppUpdateService.CurrentVersionString})!");

// 23.2 Test Update Modal & Corner "Get Updates" Box Lifecycle
vm.UpdateModal.IsOpen = false;
vm.UpdateModal.IsCornerBadgeVisible = false;

// Simulate newer release notification
Quell.Services.AppUpdateService.Instance.SimulateUpdateAvailable(
newVersion: "v1.1.0",
releaseTitle: "Quell Studio 1.1.0 - Next-Gen SQL Performance",
notes: "• Automatic GitHub Releases version detection & notification\n• GPU-Accelerated Fluid Modal Animations"
);
vm.UpdateModal.ApplyRelease(Quell.Services.AppUpdateService.Instance.LatestRelease!);
vm.UpdateModal.Open();

if (!vm.UpdateModal.IsOpen || vm.UpdateModal.IsCornerBadgeVisible)
{
throw new Exception("UpdateModal should be Open, and CornerBadge should be hidden while modal is open!");
}
if (vm.UpdateModal.LatestVersion != "v1.1.0" || !vm.UpdateModal.IsUpdateAvailable)
{
throw new Exception("UpdateModal version binding mismatch!");
}
Console.WriteLine($"[Success] New update modal auto-opened: Version={vm.UpdateModal.LatestVersion}, Title='{vm.UpdateModal.ReleaseTitle}'!");

// Simulate user dismissing / clicking (X) on the modal
Console.WriteLine("[Action] User dismissed update modal with (X)...");
vm.UpdateModal.Dismiss();

if (vm.UpdateModal.IsOpen || !vm.UpdateModal.IsCornerBadgeVisible)
{
throw new Exception("Upon modal dismissal, UpdateModal.IsOpen must be False and IsCornerBadgeVisible MUST be True!");
}
Console.WriteLine("[Success] Modal closed; Corner 'Get Updates' box is now active and visible!");

// Simulate user clicking on corner "Get Updates" box anytime later
Console.WriteLine("[Action] User clicked on corner 'Get Updates' box...");
vm.OpenUpdateModalCommand.Execute(null);

if (!vm.UpdateModal.IsOpen || vm.UpdateModal.IsCornerBadgeVisible)
{
throw new Exception("Re-opening from corner box must show UpdateModal and hide the corner badge!");
}
Console.WriteLine("[Success] Update modal successfully re-opened from corner 'Get Updates' box!");

// Reset state
vm.UpdateModal.Dismiss();
Quell.Services.AppUpdateService.Instance.Reset();

Console.WriteLine("\n>>> ALL TESTS (LAYOUT, TABLES, MODALS, TABS, SQL AUTOCOMPLETE, MULTI-DIALECT SQLITE, VECTOR ICONS, SPLASH SCREEN, STORAGE, DYNAMIC DATABASE PROVIDERS, ISOLATED STARTUP, HIERARCHICAL TREE VIEW, REAL QUERY, TARGET DATABASE ROUTING, DISCONNECT/DELETE, GRAPHICAL FOOTER CELL UPDATE, PRIMARY KEY NEUTRAL STYLING, MULTI-ROW BATCH DELETION, MULTI-COLUMN DROP, FOOTER REFRESH/FILTER, SIMULTANEOUS BATCH CELL EDITING, FOREIGN KEY NAVIGATION, HARDWARE GPU/CPU RENDERING PIPELINE & INTEGRATED TERMINAL) PASSED! <<<");
return;
}
Expand Down
Loading
Loading