1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
use dioxus::prelude::*;
use matrix_sdk::ruma::OwnedRoomId;
use crate::components::modal::Modal;
use crate::state::app_state::AppState;
/// Poll creation dialog.
#[component]
pub fn PollCreator(
room_id: String,
on_close: EventHandler<()>,
) -> Element {
let state = use_context::<Signal<AppState>>();
let mut question = use_signal(|| String::new());
let mut answers = use_signal(|| vec!["".to_string(), "".to_string()]);
let mut is_sending = use_signal(|| false);
let mut error_msg = use_signal(|| Option::<String>::None);
let can_send = !question.read().trim().is_empty()
&& answers.read().iter().filter(|a| !a.trim().is_empty()).count() >= 2;
rsx! {
Modal {
title: "Create Poll".to_string(),
on_close: move |_| on_close.call(()),
div {
class: "poll-creator",
div {
class: "poll-creator__field",
label { "Question" }
input {
class: "poll-creator__question-input",
r#type: "text",
placeholder: "Ask a question...",
value: "{question}",
oninput: move |evt| question.set(evt.value()),
}
}
div {
class: "poll-creator__field",
label { "Answers" }
for (idx, _answer) in answers.read().iter().enumerate() {
{
let val = answers.read()[idx].clone();
rsx! {
div {
class: "poll-creator__answer-row",
key: "answer-{idx}",
input {
class: "poll-creator__answer-input",
r#type: "text",
placeholder: "Option {idx + 1}",
value: "{val}",
oninput: move |evt| {
answers.write()[idx] = evt.value();
},
}
if answers.read().len() > 2 {
button {
class: "poll-creator__remove-btn",
onclick: move |_| {
answers.write().remove(idx);
},
"✕"
}
}
}
}
}
}
if answers.read().len() < 10 {
button {
class: "btn btn--secondary btn--sm",
onclick: move |_| {
answers.write().push(String::new());
},
"+ Add option"
}
}
}
if let Some(ref err) = *error_msg.read() {
div { class: "poll-creator__error", "{err}" }
}
div {
class: "poll-creator__actions",
button {
class: "btn btn--secondary",
onclick: move |_| on_close.call(()),
"Cancel"
}
button {
class: "btn btn--primary",
disabled: !can_send || *is_sending.read(),
onclick: move |_| {
let q = question.read().clone();
let ans: Vec<String> = answers.read().iter()
.filter(|a| !a.trim().is_empty())
.cloned()
.collect();
let rid = room_id.clone();
is_sending.set(true);
spawn(async move {
let client = { state.read().client.clone() };
if let Some(client) = client {
if let Ok(room_id) = OwnedRoomId::try_from(rid.as_str()) {
if let Some(room) = client.get_room(&room_id) {
// Send poll as m.poll.start event using unstable prefix
let _poll_start = serde_json::json!({
"type": "org.matrix.msc3381.poll.start",
"content": {
"org.matrix.msc3381.poll.start": {
"question": { "org.matrix.msc1767.text": q },
"kind": "org.matrix.msc3381.poll.disclosed",
"max_selections": 1,
"answers": ans.iter().enumerate().map(|(i, a)| {
serde_json::json!({
"id": format!("answer-{i}"),
"org.matrix.msc1767.text": a,
})
}).collect::<Vec<_>>(),
},
"org.matrix.msc1767.text": format!("Poll: {q}\n{}", ans.join("\n")),
}
});
// Use custom event type via Raw
// Fallback: send as text message with poll formatting
let poll_text = format!("📊 **Poll: {}**\n{}", q,
ans.iter().enumerate()
.map(|(i, a)| format!("{}. {}", i + 1, a))
.collect::<Vec<_>>()
.join("\n")
);
use matrix_sdk::ruma::events::room::message::RoomMessageEventContent;
let content = RoomMessageEventContent::text_plain(&poll_text);
match room.send(content).await {
Ok(_) => {
tracing::info!("Poll sent");
on_close.call(());
}
Err(e) => {
error_msg.set(Some(format!("Failed to send poll: {e}")));
}
}
}
}
}
is_sending.set(false);
});
},
if *is_sending.read() { "Sending..." } else { "Create Poll" }
}
}
}
}
}
}