Files
SlipItIn/SlipItIn/ViewModels/LobbyViewModel.cs
Tim Krampitz 2d0687a083
Some checks failed
Build / build (pull_request) Failing after 1m0s
Lobby: include HostUserId in GameStateDto and let client derive host status; update tests
2026-10-03 22:29:33 +02:00

213 lines
6.4 KiB
C#

using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using SlipItIn.Messages;
using SlipItIn.Services.Interfaces;
using SlipItIn.Shared.DTOs;
namespace SlipItIn.ViewModels;
public partial class LobbyViewModel : BaseViewModel,
IRecipient<GameStateChangedMessage>,
IRecipient<LobbyCreatedMessage>,
IRecipient<GameStartedMessage>,
IRecipient<ErrorOccurredMessage>,
IRecipient<ConnectionStateChangedMessage>,
IRecipient<SyncStateChangedMessage>
{
private readonly ISignalRService _signalR;
private readonly IGameStateService _gameStateService;
private readonly IAuthSessionService _authSession;
private readonly IApiService _apiService;
[ObservableProperty]
public partial string LobbyCode { get; set; } = string.Empty;
[ObservableProperty]
public partial int GameId { get; set; }
[ObservableProperty]
public partial bool IsHost { get; set; }
[ObservableProperty]
public partial bool IsConnected { get; set; }
[ObservableProperty]
public partial bool IsSyncing { get; set; }
[ObservableProperty]
public partial string SyncStatusText { get; set; } = "Bereit";
[ObservableProperty]
public partial int PendingSyncActions { get; set; }
[ObservableProperty]
public partial bool IsCurrentPlayerReady { get; set; }
[ObservableProperty]
public partial bool CanStartGame { get; set; }
public ObservableCollection<PlayerInfoDto> Players { get; } = [];
public LobbyViewModel(ISignalRService signalR, IGameStateService gameStateService, IAuthSessionService authSession, IApiService apiService)
{
_signalR = signalR;
_gameStateService = gameStateService;
_authSession = authSession;
_apiService = apiService;
IsConnected = _signalR.IsConnected;
IsActive = true;
}
/// <summary>
/// Reaktiviert das ViewModel nach einem Logout (Singleton-Lebensdauer).
/// Wird von LobbyPage.OnNavigatedTo aufgerufen.
/// </summary>
public void Activate()
{
IsConnected = _signalR.IsConnected;
IsActive = true;
}
[RelayCommand]
private async Task CreateLobbyAsync()
{
await RunSafeAsync(async () =>
{
IsHost = true;
await _signalR.CreateLobbyAsync();
}, "Lobby wird erstellt...");
}
[RelayCommand]
private async Task JoinLobbyAsync()
{
await RunSafeAsync(async () =>
{
IsHost = false;
await _signalR.JoinLobbyAsync(LobbyCode);
}, "Lobby wird beigetreten...");
}
[RelayCommand]
private async Task SetReadyAsync()
{
await RunSafeAsync(async () =>
{
var currentPlayerId = GetCurrentPlayerId();
if (currentPlayerId <= 0 || GameId <= 0)
throw new InvalidOperationException("Spielstatus ist noch nicht verfügbar.");
await _signalR.PlayerReadyAsync(GameId, currentPlayerId);
});
}
[RelayCommand]
private async Task StartGameAsync()
{
await RunSafeAsync(async () =>
{
if (GameId <= 0)
throw new InvalidOperationException("Keine aktive Lobby gefunden.");
if (!CanStartGame)
throw new InvalidOperationException("Alle Spieler müssen bereit sein, bevor das Spiel gestartet werden kann.");
await _signalR.StartGameAsync(GameId);
});
}
[RelayCommand]
private async Task LogoutAsync()
{
await RunSafeAsync(async () =>
{
IsActive = false;
await _signalR.DisconnectAsync();
await _apiService.LogoutAsync();
await _gameStateService.ClearLocalGameDataAsync();
await Shell.Current.GoToAsync("//LoginPage");
});
}
public void Receive(GameStateChangedMessage message)
{
MainThread.BeginInvokeOnMainThread(() =>
{
GameId = message.Value.GameId;
LobbyCode = message.Value.LobbyCode;
Players.Clear();
foreach (var player in message.Value.Players)
Players.Add(player);
var currentUsername = _authSession.CurrentUser?.Username;
var currentUserId = _authSession.CurrentUser?.UserId ?? 0;
IsCurrentPlayerReady = message.Value.Players.Any(p =>
p.IsReady && string.Equals(p.Username, currentUsername, StringComparison.OrdinalIgnoreCase));
// Determine whether the current user is the host based on server-provided HostUserId
IsHost = currentUserId > 0 && message.Value.HostUserId > 0 && currentUserId == message.Value.HostUserId;
CanStartGame = message.Value.Players.Count >= 2 && message.Value.Players.All(p => p.IsReady);
});
}
public void Receive(LobbyCreatedMessage message)
{
MainThread.BeginInvokeOnMainThread(() =>
{
GameId = message.Value.GameId;
LobbyCode = message.Value.LobbyCode;
});
}
public void Receive(GameStartedMessage message)
{
MainThread.BeginInvokeOnMainThread(() =>
{
try
{
Shell.Current.GoToAsync(nameof(Views.GameBoardPage));
}
catch (Exception ex)
{
StatusMessage = $"Navigation fehlgeschlagen: {ex.Message}";
}
});
}
public void Receive(ErrorOccurredMessage message)
{
MainThread.BeginInvokeOnMainThread(() => StatusMessage = message.Value);
}
public void Receive(ConnectionStateChangedMessage message)
{
MainThread.BeginInvokeOnMainThread(() => IsConnected = message.Value);
}
public void Receive(SyncStateChangedMessage message)
{
MainThread.BeginInvokeOnMainThread(() =>
{
IsSyncing = message.Value.IsSyncing;
SyncStatusText = message.Value.StatusText;
PendingSyncActions = message.Value.PendingActions;
});
}
private int GetCurrentPlayerId()
{
var username = _authSession.CurrentUser?.Username;
if (string.IsNullOrWhiteSpace(username))
return 0;
return _gameStateService.CurrentGameState?.Players
.FirstOrDefault(p => string.Equals(p.Username, username, StringComparison.OrdinalIgnoreCase))?.PlayerId ?? 0;
}
}