Implement media attachment to posts
This commit is contained in:
parent
e6701f1086
commit
1c09eda7ff
|
@ -41,6 +41,7 @@ executable(
|
|||
'src/Widgets/StatusWidget.vala',
|
||||
'src/Widgets/NotificationWidget.vala',
|
||||
'src/Widgets/AttachmentWidget.vala',
|
||||
'src/Widgets/AttachmentBox.vala',
|
||||
'src/Dialogs/PostDialog.vala',
|
||||
'src/Views/AbstractView.vala',
|
||||
'src/Views/AddAccountView.vala',
|
||||
|
|
|
@ -8,7 +8,10 @@ public class Tootle.PostDialog : Gtk.Dialog {
|
|||
private Gtk.ScrolledWindow scroll;
|
||||
private Gtk.Label counter;
|
||||
private Gtk.MenuButton visibility;
|
||||
private Gtk.Button attach;
|
||||
private Gtk.Button cancel;
|
||||
private Gtk.Button publish;
|
||||
private AttachmentBox attachments;
|
||||
|
||||
private StatusVisibility visibility_opt;
|
||||
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 content = get_content_area ();
|
||||
get_action_area ().hexpand = false;
|
||||
|
||||
visibility = get_visibility_btn ();
|
||||
var close = add_button(_("Cancel"), 5) as Gtk.Button;
|
||||
close.clicked.connect(() => {
|
||||
this.destroy ();
|
||||
});
|
||||
visibility.tooltip_text = _("Post Visibility");
|
||||
visibility.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT);
|
||||
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.get_style_context ().add_class (Gtk.STYLE_CLASS_SUGGESTED_ACTION);
|
||||
publish.clicked.connect (() => {
|
||||
|
@ -50,17 +65,23 @@ public class Tootle.PostDialog : Gtk.Dialog {
|
|||
scroll.add (text);
|
||||
scroll.show_all ();
|
||||
|
||||
attachments = new AttachmentBox (true);
|
||||
counter = new Gtk.Label ("500");
|
||||
|
||||
actions.pack_start (visibility, 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);
|
||||
|
||||
show_all ();
|
||||
attachments.hide ();
|
||||
}
|
||||
|
||||
private Gtk.MenuButton get_visibility_btn (){
|
||||
private Gtk.MenuButton get_visibility_btn () {
|
||||
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 box = new Gtk.Box (Gtk.Orientation.VERTICAL, 6);
|
||||
box.margin = 6;
|
||||
|
@ -95,7 +116,7 @@ public class Tootle.PostDialog : Gtk.Dialog {
|
|||
return button;
|
||||
}
|
||||
|
||||
private void update_counter (){
|
||||
private void update_counter () {
|
||||
var len = text.buffer.text.length;
|
||||
var remain = 500 - len;
|
||||
publish.sensitive = (remain >= 0);
|
||||
|
@ -103,13 +124,12 @@ public class Tootle.PostDialog : Gtk.Dialog {
|
|||
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){
|
||||
dialog = new PostDialog (parent);
|
||||
dialog.destroy.connect (() => {
|
||||
dialog = null;
|
||||
});
|
||||
dialog.show_all ();
|
||||
if (text != null)
|
||||
dialog.text.buffer.text = text;
|
||||
}
|
||||
|
@ -117,7 +137,7 @@ public class Tootle.PostDialog : Gtk.Dialog {
|
|||
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){
|
||||
open (parent);
|
||||
dialog.in_reply_to_id = status.id;
|
||||
|
@ -125,16 +145,18 @@ public class Tootle.PostDialog : Gtk.Dialog {
|
|||
}
|
||||
}
|
||||
|
||||
public void publish_post(){
|
||||
var text_escaped = text.buffer.text.replace (" ", "%20");
|
||||
var pars = "?status=" + text_escaped;
|
||||
public void publish_post () {
|
||||
var pars = "?status=%s".printf (Soup.URI.encode (text.buffer.text, null));
|
||||
pars += "&visibility=%s".printf (visibility_opt.to_string ());
|
||||
pars += attachments.get_uri_array ();
|
||||
if (in_reply_to_id != null)
|
||||
pars += "&in_reply_to_id=" + in_reply_to_id.to_string ();
|
||||
pars += "&visibility=" + visibility_opt.to_string ();
|
||||
pars += "&in_reply_to_id=%s".printf (in_reply_to_id.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) => {
|
||||
try{
|
||||
try {
|
||||
var root = Tootle.network.parse (mess);
|
||||
var status = Status.parse (root);
|
||||
debug (status.id.to_string ()); //TODO: Live updates
|
||||
|
|
|
@ -30,7 +30,7 @@ public class Tootle.NetManager : GLib.Object {
|
|||
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);
|
||||
}
|
||||
|
||||
|
@ -115,4 +115,14 @@ public class Tootle.NetManager : GLib.Object {
|
|||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -2,43 +2,99 @@ using Gtk;
|
|||
|
||||
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.Label? label;
|
||||
Gtk.Image? image;
|
||||
|
||||
construct {
|
||||
margin_top = 6;
|
||||
set_size_request (64, 64);
|
||||
grid = new Gtk.Grid ();
|
||||
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);
|
||||
grid.show ();
|
||||
label.hide ();
|
||||
}
|
||||
|
||||
public AttachmentWidget (Attachment att) {
|
||||
attachment = att;
|
||||
var type = attachment.type;
|
||||
rebind ();
|
||||
}
|
||||
|
||||
public void rebind () {
|
||||
var type = attachment.type;
|
||||
switch (type){
|
||||
case "image":
|
||||
image = new Gtk.Image ();
|
||||
image.vexpand = true;
|
||||
image.margin = 3;
|
||||
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);
|
||||
grid.attach (image, 0, 0);
|
||||
label.hide ();
|
||||
break;
|
||||
default:
|
||||
label = new Gtk.Label (_("Click to open %s media").printf (type));
|
||||
label.margin = 16;
|
||||
grid.attach (label, 0, 0);
|
||||
label.label = _("Click to open %s media").printf (type);
|
||||
label.show ();
|
||||
break;
|
||||
}
|
||||
|
||||
show ();
|
||||
button_press_event.connect(() => {
|
||||
if (!editable)
|
||||
Tootle.Utils.open_url (attachment.url);
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,8 +16,7 @@ public class Tootle.StatusWidget : Gtk.EventBox {
|
|||
public Tootle.RichLabel content_label;
|
||||
public Tootle.RichLabel? content_spoiler;
|
||||
Gtk.Box title_box;
|
||||
Gtk.Box attachments;
|
||||
Gtk.ScrolledWindow attachments_scroll;
|
||||
AttachmentBox attachments;
|
||||
Gtk.Grid grid;
|
||||
Gtk.Box counters;
|
||||
Gtk.Label reblogs;
|
||||
|
@ -59,6 +58,8 @@ public class Tootle.StatusWidget : Gtk.EventBox {
|
|||
content_label = new RichLabel ("");
|
||||
content_label.wrap_words ();
|
||||
|
||||
attachments = new AttachmentBox ();
|
||||
|
||||
reblogs = 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);
|
||||
});
|
||||
|
||||
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);
|
||||
revealer_box.margin_end = 12;
|
||||
revealer_box.add (content_label);
|
||||
revealer_box.add (attachments_scroll);
|
||||
revealer_box.add (attachments);
|
||||
revealer = new Revealer ();
|
||||
revealer.reveal_child = true;
|
||||
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_bottom = 6;
|
||||
counters.add (reblog);
|
||||
|
@ -111,8 +106,6 @@ public class Tootle.StatusWidget : Gtk.EventBox {
|
|||
grid.attach (counters, 2, 5, 1, 1);
|
||||
add (grid);
|
||||
show_all ();
|
||||
|
||||
attachments_scroll.hide ();
|
||||
}
|
||||
|
||||
public StatusWidget (Status status) {
|
||||
|
@ -171,10 +164,12 @@ public class Tootle.StatusWidget : Gtk.EventBox {
|
|||
}
|
||||
|
||||
if (status.attachments != null) {
|
||||
attachments_scroll.show ();
|
||||
attachments.clear ();
|
||||
foreach (Attachment attachment in status.attachments)
|
||||
attachments.add (new AttachmentWidget (attachment));
|
||||
attachments.append (attachment);
|
||||
}
|
||||
else
|
||||
attachments.destroy ();
|
||||
|
||||
destroy.connect (() => {
|
||||
if(separator != null)
|
||||
|
|
Loading…
Reference in New Issue