Show incoming request notification and implement dismissal

This commit is contained in:
Antonio Scandurra 2022-05-11 15:13:37 +02:00
parent fe89de8b11
commit 97d3616ed9
17 changed files with 688 additions and 52 deletions

View File

@ -474,6 +474,21 @@
"notification": {
"margin": {
"top": 10
},
"background": "#26232a",
"corner_radius": 6,
"padding": 12,
"border": {
"color": "#19171c",
"width": 1
},
"shadow": {
"blur": 16,
"color": "#0000003d",
"offset": [
0,
2
]
}
},
"notifications": {
@ -481,8 +496,7 @@
"margin": {
"right": 10,
"bottom": 10
},
"background": "#ff0000"
}
}
},
"editor": {
@ -1659,5 +1673,48 @@
"padding": {
"left": 6
}
},
"incoming_request_notification": {
"header_avatar": {
"height": 12,
"width": 12,
"corner_radius": 6
},
"header_message": {
"family": "Zed Sans",
"color": "#e2dfe7",
"size": 12,
"margin": {
"left": 4
}
},
"header_height": 18,
"body_message": {
"family": "Zed Sans",
"color": "#8b8792",
"size": 12,
"margin": {
"top": 6,
"bottom": 6
}
},
"button": {
"family": "Zed Sans",
"color": "#e2dfe7",
"size": 12,
"background": "#19171c",
"padding": 4,
"corner_radius": 6,
"margin": {
"left": 6
}
},
"dismiss_button": {
"color": "#8b8792",
"icon_width": 8,
"icon_height": 8,
"button_width": 8,
"button_height": 8
}
}
}

View File

@ -474,6 +474,21 @@
"notification": {
"margin": {
"top": 10
},
"background": "#e2dfe7",
"corner_radius": 6,
"padding": 12,
"border": {
"color": "#efecf4",
"width": 1
},
"shadow": {
"blur": 16,
"color": "#0000001f",
"offset": [
0,
2
]
}
},
"notifications": {
@ -481,8 +496,7 @@
"margin": {
"right": 10,
"bottom": 10
},
"background": "#ff0000"
}
}
},
"editor": {
@ -1659,5 +1673,48 @@
"padding": {
"left": 6
}
},
"incoming_request_notification": {
"header_avatar": {
"height": 12,
"width": 12,
"corner_radius": 6
},
"header_message": {
"family": "Zed Sans",
"color": "#26232a",
"size": 12,
"margin": {
"left": 4
}
},
"header_height": 18,
"body_message": {
"family": "Zed Sans",
"color": "#585260",
"size": 12,
"margin": {
"top": 6,
"bottom": 6
}
},
"button": {
"family": "Zed Sans",
"color": "#26232a",
"size": 12,
"background": "#efecf4",
"padding": 4,
"corner_radius": 6,
"margin": {
"left": 6
}
},
"dismiss_button": {
"color": "#585260",
"icon_width": 8,
"icon_height": 8,
"button_width": 8,
"button_height": 8
}
}
}

View File

@ -474,6 +474,21 @@
"notification": {
"margin": {
"top": 10
},
"background": "#1c1c1c",
"corner_radius": 6,
"padding": 12,
"border": {
"color": "#070707",
"width": 1
},
"shadow": {
"blur": 16,
"color": "#00000052",
"offset": [
0,
2
]
}
},
"notifications": {
@ -481,8 +496,7 @@
"margin": {
"right": 10,
"bottom": 10
},
"background": "#ff0000"
}
}
},
"editor": {
@ -1659,5 +1673,48 @@
"padding": {
"left": 6
}
},
"incoming_request_notification": {
"header_avatar": {
"height": 12,
"width": 12,
"corner_radius": 6
},
"header_message": {
"family": "Zed Sans",
"color": "#f1f1f1",
"size": 12,
"margin": {
"left": 4
}
},
"header_height": 18,
"body_message": {
"family": "Zed Sans",
"color": "#9c9c9c",
"size": 12,
"margin": {
"top": 6,
"bottom": 6
}
},
"button": {
"family": "Zed Sans",
"color": "#f1f1f1",
"size": 12,
"background": "#0e0e0e80",
"padding": 4,
"corner_radius": 6,
"margin": {
"left": 6
}
},
"dismiss_button": {
"color": "#9c9c9c",
"icon_width": 8,
"icon_height": 8,
"button_width": 8,
"button_height": 8
}
}
}

View File

@ -474,6 +474,21 @@
"notification": {
"margin": {
"top": 10
},
"background": "#f8f8f8",
"corner_radius": 6,
"padding": 12,
"border": {
"color": "#d5d5d5",
"width": 1
},
"shadow": {
"blur": 16,
"color": "#0000001f",
"offset": [
0,
2
]
}
},
"notifications": {
@ -481,8 +496,7 @@
"margin": {
"right": 10,
"bottom": 10
},
"background": "#ff0000"
}
}
},
"editor": {
@ -1659,5 +1673,48 @@
"padding": {
"left": 6
}
},
"incoming_request_notification": {
"header_avatar": {
"height": 12,
"width": 12,
"corner_radius": 6
},
"header_message": {
"family": "Zed Sans",
"color": "#2b2b2b",
"size": 12,
"margin": {
"left": 4
}
},
"header_height": 18,
"body_message": {
"family": "Zed Sans",
"color": "#474747",
"size": 12,
"margin": {
"top": 6,
"bottom": 6
}
},
"button": {
"family": "Zed Sans",
"color": "#2b2b2b",
"size": 12,
"background": "#f1f1f1",
"padding": 4,
"corner_radius": 6,
"margin": {
"left": 6
}
},
"dismiss_button": {
"color": "#717171",
"icon_width": 8,
"icon_height": 8,
"button_width": 8,
"button_height": 8
}
}
}

View File

@ -474,6 +474,21 @@
"notification": {
"margin": {
"top": 10
},
"background": "#073642",
"corner_radius": 6,
"padding": 12,
"border": {
"color": "#002b36",
"width": 1
},
"shadow": {
"blur": 16,
"color": "#0000003d",
"offset": [
0,
2
]
}
},
"notifications": {
@ -481,8 +496,7 @@
"margin": {
"right": 10,
"bottom": 10
},
"background": "#ff0000"
}
}
},
"editor": {
@ -1659,5 +1673,48 @@
"padding": {
"left": 6
}
},
"incoming_request_notification": {
"header_avatar": {
"height": 12,
"width": 12,
"corner_radius": 6
},
"header_message": {
"family": "Zed Sans",
"color": "#eee8d5",
"size": 12,
"margin": {
"left": 4
}
},
"header_height": 18,
"body_message": {
"family": "Zed Sans",
"color": "#93a1a1",
"size": 12,
"margin": {
"top": 6,
"bottom": 6
}
},
"button": {
"family": "Zed Sans",
"color": "#eee8d5",
"size": 12,
"background": "#002b36",
"padding": 4,
"corner_radius": 6,
"margin": {
"left": 6
}
},
"dismiss_button": {
"color": "#93a1a1",
"icon_width": 8,
"icon_height": 8,
"button_width": 8,
"button_height": 8
}
}
}

View File

@ -474,6 +474,21 @@
"notification": {
"margin": {
"top": 10
},
"background": "#eee8d5",
"corner_radius": 6,
"padding": 12,
"border": {
"color": "#fdf6e3",
"width": 1
},
"shadow": {
"blur": 16,
"color": "#0000001f",
"offset": [
0,
2
]
}
},
"notifications": {
@ -481,8 +496,7 @@
"margin": {
"right": 10,
"bottom": 10
},
"background": "#ff0000"
}
}
},
"editor": {
@ -1659,5 +1673,48 @@
"padding": {
"left": 6
}
},
"incoming_request_notification": {
"header_avatar": {
"height": 12,
"width": 12,
"corner_radius": 6
},
"header_message": {
"family": "Zed Sans",
"color": "#073642",
"size": 12,
"margin": {
"left": 4
}
},
"header_height": 18,
"body_message": {
"family": "Zed Sans",
"color": "#586e75",
"size": 12,
"margin": {
"top": 6,
"bottom": 6
}
},
"button": {
"family": "Zed Sans",
"color": "#073642",
"size": 12,
"background": "#fdf6e3",
"padding": 4,
"corner_radius": 6,
"margin": {
"left": 6
}
},
"dismiss_button": {
"color": "#586e75",
"icon_width": 8,
"icon_height": 8,
"button_width": 8,
"button_height": 8
}
}
}

View File

@ -474,6 +474,21 @@
"notification": {
"margin": {
"top": 10
},
"background": "#293256",
"corner_radius": 6,
"padding": 12,
"border": {
"color": "#202746",
"width": 1
},
"shadow": {
"blur": 16,
"color": "#0000003d",
"offset": [
0,
2
]
}
},
"notifications": {
@ -481,8 +496,7 @@
"margin": {
"right": 10,
"bottom": 10
},
"background": "#ff0000"
}
}
},
"editor": {
@ -1659,5 +1673,48 @@
"padding": {
"left": 6
}
},
"incoming_request_notification": {
"header_avatar": {
"height": 12,
"width": 12,
"corner_radius": 6
},
"header_message": {
"family": "Zed Sans",
"color": "#dfe2f1",
"size": 12,
"margin": {
"left": 4
}
},
"header_height": 18,
"body_message": {
"family": "Zed Sans",
"color": "#979db4",
"size": 12,
"margin": {
"top": 6,
"bottom": 6
}
},
"button": {
"family": "Zed Sans",
"color": "#dfe2f1",
"size": 12,
"background": "#202746",
"padding": 4,
"corner_radius": 6,
"margin": {
"left": 6
}
},
"dismiss_button": {
"color": "#979db4",
"icon_width": 8,
"icon_height": 8,
"button_width": 8,
"button_height": 8
}
}
}

View File

@ -474,6 +474,21 @@
"notification": {
"margin": {
"top": 10
},
"background": "#dfe2f1",
"corner_radius": 6,
"padding": 12,
"border": {
"color": "#f5f7ff",
"width": 1
},
"shadow": {
"blur": 16,
"color": "#0000001f",
"offset": [
0,
2
]
}
},
"notifications": {
@ -481,8 +496,7 @@
"margin": {
"right": 10,
"bottom": 10
},
"background": "#ff0000"
}
}
},
"editor": {
@ -1659,5 +1673,48 @@
"padding": {
"left": 6
}
},
"incoming_request_notification": {
"header_avatar": {
"height": 12,
"width": 12,
"corner_radius": 6
},
"header_message": {
"family": "Zed Sans",
"color": "#293256",
"size": 12,
"margin": {
"left": 4
}
},
"header_height": 18,
"body_message": {
"family": "Zed Sans",
"color": "#5e6687",
"size": 12,
"margin": {
"top": 6,
"bottom": 6
}
},
"button": {
"family": "Zed Sans",
"color": "#293256",
"size": 12,
"background": "#f5f7ff",
"padding": 4,
"corner_radius": 6,
"margin": {
"left": 6
}
},
"dismiss_button": {
"color": "#5e6687",
"icon_width": 8,
"icon_height": 8,
"button_width": 8,
"button_height": 8
}
}
}

View File

@ -356,6 +356,24 @@ impl UserStore {
)
}
pub fn dismiss_contact_request(
&mut self,
requester_id: u64,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
let client = self.client.upgrade();
cx.spawn_weak(|_, _| async move {
client
.ok_or_else(|| anyhow!("can't upgrade client reference"))?
.request(proto::RespondToContactRequest {
requester_id,
response: proto::ContactRequestResponse::Dismiss as i32,
})
.await?;
Ok(())
})
}
fn perform_contact_request<T: RequestMessage>(
&mut self,
user_id: u64,

View File

@ -1023,35 +1023,42 @@ impl Server {
.await
.user_id_for_connection(request.sender_id)?;
let requester_id = UserId::from_proto(request.payload.requester_id);
let accept = request.payload.response == proto::ContactRequestResponse::Accept as i32;
self.app_state
.db
.respond_to_contact_request(responder_id, requester_id, accept)
.await?;
if request.payload.response == proto::ContactRequestResponse::Dismiss as i32 {
self.app_state
.db
.dismiss_contact_request(responder_id, requester_id)
.await?;
} else {
let accept = request.payload.response == proto::ContactRequestResponse::Accept as i32;
self.app_state
.db
.respond_to_contact_request(responder_id, requester_id, accept)
.await?;
let store = self.store().await;
// Update responder with new contact
let mut update = proto::UpdateContacts::default();
if accept {
update.contacts.push(store.contact_for_user(requester_id));
}
update
.remove_incoming_requests
.push(requester_id.to_proto());
for connection_id in store.connection_ids_for_user(responder_id) {
self.peer.send(connection_id, update.clone())?;
}
let store = self.store().await;
// Update responder with new contact
let mut update = proto::UpdateContacts::default();
if accept {
update.contacts.push(store.contact_for_user(requester_id));
}
update
.remove_incoming_requests
.push(requester_id.to_proto());
for connection_id in store.connection_ids_for_user(responder_id) {
self.peer.send(connection_id, update.clone())?;
}
// Update requester with new contact
let mut update = proto::UpdateContacts::default();
if accept {
update.contacts.push(store.contact_for_user(responder_id));
}
update
.remove_outgoing_requests
.push(responder_id.to_proto());
for connection_id in store.connection_ids_for_user(requester_id) {
self.peer.send(connection_id, update.clone())?;
// Update requester with new contact
let mut update = proto::UpdateContacts::default();
if accept {
update.contacts.push(store.contact_for_user(responder_id));
}
update
.remove_outgoing_requests
.push(responder_id.to_proto());
for connection_id in store.connection_ids_for_user(requester_id) {
self.peer.send(connection_id, update.clone())?;
}
}
response.send(proto::Ack {})?;

View File

@ -1,13 +1,26 @@
use client::{User, UserStore};
use gpui::{color::Color, elements::*, Entity, ModelHandle, View, ViewContext};
use gpui::{
elements::*, impl_internal_actions, platform::CursorStyle, Entity, ModelHandle,
MutableAppContext, RenderContext, View, ViewContext,
};
use settings::Settings;
use std::sync::Arc;
use workspace::Notification;
impl_internal_actions!(contact_notifications, [Dismiss]);
pub fn init(cx: &mut MutableAppContext) {
cx.add_action(IncomingRequestNotification::dismiss);
}
pub struct IncomingRequestNotification {
user: Arc<User>,
user_store: ModelHandle<UserStore>,
}
#[derive(Clone)]
struct Dismiss(u64);
pub enum Event {
Dismiss,
}
@ -21,12 +34,91 @@ impl View for IncomingRequestNotification {
"IncomingRequestNotification"
}
fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox {
Empty::new()
.constrained()
.with_height(200.)
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
enum Dismiss {}
enum Reject {}
enum Accept {}
let theme = cx.global::<Settings>().theme.clone();
let theme = &theme.incoming_request_notification;
let user_id = self.user.id;
Flex::column()
.with_child(
Flex::row()
.with_children(self.user.avatar.clone().map(|avatar| {
Image::new(avatar)
.with_style(theme.header_avatar)
.aligned()
.left()
.boxed()
}))
.with_child(
Label::new(
format!("{} added you", self.user.github_login),
theme.header_message.text.clone(),
)
.contained()
.with_style(theme.header_message.container)
.aligned()
.boxed(),
)
.with_child(
MouseEventHandler::new::<Dismiss, _, _>(
self.user.id as usize,
cx,
|_, _| {
Svg::new("icons/reject.svg")
.with_color(theme.dismiss_button.color)
.constrained()
.with_width(theme.dismiss_button.icon_width)
.aligned()
.contained()
.with_style(theme.dismiss_button.container)
.constrained()
.with_width(theme.dismiss_button.button_width)
.with_height(theme.dismiss_button.button_width)
.aligned()
.boxed()
},
)
.with_cursor_style(CursorStyle::PointingHand)
.on_click(move |_, cx| cx.dispatch_action(Dismiss(user_id)))
.flex_float()
.boxed(),
)
.constrained()
.with_height(theme.header_height)
.boxed(),
)
.with_child(
Label::new(
"They won't know if you decline.".to_string(),
theme.body_message.text.clone(),
)
.contained()
.with_style(theme.body_message.container)
.boxed(),
)
.with_child(
Flex::row()
.with_child(
Label::new("Decline".to_string(), theme.button.text.clone())
.contained()
.with_style(theme.button.container)
.boxed(),
)
.with_child(
Label::new("Accept".to_string(), theme.button.text.clone())
.contained()
.with_style(theme.button.container)
.boxed(),
)
.aligned()
.right()
.boxed(),
)
.contained()
.with_background_color(Color::red())
.boxed()
}
}
@ -55,4 +147,13 @@ impl IncomingRequestNotification {
Self { user, user_store }
}
fn dismiss(&mut self, _: &Dismiss, cx: &mut ViewContext<Self>) {
self.user_store.update(cx, |store, cx| {
store
.dismiss_contact_request(self.user.id, cx)
.detach_and_log_err(cx);
});
cx.emit(Event::Dismiss);
}
}

View File

@ -55,6 +55,7 @@ pub struct RespondToContactRequest {
pub fn init(cx: &mut MutableAppContext) {
contact_finder::init(cx);
contact_notifications::init(cx);
cx.add_action(ContactsPanel::request_contact);
cx.add_action(ContactsPanel::remove_contact);
cx.add_action(ContactsPanel::respond_to_contact_request);

View File

@ -566,6 +566,7 @@ enum ContactRequestResponse {
Accept = 0;
Reject = 1;
Block = 2;
Dismiss = 3;
}
message SendChannelMessage {

View File

@ -29,6 +29,7 @@ pub struct Theme {
pub search: Search,
pub project_diagnostics: ProjectDiagnostics,
pub breadcrumbs: ContainedText,
pub incoming_request_notification: IncomingRequestNotification,
}
#[derive(Deserialize, Default)]
@ -354,6 +355,16 @@ pub struct ProjectDiagnostics {
pub tab_summary_spacing: f32,
}
#[derive(Deserialize, Default)]
pub struct IncomingRequestNotification {
pub header_avatar: ImageStyle,
pub header_message: ContainedText,
pub header_height: f32,
pub body_message: ContainedText,
pub button: ContainedText,
pub dismiss_button: IconButton,
}
#[derive(Clone, Deserialize, Default)]
pub struct Editor {
pub text_color: Color,

View File

@ -10,6 +10,7 @@ import search from "./search";
import picker from "./picker";
import workspace from "./workspace";
import projectDiagnostics from "./projectDiagnostics";
import incomingRequestNotification from "./incomingRequestNotification";
export const panel = {
padding: { top: 12, left: 12, bottom: 12, right: 12 },
@ -32,6 +33,7 @@ export default function app(theme: Theme): Object {
padding: {
left: 6,
},
}
},
incomingRequestNotification: incomingRequestNotification(theme),
};
}

View File

@ -0,0 +1,35 @@
import Theme from "../themes/theme";
import { backgroundColor, iconColor, text } from "./components";
export default function incomingRequestNotification(theme: Theme): Object {
return {
headerAvatar: {
height: 12,
width: 12,
cornerRadius: 6,
},
headerMessage: {
...text(theme, "sans", "primary", { size: "xs" }),
margin: { left: 4 }
},
headerHeight: 18,
bodyMessage: {
...text(theme, "sans", "secondary", { size: "xs" }),
margin: { top: 6, bottom: 6 },
},
button: {
...text(theme, "sans", "primary", { size: "xs" }),
background: backgroundColor(theme, "on300"),
padding: 4,
cornerRadius: 6,
margin: { left: 6 },
},
dismissButton: {
color: iconColor(theme, "secondary"),
iconWidth: 8,
iconHeight: 8,
buttonWidth: 8,
buttonHeight: 8,
}
}
}

View File

@ -1,5 +1,5 @@
import Theme from "../themes/theme";
import { backgroundColor, border, iconColor, text } from "./components";
import { backgroundColor, border, iconColor, shadow, text } from "./components";
import statusBar from "./statusBar";
export default function workspace(theme: Theme) {
@ -148,11 +148,15 @@ export default function workspace(theme: Theme) {
},
notification: {
margin: { top: 10 },
background: backgroundColor(theme, 300),
cornerRadius: 6,
padding: 12,
border: border(theme, "primary"),
shadow: shadow(theme),
},
notifications: {
width: 256,
margin: { right: 10, bottom: 10 },
background: "#ff0000",
}
};
}