use fission::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct ComposeState {
pub show_compose_modal: bool,
pub draft_to: String,
pub draft_subject: String,
pub draft_body: String,
pub status_message: String,
}
impl GlobalState for ComposeState {}
#[fission_action]
#[serde(transparent)]
pub struct SetShowComposeModal(pub bool);
#[fission_action]
#[serde(transparent)]
pub struct SetDraftTo(pub String);
#[fission_action]
#[serde(transparent)]
pub struct SetDraftSubject(pub String);
#[fission_action]
#[serde(transparent)]
pub struct SetDraftBody(pub String);
#[fission_action]
pub struct ApplyCompose;
fn on_set_show_compose_modal(
state: &mut ComposeState,
action: SetShowComposeModal,
_ctx: &mut ReducerContext<ComposeState>,
) {
state.show_compose_modal = action.0;
}
fn on_set_draft_to(
state: &mut ComposeState,
action: SetDraftTo,
_ctx: &mut ReducerContext<ComposeState>,
) {
state.draft_to = action.0;
}
fn on_set_draft_subject(
state: &mut ComposeState,
action: SetDraftSubject,
_ctx: &mut ReducerContext<ComposeState>,
) {
state.draft_subject = action.0;
}
fn on_set_draft_body(
state: &mut ComposeState,
action: SetDraftBody,
_ctx: &mut ReducerContext<ComposeState>,
) {
state.draft_body = action.0;
}
fn on_apply_compose(
state: &mut ComposeState,
_action: ApplyCompose,
_ctx: &mut ReducerContext<ComposeState>,
) {
state.status_message = format!(
"Prepared message to '{}' with subject '{}'",
state.draft_to,
state.draft_subject,
);
state.show_compose_modal = false;
}
pub struct ComposeScreen;
impl From<ComposeScreen> for Widget {
fn from(component: ComposeScreen) -> Self {
let (ctx, view) = fission::build::current::<ComposeState>();
let open_modal = ctx.bind(
SetShowComposeModal(true),
reduce_with!(on_set_show_compose_modal),
);
let close_modal = ctx.bind(
SetShowComposeModal(false),
reduce_with!(on_set_show_compose_modal),
);
let content = Column {
gap: Some(12.0),
children: vec![
Button {
on_press: Some(open_modal),
child: Some(Text::new("Compose").into()),
..Default::default()
}
.into(),
Text::new(view.state().status_message.clone()).into(),
],
..Default::default()
}
.into();
// The modal will be added in the next step.
content
}
}
let set_to = ctx.bind(
SetDraftTo(String::new()),
reduce_with!(on_set_draft_to),
);
let set_subject = ctx.bind(
SetDraftSubject(String::new()),
reduce_with!(on_set_draft_subject),
);
let set_body = ctx.bind(
SetDraftBody(String::new()),
reduce_with!(on_set_draft_body),
);
let modal_fields = Column {
gap: Some(10.0),
children: vec![
TextInput {
value: view.state().draft_to.clone(),
placeholder: Some("To".into()),
on_change: Some(set_to),
..Default::default()
}
.into(),
TextInput {
value: view.state().draft_subject.clone(),
placeholder: Some("Subject".into()),
on_change: Some(set_subject),
..Default::default()
}
.into(),
TextInput {
value: view.state().draft_body.clone(),
placeholder: Some("Write your message".into()),
on_change: Some(set_body),
multiline: true,
height: Some(180.0),
..Default::default()
}
.into(),
],
..Default::default()
}
.into();
let apply = ctx.bind(
ApplyCompose,
reduce_with!(on_apply_compose),
);
let modal = Modal {
id: WidgetId::explicit("compose_modal"),
title: "Compose message".to_string(),
is_open: view.state().show_compose_modal,
on_dismiss: Some(close_modal.clone()),
width: Some(640.0),
actions: vec![
ModalAction {
label: "Cancel".to_string(),
on_press: Some(close_modal),
is_primary: false,
},
ModalAction {
label: "Apply".to_string(),
on_press: Some(apply),
is_primary: true,
},
],
content: modal_fields,
}
.into();
Column {
gap: Some(0.0),
children: vec![content, modal],
..Default::default()
}
.into()
let modal_content = FocusScope {
id: None,
is_barrier: true,
children: vec![modal_fields],
}
.into();
Check | Expected result |
|---|---|
Build | cargo check or the relevant fission command finishes without target-specific configuration errors. |
Behavior | The screen, package, server route, or test flow described by the recipe is visible and responds to input. |
Failure path | At least one denied, missing, invalid, or empty case renders a useful message instead of silently doing nothing. |
Next link | The reference page linked from the recipe explains the exact fields, commands, or types used here. |