Message sending
This commit is contained in:
parent
061276524a
commit
41b8756ac2
@ -12,7 +12,7 @@ async-trait = "0.1"
|
|||||||
crossbeam-channel = "0.4"
|
crossbeam-channel = "0.4"
|
||||||
dirs-next = "2.0"
|
dirs-next = "2.0"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
iced = { version = "0.2", features = ["debug", "tokio_old"] }
|
iced = { version = "0.2", features = ["debug", "tokio_old", "image"] }
|
||||||
iced_futures = "0.2"
|
iced_futures = "0.2"
|
||||||
hostname = "0.3"
|
hostname = "0.3"
|
||||||
matrix-sdk = { git = "https://github.com/matrix-org/matrix-rust-sdk", rev = "e65915e" }
|
matrix-sdk = { git = "https://github.com/matrix-org/matrix-rust-sdk", rev = "e65915e" }
|
||||||
@ -20,3 +20,4 @@ matrix-sdk-common-macros = { git = "https://github.com/matrix-org/matrix-rust-sd
|
|||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
tokio = { version = "0.2", features = ["sync"] }
|
tokio = { version = "0.2", features = ["sync"] }
|
||||||
toml = "0.5"
|
toml = "0.5"
|
||||||
|
tracing-subscriber = { version = "0.2", features = ["parking_lot"] }
|
||||||
|
@ -10,6 +10,8 @@ pub mod matrix;
|
|||||||
pub mod ui;
|
pub mod ui;
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
let config_dir = dirs::config_dir().unwrap().join("retrix");
|
let config_dir = dirs::config_dir().unwrap().join("retrix");
|
||||||
// Make sure config dir exists and is not accessible by other users.
|
// Make sure config dir exists and is not accessible by other users.
|
||||||
if !config_dir.is_dir() {
|
if !config_dir.is_dir() {
|
||||||
|
118
src/matrix.rs
118
src/matrix.rs
@ -1,6 +1,13 @@
|
|||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use matrix_sdk::{
|
use matrix_sdk::{
|
||||||
events::AnyRoomEvent, events::AnySyncRoomEvent, identifiers::DeviceId, identifiers::UserId,
|
api::r0::{account::register::Request as RegistrationRequest, uiaa::AuthData},
|
||||||
reqwest::Url, Client, ClientConfig, LoopCtrl, SyncSettings,
|
events::AnyRoomEvent,
|
||||||
|
events::AnySyncRoomEvent,
|
||||||
|
identifiers::DeviceId,
|
||||||
|
identifiers::UserId,
|
||||||
|
reqwest::Url,
|
||||||
|
Client, ClientConfig, LoopCtrl, SyncSettings,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@ -25,6 +32,53 @@ impl From<Session> for matrix_sdk::Session {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn signup(
|
||||||
|
username: &str,
|
||||||
|
password: &str,
|
||||||
|
server: &str,
|
||||||
|
) -> Result<(Client, Session), Error> {
|
||||||
|
let url = Url::parse(server)?;
|
||||||
|
let client = client(url)?;
|
||||||
|
|
||||||
|
let mut request = RegistrationRequest::new();
|
||||||
|
request.username = Some(username);
|
||||||
|
request.password = Some(password);
|
||||||
|
request.initial_device_display_name = Some("retrix");
|
||||||
|
request.inhibit_login = false;
|
||||||
|
|
||||||
|
// Get UIAA session key
|
||||||
|
let uiaa = match client.register(request.clone()).await {
|
||||||
|
Err(e) => match e.uiaa_response().cloned() {
|
||||||
|
Some(uiaa) => uiaa,
|
||||||
|
None => return Err(anyhow::anyhow!("Missing UIAA response")),
|
||||||
|
},
|
||||||
|
Ok(_) => {
|
||||||
|
return Err(anyhow::anyhow!("Missing UIAA response"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Get the first step in the authentication flow (we're ignoring the rest)
|
||||||
|
let stages = uiaa.flows.get(0);
|
||||||
|
let kind = stages.and_then(|flow| flow.stages.get(0)).cloned();
|
||||||
|
|
||||||
|
// Set authentication data, fallback to password type
|
||||||
|
request.auth = Some(AuthData::DirectRequest {
|
||||||
|
kind: kind.as_deref().unwrap_or("m.login.password"),
|
||||||
|
session: uiaa.session.as_deref(),
|
||||||
|
auth_parameters: Default::default(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let response = client.register(request).await?;
|
||||||
|
|
||||||
|
let session = Session {
|
||||||
|
access_token: response.access_token.unwrap(),
|
||||||
|
user_id: response.user_id,
|
||||||
|
device_id: response.device_id.unwrap(),
|
||||||
|
homeserver: server.to_owned(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((client, session))
|
||||||
|
}
|
||||||
|
|
||||||
/// Login with credentials, creating a new authentication session
|
/// Login with credentials, creating a new authentication session
|
||||||
pub async fn login(
|
pub async fn login(
|
||||||
username: &str,
|
username: &str,
|
||||||
@ -98,12 +152,13 @@ fn write_session(session: &Session) -> Result<(), Error> {
|
|||||||
|
|
||||||
pub struct MatrixSync {
|
pub struct MatrixSync {
|
||||||
client: matrix_sdk::Client,
|
client: matrix_sdk::Client,
|
||||||
|
join: Option<tokio::task::JoinHandle<()>>,
|
||||||
//id: String,
|
//id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MatrixSync {
|
impl MatrixSync {
|
||||||
pub fn subscription(client: matrix_sdk::Client) -> iced::Subscription<AnyRoomEvent> {
|
pub fn subscription(client: matrix_sdk::Client) -> iced::Subscription<AnyRoomEvent> {
|
||||||
iced::Subscription::from_recipe(MatrixSync { client })
|
iced::Subscription::from_recipe(MatrixSync { client, join: None })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,41 +191,48 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn stream(
|
fn stream(
|
||||||
self: Box<Self>,
|
mut self: Box<Self>,
|
||||||
_input: iced_futures::BoxStream<I>,
|
_input: iced_futures::BoxStream<I>,
|
||||||
) -> iced_futures::BoxStream<Self::Output> {
|
) -> iced_futures::BoxStream<Self::Output> {
|
||||||
let (sender, receiver) = tokio::sync::mpsc::unbounded_channel();
|
let (sender, receiver) = tokio::sync::mpsc::unbounded_channel();
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
tokio::task::spawn(async move {
|
let join = tokio::task::spawn(async move {
|
||||||
client
|
client
|
||||||
.sync_with_callback(SyncSettings::new(), |response| async {
|
.sync_with_callback(
|
||||||
for (room_id, room) in response.rooms.join {
|
SyncSettings::new().timeout(Duration::from_secs(90)),
|
||||||
for event in room.timeline.events {
|
|response| async {
|
||||||
if let Ok(event) = event.deserialize() {
|
for (room_id, room) in response.rooms.join {
|
||||||
let room_id = room_id.clone();
|
for event in room.timeline.events {
|
||||||
let event = match event {
|
if let Ok(event) = event.deserialize() {
|
||||||
AnySyncRoomEvent::Message(e) => {
|
let room_id = room_id.clone();
|
||||||
AnyRoomEvent::Message(e.into_full_event(room_id))
|
let event = match event {
|
||||||
}
|
AnySyncRoomEvent::Message(e) => {
|
||||||
AnySyncRoomEvent::State(e) => {
|
AnyRoomEvent::Message(e.into_full_event(room_id))
|
||||||
AnyRoomEvent::State(e.into_full_event(room_id))
|
}
|
||||||
}
|
AnySyncRoomEvent::State(e) => {
|
||||||
AnySyncRoomEvent::RedactedMessage(e) => {
|
AnyRoomEvent::State(e.into_full_event(room_id))
|
||||||
AnyRoomEvent::RedactedMessage(e.into_full_event(room_id))
|
}
|
||||||
}
|
AnySyncRoomEvent::RedactedMessage(e) => {
|
||||||
AnySyncRoomEvent::RedactedState(e) => {
|
AnyRoomEvent::RedactedMessage(
|
||||||
AnyRoomEvent::RedactedState(e.into_full_event(room_id))
|
e.into_full_event(room_id),
|
||||||
}
|
)
|
||||||
};
|
}
|
||||||
sender.send(event).ok();
|
AnySyncRoomEvent::RedactedState(e) => {
|
||||||
|
AnyRoomEvent::RedactedState(e.into_full_event(room_id))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
sender.send(event).ok();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
LoopCtrl::Continue
|
LoopCtrl::Continue
|
||||||
})
|
},
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
|
println!("We stopped syncing!");
|
||||||
});
|
});
|
||||||
|
self.join = Some(join);
|
||||||
Box::pin(receiver)
|
Box::pin(receiver)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
187
src/ui.rs
187
src/ui.rs
@ -2,13 +2,13 @@ use std::collections::{BTreeMap, HashMap};
|
|||||||
|
|
||||||
use iced::{
|
use iced::{
|
||||||
text_input::{self, TextInput},
|
text_input::{self, TextInput},
|
||||||
Application, Button, Column, Command, Container, Element, Length, Row, Rule, Scrollable,
|
Application, Button, Column, Command, Container, Element, Length, Radio, Row, Rule, Scrollable,
|
||||||
Subscription, Text,
|
Subscription, Text,
|
||||||
};
|
};
|
||||||
use matrix_sdk::{
|
use matrix_sdk::{
|
||||||
events::{
|
events::{
|
||||||
room::message::MessageEventContent, AnyPossiblyRedactedSyncMessageEvent, AnyRoomEvent,
|
room::message::MessageEventContent, AnyMessageEventContent,
|
||||||
AnySyncMessageEvent,
|
AnyPossiblyRedactedSyncMessageEvent, AnyRoomEvent, AnySyncMessageEvent,
|
||||||
},
|
},
|
||||||
identifiers::RoomId,
|
identifiers::RoomId,
|
||||||
};
|
};
|
||||||
@ -26,6 +26,7 @@ pub enum Retrix {
|
|||||||
user: String,
|
user: String,
|
||||||
password: String,
|
password: String,
|
||||||
server: String,
|
server: String,
|
||||||
|
action: PromptAction,
|
||||||
error: Option<String>,
|
error: Option<String>,
|
||||||
},
|
},
|
||||||
AwaitLogin(std::time::Instant),
|
AwaitLogin(std::time::Instant),
|
||||||
@ -38,6 +39,10 @@ pub enum Retrix {
|
|||||||
messages: BTreeMap<RoomId, MessageEventContent>,
|
messages: BTreeMap<RoomId, MessageEventContent>,
|
||||||
selected: Option<RoomId>,
|
selected: Option<RoomId>,
|
||||||
room_scroll: iced::scrollable::State,
|
room_scroll: iced::scrollable::State,
|
||||||
|
message_scroll: iced::scrollable::State,
|
||||||
|
message_input: iced::text_input::State,
|
||||||
|
draft: String,
|
||||||
|
send_button: iced::button::State,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,18 +57,28 @@ impl Retrix {
|
|||||||
user: String::new(),
|
user: String::new(),
|
||||||
password: String::new(),
|
password: String::new(),
|
||||||
server: String::new(),
|
server: String::new(),
|
||||||
|
action: PromptAction::Login,
|
||||||
error: None,
|
error: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum PromptAction {
|
||||||
|
Login,
|
||||||
|
Signup,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
// Login form messages
|
// Login form messages
|
||||||
SetUser(String),
|
SetUser(String),
|
||||||
SetPassword(String),
|
SetPassword(String),
|
||||||
SetServer(String),
|
SetServer(String),
|
||||||
|
SetAction(PromptAction),
|
||||||
Login,
|
Login,
|
||||||
|
Signup,
|
||||||
|
// Auth result messages
|
||||||
LoggedIn(matrix_sdk::Client, matrix::Session),
|
LoggedIn(matrix_sdk::Client, matrix::Session),
|
||||||
LoginFailed(String),
|
LoginFailed(String),
|
||||||
|
|
||||||
@ -71,6 +86,8 @@ pub enum Message {
|
|||||||
ResetRooms(BTreeMap<RoomId, String>),
|
ResetRooms(BTreeMap<RoomId, String>),
|
||||||
SelectRoom(RoomId),
|
SelectRoom(RoomId),
|
||||||
Sync(AnyRoomEvent),
|
Sync(AnyRoomEvent),
|
||||||
|
SetMessage(String),
|
||||||
|
SendMessage,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Application for Retrix {
|
impl Application for Retrix {
|
||||||
@ -114,11 +131,13 @@ impl Application for Retrix {
|
|||||||
user,
|
user,
|
||||||
password,
|
password,
|
||||||
server,
|
server,
|
||||||
|
action,
|
||||||
..
|
..
|
||||||
} => match message {
|
} => match message {
|
||||||
Message::SetUser(u) => *user = u,
|
Message::SetUser(u) => *user = u,
|
||||||
Message::SetPassword(p) => *password = p,
|
Message::SetPassword(p) => *password = p,
|
||||||
Message::SetServer(s) => *server = s,
|
Message::SetServer(s) => *server = s,
|
||||||
|
Message::SetAction(a) => *action = a,
|
||||||
Message::Login => {
|
Message::Login => {
|
||||||
let user = user.clone();
|
let user = user.clone();
|
||||||
let password = password.clone();
|
let password = password.clone();
|
||||||
@ -132,6 +151,19 @@ impl Application for Retrix {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Message::Signup => {
|
||||||
|
let user = user.clone();
|
||||||
|
let password = password.clone();
|
||||||
|
let server = server.clone();
|
||||||
|
*self = Retrix::AwaitLogin(std::time::Instant::now());
|
||||||
|
return Command::perform(
|
||||||
|
async move { matrix::signup(&user, &password, &server).await },
|
||||||
|
|result| match result {
|
||||||
|
Ok((client, response)) => Message::LoggedIn(client, response),
|
||||||
|
Err(e) => Message::LoginFailed(e.to_string()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
},
|
},
|
||||||
Retrix::AwaitLogin(_) => match message {
|
Retrix::AwaitLogin(_) => match message {
|
||||||
@ -148,10 +180,14 @@ impl Application for Retrix {
|
|||||||
rooms: BTreeMap::new(),
|
rooms: BTreeMap::new(),
|
||||||
selected: None,
|
selected: None,
|
||||||
room_scroll: Default::default(),
|
room_scroll: Default::default(),
|
||||||
|
message_scroll: Default::default(),
|
||||||
|
message_input: Default::default(),
|
||||||
buttons: Default::default(),
|
buttons: Default::default(),
|
||||||
messages: Default::default(),
|
messages: Default::default(),
|
||||||
|
draft: String::new(),
|
||||||
|
send_button: Default::default(),
|
||||||
};
|
};
|
||||||
let client = client.clone();
|
/*let client = client.clone();
|
||||||
return Command::perform(
|
return Command::perform(
|
||||||
async move {
|
async move {
|
||||||
let mut rooms = BTreeMap::new();
|
let mut rooms = BTreeMap::new();
|
||||||
@ -162,15 +198,48 @@ impl Application for Retrix {
|
|||||||
rooms
|
rooms
|
||||||
},
|
},
|
||||||
|rooms| Message::ResetRooms(rooms),
|
|rooms| Message::ResetRooms(rooms),
|
||||||
);
|
);*/
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
},
|
},
|
||||||
Retrix::LoggedIn {
|
Retrix::LoggedIn {
|
||||||
rooms, selected, ..
|
rooms,
|
||||||
|
selected,
|
||||||
|
draft,
|
||||||
|
client,
|
||||||
|
..
|
||||||
} => match message {
|
} => match message {
|
||||||
Message::ResetRooms(r) => *rooms = r,
|
Message::ResetRooms(r) => *rooms = r,
|
||||||
Message::SelectRoom(r) => *selected = Some(r),
|
Message::SelectRoom(r) => *selected = Some(r),
|
||||||
|
Message::Sync(event) => match event {
|
||||||
|
AnyRoomEvent::Message(_message) => (),
|
||||||
|
AnyRoomEvent::State(_state) => (),
|
||||||
|
AnyRoomEvent::RedactedMessage(_message) => (),
|
||||||
|
AnyRoomEvent::RedactedState(_state) => (),
|
||||||
|
},
|
||||||
|
Message::SetMessage(m) => *draft = m,
|
||||||
|
Message::SendMessage => {
|
||||||
|
let selected = selected.to_owned();
|
||||||
|
let draft = draft.clone();
|
||||||
|
let client = client.clone();
|
||||||
|
return Command::perform(
|
||||||
|
async move {
|
||||||
|
client
|
||||||
|
.room_send(
|
||||||
|
&selected.unwrap(),
|
||||||
|
AnyMessageEventContent::RoomMessage(
|
||||||
|
MessageEventContent::text_plain(draft),
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
},
|
||||||
|
|result| match result {
|
||||||
|
Ok(_) => Message::SetMessage(String::new()),
|
||||||
|
Err(e) => Message::SetMessage(format!("{:?}", e)),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -187,11 +256,27 @@ impl Application for Retrix {
|
|||||||
user,
|
user,
|
||||||
password,
|
password,
|
||||||
server,
|
server,
|
||||||
|
action,
|
||||||
error,
|
error,
|
||||||
} => {
|
} => {
|
||||||
// Login form
|
// Login form
|
||||||
let mut content = Column::new()
|
let mut content = Column::new()
|
||||||
.width(500.into())
|
.width(500.into())
|
||||||
|
.push(
|
||||||
|
Row::new()
|
||||||
|
.push(Radio::new(
|
||||||
|
PromptAction::Login,
|
||||||
|
"Login",
|
||||||
|
Some(*action),
|
||||||
|
Message::SetAction,
|
||||||
|
))
|
||||||
|
.push(Radio::new(
|
||||||
|
PromptAction::Signup,
|
||||||
|
"Sign up",
|
||||||
|
Some(*action),
|
||||||
|
Message::SetAction,
|
||||||
|
)),
|
||||||
|
)
|
||||||
.push(Text::new("Username"))
|
.push(Text::new("Username"))
|
||||||
.push(TextInput::new(user_input, "Username", user, Message::SetUser).padding(5))
|
.push(TextInput::new(user_input, "Username", user, Message::SetUser).padding(5))
|
||||||
.push(Text::new("Password"))
|
.push(Text::new("Password"))
|
||||||
@ -204,8 +289,22 @@ impl Application for Retrix {
|
|||||||
.push(
|
.push(
|
||||||
TextInput::new(server_input, "Server", server, Message::SetServer)
|
TextInput::new(server_input, "Server", server, Message::SetServer)
|
||||||
.padding(5),
|
.padding(5),
|
||||||
)
|
);
|
||||||
.push(Button::new(login_button, Text::new("Login")).on_press(Message::Login));
|
let button = match *action {
|
||||||
|
PromptAction::Login => {
|
||||||
|
Button::new(login_button, Text::new("Login")).on_press(Message::Login)
|
||||||
|
}
|
||||||
|
PromptAction::Signup => {
|
||||||
|
content = content.push(
|
||||||
|
Text::new(
|
||||||
|
"NB: Signup is very naively implemented, and prone to breaking",
|
||||||
|
)
|
||||||
|
.color([0.2, 0.2, 0.0]),
|
||||||
|
);
|
||||||
|
Button::new(login_button, Text::new("Sign up")).on_press(Message::Signup)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
content = content.push(button);
|
||||||
if let Some(ref error) = error {
|
if let Some(ref error) = error {
|
||||||
content = content.push(Text::new(error).color([1.0, 0.0, 0.0]));
|
content = content.push(Text::new(error).color([1.0, 0.0, 0.0]));
|
||||||
}
|
}
|
||||||
@ -234,8 +333,12 @@ impl Application for Retrix {
|
|||||||
Retrix::LoggedIn {
|
Retrix::LoggedIn {
|
||||||
client,
|
client,
|
||||||
room_scroll,
|
room_scroll,
|
||||||
|
message_scroll,
|
||||||
|
message_input,
|
||||||
|
send_button,
|
||||||
buttons,
|
buttons,
|
||||||
selected,
|
selected,
|
||||||
|
draft,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
let mut root_row = Row::new().width(Length::Fill).height(Length::Fill);
|
let mut root_row = Row::new().width(Length::Fill).height(Length::Fill);
|
||||||
@ -290,32 +393,66 @@ impl Application for Retrix {
|
|||||||
.padding(5)
|
.padding(5)
|
||||||
.push(Text::new(room.display_name()).size(25))
|
.push(Text::new(room.display_name()).size(25))
|
||||||
.push(Rule::horizontal(2));
|
.push(Rule::horizontal(2));
|
||||||
|
let mut scroll = Scrollable::new(message_scroll)
|
||||||
|
.scrollbar_width(2)
|
||||||
|
.height(Length::Fill);
|
||||||
for message in room.messages.iter() {
|
for message in room.messages.iter() {
|
||||||
if let AnyPossiblyRedactedSyncMessageEvent::Regular(event) = message {
|
if let AnyPossiblyRedactedSyncMessageEvent::Regular(event) = message {
|
||||||
match event {
|
if let AnySyncMessageEvent::RoomMessage(room_message) = event {
|
||||||
AnySyncMessageEvent::RoomMessage(room_message) => {
|
match &room_message.content {
|
||||||
match &room_message.content {
|
MessageEventContent::Text(text) => {
|
||||||
MessageEventContent::Text(text) => {
|
let row = Row::new()
|
||||||
let row = Row::new()
|
.spacing(5)
|
||||||
.spacing(5)
|
.push(
|
||||||
.push(
|
// Render senders disambiguated name or fallback to
|
||||||
Text::new(room_message.sender.localpart())
|
// mxid
|
||||||
.color([0.2, 0.2, 1.0]),
|
Text::new(
|
||||||
|
room.joined_members
|
||||||
|
.get(&room_message.sender)
|
||||||
|
.map(|sender| sender.disambiguated_name())
|
||||||
|
.unwrap_or(room_message.sender.to_string()),
|
||||||
)
|
)
|
||||||
.push(Text::new(&text.body).width(Length::Fill))
|
.color([0.2, 0.2, 1.0]),
|
||||||
.push(Text::new(format_systime(
|
)
|
||||||
room_message.origin_server_ts,
|
.push(Text::new(&text.body).width(Length::Fill))
|
||||||
)));
|
.push(Text::new(format_systime(
|
||||||
col = col.push(row);
|
room_message.origin_server_ts,
|
||||||
}
|
)));
|
||||||
_ => (),
|
scroll = scroll.push(row);
|
||||||
}
|
}
|
||||||
|
_ => (),
|
||||||
}
|
}
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
col = col.push(scroll).push(
|
||||||
|
Row::new()
|
||||||
|
.push(
|
||||||
|
TextInput::new(
|
||||||
|
message_input,
|
||||||
|
"Write a message...",
|
||||||
|
draft,
|
||||||
|
Message::SetMessage,
|
||||||
|
)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.padding(5)
|
||||||
|
.on_submit(Message::SendMessage),
|
||||||
|
)
|
||||||
|
.push(
|
||||||
|
Button::new(send_button, Text::new("Send"))
|
||||||
|
.on_press(Message::SendMessage),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
root_row = root_row.push(col);
|
root_row = root_row.push(col);
|
||||||
|
} else {
|
||||||
|
root_row = root_row.push(
|
||||||
|
Container::new(Text::new("Select a room to start chatting"))
|
||||||
|
.center_x()
|
||||||
|
.center_y()
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
root_row.into()
|
root_row.into()
|
||||||
|
Loading…
Reference in New Issue
Block a user