44using System . Threading ;
55using System . Threading . Tasks ;
66using Avalonia . Controls ;
7+ using Avalonia . Threading ;
78using CommunityToolkit . Mvvm . ComponentModel ;
89using CommunityToolkit . Mvvm . Input ;
910using GenHub . Core . Constants ;
1011using GenHub . Core . Interfaces . Common ;
1112using GenHub . Core . Interfaces . GameInstallations ;
1213using GenHub . Core . Interfaces . GameProfiles ;
14+ using GenHub . Core . Interfaces . Notifications ;
1315using GenHub . Core . Models . Enums ;
1416using GenHub . Core . Models . GameProfile ;
17+ using GenHub . Core . Models . Notifications ;
1518using GenHub . Features . AppUpdate . Interfaces ;
1619using GenHub . Features . Downloads . ViewModels ;
1720using GenHub . Features . GameProfiles . Services ;
@@ -35,13 +38,13 @@ public partial class MainViewModel : ObservableObject, IDisposable
3538 private readonly IProfileEditorFacade _profileEditorFacade ;
3639 private readonly IVelopackUpdateManager _velopackUpdateManager ;
3740 private readonly ProfileResourceService _profileResourceService ;
41+ private readonly INotificationService _notificationService ;
3842 private readonly CancellationTokenSource _initializationCts = new ( ) ;
3943
4044 [ ObservableProperty ]
4145 private NavigationTab _selectedTab = NavigationTab . GameProfiles ;
4246
43- [ ObservableProperty ]
44- private bool _hasUpdateAvailable ;
47+
4548
4649 /// <summary>
4750 /// Initializes a new instance of the <see cref="MainViewModel"/> class.
@@ -57,6 +60,7 @@ public partial class MainViewModel : ObservableObject, IDisposable
5760 /// <param name="profileEditorFacade">Profile editor facade for automatic profile creation.</param>
5861 /// <param name="velopackUpdateManager">The Velopack update manager for checking updates.</param>
5962 /// <param name="profileResourceService">Service for accessing profile resources.</param>
63+ /// <param name="notificationService">Service for showing notifications.</param>
6064 /// <param name="logger">Logger instance.</param>
6165 public MainViewModel (
6266 GameProfileLauncherViewModel gameProfilesViewModel ,
@@ -70,6 +74,7 @@ public MainViewModel(
7074 IProfileEditorFacade profileEditorFacade ,
7175 IVelopackUpdateManager velopackUpdateManager ,
7276 ProfileResourceService profileResourceService ,
77+ INotificationService notificationService ,
7378 ILogger < MainViewModel > ? logger = null )
7479 {
7580 GameProfilesViewModel = gameProfilesViewModel ;
@@ -83,6 +88,7 @@ public MainViewModel(
8388 _profileEditorFacade = profileEditorFacade ?? throw new ArgumentNullException ( nameof ( profileEditorFacade ) ) ;
8489 _velopackUpdateManager = velopackUpdateManager ?? throw new ArgumentNullException ( nameof ( velopackUpdateManager ) ) ;
8590 _profileResourceService = profileResourceService ?? throw new ArgumentNullException ( nameof ( profileResourceService ) ) ;
91+ _notificationService = notificationService ?? throw new ArgumentNullException ( nameof ( notificationService ) ) ;
8692 _logger = logger ;
8793
8894 // Load initial settings using unified configuration
@@ -181,30 +187,7 @@ public void SelectTab(NavigationTab tab)
181187 SelectedTab = tab ;
182188 }
183189
184- /// <summary>
185- /// Shows the update notification dialog.
186- /// </summary>
187- /// <returns>A task representing the asynchronous operation.</returns>
188- [ RelayCommand ]
189- public async Task ShowUpdateDialogAsync ( )
190- {
191- try
192- {
193- var mainWindow = GetMainWindow ( ) ;
194- if ( mainWindow != null )
195- {
196- await GenHub . Features . AppUpdate . Views . UpdateNotificationWindow . ShowAsync ( mainWindow ) ;
197- }
198- else
199- {
200- _logger ? . LogWarning ( "Cannot show update dialog - main window not found" ) ;
201- }
202- }
203- catch ( Exception ex )
204- {
205- _logger ? . LogError ( ex , "Failed to show update dialog" ) ;
206- }
207- }
190+
208191
209192 /// <summary>
210193 /// Performs asynchronous initialization for the shell and all tabs.
@@ -372,115 +355,58 @@ private async Task CheckForUpdatesAsync(CancellationToken cancellationToken = de
372355
373356 try
374357 {
375- // Check if subscribed to a PR - if so, check for PR artifact updates instead
376358 var settings = _userSettingsService . Get ( ) ;
359+
360+ // Push settings to update manager (important context for other components)
377361 if ( settings . SubscribedPrNumber . HasValue )
378362 {
379- _logger ? . LogDebug ( "User subscribed to PR #{PrNumber}, checking for PR artifact updates" , settings . SubscribedPrNumber ) ;
380363 _velopackUpdateManager . SubscribedPrNumber = settings . SubscribedPrNumber ;
364+ }
381365
382- // Fetch PR list to populate artifact info
383- var prs = await _velopackUpdateManager . GetOpenPullRequestsAsync ( cancellationToken ) ;
384- var subscribedPr = prs . FirstOrDefault ( p => p . Number == settings . SubscribedPrNumber ) ;
366+ // Check if subscribed to a specific branch
367+ if ( ! string . IsNullOrEmpty ( settings . SubscribedBranch ) )
368+ {
369+ _logger ? . LogDebug ( "User subscribed to branch '{Branch}', checking for artifact updates" , settings . SubscribedBranch ) ;
370+ _velopackUpdateManager . SubscribedBranch = settings . SubscribedBranch ;
371+
372+ // Ensure PR number is cleared to avoid ambiguity
373+ _velopackUpdateManager . SubscribedPrNumber = null ;
374+
375+ var artifactUpdate = await _velopackUpdateManager . CheckForArtifactUpdatesAsync ( cancellationToken ) ;
385376
386- if ( subscribedPr ? . LatestArtifact != null )
377+ if ( artifactUpdate != null )
387378 {
388- // Compare versions (strip build metadata)
389- var currentVersionBase = AppConstants . AppVersion . Split ( '+' ) [ 0 ] ;
390- var prVersionBase = subscribedPr . LatestArtifact . Version . Split ( '+' ) [ 0 ] ;
379+ var newVersionBase = artifactUpdate . Version . Split ( '+' ) [ 0 ] ;
380+ var dismissedVersionBase = settings . DismissedUpdateVersion ? . Split ( '+' ) [ 0 ] ;
391381
392- if ( ! string . Equals ( prVersionBase , currentVersionBase , StringComparison . OrdinalIgnoreCase ) )
382+ if ( string . IsNullOrEmpty ( dismissedVersionBase ) ||
383+ ! string . Equals ( newVersionBase , dismissedVersionBase , StringComparison . OrdinalIgnoreCase ) )
393384 {
394- // Check if this PR version was dismissed
395- var dismissedVersionBase = settings . DismissedUpdateVersion ? . Split ( '+' ) [ 0 ] ;
385+ _logger ? . LogInformation ( "Branch '{Branch}' artifact update available: {Version}" , settings . SubscribedBranch , newVersionBase ) ;
396386
397- if ( string . IsNullOrEmpty ( dismissedVersionBase ) ||
398- ! string . Equals ( prVersionBase , dismissedVersionBase , StringComparison . OrdinalIgnoreCase ) )
399- {
400- _logger ? . LogInformation ( "PR #{PrNumber} artifact update available: {Version}" , subscribedPr . Number , prVersionBase ) ;
401- HasUpdateAvailable = true ;
402- return ;
403- }
404- else
387+ await Dispatcher . UIThread . InvokeAsync ( ( ) =>
405388 {
406- _logger ? . LogDebug ( "PR #{PrNumber} artifact update {Version} was dismissed" , subscribedPr . Number , prVersionBase ) ;
407- HasUpdateAvailable = false ;
408- return ;
409- }
389+ _notificationService . Show ( new NotificationMessage (
390+ NotificationType . Info ,
391+ "Update Available" ,
392+ $ "A new version ({ newVersionBase } ) is available on branch '{ settings . SubscribedBranch } '.",
393+ null ,
394+ "View Updates" ,
395+ ( ) => { SettingsViewModel . OpenUpdateWindowCommand . Execute ( null ) ; } ) ) ;
396+ } ) ;
397+ return ;
410398 }
411399 else
412400 {
413- _logger ? . LogDebug ( "Already on latest PR #{PrNumber} artifact version" , subscribedPr . Number ) ;
414- HasUpdateAvailable = false ;
401+ _logger ? . LogDebug ( "Branch '{Branch}' artifact update {Version} was dismissed" , settings . SubscribedBranch , newVersionBase ) ;
415402 return ;
416403 }
417404 }
418- else
419- {
420- _logger ? . LogDebug ( "PR #{PrNumber} has no artifacts or PR not found" , settings . SubscribedPrNumber ) ;
421-
422- // Fall through to check main branch updates
423- }
424- }
425-
426- // Check main branch updates (if not subscribed to PR or PR has no artifacts)
427- var updateInfo = await _velopackUpdateManager . CheckForUpdatesAsync ( cancellationToken ) ;
428-
429- // Check both UpdateInfo (from installed app) and GitHub API flag (works in debug too)
430- var hasUpdate = updateInfo != null || _velopackUpdateManager . HasUpdateAvailableFromGitHub ;
431-
432- if ( hasUpdate )
433- {
434- string ? latestVersion = null ;
435-
436- if ( updateInfo != null )
437- {
438- latestVersion = updateInfo . TargetFullRelease . Version . ToString ( ) ;
439- _logger ? . LogInformation ( "Update available: {Current} → {Latest}" , AppConstants . AppVersion , latestVersion ) ;
440- }
441- else if ( _velopackUpdateManager . LatestVersionFromGitHub != null )
442- {
443- latestVersion = _velopackUpdateManager . LatestVersionFromGitHub ;
444- _logger ? . LogInformation ( "Update available from GitHub API: {Version}" , latestVersion ) ;
445- }
446-
447- // Strip build metadata for comparison (everything after '+')
448- var latestVersionBase = latestVersion ? . Split ( '+' ) [ 0 ] ;
449- var currentVersionBase = AppConstants . AppVersion . Split ( '+' ) [ 0 ] ;
450-
451- // Check if this version was dismissed by the user
452- var settings2 = _userSettingsService . Get ( ) ;
453- var dismissedVersionBase = settings2 . DismissedUpdateVersion ? . Split ( '+' ) [ 0 ] ;
454-
455- if ( ! string . IsNullOrEmpty ( latestVersionBase ) &&
456- string . Equals ( latestVersionBase , dismissedVersionBase , StringComparison . OrdinalIgnoreCase ) )
457- {
458- _logger ? . LogDebug ( "Update {Version} was dismissed by user, hiding notification" , latestVersionBase ) ;
459- HasUpdateAvailable = false ;
460- }
461-
462- // Also check if we're already on this version (ignoring build metadata)
463- else if ( ! string . IsNullOrEmpty ( latestVersionBase ) &&
464- string . Equals ( latestVersionBase , currentVersionBase , StringComparison . OrdinalIgnoreCase ) )
465- {
466- _logger ? . LogDebug ( "Already on version {Version} (ignoring build metadata), hiding notification" , latestVersionBase ) ;
467- HasUpdateAvailable = false ;
468- }
469- else
470- {
471- HasUpdateAvailable = true ;
472- }
473- }
474- else
475- {
476- _logger ? . LogDebug ( "No updates available" ) ;
477- HasUpdateAvailable = false ;
478405 }
479406 }
480407 catch ( Exception ex )
481408 {
482409 _logger ? . LogError ( ex , "Exception in CheckForUpdatesAsync" ) ;
483- HasUpdateAvailable = false ;
484410 }
485411 }
486412
0 commit comments