Add ServerEntry model and server list persistence to platform trait

Introduces a ServerEntry struct in common with name, address, port,
username, and optional password fields. Extends PlatformInterface with
load_servers/save_servers methods, implemented across all platforms
(desktop persists to JSON config, web uses localStorage, mobile/stub
are stubs).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Builder
2026-03-30 02:14:03 +00:00
parent 2c22942fb3
commit 765446392d
6 changed files with 70 additions and 6 deletions
+10
View File
@@ -16,3 +16,13 @@ pub struct ServerStatus {
pub max_users: Option<u32>,
pub bandwidth: Option<u32>,
}
#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq)]
pub struct ServerEntry {
pub name: String,
pub address: String,
pub port: u16,
pub username: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub password: Option<String>,
}
+17 -1
View File
@@ -2,7 +2,7 @@ use crate::app::Command;
use color_eyre::eyre::Error;
use dioxus::hooks::UnboundedReceiver;
use etcetera::{choose_app_strategy, AppStrategy, AppStrategyArgs};
use mumble_web2_common::{ClientConfig, ServerStatus};
use mumble_web2_common::{ClientConfig, ServerEntry, ServerStatus};
use std::collections::HashMap;
use std::time::Duration;
@@ -46,6 +46,22 @@ impl super::PlatformInterface for DesktopPlatform {
save_config_map(&config).ok()
}
fn load_servers() -> Vec<ServerEntry> {
let config = load_config_map();
config
.get("servers")
.and_then(|s| serde_json::from_str(s).ok())
.unwrap_or_default()
}
fn save_servers(servers: &[ServerEntry]) {
let mut config = load_config_map();
if let Ok(json) = serde_json::to_string(servers) {
config.insert("servers".to_string(), json);
let _ = save_config_map(&config);
}
}
async fn network_connect(
address: String,
username: String,
+8 -2
View File
@@ -1,7 +1,7 @@
use crate::app::Command;
use color_eyre::eyre::Error;
use dioxus::hooks::UnboundedReceiver;
use mumble_web2_common::{ClientConfig, ServerStatus};
use mumble_web2_common::{ClientConfig, ServerEntry, ServerStatus};
use std::future::Future;
use std::time::Duration;
@@ -31,10 +31,16 @@ impl super::PlatformInterface for MobilePlatform {
None
}
fn set_default_server(server: &str) -> Option<()> {
fn set_default_server(_server: &str) -> Option<()> {
None
}
fn load_servers() -> Vec<ServerEntry> {
Vec::new()
}
fn save_servers(_servers: &[ServerEntry]) {}
async fn network_connect(
address: String,
username: String,
+7 -1
View File
@@ -7,7 +7,7 @@
use crate::{app::Command, effects::AudioProcessor};
use color_eyre::eyre::Error;
use dioxus::hooks::UnboundedReceiver;
use mumble_web2_common::{ClientConfig, ServerStatus};
use mumble_web2_common::{ClientConfig, ServerEntry, ServerStatus};
use std::future::Future;
use std::time::Duration;
@@ -90,6 +90,12 @@ pub trait PlatformInterface {
/// Save the default server URL.
fn set_default_server(server: &str) -> Option<()>;
/// Load the saved server list.
fn load_servers() -> Vec<ServerEntry>;
/// Save the server list.
fn save_servers(servers: &[ServerEntry]);
/// Async sleep for the given duration.
fn sleep(duration: Duration) -> impl Future<Output = ()>;
}
+9 -1
View File
@@ -3,7 +3,7 @@
use crate::effects::AudioProcessor;
use color_eyre::eyre::Error;
use dioxus::hooks::UnboundedReceiver;
use mumble_web2_common::{ClientConfig, ServerStatus};
use mumble_web2_common::{ClientConfig, ServerEntry, ServerStatus};
use std::future::Future;
pub struct StubPlatform;
@@ -54,6 +54,14 @@ impl super::PlatformInterface for StubPlatform {
panic!("stubbed platform")
}
fn load_servers() -> Vec<ServerEntry> {
panic!("stubbed platform")
}
fn save_servers(_servers: &[ServerEntry]) {
panic!("stubbed platform")
}
fn sleep(_duration: std::time::Duration) -> impl Future<Output = ()> {
async { panic!("stubbed platform") }
}
+19 -1
View File
@@ -6,7 +6,7 @@ use dioxus::prelude::*;
use gloo_timers::future::TimeoutFuture;
use js_sys::Float32Array;
use mumble_protocol::control::ClientControlCodec;
use mumble_web2_common::{ClientConfig, ServerStatus};
use mumble_web2_common::{ClientConfig, ServerEntry, ServerStatus};
use reqwest::Url;
use std::future::Future;
use std::sync::Arc;
@@ -129,6 +129,24 @@ impl super::PlatformInterface for WebPlatform {
None
}
fn load_servers() -> Vec<ServerEntry> {
web_sys::window()
.and_then(|w| w.local_storage().ok()?)
.and_then(|s| s.get_item("servers").ok()?)
.and_then(|json| serde_json::from_str(&json).ok())
.unwrap_or_default()
}
fn save_servers(servers: &[ServerEntry]) {
if let Ok(json) = serde_json::to_string(servers) {
if let Some(storage) = web_sys::window()
.and_then(|w| w.local_storage().ok()?)
{
let _ = storage.set_item("servers", &json);
}
}
}
async fn network_connect(
address: String,
username: String,