Message sending

This commit is contained in:
Amanda Graven 2020-12-03 12:12:07 +01:00
parent 061276524a
commit 41b8756ac2
Signed by: amanda
GPG key ID: 45C461CDC9286390
4 changed files with 256 additions and 54 deletions

View file

@ -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"] }

View file

@ -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() {

View file

@ -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,14 +191,16 @@ 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(
SyncSettings::new().timeout(Duration::from_secs(90)),
|response| async {
for (room_id, room) in response.rooms.join { for (room_id, room) in response.rooms.join {
for event in room.timeline.events { for event in room.timeline.events {
if let Ok(event) = event.deserialize() { if let Ok(event) = event.deserialize() {
@ -156,7 +213,9 @@ where
AnyRoomEvent::State(e.into_full_event(room_id)) AnyRoomEvent::State(e.into_full_event(room_id))
} }
AnySyncRoomEvent::RedactedMessage(e) => { AnySyncRoomEvent::RedactedMessage(e) => {
AnyRoomEvent::RedactedMessage(e.into_full_event(room_id)) AnyRoomEvent::RedactedMessage(
e.into_full_event(room_id),
)
} }
AnySyncRoomEvent::RedactedState(e) => { AnySyncRoomEvent::RedactedState(e) => {
AnyRoomEvent::RedactedState(e.into_full_event(room_id)) AnyRoomEvent::RedactedState(e.into_full_event(room_id))
@ -168,9 +227,12 @@ where
} }
LoopCtrl::Continue LoopCtrl::Continue
}) },
)
.await; .await;
println!("We stopped syncing!");
}); });
self.join = Some(join);
Box::pin(receiver) Box::pin(receiver)
} }
} }

163
src/ui.rs
View file

@ -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),
);
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",
) )
.push(Button::new(login_button, Text::new("Login")).on_press(Message::Login)); .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(
Text::new(room_message.sender.localpart()) // Render senders disambiguated name or fallback to
// mxid
Text::new(
room.joined_members
.get(&room_message.sender)
.map(|sender| sender.disambiguated_name())
.unwrap_or(room_message.sender.to_string()),
)
.color([0.2, 0.2, 1.0]), .color([0.2, 0.2, 1.0]),
) )
.push(Text::new(&text.body).width(Length::Fill)) .push(Text::new(&text.body).width(Length::Fill))
.push(Text::new(format_systime( .push(Text::new(format_systime(
room_message.origin_server_ts, room_message.origin_server_ts,
))); )));
col = col.push(row); 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()