1
0
mirror of https://gitlab.gnome.org/World/tootle synced 2025-02-08 07:38:39 +01:00

Implement attachments

This commit is contained in:
bleakgrey 2018-05-04 23:57:31 +03:00
parent ac16f77555
commit e0b45ee49c
11 changed files with 198 additions and 55 deletions

View File

@ -27,3 +27,8 @@
.header-counters{
background:rgba(255,255,255,.4);
}
.attachment{
background: #fff;
border-radius: 3px;
}

View File

@ -34,12 +34,14 @@ executable(
'src/API/StatusVisibility.vala',
'src/API/Notification.vala',
'src/API/NotificationType.vala',
'src/API/Attachment.vala',
'src/Widgets/HeaderBar.vala',
'src/Widgets/AlignedLabel.vala',
'src/Widgets/RichLabel.vala',
'src/Widgets/AccountsButton.vala',
'src/Widgets/StatusWidget.vala',
'src/Widgets/NotificationWidget.vala',
'src/Widgets/AttachmentWidget.vala',
'src/Dialogs/PostDialog.vala',
'src/Views/AbstractView.vala',
'src/Views/AddAccountView.vala',

23
src/API/Attachment.vala Normal file
View File

@ -0,0 +1,23 @@
public class Tootle.Attachment{
public int64 id;
public string type;
public string url;
public string preview_url;
public Attachment(int64 id){
this.id = id;
}
public static Attachment parse (Json.Object obj){
var id = int64.parse (obj.get_string_member ("id"));
var attachment = new Attachment (id);
attachment.type = obj.get_string_member ("type");
attachment.preview_url = obj.get_string_member ("preview_url");
attachment.url = obj.get_string_member ("url");
return attachment;
}
}

View File

@ -13,9 +13,11 @@ public class Tootle.Status{
public string created_at;
public bool reblogged;
public bool favorited;
public bool sensitive;
public Status? reblog;
public Mention[]? mentions;
public Tag[]? tags;
public Attachment[]? attachments;
public Status(int64 id) {
this.id = id;
@ -38,24 +40,7 @@ public class Tootle.Status{
status.reblogs_count = obj.get_int_member ("reblogs_count");
status.favourites_count = obj.get_int_member ("favourites_count");
status.content = Utils.escape_html ( obj.get_string_member ("content"));
Mention[]? _mentions = {};
obj.get_array_member ("mentions").foreach_element ((array, i, node) => {
var object = node.get_object ();
if (object != null)
_mentions += Mention.parse (object);
});
if (_mentions.length > 0)
status.mentions = _mentions;
Tag[]? _tags = {};
obj.get_array_member ("tags").foreach_element ((array, i, node) => {
var object = node.get_object ();
if (object != null)
_tags += Tag.parse (object);
});
if (_tags.length > 0)
status.tags = _tags;
status.sensitive = obj.get_boolean_member ("sensitive");
var spoiler = obj.get_string_member ("spoiler_text");
if (spoiler != "")
@ -69,6 +54,33 @@ public class Tootle.Status{
if(obj.has_member ("reblog") && obj.get_null_member("reblog") != true)
status.reblog = Status.parse (obj.get_object_member ("reblog"));
Mention[]? _mentions = {};
obj.get_array_member ("mentions").foreach_element ((array, i, node) => {
var object = node.get_object ();
if (object != null)
_mentions += Mention.parse (object);
});
if (_mentions.length > 0)
status.mentions = _mentions;
Tag[]? _tags = {};
obj.get_array_member ("tags").foreach_element ((array, i, node) => {
var object = node.get_object ();
if (object != null)
_tags += Tag.parse (object);
});
if (_tags.length > 0)
status.tags = _tags;
Attachment[]? _attachments = {};
obj.get_array_member ("media_attachments").foreach_element ((array, i, node) => {
var object = node.get_object ();
if (object != null)
_attachments += Attachment.parse (object);
});
if (_attachments.length > 0)
status.attachments = _attachments;
return status;
}

View File

@ -9,22 +9,30 @@ public class Tootle.CacheManager : GLib.Object{
path_images = GLib.Environment.get_user_special_dir (UserDirectory.DOWNLOAD);
}
//TODO: actually cache images
public CacheManager(){
Object ();
}
//TODO: actually cache images
public void load_avatar (string url, Granite.Widgets.Avatar avatar, int size){
var msg = new Soup.Message("GET", url);
msg.finished.connect(()=>{
uint8[] buf = msg.response_body.data;
msg.finished.connect(() => {
var loader = new PixbufLoader();
loader.set_size (size, size);
loader.write(buf);
loader.write(msg.response_body.data);
loader.close();
var pixbuf = loader.get_pixbuf ();
avatar.pixbuf = pixbuf;
avatar.pixbuf = loader.get_pixbuf ();
});
Tootle.network.queue(msg, (sess, mess) => {});
}
public void load_image (string url, Gtk.Image image){
var msg = new Soup.Message("GET", url);
msg.finished.connect(() => {
var loader = new PixbufLoader();
loader.write(msg.response_body.data);
loader.close();
image.set_from_pixbuf (loader.get_pixbuf ());
});
Tootle.network.queue(msg, (sess, mess) => {});
}

View File

@ -1,6 +1,10 @@
public class Tootle.Utils{
public static string escape_html(string content){
public static void open_url (string url) {
Gtk.show_uri (null, url, Gdk.CURRENT_TIME);
}
public static string escape_html (string content) {
var str = content
.replace("<br>", "\n")
.replace("</br>", "")

View File

@ -9,6 +9,7 @@ public abstract class Tootle.AbstractView : Gtk.ScrolledWindow {
construct {
view = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
view.valign = Gtk.Align.START;
hscrollbar_policy = Gtk.PolicyType.NEVER;
add (view);

View File

@ -15,9 +15,9 @@ public class Tootle.StatusView : Tootle.AbstractView {
if (is_root)
widget.highlight ();
widget.content.selectable = true;
if (widget.spoiler_content != null)
widget.spoiler_content.selectable = true;
widget.content_label.selectable = true;
if (widget.content_spoiler != null)
widget.content_spoiler.selectable = true;
widget.avatar.button_press_event.connect(widget.on_avatar_clicked);
view.pack_start (widget, false, false, 0);
}

View File

@ -0,0 +1,44 @@
using Gtk;
public class Tootle.AttachmentWidget : Gtk.EventBox {
Attachment attachment;
Gtk.Grid grid;
Gtk.Label? label;
Gtk.Image? image;
construct {
margin_top = 6;
grid = new Gtk.Grid ();
get_style_context ().add_class ("attachment");
add (grid);
}
public AttachmentWidget (Attachment att) {
attachment = att;
var type = attachment.type;
switch (type){
case "image":
image = new Gtk.Image ();
image.vexpand = true;
image.margin = 3;
image.valign = Gtk.Align.CENTER;
Tootle.cache.load_image (attachment.preview_url, image);
grid.attach (image, 0, 0);
break;
default:
label = new Gtk.Label (_("Click to open %s media").printf (type));
label.margin = 16;
grid.attach (label, 0, 0);
break;
}
button_press_event.connect(() => {
Tootle.Utils.open_url (attachment.url);
return true;
});
show_all ();
}
}

View File

@ -12,9 +12,18 @@ public class Tootle.RichLabel : Gtk.Label {
activate_link.connect (open_link);
}
public void wrap_words () {
halign = Gtk.Align.START;
single_line_mode = false;
set_line_wrap (true);
wrap_mode = Pango.WrapMode.WORD_CHAR;
justify = Gtk.Justification.LEFT;
xalign = 0;
}
public bool open_link (string url){
if (mentions != null){
foreach (Mention mention in mentions){
foreach (Mention mention in mentions) {
if (url == mention.url){
AccountView.open_from_id (mention.id);
return true;
@ -34,7 +43,7 @@ public class Tootle.RichLabel : Gtk.Label {
return true;
}
Gtk.show_uri (null, url, Gdk.CURRENT_TIME);
Tootle.Utils.open_url (url);
return true;
}

View File

@ -13,9 +13,11 @@ public class Tootle.StatusWidget : Gtk.EventBox {
public Gtk.Label title_date;
public Gtk.Label title_acct;
public Gtk.Revealer revealer;
public Tootle.RichLabel content;
public Gtk.Label? spoiler_content;
public Tootle.RichLabel content_label;
public Tootle.RichLabel content_spoiler;
Gtk.Box title_box;
Gtk.Box attachments;
Gtk.ScrolledWindow attachments_scroll;
Gtk.Grid grid;
Gtk.Box counters;
Gtk.Label reblogs;
@ -51,20 +53,10 @@ public class Tootle.StatusWidget : Gtk.EventBox {
title_date = new Gtk.Label ("");
title_date.ellipsize = Pango.EllipsizeMode.END;
title_box.pack_end (title_date, false, false, 0);
title_box.show_all ();
content = new RichLabel ("");
content.halign = Gtk.Align.START;
content.single_line_mode = false;
content.set_line_wrap (true);
content.wrap_mode = Pango.WrapMode.WORD_CHAR;
content.justify = Gtk.Justification.LEFT;
content.margin_end = 12;
content.xalign = 0;
revealer = new Revealer ();
revealer.reveal_child = true;
revealer.add (content);
content_label = new RichLabel ("");
content_label.wrap_words ();
reblogs = new Gtk.Label ("0");
favorites = new Gtk.Label ("0");
@ -88,6 +80,20 @@ 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 = new Revealer ();
revealer.reveal_child = true;
revealer.add (revealer_box);
counters = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6); //TODO: currently useless
counters.margin_top = 6;
counters.margin_bottom = 6;
@ -103,7 +109,7 @@ public class Tootle.StatusWidget : Gtk.EventBox {
grid.attach (revealer, 2, 4, 1, 1);
grid.attach (counters, 2, 5, 1, 1);
add (grid);
show_all (); //TODO: display conversations
show_all ();
}
public StatusWidget (Status status) {
@ -128,24 +134,49 @@ public class Tootle.StatusWidget : Gtk.EventBox {
grid.attach (label, 2, 0, 2, 1);
}
if (status.spoiler_text != null){
if (is_spoiler ()){
revealer.reveal_child = false;
spoiler_button = new Button.with_label (_("Toggle content"));
spoiler_content = new RichLabel (status.spoiler_text);
var spoiler_box = new Box (Gtk.Orientation.HORIZONTAL, 6);
spoiler_box.add (spoiler_content);
spoiler_box.margin_end = 12;
var spoiler_button_text = _("Toggle content");
if (status.sensitive && status.attachments != null) {
spoiler_button = new Button.from_icon_name ("mail-attachment-symbolic", Gtk.IconSize.BUTTON);
spoiler_button.label = spoiler_button_text;
spoiler_button.always_show_image = true;
spoiler_button.hexpand = true;
spoiler_button.halign = Gtk.Align.END;
content_label.margin_top = 6;
}
else {
spoiler_button = new Button.with_label (spoiler_button_text);
spoiler_button.hexpand = true;
spoiler_button.halign = Gtk.Align.END;
}
spoiler_button.clicked.connect (() => revealer.set_reveal_child (!revealer.child_revealed));
var spoiler_text = _("[ This post contains sensitive content ]");
if (status.spoiler_text != null)
spoiler_text = status.spoiler_text;
content_spoiler = new RichLabel (spoiler_text);
content_spoiler.wrap_words ();
spoiler_box.add (content_spoiler);
spoiler_box.add (spoiler_button);
spoiler_box.show_all ();
spoiler_button.clicked.connect (() => revealer.set_reveal_child (!revealer.child_revealed));
grid.attach (spoiler_box, 2, 3, 1, 1);
}
if (status.attachments != null) {
foreach (Attachment attachment in status.attachments)
attachments.add (new AttachmentWidget (attachment));
}
destroy.connect (() => {
if(separator != null)
separator.destroy ();
});
rebind ();
}
@ -157,8 +188,8 @@ public class Tootle.StatusWidget : Gtk.EventBox {
public void rebind (){
title_user.label = "<b>%s</b>".printf (status.get_formal ().account.display_name);
title_acct.label = "@" + status.account.acct;
content.label = status.content;
content.mentions = status.mentions;
content_label.label = status.content;
content_label.mentions = status.mentions;
parse_date_iso8601 (status.created_at);
reblogs.label = status.reblogs_count.to_string ();
@ -173,6 +204,10 @@ public class Tootle.StatusWidget : Gtk.EventBox {
Tootle.cache.load_avatar (status.get_formal ().account.avatar, this.avatar, this.avatar_size);
}
public bool is_spoiler (){
return status.spoiler_text != null || status.sensitive;
}
// elementary OS has old GLib, so I have to improvise
// Version >=2.56 provides DateTime.from_iso8601