Implement media attachment to posts

This commit is contained in:
bleakgrey 2018-05-09 17:20:40 +03:00
parent e6701f1086
commit 1c09eda7ff
6 changed files with 200 additions and 46 deletions

View File

@ -41,6 +41,7 @@ executable(
'src/Widgets/StatusWidget.vala', 'src/Widgets/StatusWidget.vala',
'src/Widgets/NotificationWidget.vala', 'src/Widgets/NotificationWidget.vala',
'src/Widgets/AttachmentWidget.vala', 'src/Widgets/AttachmentWidget.vala',
'src/Widgets/AttachmentBox.vala',
'src/Dialogs/PostDialog.vala', 'src/Dialogs/PostDialog.vala',
'src/Views/AbstractView.vala', 'src/Views/AbstractView.vala',
'src/Views/AddAccountView.vala', 'src/Views/AddAccountView.vala',

View File

@ -8,7 +8,10 @@ public class Tootle.PostDialog : Gtk.Dialog {
private Gtk.ScrolledWindow scroll; private Gtk.ScrolledWindow scroll;
private Gtk.Label counter; private Gtk.Label counter;
private Gtk.MenuButton visibility; private Gtk.MenuButton visibility;
private Gtk.Button attach;
private Gtk.Button cancel;
private Gtk.Button publish; private Gtk.Button publish;
private AttachmentBox attachments;
private StatusVisibility visibility_opt; private StatusVisibility visibility_opt;
protected int64? in_reply_to_id; protected int64? in_reply_to_id;
@ -25,12 +28,24 @@ public class Tootle.PostDialog : Gtk.Dialog {
var actions = get_action_area ().get_parent () as Gtk.Box; var actions = get_action_area ().get_parent () as Gtk.Box;
var content = get_content_area (); var content = get_content_area ();
get_action_area ().hexpand = false;
visibility = get_visibility_btn (); visibility = get_visibility_btn ();
var close = add_button(_("Cancel"), 5) as Gtk.Button; visibility.tooltip_text = _("Post Visibility");
close.clicked.connect(() => { visibility.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT);
this.destroy (); visibility.can_default = false;
}); visibility.set_focus_on_click (false);
attach = new Gtk.Button.from_icon_name ("mail-attachment-symbolic");
attach.tooltip_text = _("Add Media");
attach.valign = Gtk.Align.CENTER;
attach.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT);
attach.get_style_context ().remove_class ("image-button");
attach.can_default = false;
attach.set_focus_on_click (false);
attach.clicked.connect (() => attachments.select ());
cancel = add_button(_("Cancel"), 5) as Gtk.Button;
cancel.clicked.connect(() => this.destroy ());
publish = add_button(_("Toot!"), 5) as Gtk.Button; publish = add_button(_("Toot!"), 5) as Gtk.Button;
publish.get_style_context ().add_class (Gtk.STYLE_CLASS_SUGGESTED_ACTION); publish.get_style_context ().add_class (Gtk.STYLE_CLASS_SUGGESTED_ACTION);
publish.clicked.connect (() => { publish.clicked.connect (() => {
@ -50,17 +65,23 @@ public class Tootle.PostDialog : Gtk.Dialog {
scroll.add (text); scroll.add (text);
scroll.show_all (); scroll.show_all ();
attachments = new AttachmentBox (true);
counter = new Gtk.Label ("500"); counter = new Gtk.Label ("500");
actions.pack_start (visibility, false, false, 6);
actions.pack_start (counter, false, false, 6); actions.pack_start (counter, false, false, 6);
content.pack_start (scroll, false, false, 0); actions.pack_end (visibility, false, false, 0);
actions.pack_end (attach, false, false, 6);
content.pack_start (scroll, false, false, 6);
content.pack_start (attachments, false, false, 6);
content.set_size_request (350, 150); content.set_size_request (350, 150);
show_all ();
attachments.hide ();
} }
private Gtk.MenuButton get_visibility_btn (){ private Gtk.MenuButton get_visibility_btn () {
var button = new Gtk.MenuButton (); var button = new Gtk.MenuButton ();
var icon = new Gtk.Image.from_icon_name (visibility_opt.get_icon (), Gtk.IconSize.SMALL_TOOLBAR); var icon = new Gtk.Image.from_icon_name (visibility_opt.get_icon (), Gtk.IconSize.BUTTON);
var menu = new Gtk.Popover (null); var menu = new Gtk.Popover (null);
var box = new Gtk.Box (Gtk.Orientation.VERTICAL, 6); var box = new Gtk.Box (Gtk.Orientation.VERTICAL, 6);
box.margin = 6; box.margin = 6;
@ -95,7 +116,7 @@ public class Tootle.PostDialog : Gtk.Dialog {
return button; return button;
} }
private void update_counter (){ private void update_counter () {
var len = text.buffer.text.length; var len = text.buffer.text.length;
var remain = 500 - len; var remain = 500 - len;
publish.sensitive = (remain >= 0); publish.sensitive = (remain >= 0);
@ -103,13 +124,12 @@ public class Tootle.PostDialog : Gtk.Dialog {
counter.label = remain.to_string (); counter.label = remain.to_string ();
} }
public static void open (Gtk.Window? parent, string? text = null){ public static void open (Gtk.Window? parent, string? text = null) {
if(dialog == null){ if(dialog == null){
dialog = new PostDialog (parent); dialog = new PostDialog (parent);
dialog.destroy.connect (() => { dialog.destroy.connect (() => {
dialog = null; dialog = null;
}); });
dialog.show_all ();
if (text != null) if (text != null)
dialog.text.buffer.text = text; dialog.text.buffer.text = text;
} }
@ -117,7 +137,7 @@ public class Tootle.PostDialog : Gtk.Dialog {
dialog.text.buffer.text += " " + text; dialog.text.buffer.text += " " + text;
} }
public static void open_reply (Gtk.Window? parent, Status status){ public static void open_reply (Gtk.Window? parent, Status status) {
if(dialog == null){ if(dialog == null){
open (parent); open (parent);
dialog.in_reply_to_id = status.id; dialog.in_reply_to_id = status.id;
@ -125,16 +145,18 @@ public class Tootle.PostDialog : Gtk.Dialog {
} }
} }
public void publish_post(){ public void publish_post () {
var text_escaped = text.buffer.text.replace (" ", "%20"); var pars = "?status=%s".printf (Soup.URI.encode (text.buffer.text, null));
var pars = "?status=" + text_escaped; pars += "&visibility=%s".printf (visibility_opt.to_string ());
pars += attachments.get_uri_array ();
if (in_reply_to_id != null) if (in_reply_to_id != null)
pars += "&in_reply_to_id=" + in_reply_to_id.to_string (); pars += "&in_reply_to_id=%s".printf (in_reply_to_id.to_string ());
pars += "&visibility=" + visibility_opt.to_string ();
var msg = new Soup.Message("POST", Tootle.settings.instance_url + "/api/v1/statuses" + pars); var url = "%s/api/v1/statuses%s".printf (Tootle.settings.instance_url, pars);
var msg = new Soup.Message("POST", url);
debug (url);
Tootle.network.queue(msg, (sess, mess) => { Tootle.network.queue(msg, (sess, mess) => {
try{ try {
var root = Tootle.network.parse (mess); var root = Tootle.network.parse (mess);
var status = Status.parse (root); var status = Status.parse (root);
debug (status.id.to_string ()); //TODO: Live updates debug (status.id.to_string ()); //TODO: Live updates

View File

@ -30,7 +30,7 @@ public class Tootle.NetManager : GLib.Object {
finished (); finished ();
}); });
// Soup.Logger logger = new Soup.Logger (Soup.LoggerLogLevel.MINIMAL, -1); // Soup.Logger logger = new Soup.Logger (Soup.LoggerLogLevel.BODY, -1);
// session.add_feature (logger); // session.add_feature (logger);
} }
@ -115,4 +115,14 @@ public class Tootle.NetManager : GLib.Object {
Tootle.network.queue (msg); Tootle.network.queue (msg);
} }
public void load_scaled_image (string url, Gtk.Image image, int size = 64) {
var msg = new Soup.Message("GET", url);
msg.finished.connect(() => {
var stream = new MemoryInputStream.from_data(msg.response_body.data, GLib.g_free);
var pixbuf = new Gdk.Pixbuf.from_stream_at_scale (stream, size, size, true);
image.set_from_pixbuf (pixbuf);
});
Tootle.network.queue (msg);
}
} }

View File

@ -0,0 +1,70 @@
using Gtk;
using GLib;
public class Tootle.AttachmentBox : Gtk.ScrolledWindow {
private Gtk.Box box;
private bool edit_mode;
private int64[] ids;
construct {
box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6);
box.hexpand = true;
add (box);
show_all ();
}
public AttachmentBox (bool edit = false) {
Object ();
edit_mode = edit;
vscrollbar_policy = Gtk.PolicyType.NEVER;
}
public void clear () {
box.forall (widget => widget.destroy ());
}
public void append (Attachment attachment) {
var widget = new AttachmentWidget (attachment);
box.add (widget);
}
public void select () {
var filter = new Gtk.FileFilter ();
filter.add_mime_type ("image/jpeg");
filter.add_mime_type ("image/png");
filter.add_mime_type ("image/gif");
filter.add_mime_type ("video/webm");
filter.add_mime_type ("video/mp4");
var chooser = new Gtk.FileChooserDialog (
_("Select media files to add"),
null,
Gtk.FileChooserAction.OPEN,
_("_Cancel"),
Gtk.ResponseType.CANCEL,
_("_Open"),
Gtk.ResponseType.ACCEPT);
chooser.select_multiple = true;
chooser.set_filter (filter);
if (chooser.run () == Gtk.ResponseType.ACCEPT) {
show ();
foreach (unowned string uri in chooser.get_uris ()) {
var widget = new AttachmentWidget.upload (uri);
widget.uploaded.connect (id => ids += id);
box.pack_start (widget, false, false, 6);
}
}
chooser.close ();
}
public string get_uri_array () {
var str = "";
foreach (int64 item in ids)
str += "&media_ids[]=" + item.to_string ();
return str;
}
}

View File

@ -2,43 +2,99 @@ using Gtk;
public class Tootle.AttachmentWidget : Gtk.EventBox { public class Tootle.AttachmentWidget : Gtk.EventBox {
Attachment attachment; public abstract signal void uploaded (int64 id);
public abstract signal void removed (int64 id);
Attachment? attachment;
private bool editable = false;
public Gtk.Label label;
Gtk.Grid grid; Gtk.Grid grid;
Gtk.Label? label;
Gtk.Image? image; Gtk.Image? image;
construct { construct {
margin_top = 6; set_size_request (64, 64);
grid = new Gtk.Grid (); grid = new Gtk.Grid ();
get_style_context ().add_class ("attachment"); get_style_context ().add_class ("attachment");
label = new Gtk.Label ("");
label.wrap = true;
label.vexpand = true;
label.margin_start = label.margin_end = 8;
grid.attach (label, 0, 0);
add (grid); add (grid);
grid.show ();
label.hide ();
} }
public AttachmentWidget (Attachment att) { public AttachmentWidget (Attachment att) {
attachment = att; attachment = att;
var type = attachment.type; rebind ();
}
public void rebind () {
var type = attachment.type;
switch (type){ switch (type){
case "image": case "image":
image = new Gtk.Image (); image = new Gtk.Image ();
image.vexpand = true; image.vexpand = true;
image.margin = 3; image.margin = 3;
image.valign = Gtk.Align.CENTER; image.valign = Gtk.Align.CENTER;
image.show ();
if (editable)
Tootle.network.load_scaled_image (attachment.preview_url, image);
else
Tootle.network.load_image (attachment.preview_url, image); Tootle.network.load_image (attachment.preview_url, image);
grid.attach (image, 0, 0); grid.attach (image, 0, 0);
label.hide ();
break; break;
default: default:
label = new Gtk.Label (_("Click to open %s media").printf (type)); label.label = _("Click to open %s media").printf (type);
label.margin = 16; label.show ();
grid.attach (label, 0, 0);
break; break;
} }
show ();
button_press_event.connect(() => { button_press_event.connect(() => {
if (!editable)
Tootle.Utils.open_url (attachment.url); Tootle.Utils.open_url (attachment.url);
return true; return true;
}); });
show_all (); }
public AttachmentWidget.upload (string uri) {
try {
GLib.File file = File.new_for_uri (uri);
uint8[] contents;
file.load_contents (null, out contents, null);
var type = file.query_info (GLib.FileAttribute.STANDARD_CONTENT_TYPE, 0);
var mime = type.get_content_type ();
debug ("Uploading %s (%s)", uri, mime);
label.label = _("Uploading file...");
label.show ();
show ();
var buffer = new Soup.Buffer.take (contents);
var multipart = new Soup.Multipart (Soup.FORM_MIME_TYPE_MULTIPART);
multipart.append_form_file ("file", mime.replace ("/", "."), mime, buffer);
var url = "%s/api/v1/media".printf (Tootle.settings.instance_url);
var msg = Soup.Form.request_new_from_multipart (url, multipart);
Tootle.network.queue(msg, (sess, mess) => {
var root = Tootle.network.parse (mess);
attachment = Attachment.parse (root);
editable = true;
rebind ();
debug ("Uploaded media: %lld", attachment.id);
uploaded (attachment.id);
});
}
catch (Error e) {
error (e.message);
Tootle.app.error (_("File read error"), _("Can't read file %s: %s").printf (uri, e.message));
}
} }
} }

View File

@ -16,8 +16,7 @@ public class Tootle.StatusWidget : Gtk.EventBox {
public Tootle.RichLabel content_label; public Tootle.RichLabel content_label;
public Tootle.RichLabel? content_spoiler; public Tootle.RichLabel? content_spoiler;
Gtk.Box title_box; Gtk.Box title_box;
Gtk.Box attachments; AttachmentBox attachments;
Gtk.ScrolledWindow attachments_scroll;
Gtk.Grid grid; Gtk.Grid grid;
Gtk.Box counters; Gtk.Box counters;
Gtk.Label reblogs; Gtk.Label reblogs;
@ -59,6 +58,8 @@ public class Tootle.StatusWidget : Gtk.EventBox {
content_label = new RichLabel (""); content_label = new RichLabel ("");
content_label.wrap_words (); content_label.wrap_words ();
attachments = new AttachmentBox ();
reblogs = new Gtk.Label ("0"); reblogs = new Gtk.Label ("0");
favorites = new Gtk.Label ("0"); favorites = new Gtk.Label ("0");
@ -81,21 +82,15 @@ public class Tootle.StatusWidget : Gtk.EventBox {
PostDialog.open_reply (Tootle.window, this.status); PostDialog.open_reply (Tootle.window, this.status);
}); });
attachments = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6);
attachments.hexpand = true;
attachments_scroll = new ScrolledWindow (null, null);
attachments_scroll.vscrollbar_policy = Gtk.PolicyType.NEVER;
attachments_scroll.add (attachments);
var revealer_box = new Gtk.Box (Gtk.Orientation.VERTICAL, 6); var revealer_box = new Gtk.Box (Gtk.Orientation.VERTICAL, 6);
revealer_box.margin_end = 12; revealer_box.margin_end = 12;
revealer_box.add (content_label); revealer_box.add (content_label);
revealer_box.add (attachments_scroll); revealer_box.add (attachments);
revealer = new Revealer (); revealer = new Revealer ();
revealer.reveal_child = true; revealer.reveal_child = true;
revealer.add (revealer_box); revealer.add (revealer_box);
counters = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6); //TODO: currently useless counters = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6);
counters.margin_top = 6; counters.margin_top = 6;
counters.margin_bottom = 6; counters.margin_bottom = 6;
counters.add (reblog); counters.add (reblog);
@ -111,8 +106,6 @@ public class Tootle.StatusWidget : Gtk.EventBox {
grid.attach (counters, 2, 5, 1, 1); grid.attach (counters, 2, 5, 1, 1);
add (grid); add (grid);
show_all (); show_all ();
attachments_scroll.hide ();
} }
public StatusWidget (Status status) { public StatusWidget (Status status) {
@ -171,10 +164,12 @@ public class Tootle.StatusWidget : Gtk.EventBox {
} }
if (status.attachments != null) { if (status.attachments != null) {
attachments_scroll.show (); attachments.clear ();
foreach (Attachment attachment in status.attachments) foreach (Attachment attachment in status.attachments)
attachments.add (new AttachmentWidget (attachment)); attachments.append (attachment);
} }
else
attachments.destroy ();
destroy.connect (() => { destroy.connect (() => {
if(separator != null) if(separator != null)