diff --git a/Cargo.toml b/Cargo.toml index de46ba31..b0fe90e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -215,4 +215,4 @@ collapsible_else_if = "allow" too_many_arguments = "allow" blocks_in_conditions = "allow" used_underscore_binding = "allow" -module_name_repetitions = "allow" \ No newline at end of file +module_name_repetitions = "allow" diff --git a/resources/icons/verification_no.svg b/resources/icons/verification_no.svg new file mode 100644 index 00000000..7059a301 --- /dev/null +++ b/resources/icons/verification_no.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/resources/icons/verification_unk.svg b/resources/icons/verification_unk.svg new file mode 100644 index 00000000..18b3e747 --- /dev/null +++ b/resources/icons/verification_unk.svg @@ -0,0 +1,18 @@ + + + + + + + + diff --git a/resources/icons/verification_yes.svg b/resources/icons/verification_yes.svg new file mode 100644 index 00000000..a5d02837 --- /dev/null +++ b/resources/icons/verification_yes.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/home/spaces_dock.rs b/src/home/spaces_dock.rs index 70e6964b..7b194863 100644 --- a/src/home/spaces_dock.rs +++ b/src/home/spaces_dock.rs @@ -1,4 +1,9 @@ use makepad_widgets::*; +use matrix_sdk::encryption::VerificationState; + +use crate::shared::adaptive_view::DisplayContext; +use crate::sliding_sync::get_client; +use crate::verification::VerificationStateAction; live_design! { import makepad_widgets::base::*; @@ -8,6 +13,7 @@ live_design! { import crate::shared::styles::*; import crate::shared::helpers::*; import crate::shared::adaptive_view::AdaptiveView; + import crate::shared::verification_badge::*; ICON_HOME = dep("crate://self/resources/icons/home.svg") ICON_SETTINGS = dep("crate://self/resources/icons/settings.svg") @@ -16,21 +22,26 @@ live_design! { height: Fill, width: Fill } - Profile = { + Profile = {{Profile}} { + flow: Overlay width: Fit, height: Fit align: { x: 0.5, y: 0.5 } text_view = { - width: 45., height: 45., + flow: Overlay + width: 60, height: 60, align: { x: 0.5, y: 0.5 } show_bg: true, - draw_bg: { instance background_color: (COLOR_AVATAR_BG_IDLE), fn pixel(self) -> vec4 { let sdf = Sdf2d::viewport(self.pos * self.rect_size); + let c = self.rect_size * 0.5; - sdf.circle(c.x, c.x, c.x) + + let r = self.rect_size * 0.38; + + sdf.circle(c.x, c.x, r.x); sdf.fill_keep(self.background_color); return sdf.result } @@ -46,6 +57,20 @@ live_design! { text: "U" } } + { + align: { x: 1.0, y: 0.0 } + + verification_icon = { + flow: Overlay + align:{ x: 0.5, y: 0.5 } + width: 31, height: 31 + + icon_yes = {} + icon_no = {} + icon_unk = {} + } + } + verification_notice = { } } Separator = { @@ -118,7 +143,7 @@ live_design! { {} {} - + {} {} @@ -137,16 +162,127 @@ live_design! { {} {} - + {} - + {} - + {} - + {} - + {} } } } +struct VerificationNoticeText { + yes: &'static str, + no: &'static str, + unk: &'static str, +} + +impl Default for VerificationNoticeText{ + fn default() -> Self { + Self { + yes: "This device is fully verified.", + no: "This device is unverified. To view your encrypted message history, please verify it from another client.", + unk: "Verification state is unknown.", + } + } +} + +#[derive(Live, Widget)] +pub struct Profile { + #[deref] + view: View, + #[rust(VerificationState::Unknown)] + verification_state: VerificationState, + #[rust] + verification_notice_text: VerificationNoticeText, +} + +impl Profile { + fn set_verification_icon_visibility(&self) { + let (yes_visible, no_visible, unk_visible) = match self.verification_state { + VerificationState::Unknown => (false, false, true), + VerificationState::Unverified => (false, true, false), + VerificationState::Verified => (true, false, false), + }; + + self.view(id!(icon_yes)).set_visible(yes_visible); + self.view(id!(icon_no)).set_visible(no_visible); + self.view(id!(icon_unk)).set_visible(unk_visible); + } +} + +impl Widget for Profile { + fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) { + if let Event::MouseMove(e) = event { + let mut verification_notice = self.tooltip(id!(verification_notice)); + + if self.view(id!(verification_icon)).area().rect(cx).contains(e.abs) { + let text = match self.verification_state { + VerificationState::Unknown => self.verification_notice_text.unk, + VerificationState::Unverified => self.verification_notice_text.no, + VerificationState::Verified => self.verification_notice_text.yes + }; + + //Determine if it's a desktop or mobile layout, + //then we set the relative position so that the tooltip looks like following the cursor. + if cx.get_global::().is_desktop() { + verification_notice.show_with_options(cx, DVec2 {x: 65., y: 23.}, text); + } + else { + verification_notice.apply_over(cx, live!{ + content: { + + // Via setting suitable align & padding, + // we can simulate a relative position to make the tootip follow widget `Profile (U)`, + // this is not a perfect solution. + // TODO: Find a way to follow widget `Profile (U)` more precisely. + align: { x: 0.43, y: 1. } + padding: { left: 30., bottom: 31. } + } + }); + verification_notice.show_with_options(cx, DVec2 {x: 0., y: 0.}, text); + } + } + //Hide it if cursor is not hovering. + else { + verification_notice.hide(cx); + } + } + + self.match_event(cx, event); + self.view.handle_event(cx, event, scope) + } + + fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep { + self.view.draw_walk(cx, scope, walk) + } +} + +impl MatchEvent for Profile { + fn handle_action(&mut self, cx: &mut Cx, action:&Action) { + if let Some(VerificationStateAction::Update(state)) = action.downcast_ref() { + if self.verification_state != *state { + self.verification_state = *state; + + self.set_verification_icon_visibility(); + self.redraw(cx); + } + } + } +} + +impl LiveHook for Profile { + fn after_new_from_doc(&mut self, cx:&mut Cx) { + if let Some(client) = get_client() { + let current_verification_state = client.encryption().verification_state().get(); + self.verification_state = current_verification_state; + + self.set_verification_icon_visibility(); + self.redraw(cx); + } + } +} diff --git a/src/shared/mod.rs b/src/shared/mod.rs index 478b47ca..cc896b0e 100644 --- a/src/shared/mod.rs +++ b/src/shared/mod.rs @@ -11,6 +11,7 @@ pub mod search_bar; pub mod styles; pub mod text_or_image; pub mod typing_animation; +pub mod verification_badge; pub fn live_design(cx: &mut Cx) { // Order matters here, as some widget definitions depend on others. @@ -25,4 +26,5 @@ pub fn live_design(cx: &mut Cx) { adaptive_view::live_design(cx); typing_animation::live_design(cx); jump_to_bottom_button::live_design(cx); + verification_badge::live_design(cx); } diff --git a/src/shared/styles.rs b/src/shared/styles.rs index 82097d60..06e64d72 100644 --- a/src/shared/styles.rs +++ b/src/shared/styles.rs @@ -81,6 +81,8 @@ live_design! { COLOR_AVATAR_BG = #52b2ac COLOR_AVATAR_BG_IDLE = #d8d8d8 + COLOR_TOOLTIP_BG = (COLOR_SECONDARY) + COLOR_UNREAD_MESSAGE_BADGE = (COLOR_AVATAR_BG) COLOR_TEXT_IDLE = #d8d8d8 diff --git a/src/shared/verification_badge.rs b/src/shared/verification_badge.rs new file mode 100644 index 00000000..e594cc90 --- /dev/null +++ b/src/shared/verification_badge.rs @@ -0,0 +1,93 @@ +use makepad_widgets::*; + +live_design! { + import makepad_widgets::base::*; + import makepad_widgets::theme_desktop_dark::*; + import makepad_draw::shader::std::*; + + import crate::shared::styles::*; + + VERIFICATION_YES = dep("crate://self/resources/icons/verification_yes.svg") + VERIFICATION_NO = dep("crate://self/resources/icons/verification_no.svg") + VERIFICATION_UNK = dep("crate://self/resources/icons/verification_unk.svg") + + VerificationIcon = { + icon_walk: { width: 23 } + } + IconYes = { + visible: false + width: 31, height: 31 + { + draw_icon: { + svg_file: (VERIFICATION_YES), + fn get_color(self) -> vec4 { + return #x00BF00; + } + } + } + } + IconNo = { + visible: false + width: 31, height: 31 + { + draw_icon: { + svg_file: (VERIFICATION_NO), + fn get_color(self) -> vec4 { + return #xBF0000; + } + } + } + } + IconUnk = { + visible: false + width: 31, height: 31 + { + draw_icon: { + svg_file: (VERIFICATION_UNK), + fn get_color(self) -> vec4 { + return #x333333; + } + } + } + } + + VerificationNotice = { + width: Fill, height: Fill, + flow: Overlay + + draw_bg: { + fn pixel(self) -> vec4 { + return vec4(0., 0., 0., 0.0) + } + } + + content: { + flow: Overlay + + //The 'Fill' allows it shows anywhere we want over the app screen, + //our goal is to set the global relative position to make it an illusion of following the cursor. + width: Fill, height: Fill + + { + width: Fit, height: Fit, + padding: 7, + + draw_bg: { + color: (COLOR_TOOLTIP_BG), + border_width: 1.0, + border_color: #000000, + radius: 2.5 + } + + tooltip_label =