Sorting setting, display name setting, better verification, error messages, device name setting

This commit is contained in:
Amanda Graven 2020-12-08 16:40:36 +01:00
parent 67e51d234b
commit cf7c333d45
Signed by: amanda
GPG key ID: 45C461CDC9286390
2 changed files with 184 additions and 55 deletions

View file

@ -34,6 +34,7 @@ pub async fn signup(
username: &str, username: &str,
password: &str, password: &str,
server: &str, server: &str,
device_name: Option<&str>,
) -> Result<(Client, Session), Error> { ) -> Result<(Client, Session), Error> {
let url = Url::parse(server)?; let url = Url::parse(server)?;
let client = client(url)?; let client = client(url)?;
@ -41,7 +42,7 @@ pub async fn signup(
let mut request = RegistrationRequest::new(); let mut request = RegistrationRequest::new();
request.username = Some(username); request.username = Some(username);
request.password = Some(password); request.password = Some(password);
request.initial_device_display_name = Some("retrix"); request.initial_device_display_name = Some(device_name.unwrap_or("retrix"));
request.inhibit_login = false; request.inhibit_login = false;
// Get UIAA session key // Get UIAA session key
@ -82,6 +83,7 @@ pub async fn login(
username: &str, username: &str,
password: &str, password: &str,
server: &str, server: &str,
device_name: Option<&str>,
) -> Result<(Client, Session), Error> { ) -> Result<(Client, Session), Error> {
let url = Url::parse(server)?; let url = Url::parse(server)?;
let client = client(url)?; let client = client(url)?;
@ -91,7 +93,7 @@ pub async fn login(
username, username,
password, password,
None, None,
Some(&format!("retrix@{}", hostname::get()?.to_string_lossy())), Some(device_name.unwrap_or("retrix")),
) )
.await?; .await?;
let session = Session { let session = Session {

233
src/ui.rs
View file

@ -8,6 +8,7 @@ use iced::{
}; };
use matrix_sdk::{ use matrix_sdk::{
events::{ events::{
key::verification::cancel::CancelCode as VerificationCancelCode,
room::message::MessageEventContent, AnyMessageEventContent, room::message::MessageEventContent, AnyMessageEventContent,
AnyPossiblyRedactedSyncMessageEvent, AnySyncMessageEvent, AnyToDeviceEvent, AnyPossiblyRedactedSyncMessageEvent, AnySyncMessageEvent, AnyToDeviceEvent,
}, },
@ -19,15 +20,28 @@ use crate::matrix;
/// View for the login prompt /// View for the login prompt
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct PromptView { pub struct PromptView {
/// Username input field
user_input: text_input::State, user_input: text_input::State,
/// Password input field
password_input: text_input::State, password_input: text_input::State,
/// Homeserver input field
server_input: text_input::State, server_input: text_input::State,
/// Device name input field
device_input: text_input::State,
/// Button to trigger login
login_button: iced::button::State, login_button: iced::button::State,
/// Username
user: String, user: String,
/// Password
password: String, password: String,
/// Homeserver
server: String, server: String,
/// Device name to create login session under
device_name: String,
/// Whether to log in or sign up
action: PromptAction, action: PromptAction,
/// Error message
error: Option<String>, error: Option<String>,
} }
@ -39,6 +53,7 @@ impl PromptView {
pub fn view(&mut self) -> Element<Message> { pub fn view(&mut self) -> Element<Message> {
let mut content = Column::new() let mut content = Column::new()
.width(500.into()) .width(500.into())
.spacing(5)
.push( .push(
Row::new() Row::new()
.spacing(15) .spacing(15)
@ -55,36 +70,50 @@ impl PromptView {
Message::SetAction, Message::SetAction,
)), )),
) )
.push(Text::new("Username"))
.push( .push(
TextInput::new( Column::new().push(Text::new("Username")).push(
&mut self.user_input, TextInput::new(
"Username", &mut self.user_input,
&self.user, "Username",
Message::SetUser, &self.user,
) Message::SetUser,
.padding(5), )
.padding(5),
),
) )
.push(Text::new("Password"))
.push( .push(
TextInput::new( Column::new().push(Text::new("Password")).push(
&mut self.password_input, TextInput::new(
"Password", &mut self.password_input,
&self.password, "Password",
Message::SetPassword, &self.password,
) Message::SetPassword,
.password() )
.padding(5), .password()
.padding(5),
),
) )
.push(Text::new("Homeserver"))
.push( .push(
TextInput::new( Column::new().push(Text::new("Homeserver")).push(
&mut self.server_input, TextInput::new(
"Server", &mut self.server_input,
&self.server, "https://homeserver.com",
Message::SetServer, &self.server,
) Message::SetServer,
.padding(5), )
.padding(5),
),
)
.push(
Column::new().push(Text::new("Device name")).push(
TextInput::new(
&mut self.device_input,
"retrix on my laptop",
&self.device_name,
Message::SetDeviceName,
)
.padding(5),
),
); );
let button = match self.action { let button = match self.action {
PromptAction::Login => { PromptAction::Login => {
@ -93,7 +122,7 @@ impl PromptView {
PromptAction::Signup => { PromptAction::Signup => {
content = content.push( content = content.push(
Text::new("NB: Signup is very naively implemented, and prone to breaking") Text::new("NB: Signup is very naively implemented, and prone to breaking")
.color([0.2, 0.2, 0.0]), .color([1.0, 0.5, 0.0]),
); );
Button::new(&mut self.login_button, Text::new("Sign up")).on_press(Message::Signup) Button::new(&mut self.login_button, Text::new("Sign up")).on_press(Message::Signup)
} }
@ -121,24 +150,39 @@ pub enum RoomSorting {
/// Main view after successful login /// Main view after successful login
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct MainView { pub struct MainView {
/// Settings view, if open
settings_view: Option<SettingsView>, settings_view: Option<SettingsView>,
/// The matrix-sdk client
client: matrix_sdk::Client, client: matrix_sdk::Client,
session: matrix::Session, session: matrix::Session,
/// Draft of message to send
draft: String, draft: String,
/// Potential error message
error: Option<(String, iced::button::State)>, error: Option<(String, iced::button::State)>,
/// Selected room
selected: Option<RoomId>, selected: Option<RoomId>,
/// Potential verification flow
sas: Option<matrix_sdk::Sas>, sas: Option<matrix_sdk::Sas>,
/// Whether to sort rooms alphabetically or by activity
sorting: RoomSorting, sorting: RoomSorting,
rooms: BTreeMap<RoomId, String>, rooms: BTreeMap<RoomId, String>,
messages: BTreeMap<RoomId, MessageEventContent>, messages: BTreeMap<RoomId, MessageEventContent>,
/// Room list entry/button to select room
buttons: HashMap<RoomId, iced::button::State>, buttons: HashMap<RoomId, iced::button::State>,
/// Room list scrollbar state
room_scroll: iced::scrollable::State, room_scroll: iced::scrollable::State,
/// Message view scrollbar state
message_scroll: iced::scrollable::State, message_scroll: iced::scrollable::State,
/// Message draft text input
message_input: iced::text_input::State, message_input: iced::text_input::State,
/// Button to send drafted message
send_button: iced::button::State, send_button: iced::button::State,
/// Button to open settings menu
settings_button: iced::button::State, settings_button: iced::button::State,
/// Button for accepting/continuing verification
sas_accept_button: iced::button::State, sas_accept_button: iced::button::State,
/// Button for cancelling verification
sas_deny_button: iced::button::State, sas_deny_button: iced::button::State,
} }
@ -160,7 +204,7 @@ impl MainView {
messages: Default::default(), messages: Default::default(),
draft: String::new(), draft: String::new(),
send_button: Default::default(), send_button: Default::default(),
sorting: RoomSorting::Alphabetic, sorting: RoomSorting::Recent,
sas_accept_button: Default::default(), sas_accept_button: Default::default(),
sas_deny_button: Default::default(), sas_deny_button: Default::default(),
} }
@ -169,7 +213,7 @@ impl MainView {
pub fn view(&mut self) -> Element<Message> { pub fn view(&mut self) -> Element<Message> {
// If settings view is open, display that instead // If settings view is open, display that instead
if let Some(ref mut settings) = self.settings_view { if let Some(ref mut settings) = self.settings_view {
return settings.view(); return settings.view(self.sorting);
} }
let mut root_row = Row::new().width(Length::Fill).height(Length::Fill); let mut root_row = Row::new().width(Length::Fill).height(Length::Fill);
@ -177,7 +221,7 @@ impl MainView {
let joined = self.client.joined_rooms(); let joined = self.client.joined_rooms();
let rooms = futures::executor::block_on(async { joined.read().await }); let rooms = futures::executor::block_on(async { joined.read().await });
let mut room_scroll = Scrollable::new(&mut self.room_scroll) let mut room_scroll = Scrollable::new(&mut self.room_scroll)
.width(400.into()) .width(300.into())
.height(Length::Fill) .height(Length::Fill)
.scrollbar_width(5); .scrollbar_width(5);
// We have to iterate the buttons map and not the other way around to make the // We have to iterate the buttons map and not the other way around to make the
@ -220,9 +264,9 @@ impl MainView {
.collect(); .collect();
for list in [&mut dm_rooms, &mut room_rooms].iter_mut() { for list in [&mut dm_rooms, &mut room_rooms].iter_mut() {
match self.sorting { match self.sorting {
RoomSorting::Recent => list.sort_by_cached_key(|id| { RoomSorting::Recent => list.sort_by_key(|id| {
let read = block_on(async { rooms.get(id).unwrap().read().await }); let read = block_on(async { rooms.get(id).unwrap().read().await });
*read let time = read
.messages .messages
.iter() .iter()
.map(|msg| match msg { .map(|msg| match msg {
@ -232,7 +276,14 @@ impl MainView {
} }
}) })
.max() .max()
.unwrap_or(&std::time::SystemTime::now()) .copied();
match time {
Some(time) => time,
None => {
println!("couldn't get time");
std::time::SystemTime::now()
}
}
}), }),
RoomSorting::Alphabetic => list.sort_by_cached_key(|id| { RoomSorting::Alphabetic => list.sort_by_cached_key(|id| {
let read = block_on(async { rooms.get(id).unwrap().read().await }); let read = block_on(async { rooms.get(id).unwrap().read().await });
@ -359,6 +410,14 @@ impl MainView {
}; };
message_col = message_col.push(sas_row); message_col = message_col.push(sas_row);
} }
// Potential error message
if let Some((ref error, ref mut button)) = self.error {
message_col = message_col.push(
Row::new()
.push(Text::new(error).width(Length::Fill).color([1.0, 0.0, 0.0]))
.push(Button::new(button, Text::new("Close")).on_press(Message::ClearError)),
);
}
// Compose box // Compose box
message_col = message_col.push( message_col = message_col.push(
Row::new() Row::new()
@ -409,6 +468,7 @@ pub enum Message {
SetUser(String), SetUser(String),
SetPassword(String), SetPassword(String),
SetServer(String), SetServer(String),
SetDeviceName(String),
SetAction(PromptAction), SetAction(PromptAction),
Login, Login,
Signup, Signup,
@ -423,6 +483,8 @@ pub enum Message {
ErrorMessage(String), ErrorMessage(String),
/// Close error message /// Close error message
ClearError, ClearError,
/// Set how the room list is sorted
SetSort(RoomSorting),
SetVerification(Option<matrix_sdk::Sas>), SetVerification(Option<matrix_sdk::Sas>),
/// Accept verification flow /// Accept verification flow
VerificationAccept, VerificationAccept,
@ -435,7 +497,7 @@ pub enum Message {
/// Cancel verification flow /// Cancel verification flow
VerificationCancel, VerificationCancel,
/// Verification flow cancelled /// Verification flow cancelled
VerificationCancelled, VerificationCancelled(VerificationCancelCode),
/// Matrix event received /// Matrix event received
Sync(matrix::Event), Sync(matrix::Event),
/// Set contents of message compose box /// Set contents of message compose box
@ -466,6 +528,8 @@ pub enum Message {
pub struct SettingsView { pub struct SettingsView {
/// Display name to set /// Display name to set
display_name: String, display_name: String,
/// Are we saving the display name?
saving_name: bool,
/// Display name text input /// Display name text input
display_name_input: iced::text_input::State, display_name_input: iced::text_input::State,
@ -492,11 +556,11 @@ impl SettingsView {
Self::default() Self::default()
} }
fn view(&mut self) -> Element<Message> { fn view(&mut self, sort: RoomSorting) -> Element<Message> {
let content = Column::new() let content = Column::new()
.width(500.into()) .width(500.into())
.spacing(15) .spacing(5)
.push(Text::new("Profile").size(20)) .push(Text::new("Profile").size(25))
.push( .push(
Column::new().push(Text::new("Display name")).push( Column::new().push(Text::new("Display name")).push(
Row::new() Row::new()
@ -510,13 +574,30 @@ impl SettingsView {
.width(Length::Fill) .width(Length::Fill)
.padding(5), .padding(5),
) )
.push( .push(match self.saving_name {
Button::new(&mut self.display_name_button, Text::new("Save")) false => Button::new(&mut self.display_name_button, Text::new("Save"))
.on_press(Message::SaveDisplayName), .on_press(Message::SaveDisplayName),
), true => {
Button::new(&mut self.display_name_button, Text::new("Saving..."))
}
}),
), ),
) )
.push(Text::new("Encryption").size(20)) .push(Text::new("Appearance").size(25))
.push(Text::new("Sort messages by:"))
.push(Radio::new(
RoomSorting::Alphabetic,
"Name",
Some(sort),
Message::SetSort,
))
.push(Radio::new(
RoomSorting::Recent,
"Activity",
Some(sort),
Message::SetSort,
))
.push(Text::new("Encryption").size(25))
.push( .push(
Column::new() Column::new()
.push(Text::new("Import key (enter path)")) .push(Text::new("Import key (enter path)"))
@ -547,8 +628,10 @@ impl SettingsView {
.on_press(Message::ImportKeys), .on_press(Message::ImportKeys),
) )
.push( .push(
Button::new(&mut self.close_button, Text::new("Close")) Row::new().width(Length::Fill).push(
.on_press(Message::CloseSettings), Button::new(&mut self.close_button, Text::new("Close"))
.on_press(Message::CloseSettings),
),
); );
Container::new(content) Container::new(content)
.center_x() .center_x()
@ -600,14 +683,20 @@ impl Application for Retrix {
Message::SetUser(u) => prompt.user = u, Message::SetUser(u) => prompt.user = u,
Message::SetPassword(p) => prompt.password = p, Message::SetPassword(p) => prompt.password = p,
Message::SetServer(s) => prompt.server = s, Message::SetServer(s) => prompt.server = s,
Message::SetDeviceName(n) => prompt.device_name = n,
Message::SetAction(a) => prompt.action = a, Message::SetAction(a) => prompt.action = a,
Message::Login => { Message::Login => {
let user = prompt.user.clone(); let user = prompt.user.clone();
let password = prompt.password.clone(); let password = prompt.password.clone();
let server = prompt.server.clone(); let server = prompt.server.clone();
let device = prompt.device_name.clone();
let device = match device.is_empty() {
false => Some(device),
true => None,
};
*self = Retrix::AwaitLogin; *self = Retrix::AwaitLogin;
return Command::perform( return Command::perform(
async move { matrix::login(&user, &password, &server).await }, async move { matrix::login(&user, &password, &server, device.as_deref()).await },
|result| match result { |result| match result {
Ok((c, r)) => Message::LoggedIn(c, r), Ok((c, r)) => Message::LoggedIn(c, r),
Err(e) => Message::LoginFailed(e.to_string()), Err(e) => Message::LoginFailed(e.to_string()),
@ -618,9 +707,16 @@ impl Application for Retrix {
let user = prompt.user.clone(); let user = prompt.user.clone();
let password = prompt.password.clone(); let password = prompt.password.clone();
let server = prompt.server.clone(); let server = prompt.server.clone();
let device = prompt.device_name.clone();
let device = match device.is_empty() {
false => Some(device),
true => None,
};
*self = Retrix::AwaitLogin; *self = Retrix::AwaitLogin;
return Command::perform( return Command::perform(
async move { matrix::signup(&user, &password, &server).await }, async move {
matrix::signup(&user, &password, &server, device.as_deref()).await
},
|result| match result { |result| match result {
Ok((client, response)) => Message::LoggedIn(client, response), Ok((client, response)) => Message::LoggedIn(client, response),
Err(e) => Message::LoginFailed(e.to_string()), Err(e) => Message::LoginFailed(e.to_string()),
@ -655,6 +751,8 @@ impl Application for Retrix {
Retrix::LoggedIn(view) => { Retrix::LoggedIn(view) => {
match message { match message {
Message::ErrorMessage(e) => view.error = Some((e, Default::default())), Message::ErrorMessage(e) => view.error = Some((e, Default::default())),
Message::ClearError => view.error = None,
Message::SetSort(s) => view.sorting = s,
Message::ResetRooms(r) => view.rooms = r, Message::ResetRooms(r) => view.rooms = r,
Message::SelectRoom(r) => view.selected = Some(r), Message::SelectRoom(r) => view.selected = Some(r),
Message::Sync(event) => match event { Message::Sync(event) => match event {
@ -664,13 +762,18 @@ impl Application for Retrix {
let client = view.client.clone(); let client = view.client.clone();
return Command::perform( return Command::perform(
async move { async move {
tokio::time::delay_for(std::time::Duration::from_secs(2))
.await;
client.get_verification(&start.content.transaction_id).await client.get_verification(&start.content.transaction_id).await
}, },
Message::SetVerification, Message::SetVerification,
); );
} }
AnyToDeviceEvent::KeyVerificationCancel(cancel) => { AnyToDeviceEvent::KeyVerificationCancel(cancel) => {
return async { Message::SetMessage(cancel.content.reason) }.into(); return async {
Message::VerificationCancelled(cancel.content.code)
}
.into();
} }
_ => (), _ => (),
}, },
@ -684,7 +787,7 @@ impl Application for Retrix {
return Command::perform(async move { sas.accept().await }, |result| { return Command::perform(async move { sas.accept().await }, |result| {
match result { match result {
Ok(()) => Message::VerificationAccepted, Ok(()) => Message::VerificationAccepted,
Err(e) => Message::SetMessage(e.to_string()), Err(e) => Message::ErrorMessage(e.to_string()),
} }
}); });
} }
@ -696,7 +799,7 @@ impl Application for Retrix {
return Command::perform(async move { sas.confirm().await }, |result| { return Command::perform(async move { sas.confirm().await }, |result| {
match result { match result {
Ok(()) => Message::VerificationConfirmed, Ok(()) => Message::VerificationConfirmed,
Err(e) => Message::SetMessage(e.to_string()), Err(e) => Message::ErrorMessage(e.to_string()),
} }
}); });
} }
@ -707,13 +810,17 @@ impl Application for Retrix {
}; };
return Command::perform(async move { sas.cancel().await }, |result| { return Command::perform(async move { sas.cancel().await }, |result| {
match result { match result {
Ok(()) => Message::VerificationCancelled, Ok(()) => {
Err(e) => Message::SetMessage(e.to_string()), Message::VerificationCancelled(VerificationCancelCode::User)
}
Err(e) => Message::ErrorMessage(e.to_string()),
} }
}); });
} }
Message::VerificationCancelled => { Message::VerificationCancelled(code) => {
view.sas = None; view.sas = None;
return async move { Message::ErrorMessage(code.as_str().to_owned()) }
.into();
} }
Message::SetMessage(m) => view.draft = m, Message::SetMessage(m) => view.draft = m,
Message::SendMessage => { Message::SendMessage => {
@ -737,11 +844,24 @@ impl Application for Retrix {
}, },
|result| match result { |result| match result {
Ok(_) => Message::SetMessage(String::new()), Ok(_) => Message::SetMessage(String::new()),
Err(e) => Message::SetMessage(format!("{:?}", e)), Err(e) => Message::ErrorMessage(e.to_string()),
}, },
); );
} }
Message::OpenSettings => view.settings_view = Some(SettingsView::new()), Message::OpenSettings => {
view.settings_view = Some(SettingsView::new());
let client = view.client.clone();
return Command::perform(
async move {
client
.display_name()
.await
.unwrap_or_default()
.unwrap_or_default()
},
Message::SetDisplayNameInput,
);
}
Message::SetDisplayNameInput(name) => { Message::SetDisplayNameInput(name) => {
if let Some(ref mut settings) = view.settings_view { if let Some(ref mut settings) = view.settings_view {
settings.display_name = name; settings.display_name = name;
@ -751,15 +871,22 @@ impl Application for Retrix {
if let Some(ref mut settings) = view.settings_view { if let Some(ref mut settings) = view.settings_view {
let client = view.client.clone(); let client = view.client.clone();
let name = settings.display_name.clone(); let name = settings.display_name.clone();
settings.saving_name = true;
return Command::perform( return Command::perform(
async move { client.set_display_name(Some(&name)).await }, async move { client.set_display_name(Some(&name)).await },
|result| match result { |result| match result {
Ok(()) => Message::DisplayNameSaved, Ok(()) => Message::DisplayNameSaved,
Err(e) => Message::ErrorMessage(e.to_string()), // TODO: set saving to false and report error
Err(_) => Message::DisplayNameSaved,
}, },
); );
} }
} }
Message::DisplayNameSaved => {
if let Some(ref mut settings) = view.settings_view {
settings.saving_name = false;
}
}
Message::SetKeyPath(p) => { Message::SetKeyPath(p) => {
if let Some(ref mut settings) = view.settings_view { if let Some(ref mut settings) = view.settings_view {
settings.key_path = p; settings.key_path = p;