diff --git a/DorkNet.AdminMobile/Design/AppDesign.cs b/DorkNet.AdminMobile/Design/AppDesign.cs
index 21ae895..69fc8a7 100644
--- a/DorkNet.AdminMobile/Design/AppDesign.cs
+++ b/DorkNet.AdminMobile/Design/AppDesign.cs
@@ -5,28 +5,19 @@ namespace DorkNet.AdminMobile.Design;
public static class AppDesign
{
- public static readonly Color Canvas = Color.FromArgb("#0b0f14");
- public static readonly Color Surface = Color.FromArgb("#1d222c");
- public static readonly Color SurfaceGlass = Color.FromArgb("#bb222833");
- public static readonly Color SurfaceLifted = Color.FromArgb("#293141");
- public static readonly Color Text = Color.FromArgb("#f6f8fb");
- public static readonly Color Muted = Color.FromArgb("#aab4c4");
- public static readonly Color Subtle = Color.FromArgb("#748094");
- public static readonly Color Accent = Color.FromArgb("#78c7ff");
- public static readonly Color AndroidAccent = Color.FromArgb("#9ee37d");
- public static readonly Color Danger = Color.FromArgb("#f26d7d");
+ public static readonly Color Canvas = Color.FromArgb("#0f1115");
+ public static readonly Color Surface = Color.FromArgb("#171a20");
+ public static readonly Color SurfaceLifted = Color.FromArgb("#20252d");
+ public static readonly Color RowSurface = Color.FromArgb("#14171d");
+ public static readonly Color Stroke = Color.FromArgb("#2a3039");
+ public static readonly Color Text = Color.FromArgb("#f4f6fa");
+ public static readonly Color Muted = Color.FromArgb("#aeb7c6");
+ public static readonly Color Subtle = Color.FromArgb("#778294");
+ public static readonly Color Accent = Color.FromArgb("#64b5ff");
+ public static readonly Color AndroidAccent = Color.FromArgb("#b7dd72");
+ public static readonly Color Danger = Color.FromArgb("#ff6f82");
- public static Brush PageBackground => new LinearGradientBrush
- {
- StartPoint = new Point(0, 0),
- EndPoint = new Point(1, 1),
- GradientStops =
- {
- new GradientStop(Color.FromArgb("#0b0f14"), 0),
- new GradientStop(Color.FromArgb("#121927"), 0.52f),
- new GradientStop(Color.FromArgb("#10151c"), 1),
- },
- };
+ public static Brush PageBackground => new SolidColorBrush(Canvas);
public static Color PlatformAccent =>
#if ANDROID
@@ -39,7 +30,7 @@ public static class AppDesign
{
Text = text,
TextColor = Text,
- FontSize = 28,
+ FontSize = 22,
FontAttributes = FontAttributes.Bold,
LineBreakMode = LineBreakMode.TailTruncation,
};
@@ -56,26 +47,10 @@ public static class AppDesign
public static Border GlassCard(View content, Thickness? padding = null) => new()
{
Padding = padding ?? new Thickness(14),
- Stroke = Color.FromArgb("#33ffffff"),
+ Stroke = Stroke,
StrokeThickness = 1,
- Background = new LinearGradientBrush
- {
- StartPoint = new Point(0, 0),
- EndPoint = new Point(1, 1),
- GradientStops =
- {
- new GradientStop(Color.FromArgb("#cc27303d"), 0),
- new GradientStop(Color.FromArgb("#99202733"), 1),
- },
- },
- StrokeShape = new RoundRectangle { CornerRadius = 24 },
- Shadow = new Shadow
- {
- Brush = Brush.Black,
- Offset = new Point(0, 12),
- Radius = 22,
- Opacity = 0.25f,
- },
+ BackgroundColor = Surface,
+ StrokeShape = new RoundRectangle { CornerRadius = 10 },
Content = content,
};
@@ -84,10 +59,10 @@ public static class AppDesign
Text = text,
BackgroundColor = PlatformAccent,
TextColor = Colors.Black,
- CornerRadius = 22,
- HeightRequest = 48,
+ CornerRadius = 10,
+ HeightRequest = 44,
FontAttributes = FontAttributes.Bold,
- Padding = new Thickness(18, 0),
+ Padding = new Thickness(14, 0),
};
public static Button SecondaryButton(string text) => new()
@@ -95,9 +70,9 @@ public static class AppDesign
Text = text,
BackgroundColor = SurfaceLifted,
TextColor = Text,
- CornerRadius = 22,
- HeightRequest = 46,
- Padding = new Thickness(16, 0),
+ CornerRadius = 10,
+ HeightRequest = 42,
+ Padding = new Thickness(14, 0),
};
public static Button DangerButton(string text) => new()
@@ -105,9 +80,9 @@ public static class AppDesign
Text = text,
BackgroundColor = Color.FromArgb("#4c1d28"),
TextColor = Color.FromArgb("#ffd5dc"),
- CornerRadius = 22,
- HeightRequest = 44,
- Padding = new Thickness(14, 0),
+ CornerRadius = 10,
+ HeightRequest = 42,
+ Padding = new Thickness(12, 0),
};
public static Entry Entry(string placeholder, Keyboard? keyboard = null, bool secret = false) => new()
@@ -119,7 +94,22 @@ public static class AppDesign
BackgroundColor = Color.FromArgb("#19202b"),
ClearButtonVisibility = ClearButtonVisibility.WhileEditing,
Keyboard = keyboard ?? Keyboard.Text,
- HeightRequest = 48,
+ HeightRequest = 44,
+ };
+
+ public static Label Caption(string text) => new()
+ {
+ Text = text,
+ TextColor = Muted,
+ FontSize = 13,
+ LineBreakMode = LineBreakMode.WordWrap,
+ };
+
+ public static BoxView Divider() => new()
+ {
+ HeightRequest = 1,
+ Color = Stroke,
+ Opacity = 0.7,
};
public static Label StatusLabel() => new()
diff --git a/DorkNet.AdminMobile/DorkNet.AdminMobile.csproj b/DorkNet.AdminMobile/DorkNet.AdminMobile.csproj
index 8839015..4bddb53 100644
--- a/DorkNet.AdminMobile/DorkNet.AdminMobile.csproj
+++ b/DorkNet.AdminMobile/DorkNet.AdminMobile.csproj
@@ -10,9 +10,23 @@
DorkNet Admin
net.dorknet.adminmobile
1.0
- 1
+ 3
36
+ android-arm;android-arm64;android-x86;android-x64
+ false
+ false
+ None
23.0
14.2
+
+
+
+
+
+
+
+
+
+
diff --git a/DorkNet.AdminMobile/Pages/LoginPage.cs b/DorkNet.AdminMobile/Pages/LoginPage.cs
index 7c2a1ab..e31d058 100644
--- a/DorkNet.AdminMobile/Pages/LoginPage.cs
+++ b/DorkNet.AdminMobile/Pages/LoginPage.cs
@@ -43,6 +43,13 @@ public sealed class LoginPage : ContentPage
Spacing = 14,
Children =
{
+ new Image
+ {
+ Source = "dorknet.svg",
+ WidthRequest = 96,
+ HeightRequest = 96,
+ HorizontalOptions = LayoutOptions.Start,
+ },
AppDesign.Title("Admin mobile"),
new Label
{
@@ -86,11 +93,19 @@ public sealed class LoginPage : ContentPage
base.OnAppearing();
if (loaded) return;
loaded = true;
- var saved = await settings.LoadConnectionAsync();
- baseUrl.Text = saved.BaseUrl;
- cfClientId.Text = saved.CloudflareAccessClientId;
- cfClientSecret.Text = saved.CloudflareAccessClientSecret;
- cfJwt.Text = saved.CloudflareAccessJwt;
+ try
+ {
+ var saved = await settings.LoadConnectionAsync();
+ baseUrl.Text = saved.BaseUrl;
+ cfClientId.Text = saved.CloudflareAccessClientId;
+ cfClientSecret.Text = saved.CloudflareAccessClientSecret;
+ cfJwt.Text = saved.CloudflareAccessJwt;
+ }
+ catch (Exception ex)
+ {
+ baseUrl.Text = "https://admin.rec.net";
+ status.Text = $"Secure storage was reset: {ex.Message}";
+ }
}
private async void OnLogin(object? sender, EventArgs e)
diff --git a/DorkNet.AdminMobile/Pages/PlayersPage.cs b/DorkNet.AdminMobile/Pages/PlayersPage.cs
index 343094f..4e22630 100644
--- a/DorkNet.AdminMobile/Pages/PlayersPage.cs
+++ b/DorkNet.AdminMobile/Pages/PlayersPage.cs
@@ -11,6 +11,7 @@ public sealed class PlayersPage : ContentPage
private readonly Entry search = AppDesign.Entry("Search players");
private readonly CollectionView list = new();
private readonly Label status = AppDesign.StatusLabel();
+ private readonly Button refresh = AppDesign.SecondaryButton("Refresh");
private List players = [];
public PlayersPage(AdminApiClient api)
@@ -19,10 +20,26 @@ public sealed class PlayersPage : ContentPage
Title = "Players";
Background = AppDesign.PageBackground;
- var refresh = AppDesign.SecondaryButton("Refresh");
refresh.Clicked += async (_, _) => await LoadAsync();
search.Completed += async (_, _) => await LoadAsync();
+ list.SelectionMode = SelectionMode.Single;
+ list.ItemSizingStrategy = ItemSizingStrategy.MeasureFirstItem;
+ list.SelectionChanged += async (_, e) =>
+ {
+ if (e.CurrentSelection.FirstOrDefault() is PlayerSummary player)
+ {
+ list.SelectedItem = null;
+ await PlayerActionsAsync(player);
+ }
+ };
+ list.EmptyView = new Label
+ {
+ Text = "No players loaded",
+ TextColor = AppDesign.Subtle,
+ HorizontalTextAlignment = TextAlignment.Center,
+ VerticalTextAlignment = TextAlignment.Center,
+ };
list.ItemTemplate = new DataTemplate(() =>
{
var name = new Label { TextColor = AppDesign.Text, FontAttributes = FontAttributes.Bold, FontSize = 16 };
@@ -34,40 +51,75 @@ public sealed class PlayersPage : ContentPage
var online = new Label { TextColor = AppDesign.AndroidAccent, FontSize = 12, FontAttributes = FontAttributes.Bold };
online.SetBinding(Label.IsVisibleProperty, nameof(PlayerSummary.Online));
online.Text = "ONLINE";
-
- var kick = MiniButton("Kick");
- kick.Clicked += async (s, _) =>
+ var chevron = new Label
{
- if ((s as Button)?.BindingContext is PlayerSummary p)
- await ConfirmAction($"Kick @{p.Username}?", () => api.KickPlayerAsync(p.Id, "Kicked from mobile admin."));
+ Text = ">",
+ TextColor = AppDesign.Subtle,
+ FontSize = 18,
+ VerticalTextAlignment = TextAlignment.Center,
};
- var resetAvatar = MiniButton("Reset avatar");
- resetAvatar.Clicked += async (s, _) =>
+ var row = new Grid
{
- if ((s as Button)?.BindingContext is PlayerSummary p)
- await ConfirmAction($"Reset @{p.Username}'s avatar?", () => api.ResetAvatarAsync(p.Id));
+ RowDefinitions =
+ {
+ new RowDefinition { Height = GridLength.Auto },
+ new RowDefinition { Height = GridLength.Auto },
+ new RowDefinition { Height = GridLength.Auto },
+ },
+ ColumnDefinitions =
+ {
+ new ColumnDefinition(),
+ new ColumnDefinition { Width = GridLength.Auto },
+ new ColumnDefinition { Width = GridLength.Auto },
+ },
+ Padding = new Thickness(12, 10),
+ RowSpacing = 2,
+ ColumnSpacing = 10,
+ BackgroundColor = AppDesign.RowSurface,
};
+ row.Add(name, 0, 0);
+ row.Add(online, 1, 0);
+ row.Add(handle, 0, 1);
+ row.Add(detail, 0, 2);
+ row.Add(chevron, 2, 0);
+ Grid.SetRowSpan(chevron, 3);
- var actions = new HorizontalStackLayout { Spacing = 8, Children = { kick, resetAvatar } };
- return AppDesign.GlassCard(new VerticalStackLayout
+ return new VerticalStackLayout
{
- Spacing = 6,
- Children = { name, handle, detail, online, actions },
- }, new Thickness(12, 10));
+ Spacing = 0,
+ Children = { row, AppDesign.Divider() },
+ };
});
- Content = new VerticalStackLayout
+ var searchRow = new Grid
{
- Padding = 16,
- Spacing = 12,
- Children =
+ ColumnSpacing = 8,
+ ColumnDefinitions =
{
- AppDesign.Title("Players"),
- new HorizontalStackLayout { Spacing = 8, Children = { search, refresh } },
- list,
- status,
+ new ColumnDefinition(),
+ new ColumnDefinition { Width = GridLength.Auto },
},
};
+ searchRow.Add(search, 0, 0);
+ searchRow.Add(refresh, 1, 0);
+
+ var grid = new Grid
+ {
+ Padding = 16,
+ RowSpacing = 12,
+ RowDefinitions =
+ {
+ new RowDefinition { Height = GridLength.Auto },
+ new RowDefinition { Height = GridLength.Auto },
+ new RowDefinition(),
+ new RowDefinition { Height = GridLength.Auto },
+ },
+ };
+ grid.Add(AppDesign.Title("Players"), 0, 0);
+ grid.Add(searchRow, 0, 1);
+ grid.Add(list, 0, 2);
+ grid.Add(status, 0, 3);
+ Content = grid;
}
protected override async void OnAppearing()
@@ -80,6 +132,7 @@ public sealed class PlayersPage : ContentPage
{
try
{
+ refresh.IsEnabled = false;
status.Text = "Loading...";
players = await api.GetPlayersAsync(search.Text);
list.ItemsSource = players;
@@ -89,6 +142,33 @@ public sealed class PlayersPage : ContentPage
{
status.Text = ex.Message;
}
+ finally
+ {
+ refresh.IsEnabled = true;
+ }
+ }
+
+ private async Task PlayerActionsAsync(PlayerSummary player)
+ {
+ var action = await DisplayActionSheet(
+ $"@{player.Username}",
+ "Cancel",
+ "Remove account",
+ "Kick",
+ "Reset avatar");
+
+ switch (action)
+ {
+ case "Kick":
+ await ConfirmAction($"Kick @{player.Username}?", () => api.KickPlayerAsync(player.Id, "Kicked from mobile admin."));
+ break;
+ case "Reset avatar":
+ await ConfirmAction($"Reset @{player.Username}'s avatar?", () => api.ResetAvatarAsync(player.Id));
+ break;
+ case "Remove account":
+ await RemoveAccountAsync(player);
+ break;
+ }
}
private async Task ConfirmAction(string message, Func action)
@@ -105,13 +185,38 @@ public sealed class PlayersPage : ContentPage
}
}
- private static Button MiniButton(string text) => new()
+ private async Task RemoveAccountAsync(PlayerSummary player)
{
- Text = text,
- FontSize = 12,
- Padding = new Thickness(10, 6),
- CornerRadius = 6,
- BackgroundColor = AppDesign.SurfaceLifted,
- TextColor = AppDesign.Text,
- };
+ var username = await DisplayPromptAsync(
+ "Verify username",
+ $"Type {player.Username} exactly to remove @{player.Username}.",
+ "Next",
+ "Cancel",
+ maxLength: Math.Max(32, player.Username.Length),
+ keyboard: Keyboard.Text);
+ if (!string.Equals(username?.Trim(), player.Username, StringComparison.Ordinal)) return;
+
+ var phrase = $"DELETE {player.Id}";
+ var typedPhrase = await DisplayPromptAsync(
+ "Verify removal",
+ $"Type {phrase} exactly. This cannot be undone.",
+ "Remove",
+ "Cancel",
+ maxLength: phrase.Length,
+ keyboard: Keyboard.Text);
+ if (!string.Equals(typedPhrase?.Trim(), phrase, StringComparison.Ordinal)) return;
+
+ if (!await DisplayAlert("Remove account", $"Permanently remove @{player.Username}?", "Remove", "Cancel")) return;
+
+ try
+ {
+ status.Text = $"Removing @{player.Username}...";
+ await api.DeletePlayerAsync(player.Id, player.Username, phrase, "Removed from mobile admin.");
+ await LoadAsync();
+ }
+ catch (Exception ex)
+ {
+ await DisplayAlert("Removal failed", ex.Message, "OK");
+ }
+ }
}
diff --git a/DorkNet.AdminMobile/Pages/RoomsPage.cs b/DorkNet.AdminMobile/Pages/RoomsPage.cs
index 26588e1..54cf326 100644
--- a/DorkNet.AdminMobile/Pages/RoomsPage.cs
+++ b/DorkNet.AdminMobile/Pages/RoomsPage.cs
@@ -11,6 +11,7 @@ public sealed class RoomsPage : ContentPage
private readonly CollectionView rooms = new();
private readonly CollectionView instances = new();
private readonly Label status = AppDesign.StatusLabel();
+ private readonly Button refresh = AppDesign.SecondaryButton("Refresh");
private bool loaded;
public RoomsPage(AdminApiClient api)
@@ -19,18 +20,28 @@ public sealed class RoomsPage : ContentPage
Title = "Rooms";
Background = AppDesign.PageBackground;
+ rooms.SelectionMode = SelectionMode.None;
+ rooms.ItemSizingStrategy = ItemSizingStrategy.MeasureFirstItem;
+ rooms.EmptyView = EmptyList("No recent rooms");
rooms.ItemTemplate = new DataTemplate(() =>
{
var name = new Label { TextColor = AppDesign.Text, FontAttributes = FontAttributes.Bold, FontSize = 15 };
name.SetBinding(Label.TextProperty, nameof(RoomSummary.Name));
var meta = new Label { TextColor = AppDesign.Muted, FontSize = 12 };
meta.SetBinding(Label.TextProperty, new Binding(nameof(RoomSummary.Id), stringFormat: "Room #{0}"));
- return AppDesign.GlassCard(new VerticalStackLayout
+ var row = new VerticalStackLayout
{
+ Padding = new Thickness(12, 9),
+ Spacing = 2,
+ BackgroundColor = AppDesign.RowSurface,
Children = { name, meta },
- }, new Thickness(12, 9));
+ };
+ return new VerticalStackLayout { Spacing = 0, Children = { row, AppDesign.Divider() } };
});
+ instances.SelectionMode = SelectionMode.None;
+ instances.ItemSizingStrategy = ItemSizingStrategy.MeasureFirstItem;
+ instances.EmptyView = EmptyList("No active instances");
instances.ItemTemplate = new DataTemplate(() =>
{
var name = new Label { TextColor = AppDesign.Text, FontAttributes = FontAttributes.Bold, FontSize = 15 };
@@ -39,35 +50,51 @@ public sealed class RoomsPage : ContentPage
meta.SetBinding(Label.TextProperty, new Binding(nameof(InstanceSummary.PhotonRegionId), stringFormat: "Region {0}"));
var count = new Label { TextColor = AppDesign.Subtle, FontSize = 12 };
count.SetBinding(Label.TextProperty, new Binding("Participants.Count", stringFormat: "{0} participants"));
- return AppDesign.GlassCard(new VerticalStackLayout
+ var row = new VerticalStackLayout
{
+ Padding = new Thickness(12, 9),
+ Spacing = 2,
+ BackgroundColor = AppDesign.RowSurface,
Children = { name, meta, count },
- }, new Thickness(12, 9));
+ };
+ return new VerticalStackLayout { Spacing = 0, Children = { row, AppDesign.Divider() } };
});
- var refresh = AppDesign.SecondaryButton("Refresh");
refresh.Clicked += async (_, _) => await LoadAsync();
- Content = new ScrollView
+ var grid = new Grid
{
- Content = new VerticalStackLayout
+ Padding = 16,
+ RowSpacing = 10,
+ RowDefinitions =
{
- Padding = 16,
- Spacing = 14,
- Children =
- {
- AppDesign.Title("Rooms"),
- refresh,
- new Label { Text = "Live instances", TextColor = AppDesign.Muted, FontAttributes = FontAttributes.Bold },
- instances,
- new Label { Text = "Recent rooms", TextColor = AppDesign.Muted, FontAttributes = FontAttributes.Bold },
- rooms,
- status,
- },
+ new RowDefinition { Height = GridLength.Auto },
+ new RowDefinition { Height = GridLength.Auto },
+ new RowDefinition { Height = GridLength.Auto },
+ new RowDefinition { Height = new GridLength(1, GridUnitType.Star) },
+ new RowDefinition { Height = GridLength.Auto },
+ new RowDefinition { Height = new GridLength(2, GridUnitType.Star) },
+ new RowDefinition { Height = GridLength.Auto },
},
};
+ grid.Add(AppDesign.Title("Rooms"), 0, 0);
+ grid.Add(refresh, 0, 1);
+ grid.Add(new Label { Text = "Live instances", TextColor = AppDesign.Muted, FontAttributes = FontAttributes.Bold }, 0, 2);
+ grid.Add(instances, 0, 3);
+ grid.Add(new Label { Text = "Recent rooms", TextColor = AppDesign.Muted, FontAttributes = FontAttributes.Bold }, 0, 4);
+ grid.Add(rooms, 0, 5);
+ grid.Add(status, 0, 6);
+ Content = grid;
}
+ private static Label EmptyList(string text) => new()
+ {
+ Text = text,
+ TextColor = AppDesign.Subtle,
+ HorizontalTextAlignment = TextAlignment.Center,
+ VerticalTextAlignment = TextAlignment.Center,
+ };
+
protected override async void OnAppearing()
{
base.OnAppearing();
@@ -82,6 +109,7 @@ public sealed class RoomsPage : ContentPage
{
try
{
+ refresh.IsEnabled = false;
status.Text = "Loading...";
var instanceRows = await api.GetInstancesAsync();
var roomRows = await api.GetRoomsAsync();
@@ -93,5 +121,9 @@ public sealed class RoomsPage : ContentPage
{
status.Text = ex.Message;
}
+ finally
+ {
+ refresh.IsEnabled = true;
+ }
}
}
diff --git a/DorkNet.AdminMobile/Pages/SettingsPage.cs b/DorkNet.AdminMobile/Pages/SettingsPage.cs
index 025010d..702ac45 100644
--- a/DorkNet.AdminMobile/Pages/SettingsPage.cs
+++ b/DorkNet.AdminMobile/Pages/SettingsPage.cs
@@ -96,7 +96,7 @@ public sealed class SettingsPage : ContentPage
}
}
- private static Grid Row(string label, View control)
+ private static View Row(string label, View control)
{
var grid = new Grid
{
diff --git a/DorkNet.AdminMobile/Platforms/Android/AndroidManifest.xml b/DorkNet.AdminMobile/Platforms/Android/AndroidManifest.xml
index 3e846aa..6ba59a9 100644
--- a/DorkNet.AdminMobile/Platforms/Android/AndroidManifest.xml
+++ b/DorkNet.AdminMobile/Platforms/Android/AndroidManifest.xml
@@ -2,4 +2,10 @@
+
diff --git a/DorkNet.AdminMobile/Platforms/Android/MainActivity.cs b/DorkNet.AdminMobile/Platforms/Android/MainActivity.cs
index 041fc42..0215b01 100644
--- a/DorkNet.AdminMobile/Platforms/Android/MainActivity.cs
+++ b/DorkNet.AdminMobile/Platforms/Android/MainActivity.cs
@@ -5,7 +5,12 @@ using Microsoft.Maui;
namespace DorkNet.AdminMobile;
[Activity(
+ Label = "DorkNet Admin",
+ Icon = "@mipmap/appicon",
+ RoundIcon = "@mipmap/appicon_round",
MainLauncher = true,
+ Exported = true,
+ Theme = "@style/Maui.SplashTheme",
ConfigurationChanges =
ConfigChanges.ScreenSize |
ConfigChanges.Orientation |
diff --git a/DorkNet.AdminMobile/Resources/AppIcon/appicon.svg b/DorkNet.AdminMobile/Resources/AppIcon/appicon.svg
new file mode 100644
index 0000000..2bf84c6
--- /dev/null
+++ b/DorkNet.AdminMobile/Resources/AppIcon/appicon.svg
@@ -0,0 +1,71 @@
+
+
+
diff --git a/DorkNet.AdminMobile/Resources/AppIcon/appiconfg.svg b/DorkNet.AdminMobile/Resources/AppIcon/appiconfg.svg
new file mode 100644
index 0000000..2bf84c6
--- /dev/null
+++ b/DorkNet.AdminMobile/Resources/AppIcon/appiconfg.svg
@@ -0,0 +1,71 @@
+
+
+
diff --git a/DorkNet.AdminMobile/Resources/Brand/dorknet.svg b/DorkNet.AdminMobile/Resources/Brand/dorknet.svg
new file mode 100644
index 0000000..2bf84c6
--- /dev/null
+++ b/DorkNet.AdminMobile/Resources/Brand/dorknet.svg
@@ -0,0 +1,71 @@
+
+
+
diff --git a/DorkNet.AdminMobile/Resources/Splash/splash.svg b/DorkNet.AdminMobile/Resources/Splash/splash.svg
new file mode 100644
index 0000000..2bf84c6
--- /dev/null
+++ b/DorkNet.AdminMobile/Resources/Splash/splash.svg
@@ -0,0 +1,71 @@
+
+
+
diff --git a/DorkNet.AdminMobile/Services/AdminApiClient.cs b/DorkNet.AdminMobile/Services/AdminApiClient.cs
index c299ba1..296376d 100644
--- a/DorkNet.AdminMobile/Services/AdminApiClient.cs
+++ b/DorkNet.AdminMobile/Services/AdminApiClient.cs
@@ -61,6 +61,12 @@ public sealed class AdminApiClient(SecureAdminSettings settings)
public Task ResetAvatarAsync(long playerId) =>
SendAsync