feat(sync): account deletion flow + handle_sync_buttons refactor
Adds a two-step account-deletion UX: "Delete Account" button in the Settings sync section (visible only when server backend is configured) fires DeleteAccountRequestEvent → SyncSetupPlugin opens a confirmation modal. "Delete Forever" spawns an async delete_account task; on success SyncLogoutRequestEvent clears local credentials and resets the backend. Errors surface via InfoToast. Also splits handle_settings_buttons into handle_settings_buttons + handle_sync_buttons to stay within Bevy's 16-parameter system limit. Sync buttons (Sync Now, Connect, Disconnect, Delete Account) are now handled in the dedicated handle_sync_buttons system. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -140,6 +140,12 @@ pub struct SyncConfigureRequestEvent;
|
|||||||
#[derive(Message, Debug, Clone, Copy, Default)]
|
#[derive(Message, Debug, Clone, Copy, Default)]
|
||||||
pub struct SyncLogoutRequestEvent;
|
pub struct SyncLogoutRequestEvent;
|
||||||
|
|
||||||
|
/// Request to open the account-deletion confirmation modal. Fired by the
|
||||||
|
/// "Delete Account" button in the Settings sync section (visible only when
|
||||||
|
/// a server backend is configured). Consumed by `SyncSetupPlugin`.
|
||||||
|
#[derive(Message, Debug, Clone, Copy, Default)]
|
||||||
|
pub struct DeleteAccountRequestEvent;
|
||||||
|
|
||||||
/// Request to toggle the pause overlay. Fired by the HUD "Pause" button so
|
/// Request to toggle the pause overlay. Fired by the HUD "Pause" button so
|
||||||
/// the same toggle path runs whether the player presses `Esc` or clicks.
|
/// the same toggle path runs whether the player presses `Esc` or clicks.
|
||||||
/// Consumed by `pause_plugin::toggle_pause`, which honours the same drag /
|
/// Consumed by `pause_plugin::toggle_pause`, which honours the same drag /
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ use solitaire_data::{
|
|||||||
use solitaire_data::settings::SyncBackend;
|
use solitaire_data::settings::SyncBackend;
|
||||||
|
|
||||||
use crate::events::{
|
use crate::events::{
|
||||||
InfoToastEvent, ManualSyncRequestEvent, SyncConfigureRequestEvent, SyncLogoutRequestEvent,
|
DeleteAccountRequestEvent, InfoToastEvent, ManualSyncRequestEvent, SyncConfigureRequestEvent,
|
||||||
ToggleSettingsRequestEvent,
|
SyncLogoutRequestEvent, ToggleSettingsRequestEvent,
|
||||||
};
|
};
|
||||||
use crate::font_plugin::FontResource;
|
use crate::font_plugin::FontResource;
|
||||||
use crate::progress_plugin::ProgressResource;
|
use crate::progress_plugin::ProgressResource;
|
||||||
@@ -240,6 +240,8 @@ enum SettingsButton {
|
|||||||
ConnectSync,
|
ConnectSync,
|
||||||
/// Disconnect from the sync server (shown when backend = SolitaireServer).
|
/// Disconnect from the sync server (shown when backend = SolitaireServer).
|
||||||
DisconnectSync,
|
DisconnectSync,
|
||||||
|
/// Open the account-deletion confirmation modal.
|
||||||
|
DeleteAccount,
|
||||||
Done,
|
Done,
|
||||||
/// Select a specific card-back by index from the picker row.
|
/// Select a specific card-back by index from the picker row.
|
||||||
SelectCardBack(usize),
|
SelectCardBack(usize),
|
||||||
@@ -295,6 +297,7 @@ impl SettingsButton {
|
|||||||
SettingsButton::SyncNow => 90,
|
SettingsButton::SyncNow => 90,
|
||||||
SettingsButton::ConnectSync => 91,
|
SettingsButton::ConnectSync => 91,
|
||||||
SettingsButton::DisconnectSync => 92,
|
SettingsButton::DisconnectSync => 92,
|
||||||
|
SettingsButton::DeleteAccount => 93,
|
||||||
// Done is tagged by `attach_focusable_to_modal_buttons` and
|
// Done is tagged by `attach_focusable_to_modal_buttons` and
|
||||||
// never reaches `attach_focusable_to_settings_buttons`; the
|
// never reaches `attach_focusable_to_settings_buttons`; the
|
||||||
// value here is only a fallback for completeness.
|
// value here is only a fallback for completeness.
|
||||||
@@ -346,6 +349,7 @@ impl Plugin for SettingsPlugin {
|
|||||||
.add_message::<ManualSyncRequestEvent>()
|
.add_message::<ManualSyncRequestEvent>()
|
||||||
.add_message::<SyncConfigureRequestEvent>()
|
.add_message::<SyncConfigureRequestEvent>()
|
||||||
.add_message::<SyncLogoutRequestEvent>()
|
.add_message::<SyncLogoutRequestEvent>()
|
||||||
|
.add_message::<DeleteAccountRequestEvent>()
|
||||||
.add_message::<ToggleSettingsRequestEvent>()
|
.add_message::<ToggleSettingsRequestEvent>()
|
||||||
.add_message::<InfoToastEvent>()
|
.add_message::<InfoToastEvent>()
|
||||||
.add_message::<bevy::input::mouse::MouseWheel>()
|
.add_message::<bevy::input::mouse::MouseWheel>()
|
||||||
@@ -372,6 +376,7 @@ impl Plugin for SettingsPlugin {
|
|||||||
(
|
(
|
||||||
sync_settings_panel_visibility,
|
sync_settings_panel_visibility,
|
||||||
handle_settings_buttons,
|
handle_settings_buttons,
|
||||||
|
handle_sync_buttons,
|
||||||
update_sync_status_text,
|
update_sync_status_text,
|
||||||
update_card_back_text,
|
update_card_back_text,
|
||||||
update_background_text,
|
update_background_text,
|
||||||
@@ -853,7 +858,6 @@ fn handle_settings_buttons(
|
|||||||
mut screen: ResMut<SettingsScreen>,
|
mut screen: ResMut<SettingsScreen>,
|
||||||
path: Res<SettingsStoragePath>,
|
path: Res<SettingsStoragePath>,
|
||||||
mut changed: MessageWriter<SettingsChangedEvent>,
|
mut changed: MessageWriter<SettingsChangedEvent>,
|
||||||
mut manual_sync: MessageWriter<ManualSyncRequestEvent>,
|
|
||||||
mut sfx_text: Query<&mut Text, (With<SfxVolumeText>, Without<MusicVolumeText>, Without<DrawModeText>, Without<ThemeText>, Without<AnimSpeedText>, Without<ColorBlindText>, Without<HighContrastText>, Without<ReduceMotionText>)>,
|
mut sfx_text: Query<&mut Text, (With<SfxVolumeText>, Without<MusicVolumeText>, Without<DrawModeText>, Without<ThemeText>, Without<AnimSpeedText>, Without<ColorBlindText>, Without<HighContrastText>, Without<ReduceMotionText>)>,
|
||||||
mut music_text: Query<&mut Text, (With<MusicVolumeText>, Without<SfxVolumeText>, Without<DrawModeText>, Without<ThemeText>, Without<AnimSpeedText>, Without<ColorBlindText>, Without<HighContrastText>, Without<ReduceMotionText>)>,
|
mut music_text: Query<&mut Text, (With<MusicVolumeText>, Without<SfxVolumeText>, Without<DrawModeText>, Without<ThemeText>, Without<AnimSpeedText>, Without<ColorBlindText>, Without<HighContrastText>, Without<ReduceMotionText>)>,
|
||||||
mut draw_text: Query<&mut Text, (With<DrawModeText>, Without<SfxVolumeText>, Without<MusicVolumeText>, Without<ThemeText>, Without<AnimSpeedText>, Without<ColorBlindText>, Without<HighContrastText>, Without<ReduceMotionText>)>,
|
mut draw_text: Query<&mut Text, (With<DrawModeText>, Without<SfxVolumeText>, Without<MusicVolumeText>, Without<ThemeText>, Without<AnimSpeedText>, Without<ColorBlindText>, Without<HighContrastText>, Without<ReduceMotionText>)>,
|
||||||
@@ -862,8 +866,6 @@ fn handle_settings_buttons(
|
|||||||
mut color_blind_text: Query<&mut Text, (With<ColorBlindText>, Without<SfxVolumeText>, Without<MusicVolumeText>, Without<DrawModeText>, Without<ThemeText>, Without<AnimSpeedText>, Without<HighContrastText>, Without<ReduceMotionText>)>,
|
mut color_blind_text: Query<&mut Text, (With<ColorBlindText>, Without<SfxVolumeText>, Without<MusicVolumeText>, Without<DrawModeText>, Without<ThemeText>, Without<AnimSpeedText>, Without<HighContrastText>, Without<ReduceMotionText>)>,
|
||||||
mut high_contrast_text: Query<&mut Text, (With<HighContrastText>, Without<SfxVolumeText>, Without<MusicVolumeText>, Without<DrawModeText>, Without<ThemeText>, Without<AnimSpeedText>, Without<ColorBlindText>, Without<ReduceMotionText>)>,
|
mut high_contrast_text: Query<&mut Text, (With<HighContrastText>, Without<SfxVolumeText>, Without<MusicVolumeText>, Without<DrawModeText>, Without<ThemeText>, Without<AnimSpeedText>, Without<ColorBlindText>, Without<ReduceMotionText>)>,
|
||||||
mut reduce_motion_text: Query<&mut Text, (With<ReduceMotionText>, Without<SfxVolumeText>, Without<MusicVolumeText>, Without<DrawModeText>, Without<ThemeText>, Without<AnimSpeedText>, Without<ColorBlindText>, Without<HighContrastText>)>,
|
mut reduce_motion_text: Query<&mut Text, (With<ReduceMotionText>, Without<SfxVolumeText>, Without<MusicVolumeText>, Without<DrawModeText>, Without<ThemeText>, Without<AnimSpeedText>, Without<ColorBlindText>, Without<HighContrastText>)>,
|
||||||
mut configure_sync: MessageWriter<SyncConfigureRequestEvent>,
|
|
||||||
mut logout_sync: MessageWriter<SyncLogoutRequestEvent>,
|
|
||||||
) {
|
) {
|
||||||
for (interaction, button) in &interaction_query {
|
for (interaction, button) in &interaction_query {
|
||||||
if *interaction != Interaction::Pressed {
|
if *interaction != Interaction::Pressed {
|
||||||
@@ -1068,14 +1070,11 @@ fn handle_settings_buttons(
|
|||||||
changed.write(SettingsChangedEvent(settings.0.clone()));
|
changed.write(SettingsChangedEvent(settings.0.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SettingsButton::SyncNow => {
|
SettingsButton::SyncNow
|
||||||
manual_sync.write(ManualSyncRequestEvent);
|
| SettingsButton::ConnectSync
|
||||||
}
|
| SettingsButton::DisconnectSync
|
||||||
SettingsButton::ConnectSync => {
|
| SettingsButton::DeleteAccount => {
|
||||||
configure_sync.write(SyncConfigureRequestEvent);
|
// Handled by `handle_sync_buttons`.
|
||||||
}
|
|
||||||
SettingsButton::DisconnectSync => {
|
|
||||||
logout_sync.write(SyncLogoutRequestEvent);
|
|
||||||
}
|
}
|
||||||
SettingsButton::Done => {
|
SettingsButton::Done => {
|
||||||
screen.0 = false;
|
screen.0 = false;
|
||||||
@@ -1084,6 +1083,30 @@ fn handle_settings_buttons(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Handles sync-related settings buttons: Sync Now, Connect, Disconnect,
|
||||||
|
/// and Delete Account. Split from `handle_settings_buttons` to stay within
|
||||||
|
/// Bevy's 16-parameter system limit.
|
||||||
|
fn handle_sync_buttons(
|
||||||
|
interaction_query: Query<(&Interaction, &SettingsButton), Changed<Interaction>>,
|
||||||
|
mut manual_sync: MessageWriter<ManualSyncRequestEvent>,
|
||||||
|
mut configure_sync: MessageWriter<SyncConfigureRequestEvent>,
|
||||||
|
mut logout_sync: MessageWriter<SyncLogoutRequestEvent>,
|
||||||
|
mut delete_account: MessageWriter<DeleteAccountRequestEvent>,
|
||||||
|
) {
|
||||||
|
for (interaction, button) in &interaction_query {
|
||||||
|
if *interaction != Interaction::Pressed {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
match button {
|
||||||
|
SettingsButton::SyncNow => { manual_sync.write(ManualSyncRequestEvent); }
|
||||||
|
SettingsButton::ConnectSync => { configure_sync.write(SyncConfigureRequestEvent); }
|
||||||
|
SettingsButton::DisconnectSync => { logout_sync.write(SyncLogoutRequestEvent); }
|
||||||
|
SettingsButton::DeleteAccount => { delete_account.write(DeleteAccountRequestEvent); }
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn draw_mode_label(mode: &DrawMode) -> String {
|
fn draw_mode_label(mode: &DrawMode) -> String {
|
||||||
match mode {
|
match mode {
|
||||||
DrawMode::DrawOne => "Draw 1".into(),
|
DrawMode::DrawOne => "Draw 1".into(),
|
||||||
@@ -2335,6 +2358,13 @@ fn sync_row(
|
|||||||
SettingsButton::DisconnectSync,
|
SettingsButton::DisconnectSync,
|
||||||
"Disconnect",
|
"Disconnect",
|
||||||
"Unlink this device from the sync server.".to_string(),
|
"Unlink this device from the sync server.".to_string(),
|
||||||
|
button_font.clone(),
|
||||||
|
);
|
||||||
|
small_button(
|
||||||
|
row,
|
||||||
|
SettingsButton::DeleteAccount,
|
||||||
|
"Delete Account",
|
||||||
|
"Permanently delete your account and all server data. Cannot be undone.".to_string(),
|
||||||
button_font,
|
button_font,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,19 @@
|
|||||||
//!
|
//!
|
||||||
//! `SyncLogoutRequestEvent` → `handle_logout` clears tokens, resets
|
//! `SyncLogoutRequestEvent` → `handle_logout` clears tokens, resets
|
||||||
//! `SyncBackend::Local`, swaps provider, closes settings, shows toast.
|
//! `SyncBackend::Local`, swaps provider, closes settings, shows toast.
|
||||||
|
//!
|
||||||
|
//! # Flow (delete account)
|
||||||
|
//!
|
||||||
|
//! 1. Player clicks "Delete Account" in Settings.
|
||||||
|
//! 2. `DeleteAccountRequestEvent` → `open_delete_confirm_modal` spawns a
|
||||||
|
//! two-button confirmation modal.
|
||||||
|
//! 3. "Cancel" → despawn modal.
|
||||||
|
//! 4. "Delete Forever" → `handle_delete_confirm` → async task on
|
||||||
|
//! `AsyncComputeTaskPool` calling `SyncProvider::delete_account`.
|
||||||
|
//! 5. `poll_delete_task` harvests the result:
|
||||||
|
//! - **Ok**: fire `SyncLogoutRequestEvent` (clears tokens + resets backend)
|
||||||
|
//! + toast.
|
||||||
|
//! - **Err**: display error in a toast; modal is already closed.
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@@ -34,7 +47,8 @@ use solitaire_data::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::events::{
|
use crate::events::{
|
||||||
InfoToastEvent, ManualSyncRequestEvent, SyncConfigureRequestEvent, SyncLogoutRequestEvent,
|
DeleteAccountRequestEvent, InfoToastEvent, ManualSyncRequestEvent, SyncConfigureRequestEvent,
|
||||||
|
SyncLogoutRequestEvent,
|
||||||
};
|
};
|
||||||
use crate::font_plugin::FontResource;
|
use crate::font_plugin::FontResource;
|
||||||
use crate::settings_plugin::{SettingsResource, SettingsScreen, SettingsStoragePath};
|
use crate::settings_plugin::{SettingsResource, SettingsScreen, SettingsStoragePath};
|
||||||
@@ -128,6 +142,22 @@ struct PendingAuthTask {
|
|||||||
username: String,
|
username: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Marker on the account-deletion confirmation modal root.
|
||||||
|
#[derive(Component, Debug)]
|
||||||
|
struct DeleteConfirmScreen;
|
||||||
|
|
||||||
|
/// Marks the "Delete Forever" confirmation button.
|
||||||
|
#[derive(Component, Debug)]
|
||||||
|
struct DeleteConfirmButton;
|
||||||
|
|
||||||
|
/// Marks the cancel button inside the delete-confirm modal.
|
||||||
|
#[derive(Component, Debug)]
|
||||||
|
struct DeleteCancelButton;
|
||||||
|
|
||||||
|
/// In-flight account-deletion task.
|
||||||
|
#[derive(Resource, Default)]
|
||||||
|
struct PendingDeleteTask(Option<Task<Result<(), SyncError>>>);
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Plugin
|
// Plugin
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -139,8 +169,10 @@ impl Plugin for SyncSetupPlugin {
|
|||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.init_resource::<SyncFocusedField>()
|
app.init_resource::<SyncFocusedField>()
|
||||||
.init_resource::<PendingAuthTask>()
|
.init_resource::<PendingAuthTask>()
|
||||||
|
.init_resource::<PendingDeleteTask>()
|
||||||
.add_message::<SyncConfigureRequestEvent>()
|
.add_message::<SyncConfigureRequestEvent>()
|
||||||
.add_message::<SyncLogoutRequestEvent>()
|
.add_message::<SyncLogoutRequestEvent>()
|
||||||
|
.add_message::<DeleteAccountRequestEvent>()
|
||||||
.add_message::<ManualSyncRequestEvent>()
|
.add_message::<ManualSyncRequestEvent>()
|
||||||
.add_message::<InfoToastEvent>()
|
.add_message::<InfoToastEvent>()
|
||||||
.add_systems(
|
.add_systems(
|
||||||
@@ -153,6 +185,10 @@ impl Plugin for SyncSetupPlugin {
|
|||||||
poll_auth_task,
|
poll_auth_task,
|
||||||
handle_cancel,
|
handle_cancel,
|
||||||
handle_logout,
|
handle_logout,
|
||||||
|
open_delete_confirm_modal,
|
||||||
|
handle_delete_cancel,
|
||||||
|
handle_delete_confirm,
|
||||||
|
poll_delete_task,
|
||||||
)
|
)
|
||||||
.chain(),
|
.chain(),
|
||||||
);
|
);
|
||||||
@@ -480,6 +516,94 @@ fn handle_logout(
|
|||||||
toast.write(InfoToastEvent("Disconnected from sync server".to_string()));
|
toast.write(InfoToastEvent("Disconnected from sync server".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Opens the account-deletion confirmation modal when `DeleteAccountRequestEvent` fires.
|
||||||
|
fn open_delete_confirm_modal(
|
||||||
|
mut events: MessageReader<DeleteAccountRequestEvent>,
|
||||||
|
existing: Query<(), With<DeleteConfirmScreen>>,
|
||||||
|
mut commands: Commands,
|
||||||
|
font_res: Option<Res<FontResource>>,
|
||||||
|
) {
|
||||||
|
if events.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
events.clear();
|
||||||
|
if !existing.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
spawn_delete_confirm_modal(&mut commands, font_res.as_deref());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Despawns the delete-confirm modal on the cancel button or Escape.
|
||||||
|
fn handle_delete_cancel(
|
||||||
|
cancel_q: Query<&Interaction, (Changed<Interaction>, With<DeleteCancelButton>)>,
|
||||||
|
keys: Res<ButtonInput<KeyCode>>,
|
||||||
|
screen: Query<Entity, With<DeleteConfirmScreen>>,
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
let cancelled = cancel_q.iter().any(|i| *i == Interaction::Pressed)
|
||||||
|
|| keys.just_pressed(KeyCode::Escape);
|
||||||
|
if !cancelled || screen.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for entity in &screen {
|
||||||
|
commands.entity(entity).despawn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Spawns the async delete-account task when "Delete Forever" is clicked.
|
||||||
|
fn handle_delete_confirm(
|
||||||
|
confirm_q: Query<&Interaction, (Changed<Interaction>, With<DeleteConfirmButton>)>,
|
||||||
|
provider: Res<SyncProviderResource>,
|
||||||
|
mut pending: ResMut<PendingDeleteTask>,
|
||||||
|
screen: Query<Entity, With<DeleteConfirmScreen>>,
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
if !confirm_q.iter().any(|i| *i == Interaction::Pressed) || pending.0.is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Despawn the confirmation modal immediately so the player can't double-click.
|
||||||
|
for entity in &screen {
|
||||||
|
commands.entity(entity).despawn();
|
||||||
|
}
|
||||||
|
let provider = provider.0.clone();
|
||||||
|
pending.0 = Some(AsyncComputeTaskPool::get().spawn(async move {
|
||||||
|
tokio::runtime::Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.map_err(|e| SyncError::Network(format!("tokio rt: {e}")))?
|
||||||
|
.block_on(provider.delete_account())
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Polls the in-flight delete-account task. On success fires `SyncLogoutRequestEvent`.
|
||||||
|
fn poll_delete_task(
|
||||||
|
mut pending: ResMut<PendingDeleteTask>,
|
||||||
|
mut logout: MessageWriter<SyncLogoutRequestEvent>,
|
||||||
|
mut toast: MessageWriter<InfoToastEvent>,
|
||||||
|
) {
|
||||||
|
let Some(task) = pending.0.as_mut() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(result) = future::block_on(future::poll_once(task)) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
pending.0 = None;
|
||||||
|
match result {
|
||||||
|
Ok(()) => {
|
||||||
|
logout.write(SyncLogoutRequestEvent);
|
||||||
|
toast.write(InfoToastEvent("Account deleted".to_string()));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
let msg = match e {
|
||||||
|
SyncError::Auth(_) => "Not authorised — try reconnecting first".to_string(),
|
||||||
|
SyncError::Network(m) => format!("Network error: {m}"),
|
||||||
|
other => format!("Delete failed: {other}"),
|
||||||
|
};
|
||||||
|
toast.write(InfoToastEvent(msg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// UI construction
|
// UI construction
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -666,6 +790,75 @@ fn make_font(font_res: Option<&FontResource>, size: f32) -> TextFont {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn spawn_delete_confirm_modal(commands: &mut Commands, font_res: Option<&FontResource>) {
|
||||||
|
spawn_modal(commands, DeleteConfirmScreen, Z_MODAL_PANEL + 2, |card| {
|
||||||
|
// Header.
|
||||||
|
card.spawn(Node {
|
||||||
|
padding: UiRect::new(VAL_SPACE_4, VAL_SPACE_4, VAL_SPACE_3, VAL_SPACE_2),
|
||||||
|
..default()
|
||||||
|
})
|
||||||
|
.with_children(|h| {
|
||||||
|
h.spawn((
|
||||||
|
Text::new("Delete Account"),
|
||||||
|
make_font(font_res, TYPE_BODY_LG),
|
||||||
|
TextColor(STATE_DANGER),
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Body.
|
||||||
|
card.spawn(Node {
|
||||||
|
flex_direction: FlexDirection::Column,
|
||||||
|
row_gap: VAL_SPACE_2,
|
||||||
|
padding: UiRect::axes(VAL_SPACE_4, VAL_SPACE_2),
|
||||||
|
..default()
|
||||||
|
})
|
||||||
|
.with_children(|body| {
|
||||||
|
body.spawn((
|
||||||
|
Text::new(
|
||||||
|
"This permanently deletes your account and all server data.\n\
|
||||||
|
Local progress is kept. This cannot be undone.",
|
||||||
|
),
|
||||||
|
make_font(font_res, TYPE_BODY),
|
||||||
|
TextColor(TEXT_SECONDARY),
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Actions.
|
||||||
|
card.spawn(Node {
|
||||||
|
flex_direction: FlexDirection::Row,
|
||||||
|
justify_content: JustifyContent::FlexEnd,
|
||||||
|
column_gap: VAL_SPACE_2,
|
||||||
|
padding: UiRect::new(VAL_SPACE_4, VAL_SPACE_4, VAL_SPACE_2, VAL_SPACE_3),
|
||||||
|
..default()
|
||||||
|
})
|
||||||
|
.with_children(|actions| {
|
||||||
|
spawn_action_button(actions, DeleteCancelButton, "Cancel", false, font_res);
|
||||||
|
// "Delete Forever" button — danger styling (STATE_DANGER background).
|
||||||
|
actions
|
||||||
|
.spawn((
|
||||||
|
DeleteConfirmButton,
|
||||||
|
Button,
|
||||||
|
Node {
|
||||||
|
padding: UiRect::axes(VAL_SPACE_3, VAL_SPACE_2),
|
||||||
|
justify_content: JustifyContent::Center,
|
||||||
|
border: UiRect::all(Val::Px(1.0)),
|
||||||
|
border_radius: BorderRadius::all(Val::Px(RADIUS_SM)),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
BackgroundColor(STATE_DANGER),
|
||||||
|
BorderColor::all(STATE_DANGER),
|
||||||
|
))
|
||||||
|
.with_children(|b| {
|
||||||
|
b.spawn((
|
||||||
|
Text::new("Delete Forever"),
|
||||||
|
make_font(font_res, TYPE_BODY),
|
||||||
|
TextColor(TEXT_PRIMARY),
|
||||||
|
));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the display string for a field — password fields show bullets.
|
/// Returns the display string for a field — password fields show bullets.
|
||||||
fn display_text(raw: &str, kind: SyncFieldKind) -> String {
|
fn display_text(raw: &str, kind: SyncFieldKind) -> String {
|
||||||
if kind == SyncFieldKind::Password {
|
if kind == SyncFieldKind::Password {
|
||||||
|
|||||||
Reference in New Issue
Block a user