refactor avatar display: unify rendering and resolve at use time

- Extract buildAvatarWidget() in common.dart to share avatar rendering
    logic across desktop settings, desktop CM and mobile CM
  - Add resolve_avatar_url() in Rust, exposed via FFI (SyncReturn),
    to resolve relative avatar paths (e.g. "/avatar/xxx") to absolute URLs
  - Store avatar as-is in local config, only resolve when displaying
    (settings page) or sending (LoginRequest)
  - Resolve avatar in LoginRequest before sending to remote peer
  - Add error handling for network image load failures
  - Guard against empty client.name[0] crash
  - Show avatar in mobile settings page account tile

Signed-off-by: 21pages <sunboeasy@gmail.com>
This commit is contained in:
21pages
2026-03-03 21:38:46 +08:00
parent 890282e385
commit d9e2107fb8
10 changed files with 90 additions and 108 deletions

View File

@@ -245,39 +245,20 @@ pub fn get_builtin_option(key: &str) -> String {
#[inline]
pub fn set_local_option(key: String, value: String) {
let value = normalize_local_option_value(&key, value);
LocalConfig::set_option(key.clone(), value);
}
fn normalize_local_option_value(key: &str, value: String) -> String {
if key != "user_info" || value.is_empty() {
return value;
/// Resolve relative avatar path (e.g. "/avatar/xxx") to absolute URL
/// by prepending the API server address.
pub fn resolve_avatar_url(avatar: String) -> String {
let avatar = avatar.trim().to_owned();
if avatar.starts_with('/') {
let api_server = get_api_server();
if !api_server.is_empty() {
return format!("{}{}", api_server.trim_end_matches('/'), avatar);
}
}
let Ok(mut v) = serde_json::from_str::<serde_json::Value>(&value) else {
return value;
};
let Some(obj) = v.as_object_mut() else {
return value;
};
let Some(avatar) = obj
.get("avatar")
.and_then(|x| x.as_str())
.map(|x| x.trim().to_owned())
else {
return value;
};
if !avatar.starts_with('/') {
return value;
}
let api_server = get_api_server();
if api_server.is_empty() {
return value;
}
obj.insert(
"avatar".to_owned(),
serde_json::Value::String(format!("{}{}", api_server.trim_end_matches('/'), avatar)),
);
serde_json::to_string(&v).unwrap_or(value)
avatar
}
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]