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/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',
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue