Commit b6f81380 authored by Daniel García Moreno's avatar Daniel García Moreno

stickers: Stickers popover and send

This patch adds a new button to the main window to show the stickers. A
popover is shown when you click in this new button with all the stickers
group.

If you've purchased the stickers you'll view all the stickers there and
you can click over one sticker to send it.

Stickers not purchased are shown as a preview thumbnail and description
and a button to add is there too but not working yet.
parent fe9cbe13
...@@ -5,6 +5,8 @@ use backend::types::BKResponse; ...@@ -5,6 +5,8 @@ use backend::types::BKResponse;
use backend::types::Backend; use backend::types::Backend;
use util::dw_media; use util::dw_media;
use util::download_file;
use util::cache_dir_path;
pub fn get_thumb_async(bk: &Backend, media: String, tx: Sender<String>) -> Result<(), Error> { pub fn get_thumb_async(bk: &Backend, media: String, tx: Sender<String>) -> Result<(), Error> {
let baseu = bk.get_base_url()?; let baseu = bk.get_base_url()?;
...@@ -41,3 +43,19 @@ pub fn get_media(bk: &Backend, media: String) -> Result<(), Error> { ...@@ -41,3 +43,19 @@ pub fn get_media(bk: &Backend, media: String) -> Result<(), Error> {
Ok(()) Ok(())
} }
pub fn get_file_async(url: String, tx: Sender<String>) -> Result<(), Error> {
let fname;
{
let name = url.split("/").last().unwrap_or_default();
fname = cache_dir_path("files", name)?.clone();
}
thread::spawn(move || {
match download_file(&url, fname, None) {
Ok(fname) => { tx.send(fname).unwrap(); }
Err(_) => { tx.send(String::from("")).unwrap(); }
};
});
Ok(())
}
...@@ -280,6 +280,10 @@ impl Backend { ...@@ -280,6 +280,10 @@ impl Backend {
let r = media::get_media(self, media); let r = media::get_media(self, media);
bkerror!(r, tx, BKResponse::CommandError); bkerror!(r, tx, BKResponse::CommandError);
} }
Ok(BKCommand::GetFileAsync(url, ctx)) => {
let r = media::get_file_async(url, ctx);
bkerror!(r, tx, BKResponse::CommandError);
}
// Directory module // Directory module
...@@ -308,6 +312,10 @@ impl Backend { ...@@ -308,6 +312,10 @@ impl Backend {
let r = stickers::list(self); let r = stickers::list(self);
bkerror!(r, tx, BKResponse::StickersError); bkerror!(r, tx, BKResponse::StickersError);
} }
Ok(BKCommand::SendSticker(room, sticker)) => {
let r = stickers::send(self, room, &sticker);
bkerror!(r, tx, BKResponse::StickersError);
}
// Internal commands // Internal commands
Ok(BKCommand::ShutDown) => { Ok(BKCommand::ShutDown) => {
......
extern crate serde_json; extern crate serde_json;
extern crate md5;
extern crate chrono;
use self::chrono::prelude::*;
use std::thread; use std::thread;
use util::json_q; use util::json_q;
...@@ -9,6 +14,7 @@ use error::Error; ...@@ -9,6 +14,7 @@ use error::Error;
use backend::types::Backend; use backend::types::Backend;
use backend::types::BKResponse; use backend::types::BKResponse;
use types::StickerGroup; use types::StickerGroup;
use types::Sticker;
use self::serde_json::Value as JsonValue; use self::serde_json::Value as JsonValue;
...@@ -78,3 +84,32 @@ pub fn get_sticker_widget_id(bk: &Backend) -> Result<String, Error> { ...@@ -78,3 +84,32 @@ pub fn get_sticker_widget_id(bk: &Backend) -> Result<String, Error> {
Err(err) => { Err(err) } Err(err) => { Err(err) }
} }
} }
pub fn send(bk: &Backend, roomid: String, sticker: &Sticker) -> Result<(), Error> {
let now = Local::now();
let msg = format!("{}{}{}", roomid, sticker.name, now.to_string());
let digest = md5::compute(msg.as_bytes());
let msgid = format!("{:x}", digest);
let url = bk.url(&format!("rooms/{}/send/m.sticker/{}", roomid, msgid), vec![])?;
let attrs = json!({
"body": sticker.body.clone(),
"url": sticker.url.clone(),
"info": {
"w": sticker.size.0,
"h": sticker.size.1,
"thumbnail_url": sticker.thumbnail.clone(),
},
});
let tx = bk.tx.clone();
query!("put", &url, &attrs,
move |_| {
tx.send(BKResponse::SendMsg).unwrap();
},
|err| { tx.send(BKResponse::SendMsgError(err)).unwrap(); }
);
Ok(())
}
...@@ -121,6 +121,9 @@ pub fn sync(bk: &Backend) -> Result<(), Error> { ...@@ -121,6 +121,9 @@ pub fn sync(bk: &Backend) -> Result<(), Error> {
"m.room.member" => { "m.room.member" => {
tx.send(BKResponse::RoomMemberEvent(ev)).unwrap(); tx.send(BKResponse::RoomMemberEvent(ev)).unwrap();
} }
"m.sticker" => {
// This event is managed in the room list
}
_ => { _ => {
println!("EVENT NOT MANAGED: {:?}", ev); println!("EVENT NOT MANAGED: {:?}", ev);
} }
......
...@@ -9,6 +9,7 @@ use types::Protocol; ...@@ -9,6 +9,7 @@ use types::Protocol;
use types::Room; use types::Room;
use types::Event; use types::Event;
use types::StickerGroup; use types::StickerGroup;
use types::Sticker;
use cache::CacheMap; use cache::CacheMap;
...@@ -31,6 +32,7 @@ pub enum BKCommand { ...@@ -31,6 +32,7 @@ pub enum BKCommand {
GetMessageContext(Message), GetMessageContext(Message),
GetRoomAvatar(String), GetRoomAvatar(String),
GetThumbAsync(String, Sender<String>), GetThumbAsync(String, Sender<String>),
GetFileAsync(String, Sender<String>),
GetAvatarAsync(Option<Member>, Sender<String>), GetAvatarAsync(Option<Member>, Sender<String>),
GetMedia(String), GetMedia(String),
GetUserInfoAsync(String, Sender<(String, String)>), GetUserInfoAsync(String, Sender<(String, String)>),
...@@ -56,6 +58,7 @@ pub enum BKCommand { ...@@ -56,6 +58,7 @@ pub enum BKCommand {
UserSearch(String), UserSearch(String),
Invite(String, String), Invite(String, String),
ListStickers, ListStickers,
SendSticker(String, Sticker),
} }
#[derive(Debug)] #[derive(Debug)]
......
...@@ -8,10 +8,10 @@ use self::serde_json::Value as JsonValue; ...@@ -8,10 +8,10 @@ use self::serde_json::Value as JsonValue;
pub struct Sticker { pub struct Sticker {
pub name: String, pub name: String,
pub description: String, pub description: String,
pub file: String,
pub body: String, pub body: String,
pub thumbnail: String, pub thumbnail: String,
pub url: String, pub url: String,
pub size: (i32, i32),
} }
#[derive(Debug)] #[derive(Debug)]
...@@ -39,13 +39,15 @@ impl StickerGroup { ...@@ -39,13 +39,15 @@ impl StickerGroup {
for img in d["images"].as_array().unwrap_or(&vec![]).iter() { for img in d["images"].as_array().unwrap_or(&vec![]).iter() {
let c = &img["content"]; let c = &img["content"];
let w = c["info"]["h"].as_i64().unwrap_or_default();
let h = c["info"]["h"].as_i64().unwrap_or_default();
stickers.push(Sticker { stickers.push(Sticker {
name: img["name"].as_str().unwrap_or_default().to_string(), name: img["name"].as_str().unwrap_or_default().to_string(),
description: img["description"].as_str().unwrap_or_default().to_string(), description: img["description"].as_str().unwrap_or_default().to_string(),
file: img["file"].as_str().unwrap_or_default().to_string(),
body: c["body"].as_str().unwrap_or_default().to_string(), body: c["body"].as_str().unwrap_or_default().to_string(),
url: c["url"].as_str().unwrap_or_default().to_string(), url: c["url"].as_str().unwrap_or_default().to_string(),
thumbnail: c["info"]["thumbnail_url"].as_str().unwrap_or_default().to_string(), thumbnail: c["info"]["thumbnail_url"].as_str().unwrap_or_default().to_string(),
size: (w as i32, h as i32),
}); });
} }
......
...@@ -493,6 +493,10 @@ pub fn dw_media(base: &Url, ...@@ -493,6 +493,10 @@ pub fn dw_media(base: &Url,
Some(d) => String::from(d), Some(d) => String::from(d),
}; };
download_file(url.as_str(), fname, dest)
}
pub fn download_file(url: &str, fname: String, dest: Option<&str>) -> Result<String, Error> {
let pathname = fname.clone(); let pathname = fname.clone();
let p = Path::new(&pathname); let p = Path::new(&pathname);
if p.is_file() { if p.is_file() {
...@@ -508,7 +512,7 @@ pub fn dw_media(base: &Url, ...@@ -508,7 +512,7 @@ pub fn dw_media(base: &Url,
} }
let mut file = File::create(&fname)?; let mut file = File::create(&fname)?;
let buffer = get_media(url.as_str())?; let buffer = get_media(url)?;
file.write_all(&buffer)?; file.write_all(&buffer)?;
Ok(fname) Ok(fname)
......
...@@ -9,8 +9,8 @@ fractal-gtk/res/ui/add_room_menu.ui ...@@ -9,8 +9,8 @@ fractal-gtk/res/ui/add_room_menu.ui
fractal-gtk/res/ui/autocomplete.ui fractal-gtk/res/ui/autocomplete.ui
fractal-gtk/res/ui/direct_chat.ui fractal-gtk/res/ui/direct_chat.ui
fractal-gtk/res/ui/filechooser.ui fractal-gtk/res/ui/filechooser.ui
fractal-gtk/res/ui/invite_user.ui
fractal-gtk/res/ui/invite.ui fractal-gtk/res/ui/invite.ui
fractal-gtk/res/ui/invite_user.ui
fractal-gtk/res/ui/join_room.ui fractal-gtk/res/ui/join_room.ui
fractal-gtk/res/ui/leave_room.ui fractal-gtk/res/ui/leave_room.ui
fractal-gtk/res/ui/main_window.ui fractal-gtk/res/ui/main_window.ui
...@@ -19,6 +19,8 @@ fractal-gtk/res/ui/members.ui ...@@ -19,6 +19,8 @@ fractal-gtk/res/ui/members.ui
fractal-gtk/res/ui/new_room.ui fractal-gtk/res/ui/new_room.ui
fractal-gtk/res/ui/room_config.ui fractal-gtk/res/ui/room_config.ui
fractal-gtk/res/ui/room_menu.ui fractal-gtk/res/ui/room_menu.ui
fractal-gtk/res/ui/sticker_group.ui
fractal-gtk/res/ui/stickers_popover.ui
fractal-gtk/res/ui/user_menu.ui fractal-gtk/res/ui/user_menu.ui
fractal-gtk/src/app/backend_loop.rs fractal-gtk/src/app/backend_loop.rs
fractal-gtk/src/app/mod.rs fractal-gtk/src/app/mod.rs
...@@ -31,6 +33,7 @@ fractal-gtk/src/appop/member.rs ...@@ -31,6 +33,7 @@ fractal-gtk/src/appop/member.rs
fractal-gtk/src/appop/message.rs fractal-gtk/src/appop/message.rs
fractal-gtk/src/appop/mod.rs fractal-gtk/src/appop/mod.rs
fractal-gtk/src/appop/room.rs fractal-gtk/src/appop/room.rs
fractal-gtk/src/appop/stickers.rs
fractal-gtk/src/appop/sync.rs fractal-gtk/src/appop/sync.rs
fractal-gtk/src/widgets/room.rs fractal-gtk/src/widgets/room.rs
fractal-gtk/src/widgets/roomlist.rs fractal-gtk/src/widgets/roomlist.rs
...@@ -22,5 +22,7 @@ ...@@ -22,5 +22,7 @@
<file preprocess="xml-stripblanks">ui/room_menu.ui</file> <file preprocess="xml-stripblanks">ui/room_menu.ui</file>
<file preprocess="xml-stripblanks">ui/user_menu.ui</file> <file preprocess="xml-stripblanks">ui/user_menu.ui</file>
<file preprocess="xml-stripblanks">ui/markdown_popover.ui</file> <file preprocess="xml-stripblanks">ui/markdown_popover.ui</file>
<file preprocess="xml-stripblanks">ui/stickers_popover.ui</file>
<file preprocess="xml-stripblanks">ui/sticker_group.ui</file>
</gresource> </gresource>
</gresources> </gresources>
...@@ -347,6 +347,25 @@ ...@@ -347,6 +347,25 @@
<property name="position">2</property> <property name="position">2</property>
</packing> </packing>
</child> </child>
<child>
<object class="GtkMenuButton" id="stickers_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<child>
<object class="GtkImage" id="stickers_img">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">face-wink-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
......
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkWindow" id="container">
<property name="can_focus">False</property>
<child>
<placeholder/>
</child>
<child>
<object class="GtkBox" id="widget">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="name">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">label</property>
<property name="xalign">0.5</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="content">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImage" id="thumb">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">6</property>
<property name="margin_right">6</property>
<property name="icon_name">image-loading-symbolic</property>
<property name="icon_size">6</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="desc">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">6</property>
<property name="margin_right">6</property>
<property name="label" translatable="yes">desc</property>
<property name="wrap">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="btn">
<property name="label" translatable="yes">Add</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="valign">center</property>
<property name="margin_left">6</property>
<property name="margin_right">6</property>
<property name="margin_top">6</property>
<property name="margin_bottom">6</property>
<style>
<class name="suggested-action"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</interface>
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkPopover" id="stickers_popover">
<property name="width_request">400</property>
<property name="height_request">400</property>
<property name="can_focus">False</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">6</property>
<property name="margin_right">6</property>
<property name="margin_top">6</property>
<property name="margin_bottom">6</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Stickers</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkStack" id="stickers_stack">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkSpinner">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="active">True</property>
</object>
<packing>
<property name="name">loading</property>
<property name="title" translatable="yes">loading</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkBox" id="stickers_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<placeholder/>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="name">view</property>
<property name="title" translatable="yes">view</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</interface>
...@@ -11,6 +11,7 @@ use glib; ...@@ -11,6 +11,7 @@ use glib;
use types::Message; use types::Message;
use types::Room; use types::Room;
use types::Member; use types::Member;
use types::Sticker;
#[derive(Debug)] #[derive(Debug)]
...@@ -21,6 +22,7 @@ pub enum InternalCommand { ...@@ -21,6 +22,7 @@ pub enum InternalCommand {
SelectRoom(Room), SelectRoom(Room),
LoadMoreNormal, LoadMoreNormal,
RemoveInv(String), RemoveInv(String),
SendSticker(Sticker),
ToInvite(Member), ToInvite(Member),
RmInvite(String), RmInvite(String),
...@@ -57,6 +59,9 @@ pub fn appop_loop(rx: Receiver<InternalCommand>) { ...@@ -57,6 +59,9 @@ pub fn appop_loop(rx: Receiver<InternalCommand>) {
Ok(InternalCommand::RemoveInv(rid)) => { Ok(InternalCommand::RemoveInv(rid)) => {
APPOP!(remove_inv, (rid)); APPOP!(remove_inv, (rid));
} }
Ok(InternalCommand::SendSticker(sticker)) => {
APPOP!(send_sticker, (sticker));
}
Err(_) => { Err(_) => {
break; break;
} }
......
...@@ -21,6 +21,7 @@ mod scroll; ...@@ -21,6 +21,7 @@ mod scroll;
mod search; mod search;
mod send; mod send;
mod spellcheck; mod spellcheck;
mod stickers;
use app::App; use app::App;
...@@ -73,6 +74,7 @@ impl App { ...@@ -73,6 +74,7 @@ impl App {
self.connect_send(); self.connect_send();
self.connect_attach(); self.connect_attach();
self.connect_markdown(); self.connect_markdown();
self.connect_stickers();
self.connect_autocomplete(); self.connect_autocomplete();
self.connect_spellcheck(); self.connect_spellcheck();
......
extern crate gtk;
use self::gtk::prelude::*;
use app::App;
impl App {
pub fn connect_stickers(&self) {
let popover_btn: gtk::MenuButton = self.ui.builder
.get_object("stickers_button")
.expect("Couldn't find stickers_button in ui file.");
let popover: gtk::Popover = self.ui.builder
.get_object("stickers_popover")
.expect("Couldn't find stickers_popover in ui file.");
popover_btn.set_popover(Some(&popover));
}
}
...@@ -15,6 +15,7 @@ use types::Member; ...@@ -15,6 +15,7 @@ use types::Member;
use types::Message; use types::Message;
use types::Room; use types::Room;
use types::RoomList; use types::RoomList;
use types::StickerGroup;
use passwd::PasswordStorage; use passwd::PasswordStorage;
...@@ -88,6 +89,8 @@ pub struct AppOp { ...@@ -88,6 +89,8 @@ pub struct AppOp {
pub md_enabled: bool, pub md_enabled: bool,
invite_list: Vec<Member>, invite_list: Vec<Member>,
search_type: SearchType, search_type: SearchType,
pub stickers: Vec<StickerGroup>,
} }
impl PasswordStorage for AppOp {} impl PasswordStorage for AppOp {}
...@@ -134,6 +137,7 @@ impl AppOp { ...@@ -134,6 +137,7 @@ impl AppOp {
invitation_roomid: None, invitation_roomid: None,
invite_list: vec![], invite_list: vec![],
search_type: SearchType::Invite, search_type: SearchType::Invite,
stickers: vec![],
} }
} }
......
...@@ -231,6 +231,9 @@ impl AppOp { ...@@ -231,6 +231,9 @@ impl AppOp {
if getmessages { if getmessages {
self.backend.send(BKCommand::GetRoomMessages(self.active_room.clone().unwrap_or_default())).unwrap(); self.backend.send(BKCommand::GetRoomMessages(self.active_room.clone().unwrap_or_default())).unwrap();
} }
// redrawing the stickers
self.stickers_draw();
} }
pub fn really_leave_active_room(&mut self) { pub fn really_leave_active_room(&mut self) {
......
use appop::AppOp; extern crate glib;
extern crate gtk;
extern crate gdk_pixbuf;
extern crate chrono;
use self::chrono::prelude::*;
use self::gtk::ContainerExt;
use self::gtk::StackExt;
use self::gtk::WidgetExt;
use self::gtk::LabelExt;
use self::gtk::BoxExt;
use self::gdk_pixbuf::Pixbuf;
use self::gtk::ImageExt;
use self::gdk_pixbuf::PixbufExt;
use std::sync::mpsc::channel;
use std::sync::mpsc::{Sender, Receiver};
use std::sync::mpsc::TryRecvError;
use std::cmp::Ordering;
use app::InternalCommand;
use appop::AppOp;
use backend::BKCommand;
use types::StickerGroup; use types::StickerGroup;
use types::Sticker; use types::Sticker;
use types::Message;
use util::load_thumb;
impl AppOp { impl AppOp {
pub fn stickers_loaded(&self, stickers: Vec<StickerGroup>) { pub fn stickers_loaded(&mut self, stickers: Vec<StickerGroup>) {
for sg in stickers { self.stickers = stickers;
println!("STICKER GROUP: {}, {}", sg.name, sg.purchased); self.stickers.sort_by(|x, y| {
if x.purchased == y.purchased {
return x.name.cmp(&y.name);