Files
Barış Keser ce110c8100 feat(launcher): show server in multiplayer list + user auto-connect toggle
- Add Services/ServersDat.cs (self-contained big-endian NBT codec) to write/
  merge .AthenaStudios/servers.dat so the Athena server auto-appears in the
  in-game Multiplayer list. Existing entries preserved, deduped by IP.
- Write servers.dat on every launch (independent of auto-connect).
- Add an auto-connect-on-launch user setting (LocalSettings.AutoConnectOnLaunch
  + Settings checkbox); effective server-join = backend AutoConnect AND the user
  setting, so players can opt out of auto-join.
- Remove the account delete button from AccountsView: users keep their name
  slots; only admins delete via the web panel (backend now 403s the self-delete
  endpoint).
2026-06-14 13:06:34 +03:00

133 lines
4.8 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Platform.Storage;
using OfflineMinecraftLauncher.Services;
namespace OfflineMinecraftLauncher.Views;
public partial class AccountsView : UserControl
{
private readonly AuthService _auth;
private readonly LocalSettings _settings;
private const int Cap = 5;
public event Action<Account>? AccountChosen;
public AccountsView(AuthService auth, LocalSettings settings)
{
_auth = auth;
_settings = settings;
InitializeComponent();
}
public async Task RefreshAsync()
{
AccountList.Children.Clear();
Empty.IsVisible = true;
Empty.Text = "Yükleniyor...";
var accounts = await _auth.GetAccountsAsync();
CapLabel.Text = $"{accounts.Count} / {Cap} hesap";
AddButton.IsEnabled = accounts.Count < Cap;
if (accounts.Count == 0)
{
Empty.Text = "Henüz hesabın yok. Yukarıdan bir tane ekle.";
return;
}
Empty.IsVisible = false;
foreach (var a in accounts) AccountList.Children.Add(BuildRow(a));
}
private Control BuildRow(Account a)
{
bool selected = a.Id == _settings.SelectedAccountId;
var grid = new Grid { ColumnDefinitions = new ColumnDefinitions("*,Auto") };
var left = new StackPanel { Spacing = 2, VerticalAlignment = VerticalAlignment.Center };
var nameLine = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8 };
nameLine.Children.Add(new TextBlock { Text = a.Username, FontWeight = FontWeight.SemiBold, FontSize = 15 });
if (selected)
nameLine.Children.Add(new TextBlock { Text = "• seçili", Classes = { "muted" }, VerticalAlignment = VerticalAlignment.Center });
if (a.IsBanned)
nameLine.Children.Add(new TextBlock { Text = "• BANLI", Foreground = Res("DangerBrush"), VerticalAlignment = VerticalAlignment.Center });
left.Children.Add(nameLine);
left.Children.Add(new TextBlock
{
Text = string.IsNullOrEmpty(a.SkinHash) ? "skin: varsayılan" : "skin: özel ✓",
Classes = { "muted" },
FontSize = 11
});
Grid.SetColumn(left, 0);
grid.Children.Add(left);
var btns = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8 };
var selBtn = new Button { Classes = { "ghost" }, Content = selected ? "Seçili" : "Seç" };
selBtn.Click += (_, _) => AccountChosen?.Invoke(a);
var skinBtn = new Button { Classes = { "ghost" }, Content = "Skin Yükle" };
skinBtn.Click += async (_, _) => await UploadSkinAsync(a);
// Hesap silme kaldırıldı: kullanıcılar 5 isim haklarını silemez; yalnız
// yöneticiler web panelinden siler (backend de DELETE /api/accounts'u 403'ler).
btns.Children.Add(selBtn);
btns.Children.Add(skinBtn);
Grid.SetColumn(btns, 1);
grid.Children.Add(btns);
return new Border
{
Classes = { "card" },
Child = grid,
BorderBrush = selected ? Res("AccentBrush") : Res("BorderBrush")
};
}
private async void OnAdd(object? sender, RoutedEventArgs e)
{
var name = (NewName.Text ?? "").Trim();
if (!Regex.IsMatch(name, "^[A-Za-z0-9_]{3,16}$"))
{
AddStatus.Text = "Geçersiz ad (3-16 karakter; harf, rakam, _).";
return;
}
AddButton.IsEnabled = false;
AddStatus.Text = "Ekleniyor...";
var (ok, err) = await _auth.CreateAccountAsync(name);
AddStatus.Text = ok ? "Hesap eklendi ✓" : ("Hata: " + err);
if (ok) NewName.Text = "";
await RefreshAsync();
}
private async Task UploadSkinAsync(Account a)
{
var top = TopLevel.GetTopLevel(this);
if (top is null) return;
var files = await top.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{
Title = "Skin PNG seç (64x64 veya 64x32)",
AllowMultiple = false,
FileTypeFilter = new[] { new FilePickerFileType("PNG") { Patterns = new[] { "*.png" } } }
});
if (files.Count == 0) return;
var path = files[0].TryGetLocalPath();
if (string.IsNullOrEmpty(path))
{
AddStatus.Text = "Dosya yolu okunamadı.";
return;
}
string model = SkinModel.SelectedIndex == 1 ? "slim" : "default";
AddStatus.Text = "Skin yükleniyor...";
var (ok, msg) = await _auth.UploadSkinAsync(a.Id, path, model);
AddStatus.Text = msg;
await RefreshAsync();
}
private IBrush? Res(string key) => this.FindResource(key) as IBrush;
}