feat(engine): wire avatar download and display into profile modal
Build and Deploy / build-and-push (push) Successful in 4m15s
Build and Deploy / build-and-push (push) Successful in 4m15s
- Add avatar_plugin: AvatarPlugin, AvatarResource, AvatarFetchEvent
- After AvatarFetchEvent fires, spawns an async reqwest download task
- On completion, decodes image bytes via image::load_from_memory →
Image::from_dynamic and inserts into Assets<Image>
- Expand auth task to also call fetch_me_with_token immediately after
login/register so avatar_url is available without a second round-trip
- poll_auth_task fires AvatarFetchEvent when avatar_url is Some, building
the full URL from base_url + relative avatar path
- Profile modal shows 48px circular avatar ImageNode when AvatarResource
is populated, or an initials disc (first letter of username) as fallback
- Add image = "0.25" and reqwest to solitaire_engine deps
- Add fetch_me_with_token helper to SolitaireServerClient for use when
the access token hasn't been persisted to keychain yet
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -46,6 +46,7 @@ use solitaire_data::{
|
||||
SyncError,
|
||||
};
|
||||
|
||||
use crate::avatar_plugin::AvatarFetchEvent;
|
||||
use crate::events::{
|
||||
DeleteAccountRequestEvent, InfoToastEvent, ManualSyncRequestEvent, SyncConfigureRequestEvent,
|
||||
SyncLogoutRequestEvent,
|
||||
@@ -135,9 +136,12 @@ impl SyncFocusedField {
|
||||
/// In-flight login/register task. `url` and `username` are preserved so the
|
||||
/// poll system can update settings and provider on success without re-reading
|
||||
/// the (already-despawned or cleared) form fields.
|
||||
/// Return type of the async auth + profile-fetch task.
|
||||
type AuthTaskResult = Result<(String, String, Option<String>), SyncError>;
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
struct PendingAuthTask {
|
||||
task: Option<Task<Result<(String, String), SyncError>>>,
|
||||
task: Option<Task<AuthTaskResult>>,
|
||||
url: String,
|
||||
username: String,
|
||||
}
|
||||
@@ -366,11 +370,18 @@ fn handle_auth_button(
|
||||
.build()
|
||||
.map_err(|e| SyncError::Network(format!("tokio rt: {e}")))?
|
||||
.block_on(async {
|
||||
if is_register {
|
||||
client.register(&pw).await
|
||||
let (access_token, refresh_token) = if is_register {
|
||||
client.register(&pw).await?
|
||||
} else {
|
||||
client.login(&pw).await
|
||||
}
|
||||
client.login(&pw).await?
|
||||
};
|
||||
// Fetch avatar URL immediately while we have the fresh token.
|
||||
let avatar_url = client
|
||||
.fetch_me_with_token(&access_token)
|
||||
.await
|
||||
.ok()
|
||||
.and_then(|(_, url)| url);
|
||||
Ok((access_token, refresh_token, avatar_url))
|
||||
})
|
||||
});
|
||||
|
||||
@@ -393,6 +404,7 @@ fn poll_auth_task(
|
||||
mut commands: Commands,
|
||||
mut manual_sync: MessageWriter<ManualSyncRequestEvent>,
|
||||
mut toast: MessageWriter<InfoToastEvent>,
|
||||
mut avatar_fetch: MessageWriter<AvatarFetchEvent>,
|
||||
) {
|
||||
let Some(task) = pending.task.as_mut() else {
|
||||
return;
|
||||
@@ -407,7 +419,7 @@ fn poll_auth_task(
|
||||
}
|
||||
|
||||
match result {
|
||||
Ok((access_token, refresh_token)) => {
|
||||
Ok((access_token, refresh_token, fetched_avatar_url)) => {
|
||||
let url = pending.url.clone();
|
||||
let username = pending.username.clone();
|
||||
|
||||
@@ -424,7 +436,7 @@ fn poll_auth_task(
|
||||
settings.0.sync_backend = SyncBackend::SolitaireServer {
|
||||
url: url.clone(),
|
||||
username: username.clone(),
|
||||
avatar_url: None,
|
||||
avatar_url: fetched_avatar_url.clone(),
|
||||
};
|
||||
if let Some(path) = &settings_path.0
|
||||
&& let Err(e) = save_settings_to(path, &settings.0)
|
||||
@@ -438,6 +450,14 @@ fn poll_auth_task(
|
||||
// Kick off an immediate pull with the new provider.
|
||||
manual_sync.write(ManualSyncRequestEvent);
|
||||
|
||||
// Trigger avatar download if the server reported a profile picture.
|
||||
if let Some(ref rel_url) = fetched_avatar_url {
|
||||
let base = pending.url.trim_end_matches('/').to_string();
|
||||
avatar_fetch.write(AvatarFetchEvent {
|
||||
url: format!("{base}{rel_url}"),
|
||||
});
|
||||
}
|
||||
|
||||
// Close both the setup modal and the settings panel.
|
||||
for entity in &screen {
|
||||
commands.entity(entity).despawn();
|
||||
|
||||
Reference in New Issue
Block a user