Split ui into modules, move some types to matrix.rs
parent
a212a0b3a0
commit
45c4b62150
|
@ -1,9 +1,9 @@
|
||||||
use std::time::Duration;
|
use std::time::{Duration, SystemTime};
|
||||||
|
|
||||||
use matrix_sdk::{
|
use matrix_sdk::{
|
||||||
api::r0::{account::register::Request as RegistrationRequest, uiaa::AuthData},
|
api::r0::{account::register::Request as RegistrationRequest, uiaa::AuthData},
|
||||||
events::{AnyRoomEvent, AnySyncRoomEvent, AnyToDeviceEvent},
|
events::{AnyRoomEvent, AnySyncRoomEvent, AnyToDeviceEvent},
|
||||||
identifiers::{DeviceId, UserId},
|
identifiers::{DeviceId, EventId, UserId},
|
||||||
reqwest::Url,
|
reqwest::Url,
|
||||||
Client, ClientConfig, LoopCtrl, SyncSettings,
|
Client, ClientConfig, LoopCtrl, SyncSettings,
|
||||||
};
|
};
|
||||||
|
@ -181,6 +181,7 @@ impl EventEmitter for Callback {
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
Room(AnyRoomEvent),
|
Room(AnyRoomEvent),
|
||||||
ToDevice(AnyToDeviceEvent),
|
ToDevice(AnyToDeviceEvent),
|
||||||
|
Token(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<H, I> iced_futures::subscription::Recipe<H, I> for MatrixSync
|
impl<H, I> iced_futures::subscription::Recipe<H, I> for MatrixSync
|
||||||
|
@ -210,6 +211,7 @@ where
|
||||||
.timeout(Duration::from_secs(90))
|
.timeout(Duration::from_secs(90))
|
||||||
.full_state(true),
|
.full_state(true),
|
||||||
|response| async {
|
|response| async {
|
||||||
|
sender.send(Event::Token(response.next_batch)).ok();
|
||||||
for (id, room) in response.rooms.join {
|
for (id, room) in response.rooms.join {
|
||||||
for event in room.timeline.events {
|
for event in room.timeline.events {
|
||||||
let event = match event.deserialize() {
|
let event = match event.deserialize() {
|
||||||
|
@ -241,14 +243,38 @@ where
|
||||||
};
|
};
|
||||||
sender.send(Event::ToDevice(event)).ok();
|
sender.send(Event::ToDevice(event)).ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
LoopCtrl::Continue
|
LoopCtrl::Continue
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
println!("We stopped syncing!");
|
|
||||||
});
|
});
|
||||||
self.join = Some(join);
|
self.join = Some(join);
|
||||||
Box::pin(receiver)
|
Box::pin(receiver)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait AnyRoomEventExt {
|
||||||
|
fn event_id(&self) -> &EventId;
|
||||||
|
/// Gets the ´origin_server_ts` member of the underlying event
|
||||||
|
fn origin_server_ts(&self) -> SystemTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AnyRoomEventExt for AnyRoomEvent {
|
||||||
|
fn event_id(&self) -> &EventId {
|
||||||
|
match self {
|
||||||
|
AnyRoomEvent::Message(e) => e.event_id(),
|
||||||
|
AnyRoomEvent::State(e) => e.event_id(),
|
||||||
|
AnyRoomEvent::RedactedMessage(e) => e.event_id(),
|
||||||
|
AnyRoomEvent::RedactedState(e) => e.event_id(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn origin_server_ts(&self) -> SystemTime {
|
||||||
|
match self {
|
||||||
|
AnyRoomEvent::Message(e) => e.origin_server_ts(),
|
||||||
|
AnyRoomEvent::State(e) => e.origin_server_ts(),
|
||||||
|
AnyRoomEvent::RedactedMessage(e) => e.origin_server_ts(),
|
||||||
|
AnyRoomEvent::RedactedState(e) => e.origin_server_ts(),
|
||||||
|
}
|
||||||
|
.to_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
825
src/ui.rs
825
src/ui.rs
|
@ -1,13 +1,12 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::{BTreeMap, VecDeque},
|
collections::{BTreeMap, HashSet},
|
||||||
time::SystemTime,
|
time::SystemTime,
|
||||||
};
|
};
|
||||||
|
|
||||||
use futures::executor::block_on;
|
use futures::executor::block_on;
|
||||||
use iced::{
|
use iced::{
|
||||||
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, TextInput,
|
||||||
Subscription, Text,
|
|
||||||
};
|
};
|
||||||
use matrix_sdk::{
|
use matrix_sdk::{
|
||||||
events::{
|
events::{
|
||||||
|
@ -15,151 +14,18 @@ use matrix_sdk::{
|
||||||
room::message::MessageEventContent, AnyMessageEvent, AnyMessageEventContent,
|
room::message::MessageEventContent, AnyMessageEvent, AnyMessageEventContent,
|
||||||
AnyPossiblyRedactedSyncMessageEvent, AnyRoomEvent, AnyStateEvent, AnyToDeviceEvent,
|
AnyPossiblyRedactedSyncMessageEvent, AnyRoomEvent, AnyStateEvent, AnyToDeviceEvent,
|
||||||
},
|
},
|
||||||
identifiers::{RoomAliasId, RoomId, UserId},
|
identifiers::{EventId, RoomAliasId, RoomId, UserId},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::matrix;
|
use crate::matrix::{self, AnyRoomEventExt};
|
||||||
|
|
||||||
pub trait AnyRoomEventExt {
|
pub mod prompt;
|
||||||
fn origin_server_ts(&self) -> SystemTime;
|
pub mod settings;
|
||||||
}
|
|
||||||
|
|
||||||
impl AnyRoomEventExt for AnyRoomEvent {
|
use prompt::{PromptAction, PromptView};
|
||||||
fn origin_server_ts(&self) -> SystemTime {
|
use settings::SettingsView;
|
||||||
match self {
|
|
||||||
AnyRoomEvent::Message(e) => e.origin_server_ts(),
|
|
||||||
AnyRoomEvent::State(e) => e.origin_server_ts(),
|
|
||||||
AnyRoomEvent::RedactedMessage(e) => e.origin_server_ts(),
|
|
||||||
AnyRoomEvent::RedactedState(e) => e.origin_server_ts(),
|
|
||||||
}
|
|
||||||
.to_owned()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// View for the login prompt
|
|
||||||
#[derive(Debug, Clone, Default)]
|
|
||||||
pub struct PromptView {
|
|
||||||
/// Username input field
|
|
||||||
user_input: text_input::State,
|
|
||||||
/// Password input field
|
|
||||||
password_input: text_input::State,
|
|
||||||
/// Homeserver input field
|
|
||||||
server_input: text_input::State,
|
|
||||||
/// Device name input field
|
|
||||||
device_input: text_input::State,
|
|
||||||
/// Button to trigger login
|
|
||||||
login_button: iced::button::State,
|
|
||||||
|
|
||||||
/// Username
|
|
||||||
user: String,
|
|
||||||
/// Password
|
|
||||||
password: String,
|
|
||||||
/// Homeserver
|
|
||||||
server: String,
|
|
||||||
/// Device name to create login session under
|
|
||||||
device_name: String,
|
|
||||||
/// Whether to log in or sign up
|
|
||||||
action: PromptAction,
|
|
||||||
/// Error message
|
|
||||||
error: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PromptView {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view(&mut self) -> Element<Message> {
|
|
||||||
let mut content = Column::new()
|
|
||||||
.width(500.into())
|
|
||||||
.spacing(5)
|
|
||||||
.push(
|
|
||||||
Row::new()
|
|
||||||
.spacing(15)
|
|
||||||
.push(Radio::new(
|
|
||||||
PromptAction::Login,
|
|
||||||
"Login",
|
|
||||||
Some(self.action),
|
|
||||||
Message::SetAction,
|
|
||||||
))
|
|
||||||
.push(Radio::new(
|
|
||||||
PromptAction::Signup,
|
|
||||||
"Sign up",
|
|
||||||
Some(self.action),
|
|
||||||
Message::SetAction,
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
.push(
|
|
||||||
Column::new().push(Text::new("Username")).push(
|
|
||||||
TextInput::new(
|
|
||||||
&mut self.user_input,
|
|
||||||
"Username",
|
|
||||||
&self.user,
|
|
||||||
Message::SetUser,
|
|
||||||
)
|
|
||||||
.padding(5),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.push(
|
|
||||||
Column::new().push(Text::new("Password")).push(
|
|
||||||
TextInput::new(
|
|
||||||
&mut self.password_input,
|
|
||||||
"Password",
|
|
||||||
&self.password,
|
|
||||||
Message::SetPassword,
|
|
||||||
)
|
|
||||||
.password()
|
|
||||||
.padding(5),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.push(
|
|
||||||
Column::new().push(Text::new("Homeserver")).push(
|
|
||||||
TextInput::new(
|
|
||||||
&mut self.server_input,
|
|
||||||
"https://homeserver.com",
|
|
||||||
&self.server,
|
|
||||||
Message::SetServer,
|
|
||||||
)
|
|
||||||
.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 {
|
|
||||||
PromptAction::Login => {
|
|
||||||
Button::new(&mut self.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([1.0, 0.5, 0.0]),
|
|
||||||
);
|
|
||||||
Button::new(&mut self.login_button, Text::new("Sign up")).on_press(Message::Signup)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
content = content.push(button);
|
|
||||||
if let Some(ref error) = self.error {
|
|
||||||
content = content.push(Text::new(error).color([1.0, 0.0, 0.0]));
|
|
||||||
}
|
|
||||||
|
|
||||||
Container::new(content)
|
|
||||||
.center_x()
|
|
||||||
.center_y()
|
|
||||||
.width(iced::Length::Fill)
|
|
||||||
.height(iced::Length::Fill)
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// What order to sort rooms in in the room list.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum RoomSorting {
|
pub enum RoomSorting {
|
||||||
Recent,
|
Recent,
|
||||||
|
@ -198,57 +64,70 @@ impl RoomEntry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Alternate storage strategies: HashMap<EventId, Event>+Vec<EventId>,
|
||||||
|
// HashSet<EventId>+BTreemap<Event + Ord(origin_server_ts)>
|
||||||
|
/// Message history/event cache for a given room.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct MessageBuffer {
|
pub struct MessageBuffer {
|
||||||
messages: VecDeque<AnyRoomEvent>,
|
/// The messages we have stored
|
||||||
|
messages: Vec<AnyRoomEvent>,
|
||||||
|
/// Set of event id's we have
|
||||||
|
known_ids: HashSet<EventId>,
|
||||||
/// Token for the start of the messages we have
|
/// Token for the start of the messages we have
|
||||||
start: String,
|
start: Option<String>,
|
||||||
/// Token for the end of the messages we have
|
/// Token for the end of the messages we have
|
||||||
end: String,
|
end: Option<String>,
|
||||||
/// Most recent activity in the room
|
/// Most recent activity in the room
|
||||||
updated: std::time::SystemTime,
|
updated: std::time::SystemTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for MessageBuffer {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
messages: Default::default(),
|
|
||||||
start: String::new(),
|
|
||||||
end: String::new(),
|
|
||||||
updated: SystemTime::UNIX_EPOCH,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MessageBuffer {
|
impl MessageBuffer {
|
||||||
/// Sorts the messages by send time
|
/// Sorts the messages by send time
|
||||||
fn sort(&mut self) {
|
fn sort(&mut self) {
|
||||||
self.messages
|
self.messages
|
||||||
.make_contiguous()
|
|
||||||
.sort_unstable_by(|a, b| a.origin_server_ts().cmp(&b.origin_server_ts()))
|
.sort_unstable_by(|a, b| a.origin_server_ts().cmp(&b.origin_server_ts()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the send time of the most recently sent message
|
/// Gets the send time of the most recently sent message
|
||||||
fn update_time(&mut self) {
|
fn update_time(&mut self) {
|
||||||
self.updated = match self.messages.back() {
|
self.updated = match self.messages.last() {
|
||||||
Some(message) => message.origin_server_ts(),
|
Some(message) => message.origin_server_ts(),
|
||||||
None => SystemTime::UNIX_EPOCH,
|
None => SystemTime::UNIX_EPOCH,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
/// Insert a message that's probably the most recent
|
|
||||||
pub fn push_back(&mut self, event: AnyRoomEvent) {
|
/// Add a message to the buffer.
|
||||||
self.messages.push_back(event);
|
pub fn push(&mut self, event: AnyRoomEvent) {
|
||||||
|
self.known_ids.insert(event.event_id().clone());
|
||||||
|
self.messages.push(event);
|
||||||
self.sort();
|
self.sort();
|
||||||
self.update_time();
|
self.update_time();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_front(&mut self, event: AnyRoomEvent) {
|
/// Adds several messages to the buffer
|
||||||
self.messages.push_front(event);
|
pub fn append(&mut self, mut events: Vec<AnyRoomEvent>) {
|
||||||
|
events.retain(|e| !self.known_ids.contains(e.event_id()));
|
||||||
|
for event in events.iter() {
|
||||||
|
self.known_ids.insert(event.event_id().clone());
|
||||||
|
}
|
||||||
|
self.messages.append(&mut events);
|
||||||
self.sort();
|
self.sort();
|
||||||
self.update_time();
|
self.update_time();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for MessageBuffer {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
messages: Default::default(),
|
||||||
|
known_ids: Default::default(),
|
||||||
|
start: None,
|
||||||
|
end: None,
|
||||||
|
updated: SystemTime::UNIX_EPOCH,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Main view after successful login
|
/// Main view after successful login
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct MainView {
|
pub struct MainView {
|
||||||
|
@ -256,6 +135,8 @@ pub struct MainView {
|
||||||
settings_view: Option<SettingsView>,
|
settings_view: Option<SettingsView>,
|
||||||
/// The matrix-sdk client
|
/// The matrix-sdk client
|
||||||
client: matrix_sdk::Client,
|
client: matrix_sdk::Client,
|
||||||
|
/// Sync token to use for backfill calls
|
||||||
|
sync_token: String,
|
||||||
session: matrix::Session,
|
session: matrix::Session,
|
||||||
/// Draft of message to send
|
/// Draft of message to send
|
||||||
draft: String,
|
draft: String,
|
||||||
|
@ -267,11 +148,12 @@ pub struct MainView {
|
||||||
sas: Option<matrix_sdk::Sas>,
|
sas: Option<matrix_sdk::Sas>,
|
||||||
/// Whether to sort rooms alphabetically or by activity
|
/// Whether to sort rooms alphabetically or by activity
|
||||||
sorting: RoomSorting,
|
sorting: RoomSorting,
|
||||||
|
/// Room state
|
||||||
rooms: BTreeMap<RoomId, RoomEntry>,
|
rooms: BTreeMap<RoomId, RoomEntry>,
|
||||||
|
|
||||||
/// Room list entry/button to select room
|
/// Room list entries for direct conversations
|
||||||
dm_buttons: Vec<iced::button::State>,
|
dm_buttons: Vec<iced::button::State>,
|
||||||
///
|
/// Room list entries for group conversations
|
||||||
group_buttons: Vec<iced::button::State>,
|
group_buttons: Vec<iced::button::State>,
|
||||||
/// Room list scrollbar state
|
/// Room list scrollbar state
|
||||||
room_scroll: iced::scrollable::State,
|
room_scroll: iced::scrollable::State,
|
||||||
|
@ -294,6 +176,7 @@ impl MainView {
|
||||||
Self {
|
Self {
|
||||||
client,
|
client,
|
||||||
session,
|
session,
|
||||||
|
sync_token: String::new(),
|
||||||
settings_view: None,
|
settings_view: None,
|
||||||
settings_button: Default::default(),
|
settings_button: Default::default(),
|
||||||
error: None,
|
error: None,
|
||||||
|
@ -569,18 +452,6 @@ pub enum Retrix {
|
||||||
LoggedIn(MainView),
|
LoggedIn(MainView),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum PromptAction {
|
|
||||||
Login,
|
|
||||||
Signup,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for PromptAction {
|
|
||||||
fn default() -> Self {
|
|
||||||
PromptAction::Login
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
// Login form messages
|
// Login form messages
|
||||||
|
@ -596,7 +467,11 @@ pub enum Message {
|
||||||
LoginFailed(String),
|
LoginFailed(String),
|
||||||
|
|
||||||
// Main state messages
|
// Main state messages
|
||||||
ResetRooms(BTreeMap<RoomId, RoomEntry>),
|
/// Reset state for room
|
||||||
|
ResetRoom(RoomId, RoomEntry),
|
||||||
|
/// Get backfill for given room
|
||||||
|
BackFill(RoomId),
|
||||||
|
/// View messages from this room
|
||||||
SelectRoom(RoomId),
|
SelectRoom(RoomId),
|
||||||
/// Set error message
|
/// Set error message
|
||||||
ErrorMessage(String),
|
ErrorMessage(String),
|
||||||
|
@ -604,6 +479,7 @@ pub enum Message {
|
||||||
ClearError,
|
ClearError,
|
||||||
/// Set how the room list is sorted
|
/// Set how the room list is sorted
|
||||||
SetSort(RoomSorting),
|
SetSort(RoomSorting),
|
||||||
|
/// Set verification flow
|
||||||
SetVerification(Option<matrix_sdk::Sas>),
|
SetVerification(Option<matrix_sdk::Sas>),
|
||||||
/// Accept verification flow
|
/// Accept verification flow
|
||||||
VerificationAccept,
|
VerificationAccept,
|
||||||
|
@ -621,6 +497,8 @@ pub enum Message {
|
||||||
VerificationClose,
|
VerificationClose,
|
||||||
/// Matrix event received
|
/// Matrix event received
|
||||||
Sync(matrix::Event),
|
Sync(matrix::Event),
|
||||||
|
/// Update the sync token to use
|
||||||
|
SyncToken(String),
|
||||||
/// Set contents of message compose box
|
/// Set contents of message compose box
|
||||||
SetMessage(String),
|
SetMessage(String),
|
||||||
/// Send the contents of the compose box to the selected room
|
/// Send the contents of the compose box to the selected room
|
||||||
|
@ -645,124 +523,6 @@ pub enum Message {
|
||||||
ImportKeys,
|
ImportKeys,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default, Debug)]
|
|
||||||
pub struct SettingsView {
|
|
||||||
/// Display name to set
|
|
||||||
display_name: String,
|
|
||||||
/// Are we saving the display name?
|
|
||||||
saving_name: bool,
|
|
||||||
|
|
||||||
/// Display name text input
|
|
||||||
display_name_input: iced::text_input::State,
|
|
||||||
/// Button to set display name
|
|
||||||
display_name_button: iced::button::State,
|
|
||||||
|
|
||||||
/// Path to import encryption keys from
|
|
||||||
key_path: String,
|
|
||||||
/// Password to decrypt the keys with
|
|
||||||
key_password: String,
|
|
||||||
|
|
||||||
/// Encryption key path entry
|
|
||||||
key_path_input: iced::text_input::State,
|
|
||||||
/// Entry for key password
|
|
||||||
key_password_input: iced::text_input::State,
|
|
||||||
/// Button to import keys
|
|
||||||
key_import_button: iced::button::State,
|
|
||||||
/// Button to close settings view
|
|
||||||
close_button: iced::button::State,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SettingsView {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(&mut self, sort: RoomSorting) -> Element<Message> {
|
|
||||||
let content = Column::new()
|
|
||||||
.width(500.into())
|
|
||||||
.spacing(5)
|
|
||||||
.push(Text::new("Profile").size(25))
|
|
||||||
.push(
|
|
||||||
Column::new().push(Text::new("Display name")).push(
|
|
||||||
Row::new()
|
|
||||||
.push(
|
|
||||||
TextInput::new(
|
|
||||||
&mut self.display_name_input,
|
|
||||||
"Alice",
|
|
||||||
&self.display_name,
|
|
||||||
Message::SetDisplayNameInput,
|
|
||||||
)
|
|
||||||
.width(Length::Fill)
|
|
||||||
.padding(5),
|
|
||||||
)
|
|
||||||
.push(match self.saving_name {
|
|
||||||
false => Button::new(&mut self.display_name_button, Text::new("Save"))
|
|
||||||
.on_press(Message::SaveDisplayName),
|
|
||||||
true => {
|
|
||||||
Button::new(&mut self.display_name_button, Text::new("Saving..."))
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.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(
|
|
||||||
Column::new()
|
|
||||||
.push(Text::new("Import key (enter path)"))
|
|
||||||
.push(
|
|
||||||
TextInput::new(
|
|
||||||
&mut self.key_path_input,
|
|
||||||
"/home/user/exported_keys.txt",
|
|
||||||
&self.key_path,
|
|
||||||
Message::SetKeyPath,
|
|
||||||
)
|
|
||||||
.padding(5),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.push(
|
|
||||||
Column::new().push(Text::new("Key password")).push(
|
|
||||||
TextInput::new(
|
|
||||||
&mut self.key_password_input,
|
|
||||||
"SecretPassword42",
|
|
||||||
&self.key_password,
|
|
||||||
Message::SetKeyPassword,
|
|
||||||
)
|
|
||||||
.password()
|
|
||||||
.padding(5),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.push(
|
|
||||||
Button::new(&mut self.key_import_button, Text::new("Import keys"))
|
|
||||||
.on_press(Message::ImportKeys),
|
|
||||||
)
|
|
||||||
.push(
|
|
||||||
Row::new().width(Length::Fill).push(
|
|
||||||
Button::new(&mut self.close_button, Text::new("Close"))
|
|
||||||
.on_press(Message::CloseSettings),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
Container::new(content)
|
|
||||||
.center_x()
|
|
||||||
.center_y()
|
|
||||||
.width(Length::Fill)
|
|
||||||
.height(Length::Fill)
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Application for Retrix {
|
impl Application for Retrix {
|
||||||
type Message = Message;
|
type Message = Message;
|
||||||
type Executor = iced::executor::Default;
|
type Executor = iced::executor::Default;
|
||||||
|
@ -854,248 +614,251 @@ impl Application for Retrix {
|
||||||
}
|
}
|
||||||
Message::LoggedIn(client, session) => {
|
Message::LoggedIn(client, session) => {
|
||||||
*self = Retrix::LoggedIn(MainView::new(client.clone(), session));
|
*self = Retrix::LoggedIn(MainView::new(client.clone(), session));
|
||||||
return Command::perform(
|
let joined = client.joined_rooms();
|
||||||
async move {
|
let read = block_on(async { joined.read().await });
|
||||||
let mut rooms: BTreeMap<RoomId, RoomEntry> = BTreeMap::new();
|
let mut commands: Vec<Command<Message>> = Vec::new();
|
||||||
for (id, room) in client.joined_rooms().read().await.iter() {
|
for (id, room) in read.iter() {
|
||||||
let room = room.read().await;
|
let id = id.clone();
|
||||||
let entry = rooms.entry(id.clone()).or_default();
|
let room = room.clone();
|
||||||
|
let client = client.clone();
|
||||||
|
let command = async move {
|
||||||
|
let room = room.read().await;
|
||||||
|
let mut entry = RoomEntry::default();
|
||||||
|
|
||||||
entry.direct = room.direct_target.clone();
|
entry.direct = room.direct_target.clone();
|
||||||
// Display name calculation for DMs is bronk so we're doing it
|
// Display name calculation for DMs is bronk so we're doing it
|
||||||
// ourselves
|
// ourselves
|
||||||
match entry.direct {
|
match entry.direct {
|
||||||
Some(ref direct) => {
|
Some(ref direct) => {
|
||||||
let request = matrix_sdk::api::r0::profile::get_display_name::Request::new(direct);
|
let request = matrix_sdk::api::r0::profile::get_display_name::Request::new(direct);
|
||||||
if let Ok(response) = client.send(request).await {
|
if let Ok(response) = client.send(request).await {
|
||||||
if let Some(name) = response.displayname {
|
if let Some(name) = response.displayname {
|
||||||
entry.name = name;
|
entry.name = name;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => entry.name = room.display_name(),
|
|
||||||
}
|
}
|
||||||
let messages = room
|
None => entry.name = room.display_name(),
|
||||||
.messages
|
|
||||||
.iter()
|
|
||||||
.cloned()
|
|
||||||
.map(|event| match event {
|
|
||||||
AnyPossiblyRedactedSyncMessageEvent::Redacted(e) => {
|
|
||||||
AnyRoomEvent::RedactedMessage(
|
|
||||||
e.into_full_event(id.clone()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
AnyPossiblyRedactedSyncMessageEvent::Regular(e) => {
|
|
||||||
AnyRoomEvent::Message(e.into_full_event(id.clone()))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
entry.messages.messages = messages;
|
|
||||||
}
|
}
|
||||||
rooms
|
let messages = room
|
||||||
},
|
.messages
|
||||||
Message::ResetRooms,
|
.iter()
|
||||||
);
|
.cloned()
|
||||||
|
.map(|event| match event {
|
||||||
|
AnyPossiblyRedactedSyncMessageEvent::Redacted(e) => {
|
||||||
|
AnyRoomEvent::RedactedMessage(
|
||||||
|
e.into_full_event(id.clone()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
AnyPossiblyRedactedSyncMessageEvent::Regular(e) => {
|
||||||
|
AnyRoomEvent::Message(e.into_full_event(id.clone()))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
entry.messages.messages = messages;
|
||||||
|
Message::ResetRoom(id, entry)
|
||||||
|
}.into();
|
||||||
|
commands.push(command)
|
||||||
|
}
|
||||||
|
return Command::batch(commands);
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
},
|
},
|
||||||
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::ClearError => view.error = None,
|
Message::SetSort(s) => view.sorting = s,
|
||||||
Message::SetSort(s) => view.sorting = s,
|
Message::ResetRoom(id, room) => {
|
||||||
Message::ResetRooms(r) => view.rooms = r,
|
view.rooms.insert(id, room).and(Some(())).unwrap_or(())
|
||||||
Message::SelectRoom(r) => view.selected = Some(r),
|
|
||||||
Message::Sync(event) => match event {
|
|
||||||
matrix::Event::Room(event) => match event {
|
|
||||||
AnyRoomEvent::Message(event) => {
|
|
||||||
let room = view.rooms.entry(event.room_id().clone()).or_default();
|
|
||||||
room.messages.push_back(AnyRoomEvent::Message(event));
|
|
||||||
}
|
|
||||||
AnyRoomEvent::State(event) => match event {
|
|
||||||
AnyStateEvent::RoomCanonicalAlias(ref alias) => {
|
|
||||||
let room = view.rooms.entry(alias.room_id.clone()).or_default();
|
|
||||||
room.alias = alias.content.alias.clone();
|
|
||||||
room.messages.push_back(AnyRoomEvent::State(event));
|
|
||||||
}
|
|
||||||
AnyStateEvent::RoomName(ref name) => {
|
|
||||||
let room = view.rooms.entry(name.room_id.clone()).or_default();
|
|
||||||
room.display_name = name.content.name().map(String::from);
|
|
||||||
room.messages.push_back(AnyRoomEvent::State(event));
|
|
||||||
}
|
|
||||||
AnyStateEvent::RoomTopic(ref topic) => {
|
|
||||||
let room = view.rooms.entry(topic.room_id.clone()).or_default();
|
|
||||||
room.topic = topic.content.topic.clone();
|
|
||||||
room.messages.push_back(AnyRoomEvent::State(event));
|
|
||||||
}
|
|
||||||
ref any => {
|
|
||||||
// Ensure room exists
|
|
||||||
let room = view.rooms.entry(any.room_id().clone()).or_default();
|
|
||||||
room.messages.push_back(AnyRoomEvent::State(event));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => (),
|
|
||||||
},
|
|
||||||
matrix::Event::ToDevice(event) => match event {
|
|
||||||
AnyToDeviceEvent::KeyVerificationStart(start) => {
|
|
||||||
let client = view.client.clone();
|
|
||||||
return Command::perform(
|
|
||||||
async move {
|
|
||||||
tokio::time::delay_for(std::time::Duration::from_secs(2))
|
|
||||||
.await;
|
|
||||||
client.get_verification(&start.content.transaction_id).await
|
|
||||||
},
|
|
||||||
Message::SetVerification,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
AnyToDeviceEvent::KeyVerificationCancel(cancel) => {
|
|
||||||
return async {
|
|
||||||
Message::VerificationCancelled(cancel.content.code)
|
|
||||||
}
|
|
||||||
.into();
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Message::SetVerification(v) => view.sas = v,
|
|
||||||
Message::VerificationAccept => {
|
|
||||||
let sas = match &view.sas {
|
|
||||||
Some(sas) => sas.clone(),
|
|
||||||
None => return Command::none(),
|
|
||||||
};
|
|
||||||
return Command::perform(async move { sas.accept().await }, |result| {
|
|
||||||
match result {
|
|
||||||
Ok(()) => Message::VerificationAccepted,
|
|
||||||
Err(e) => Message::ErrorMessage(e.to_string()),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Message::VerificationConfirm => {
|
|
||||||
let sas = match &view.sas {
|
|
||||||
Some(sas) => sas.clone(),
|
|
||||||
None => return Command::none(),
|
|
||||||
};
|
|
||||||
return Command::perform(async move { sas.confirm().await }, |result| {
|
|
||||||
match result {
|
|
||||||
Ok(()) => Message::VerificationConfirmed,
|
|
||||||
Err(e) => Message::ErrorMessage(e.to_string()),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Message::VerificationCancel => {
|
|
||||||
let sas = match &view.sas {
|
|
||||||
Some(sas) => sas.clone(),
|
|
||||||
None => return Command::none(),
|
|
||||||
};
|
|
||||||
return Command::perform(async move { sas.cancel().await }, |result| {
|
|
||||||
match result {
|
|
||||||
Ok(()) => {
|
|
||||||
Message::VerificationCancelled(VerificationCancelCode::User)
|
|
||||||
}
|
|
||||||
Err(e) => Message::ErrorMessage(e.to_string()),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Message::VerificationCancelled(code) => {
|
|
||||||
view.sas = None;
|
|
||||||
return async move { Message::ErrorMessage(code.as_str().to_owned()) }
|
|
||||||
.into();
|
|
||||||
}
|
|
||||||
Message::VerificationClose => view.sas = None,
|
|
||||||
Message::SetMessage(m) => view.draft = m,
|
|
||||||
Message::SendMessage => {
|
|
||||||
let selected = match view.selected.clone() {
|
|
||||||
Some(selected) => selected,
|
|
||||||
None => return Command::none(),
|
|
||||||
};
|
|
||||||
let draft = view.draft.clone();
|
|
||||||
let client = view.client.clone();
|
|
||||||
return Command::perform(
|
|
||||||
async move {
|
|
||||||
client
|
|
||||||
.room_send(
|
|
||||||
&selected,
|
|
||||||
AnyMessageEventContent::RoomMessage(
|
|
||||||
MessageEventContent::text_plain(draft),
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
},
|
|
||||||
|result| match result {
|
|
||||||
Ok(_) => Message::SetMessage(String::new()),
|
|
||||||
Err(e) => Message::ErrorMessage(e.to_string()),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
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) => {
|
|
||||||
if let Some(ref mut settings) = view.settings_view {
|
|
||||||
settings.display_name = name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Message::SaveDisplayName => {
|
|
||||||
if let Some(ref mut settings) = view.settings_view {
|
|
||||||
let client = view.client.clone();
|
|
||||||
let name = settings.display_name.clone();
|
|
||||||
settings.saving_name = true;
|
|
||||||
return Command::perform(
|
|
||||||
async move { client.set_display_name(Some(&name)).await },
|
|
||||||
|result| match result {
|
|
||||||
Ok(()) => Message::DisplayNameSaved,
|
|
||||||
// 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) => {
|
|
||||||
if let Some(ref mut settings) = view.settings_view {
|
|
||||||
settings.key_path = p;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Message::SetKeyPassword(p) => {
|
|
||||||
if let Some(ref mut settings) = view.settings_view {
|
|
||||||
settings.key_password = p;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Message::ImportKeys => {
|
|
||||||
if let Some(ref settings) = view.settings_view {
|
|
||||||
let path = std::path::PathBuf::from(&settings.key_path);
|
|
||||||
let password = settings.key_password.clone();
|
|
||||||
let client = view.client.clone();
|
|
||||||
return Command::perform(
|
|
||||||
async move { client.import_keys(path, &password).await },
|
|
||||||
|result| match result {
|
|
||||||
Ok(_) => Message::SetKeyPassword(String::new()),
|
|
||||||
// TODO: Actual error reporting here
|
|
||||||
Err(e) => Message::SetKeyPath(e.to_string()),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Message::CloseSettings => view.settings_view = None,
|
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
}
|
Message::SelectRoom(r) => view.selected = Some(r),
|
||||||
|
Message::Sync(event) => match event {
|
||||||
|
matrix::Event::Room(event) => match event {
|
||||||
|
AnyRoomEvent::Message(event) => {
|
||||||
|
let room = view.rooms.entry(event.room_id().clone()).or_default();
|
||||||
|
room.messages.push(AnyRoomEvent::Message(event));
|
||||||
|
}
|
||||||
|
AnyRoomEvent::State(event) => match event {
|
||||||
|
AnyStateEvent::RoomCanonicalAlias(ref alias) => {
|
||||||
|
let room = view.rooms.entry(alias.room_id.clone()).or_default();
|
||||||
|
room.alias = alias.content.alias.clone();
|
||||||
|
room.messages.push(AnyRoomEvent::State(event));
|
||||||
|
}
|
||||||
|
AnyStateEvent::RoomName(ref name) => {
|
||||||
|
let room = view.rooms.entry(name.room_id.clone()).or_default();
|
||||||
|
room.display_name = name.content.name().map(String::from);
|
||||||
|
room.messages.push(AnyRoomEvent::State(event));
|
||||||
|
}
|
||||||
|
AnyStateEvent::RoomTopic(ref topic) => {
|
||||||
|
let room = view.rooms.entry(topic.room_id.clone()).or_default();
|
||||||
|
room.topic = topic.content.topic.clone();
|
||||||
|
room.messages.push(AnyRoomEvent::State(event));
|
||||||
|
}
|
||||||
|
ref any => {
|
||||||
|
// Ensure room exists
|
||||||
|
let room = view.rooms.entry(any.room_id().clone()).or_default();
|
||||||
|
room.messages.push(AnyRoomEvent::State(event));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
matrix::Event::ToDevice(event) => match event {
|
||||||
|
AnyToDeviceEvent::KeyVerificationStart(start) => {
|
||||||
|
let client = view.client.clone();
|
||||||
|
return Command::perform(
|
||||||
|
async move {
|
||||||
|
tokio::time::delay_for(std::time::Duration::from_secs(2)).await;
|
||||||
|
client.get_verification(&start.content.transaction_id).await
|
||||||
|
},
|
||||||
|
Message::SetVerification,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
AnyToDeviceEvent::KeyVerificationCancel(cancel) => {
|
||||||
|
return async { Message::VerificationCancelled(cancel.content.code) }
|
||||||
|
.into();
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
matrix::Event::Token(token) => {
|
||||||
|
view.sync_token = token;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Message::SetVerification(v) => view.sas = v,
|
||||||
|
Message::VerificationAccept => {
|
||||||
|
let sas = match &view.sas {
|
||||||
|
Some(sas) => sas.clone(),
|
||||||
|
None => return Command::none(),
|
||||||
|
};
|
||||||
|
return Command::perform(
|
||||||
|
async move { sas.accept().await },
|
||||||
|
|result| match result {
|
||||||
|
Ok(()) => Message::VerificationAccepted,
|
||||||
|
Err(e) => Message::ErrorMessage(e.to_string()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Message::VerificationConfirm => {
|
||||||
|
let sas = match &view.sas {
|
||||||
|
Some(sas) => sas.clone(),
|
||||||
|
None => return Command::none(),
|
||||||
|
};
|
||||||
|
return Command::perform(async move { sas.confirm().await }, |result| {
|
||||||
|
match result {
|
||||||
|
Ok(()) => Message::VerificationConfirmed,
|
||||||
|
Err(e) => Message::ErrorMessage(e.to_string()),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Message::VerificationCancel => {
|
||||||
|
let sas = match &view.sas {
|
||||||
|
Some(sas) => sas.clone(),
|
||||||
|
None => return Command::none(),
|
||||||
|
};
|
||||||
|
return Command::perform(
|
||||||
|
async move { sas.cancel().await },
|
||||||
|
|result| match result {
|
||||||
|
Ok(()) => Message::VerificationCancelled(VerificationCancelCode::User),
|
||||||
|
Err(e) => Message::ErrorMessage(e.to_string()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Message::VerificationCancelled(code) => {
|
||||||
|
view.sas = None;
|
||||||
|
return async move { Message::ErrorMessage(code.as_str().to_owned()) }.into();
|
||||||
|
}
|
||||||
|
Message::VerificationClose => view.sas = None,
|
||||||
|
Message::SetMessage(m) => view.draft = m,
|
||||||
|
Message::SendMessage => {
|
||||||
|
let selected = match view.selected.clone() {
|
||||||
|
Some(selected) => selected,
|
||||||
|
None => return Command::none(),
|
||||||
|
};
|
||||||
|
let draft = view.draft.clone();
|
||||||
|
let client = view.client.clone();
|
||||||
|
return Command::perform(
|
||||||
|
async move {
|
||||||
|
client
|
||||||
|
.room_send(
|
||||||
|
&selected,
|
||||||
|
AnyMessageEventContent::RoomMessage(
|
||||||
|
MessageEventContent::text_plain(draft),
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
},
|
||||||
|
|result| match result {
|
||||||
|
Ok(_) => Message::SetMessage(String::new()),
|
||||||
|
Err(e) => Message::ErrorMessage(e.to_string()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
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) => {
|
||||||
|
if let Some(ref mut settings) = view.settings_view {
|
||||||
|
settings.display_name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::SaveDisplayName => {
|
||||||
|
if let Some(ref mut settings) = view.settings_view {
|
||||||
|
let client = view.client.clone();
|
||||||
|
let name = settings.display_name.clone();
|
||||||
|
settings.saving_name = true;
|
||||||
|
return Command::perform(
|
||||||
|
async move { client.set_display_name(Some(&name)).await },
|
||||||
|
|result| match result {
|
||||||
|
Ok(()) => Message::DisplayNameSaved,
|
||||||
|
// 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) => {
|
||||||
|
if let Some(ref mut settings) = view.settings_view {
|
||||||
|
settings.key_path = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::SetKeyPassword(p) => {
|
||||||
|
if let Some(ref mut settings) = view.settings_view {
|
||||||
|
settings.key_password = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::ImportKeys => {
|
||||||
|
if let Some(ref settings) = view.settings_view {
|
||||||
|
let path = std::path::PathBuf::from(&settings.key_path);
|
||||||
|
let password = settings.key_password.clone();
|
||||||
|
let client = view.client.clone();
|
||||||
|
return Command::perform(
|
||||||
|
async move { client.import_keys(path, &password).await },
|
||||||
|
|result| match result {
|
||||||
|
Ok(_) => Message::SetKeyPassword(String::new()),
|
||||||
|
// TODO: Actual error reporting here
|
||||||
|
Err(e) => Message::SetKeyPath(e.to_string()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::CloseSettings => view.settings_view = None,
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,141 @@
|
||||||
|
//! Login prompt
|
||||||
|
|
||||||
|
use iced::{text_input, Button, Column, Container, Element, Radio, Row, Text, TextInput};
|
||||||
|
|
||||||
|
use crate::ui::Message;
|
||||||
|
|
||||||
|
/// View for the login prompt
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct PromptView {
|
||||||
|
/// Username input field
|
||||||
|
pub user_input: text_input::State,
|
||||||
|
/// Password input field
|
||||||
|
pub password_input: text_input::State,
|
||||||
|
/// Homeserver input field
|
||||||
|
pub server_input: text_input::State,
|
||||||
|
/// Device name input field
|
||||||
|
pub device_input: text_input::State,
|
||||||
|
/// Button to trigger login
|
||||||
|
pub login_button: iced::button::State,
|
||||||
|
|
||||||
|
/// Username
|
||||||
|
pub user: String,
|
||||||
|
/// Password
|
||||||
|
pub password: String,
|
||||||
|
/// Homeserver
|
||||||
|
pub server: String,
|
||||||
|
/// Device name to create login session under
|
||||||
|
pub device_name: String,
|
||||||
|
/// Whether to log in or sign up
|
||||||
|
pub action: PromptAction,
|
||||||
|
/// Error message
|
||||||
|
pub error: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PromptView {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view(&mut self) -> Element<Message> {
|
||||||
|
let mut content = Column::new()
|
||||||
|
.width(500.into())
|
||||||
|
.spacing(5)
|
||||||
|
.push(
|
||||||
|
Row::new()
|
||||||
|
.spacing(15)
|
||||||
|
.push(Radio::new(
|
||||||
|
PromptAction::Login,
|
||||||
|
"Login",
|
||||||
|
Some(self.action),
|
||||||
|
Message::SetAction,
|
||||||
|
))
|
||||||
|
.push(Radio::new(
|
||||||
|
PromptAction::Signup,
|
||||||
|
"Sign up",
|
||||||
|
Some(self.action),
|
||||||
|
Message::SetAction,
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.push(
|
||||||
|
Column::new().push(Text::new("Username")).push(
|
||||||
|
TextInput::new(
|
||||||
|
&mut self.user_input,
|
||||||
|
"Username",
|
||||||
|
&self.user,
|
||||||
|
Message::SetUser,
|
||||||
|
)
|
||||||
|
.padding(5),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.push(
|
||||||
|
Column::new().push(Text::new("Password")).push(
|
||||||
|
TextInput::new(
|
||||||
|
&mut self.password_input,
|
||||||
|
"Password",
|
||||||
|
&self.password,
|
||||||
|
Message::SetPassword,
|
||||||
|
)
|
||||||
|
.password()
|
||||||
|
.padding(5),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.push(
|
||||||
|
Column::new().push(Text::new("Homeserver")).push(
|
||||||
|
TextInput::new(
|
||||||
|
&mut self.server_input,
|
||||||
|
"https://homeserver.com",
|
||||||
|
&self.server,
|
||||||
|
Message::SetServer,
|
||||||
|
)
|
||||||
|
.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 {
|
||||||
|
PromptAction::Login => {
|
||||||
|
Button::new(&mut self.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([1.0, 0.5, 0.0]),
|
||||||
|
);
|
||||||
|
Button::new(&mut self.login_button, Text::new("Sign up")).on_press(Message::Signup)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
content = content.push(button);
|
||||||
|
if let Some(ref error) = self.error {
|
||||||
|
content = content.push(Text::new(error).color([1.0, 0.0, 0.0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
Container::new(content)
|
||||||
|
.center_x()
|
||||||
|
.center_y()
|
||||||
|
.width(iced::Length::Fill)
|
||||||
|
.height(iced::Length::Fill)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum PromptAction {
|
||||||
|
Login,
|
||||||
|
Signup,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PromptAction {
|
||||||
|
fn default() -> Self {
|
||||||
|
PromptAction::Login
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,124 @@
|
||||||
|
//! Settings view.
|
||||||
|
|
||||||
|
use iced::{Button, Column, Container, Element, Length, Radio, Row, Text, TextInput};
|
||||||
|
|
||||||
|
use super::{Message, RoomSorting};
|
||||||
|
|
||||||
|
/// Settings menu
|
||||||
|
#[derive(Clone, Default, Debug)]
|
||||||
|
pub struct SettingsView {
|
||||||
|
/// Display name to set
|
||||||
|
pub display_name: String,
|
||||||
|
/// Are we saving the display name?
|
||||||
|
pub saving_name: bool,
|
||||||
|
|
||||||
|
/// Display name text input
|
||||||
|
pub display_name_input: iced::text_input::State,
|
||||||
|
/// Button to set display name
|
||||||
|
pub display_name_button: iced::button::State,
|
||||||
|
|
||||||
|
/// Path to import encryption keys from
|
||||||
|
pub key_path: String,
|
||||||
|
/// Password to decrypt the keys with
|
||||||
|
pub key_password: String,
|
||||||
|
|
||||||
|
/// Encryption key path entry
|
||||||
|
pub key_path_input: iced::text_input::State,
|
||||||
|
/// Entry for key password
|
||||||
|
pub key_password_input: iced::text_input::State,
|
||||||
|
/// Button to import keys
|
||||||
|
pub key_import_button: iced::button::State,
|
||||||
|
/// Button to close settings view
|
||||||
|
pub close_button: iced::button::State,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SettingsView {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view(&mut self, sort: RoomSorting) -> Element<Message> {
|
||||||
|
let content = Column::new()
|
||||||
|
.width(500.into())
|
||||||
|
.spacing(5)
|
||||||
|
.push(Text::new("Profile").size(25))
|
||||||
|
.push(
|
||||||
|
Column::new().push(Text::new("Display name")).push(
|
||||||
|
Row::new()
|
||||||
|
.push(
|
||||||
|
TextInput::new(
|
||||||
|
&mut self.display_name_input,
|
||||||
|
"Alice",
|
||||||
|
&self.display_name,
|
||||||
|
Message::SetDisplayNameInput,
|
||||||
|
)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.padding(5),
|
||||||
|
)
|
||||||
|
.push(match self.saving_name {
|
||||||
|
false => Button::new(&mut self.display_name_button, Text::new("Save"))
|
||||||
|
.on_press(Message::SaveDisplayName),
|
||||||
|
true => {
|
||||||
|
Button::new(&mut self.display_name_button, Text::new("Saving..."))
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.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(
|
||||||
|
Column::new()
|
||||||
|
.push(Text::new("Import key (enter path)"))
|
||||||
|
.push(
|
||||||
|
TextInput::new(
|
||||||
|
&mut self.key_path_input,
|
||||||
|
"/home/user/exported_keys.txt",
|
||||||
|
&self.key_path,
|
||||||
|
Message::SetKeyPath,
|
||||||
|
)
|
||||||
|
.padding(5),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.push(
|
||||||
|
Column::new().push(Text::new("Key password")).push(
|
||||||
|
TextInput::new(
|
||||||
|
&mut self.key_password_input,
|
||||||
|
"SecretPassword42",
|
||||||
|
&self.key_password,
|
||||||
|
Message::SetKeyPassword,
|
||||||
|
)
|
||||||
|
.password()
|
||||||
|
.padding(5),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.push(
|
||||||
|
Button::new(&mut self.key_import_button, Text::new("Import keys"))
|
||||||
|
.on_press(Message::ImportKeys),
|
||||||
|
)
|
||||||
|
.push(
|
||||||
|
Row::new().width(Length::Fill).push(
|
||||||
|
Button::new(&mut self.close_button, Text::new("Close"))
|
||||||
|
.on_press(Message::CloseSettings),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
Container::new(content)
|
||||||
|
.center_x()
|
||||||
|
.center_y()
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue