diff --git a/.gitignore b/.gitignore index 58b9868..95cc4e1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,4 @@ _ignore build build.sh -build-po.sh -uninstall.sh *~ diff --git a/README.md b/README.md index 0ebdf86..3284cdc 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ Simple [Mastodon](https://github.com/tootsuite/mastodon) client for Linux ![Screenshot](https://raw.githubusercontent.com/bleakgrey/tootle/master/data/screenshot.png) + ### Installation This project is undergoing a major rewrite and will be published in the near future. @@ -10,30 +11,36 @@ To help the project, please build it manually and help test it. Download on Flathub -### Building -To build the app, make sure you have these dependencies: -* meson -* valac -* libgtk-3-dev -* libsoup2.4-dev -* libgranite-dev -* libjson-glib-dev -* libhandy-1.0-dev -Then run `install.sh` in the project directory to install the app. +### Building +If the options above are not available to you, you can build the app from source: + +1. Make sure you have these dependencies: + - [x] meson + - [x] valac + - [x] libgtk-3-dev + - [x] libsoup2.4-dev + - [x] libgranite-dev + - [x] libjson-glib-dev + - [ ] libhandy-1.0-dev + + *Note: Unchecked items will be installed automatically if not present in the system.* + +2. Run `install.sh` in the project directory. The app will launch automatically on success. + ### Contributing - -You're always welcome to help the project in many ways: -* Donating with [LiberaPay](https://liberapay.com/bleakgrey/) to keep the developer happy and motivated -* Reporting issues and bugs -* Submitting pull requests +Please consider donating with [LiberaPay](https://liberapay.com/bleakgrey/) to keep the developer happy. Donate using Liberapay +You can always help by reporting bugs, submitting pull requests, and suggesting ideas. + + ### Credits * Icon design by [Tobias Bernard](https://github.com/bertob) * French translation by [@Larnicone](https://github.com/Larnicone) * Polish translation by [@m4sk1n](https://github.com/m4sk1n) * German translation by [@koyuawsmbrtn](https://github.com/koyuawsmbrtn) * Simplified Chinese translation by [@gloomy-ghost](https://github.com/gloomy-ghost) + diff --git a/data/app.css b/data/app.css index 9fff159..3df38e7 100644 --- a/data/app.css +++ b/data/app.css @@ -7,6 +7,13 @@ background: rgba (150, 150, 150, 0.2); } +.header-title-button { + margin: 0px; + border-radius: 0px; + border-top: none; + border-bottom: none; +} + .padded.app-view { margin: 32px 0 32px 0; } diff --git a/data/com.github.bleakgrey.tootle.gschema.xml b/data/com.github.bleakgrey.tootle.gschema.xml index 51228ec..32cef76 100644 --- a/data/com.github.bleakgrey.tootle.gschema.xml +++ b/data/com.github.bleakgrey.tootle.gschema.xml @@ -29,7 +29,7 @@ 20 - 100 + 120 true diff --git a/data/gresource.xml b/data/gresource.xml index de95b28..d2232db 100644 --- a/data/gresource.xml +++ b/data/gresource.xml @@ -8,6 +8,8 @@ ui/widgets/status.ui ui/widgets/accounts_button.ui ui/widgets/accounts_button_item.ui + ui/widgets/profile_field_row.ui + ui/widgets/timeline_filter.ui ui/dialogs/compose.ui ui/dialogs/main.ui ui/dialogs/preferences.ui diff --git a/data/ui/dialogs/main.ui b/data/ui/dialogs/main.ui index 423be63..d827b9b 100644 --- a/data/ui/dialogs/main.ui +++ b/data/ui/dialogs/main.ui @@ -64,43 +64,99 @@ True Tootle + + 1 + - - True - True - - - True - False - go-previous-symbolic - - - - - - + True - True - True + False - + True - False - document-edit-symbolic + + + False + True + 0 + + + + True + True + True + + + True + False + document-edit-symbolic + + + + + False + True + 1 + + + 1 - + True + False + slide-right + + + True + False + 6 + + + True + False + + + False + True + 0 + + + + + True + True + True + + + True + False + go-previous-symbolic + + + + + False + True + 1 + + + + - end 2 diff --git a/data/ui/views/profile_header.ui b/data/ui/views/profile_header.ui index 24dcb36..936cc7c 100644 --- a/data/ui/views/profile_header.ui +++ b/data/ui/views/profile_header.ui @@ -1,7 +1,7 @@ - + False @@ -101,212 +101,10 @@ 8 8 - - True - False - none - False - - - True - False - False - False - - - True - True - False - 8 - 8 - 8 - 8 - True - 25 - - - - - - - - 1 - 2 - - - - - True - False - none - False - - - True - False - False - False - - - True - False - 8 - 8 - 8 - 8 - 8 - - - 40 - True - False - True - 32 - - - True - True - False - True - True - - - True - False - 0 Posts - True - - - - - - True - True - 0 - - - - - True - True - False - True - posts_tab - - - True - False - 0 Follows - True - - - - - - True - True - 1 - - - - - True - True - False - True - posts_tab - - - True - False - 0 Followers - True - - - - - - True - True - 2 - - - - - - False - True - 0 - - - - - True - False - vertical - - - False - True - 1 - - - - - True - True - True - none - True - filter_popover - - - True - False - view-more-symbolic - - - - - - False - True - 2 - - - - - - - - - - - 1 - 3 - - - - + True False + True none False @@ -328,13 +126,13 @@ 8 - True - 8 - 8 - 8 - 8 - 128 - + True + 8 + 8 + 8 + 8 + 96 + False True @@ -351,10 +149,10 @@ vertical 8 - - True - True - + + True + True + False True @@ -362,10 +160,10 @@ - - True - True - + + False + 0.5 + False True @@ -399,7 +197,7 @@ 8 8 - + True False False @@ -462,6 +260,27 @@ + + + True + False + False + False + + + True + True + False + 8 + 8 + 8 + 8 + True + 25 + + + + @@ -480,11 +299,5 @@ - - - - - - diff --git a/data/ui/widgets/accounts_button.ui b/data/ui/widgets/accounts_button.ui index 142cc1f..01c2e51 100644 --- a/data/ui/widgets/accounts_button.ui +++ b/data/ui/widgets/accounts_button.ui @@ -215,7 +215,7 @@ True - 24 + 28 True diff --git a/data/ui/widgets/profile_field_row.ui b/data/ui/widgets/profile_field_row.ui new file mode 100644 index 0000000..5ea6787 --- /dev/null +++ b/data/ui/widgets/profile_field_row.ui @@ -0,0 +1,69 @@ + + + + + + diff --git a/data/ui/widgets/status.ui b/data/ui/widgets/status.ui index d7450b3..4053284 100644 --- a/data/ui/widgets/status.ui +++ b/data/ui/widgets/status.ui @@ -41,9 +41,8 @@ True False end - start 8 - applications-development-symbolic + oops 1 @@ -155,63 +154,6 @@ 3 - - - True - False - True - 8 - - - True - Handle - end - 0 - - - False - True - 0 - - - - - True - False - 0.5 - end - Yesterday - 0 - - - False - True - end - 1 - - - - - True - False - end - view-pin-symbolic - 1 - - - False - True - end - 2 - - - - - 1 - 1 - 3 - - True @@ -224,15 +166,15 @@ vertical - True - False - True - Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. - True - word-char - 15 - 0 - + True + False + True + Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. + True + word-char + 15 + 0 + False True @@ -241,9 +183,9 @@ - True - 8 - + True + 8 + False True @@ -261,23 +203,24 @@ - 48 - 48 - start - true - + 48 + 48 + start + true + 0 1 - 4 + 1 - True - end - 0 - + True + end + 0 + 8 + 1 0 @@ -286,21 +229,94 @@ - True - False - True - Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. - True - word-char - 15 - 0 - + True + False + True + Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. + True + word-char + 15 + 0 + 8 + 1 2 3 + + + True + False + True + fill + True + + + True + False + True + Name + end + + + 0 + 0 + 2 + + + + + True + False + Handle + 0.5 + end + + + 0 + 1 + 4 + + + + + True + False + 8 + 8 + view-pin-symbolic + 1 + + + 2 + 0 + + + + + True + False + 0.5 + 8 + 8 + Yesterday + end + start + + + 3 + 0 + + + + + 1 + 1 + 3 + + diff --git a/data/ui/widgets/timeline_filter.ui b/data/ui/widgets/timeline_filter.ui new file mode 100644 index 0000000..648b5ee --- /dev/null +++ b/data/ui/widgets/timeline_filter.ui @@ -0,0 +1,345 @@ + + + + + + False + + + True + False + 8 + 8 + 8 + 8 + vertical + + + True + False + vertical + 8 + + + True + False + Show: + 0 + + + + + + False + True + 0 + + + + + True + False + vertical + 8 + + + statuses + True + True + False + True + True + + + True + False + 0 Posts + 0 + + + + + False + True + 0 + + + + + following + True + True + False + True + radio_source + + + True + False + 0 Follows + 0 + + + + + False + True + 1 + + + + + followers + True + True + False + True + radio_source + + + True + False + 0 Followers + 0 + + + + + False + True + 2 + + + + + False + True + 1 + + + + + False + True + 0 + + + + + True + False + + + True + False + vertical + + + True + False + 8 + 8 + vertical + + + False + True + 0 + + + + + True + False + vertical + + + True + False + FIlter: + 0 + + + + + + False + True + 0 + + + + + True + False + 8 + vertical + 8 + + + True + True + False + True + True + + + True + False + None + 0 + + + + + False + True + 0 + + + + + True + True + False + True + radio_post_filter + + + True + False + Include Replies + 0 + + + + + False + True + 1 + + + + + True + True + False + True + radio_post_filter + + + True + False + Only Media + 0 + + + + + False + True + 2 + + + + + False + True + 1 + + + + + False + True + 1 + + + + + + + False + True + 1 + + + + + + + diff --git a/install.sh b/install.sh index 8db4601..47bf6d8 100755 --- a/install.sh +++ b/install.sh @@ -1,4 +1,6 @@ -meson build --prefix=/usr +clear +meson build cd build +ninja sudo ninja install com.github.bleakgrey.tootle diff --git a/meson.build b/meson.build index d693198..ead80fc 100644 --- a/meson.build +++ b/meson.build @@ -26,18 +26,23 @@ asresources = gnome.compile_resources( c_name: 'as' ) -libhandy_dep = dependency('libhandy-1', version: '>= 0.80.0') +libhandy_dep = dependency('libhandy-1', version: '>= 0.80.0', required: false) if not libhandy_dep.found() libhandy = subproject( 'libhandy', - install: false, default_options: [ 'examples=false', - 'package_subdir=' + meson.project_name(), + 'glade_catalog=disabled', 'tests=false', ] ) - libhandy_dep = libhandy.get_variable('libhandy_dep') + + libhandy_dep = declare_dependency( + dependencies: [ + libhandy.get_variable('libhandy_dep'), + libhandy.get_variable('libhandy_vapi'), + ] + ) endif executable( @@ -58,6 +63,7 @@ executable( 'src/Services/Cache.vala', 'src/Services/Network.vala', 'src/API/Account.vala', + 'src/API/AccountField.vala', 'src/API/Relationship.vala', 'src/API/Mention.vala', 'src/API/Tag.vala', @@ -71,6 +77,7 @@ executable( 'src/Widgets/Widgetizable.vala', 'src/Widgets/Avatar.vala', 'src/Widgets/AccountsButton.vala', + 'src/Widgets/TimelineFilter.vala', 'src/Widgets/RichLabel.vala', 'src/Widgets/Status.vala', 'src/Widgets/Notification.vala', diff --git a/src/API/Account.vala b/src/API/Account.vala index c8c3599..57eb4dc 100644 --- a/src/API/Account.vala +++ b/src/API/Account.vala @@ -1,4 +1,4 @@ -public class Tootle.API.Account : Entity { +public class Tootle.API.Account : Entity, Widgetizable { public string id { get; set; } public string username { get; set; } @@ -21,6 +21,13 @@ public class Tootle.API.Account : Entity { public int64 following_count { get; set; } public int64 statuses_count { get; set; } public Relationship? rs { get; set; default = null; } + public Gee.ArrayList? fields { get; set; default = null; } + + public string handle { + owned get { + return "@" + acct; + } + } public static Account from (Json.Node node) throws Error { return Entity.from_json (typeof (API.Account), node) as API.Account; @@ -30,6 +37,13 @@ public class Tootle.API.Account : Entity { return id == accounts.active.id; } + public override Gtk.Widget to_widget () { + var status = new API.Status.from_account (this); + var w = new Widgets.Status (status); + w.button_press_event.connect (w.open); + return w; + } + public Request get_relationship () { return new Request.GET ("/api/v1/accounts/relationships") .with_account (accounts.active) diff --git a/src/API/AccountField.vala b/src/API/AccountField.vala new file mode 100644 index 0000000..1a5f8ad --- /dev/null +++ b/src/API/AccountField.vala @@ -0,0 +1,7 @@ +public class Tootle.API.AccountField : Entity { + + public string name { get; set; } + public string val { get; set; } + public string? verified_at { get; set; } + +} diff --git a/src/API/Entity.vala b/src/API/Entity.vala index 9d31eaa..a5f54cc 100644 --- a/src/API/Entity.vala +++ b/src/API/Entity.vala @@ -36,12 +36,19 @@ public class Tootle.Entity : GLib.Object, Widgetizable, Json.Serializable { if (obj == null) throw new Oopsie.PARSING (@"Received Json.Node for $(type.name ()) is not a Json.Object!"); + //Replace with something more elegant var kind = obj.get_member ("type"); if (kind != null) { obj.set_member ("kind", kind); obj.remove_member ("type"); } + var val = obj.get_member ("value"); + if (val != null) { + obj.set_member ("val", val); + obj.remove_member ("value"); + } + return Json.gobject_deserialize (type, node) as Entity; } @@ -67,6 +74,8 @@ public class Tootle.Entity : GLib.Object, Widgetizable, Json.Serializable { if (type.is_a (typeof (Gee.ArrayList))) { Type contains; + + //There has to be a better way switch (prop) { case "media-attachments": contains = typeof (API.Attachment); @@ -74,6 +83,9 @@ public class Tootle.Entity : GLib.Object, Widgetizable, Json.Serializable { case "mentions": contains = typeof (API.Mention); break; + case "fields": + contains = typeof (API.AccountField); + break; default: contains = typeof (Entity); break; diff --git a/src/Dialogs/MainWindow.vala b/src/Dialogs/MainWindow.vala index a13fb53..dbe1771 100644 --- a/src/Dialogs/MainWindow.vala +++ b/src/Dialogs/MainWindow.vala @@ -12,7 +12,9 @@ public class Tootle.Dialogs.MainWindow: Gtk.Window, ISavedWindow { protected Stack timeline_stack; [GtkChild] - protected HeaderBar header; + public HeaderBar header; + [GtkChild] + protected Revealer view_navigation; [GtkChild] protected Button back_button; [GtkChild] @@ -75,6 +77,10 @@ public class Tootle.Dialogs.MainWindow: Gtk.Window, ISavedWindow { } public bool open_view (Views.Base widget) { + var curr = view_stack.visible_child as Views.Base; + if (curr != null) + curr.current = false; + var i = get_visible_id (); i++; widget.stack_pos = i; @@ -82,6 +88,7 @@ public class Tootle.Dialogs.MainWindow: Gtk.Window, ISavedWindow { view_stack.add_named (widget, i.to_string ()); view_stack.set_visible_child_name (i.to_string ()); update_header (); + widget.current = true; return true; } @@ -92,8 +99,13 @@ public class Tootle.Dialogs.MainWindow: Gtk.Window, ISavedWindow { var child = view_stack.get_child_by_name (i.to_string ()); view_stack.set_visible_child_name ((i-1).to_string ()); + (child as Views.Base).current = false; child.destroy (); update_header (); + + var curr = view_stack.visible_child as Views.Base; + if (curr != null) + curr.current = true; return true; } @@ -136,8 +148,10 @@ public class Tootle.Dialogs.MainWindow: Gtk.Window, ISavedWindow { bool primary_mode = get_visible_id () == 0; switcher_navbar.visible = timeline_switcher.sensitive = primary_mode; timeline_switcher.opacity = primary_mode ? 1 : 0; //Prevent HeaderBar height jitter - compose_button.visible = primary_mode; - back_button.visible = !primary_mode; + view_navigation.reveal_child = !primary_mode; + + if (primary_mode) + header.custom_title = timeline_switcher; } void on_timeline_changed (ParamSpec spec) { diff --git a/src/Html.vala b/src/Html.vala index da58fdf..c83b2f1 100644 --- a/src/Html.vala +++ b/src/Html.vala @@ -5,22 +5,6 @@ public class Tootle.Html { return GLib.Markup.escape_text (all_tags.replace (content, -1, 0, "")); } - public static string escape_pango_entities (string str) { - return str - .replace (" ", " ") - .replace ("'", "'") - .replace ("& ", "&"); - } - - public static string restore_entities (string str) { - return str - .replace ("&", "&") - .replace ("<", "<") - .replace (">", ">") - .replace ("'", "'") - .replace (""", "\""); - } - public static string simplify (string str) { var divided = str .replace("
", "\n") @@ -35,11 +19,11 @@ public class Tootle.Html { while (simplified.has_suffix ("\n")) simplified = simplified.slice (0, simplified.last_index_of ("\n")); - return escape_pango_entities (simplified); + return simplified; } public static string uri_encode (string str) { - var restored = restore_entities (str); + var restored = Widgets.RichLabel.restore_entities (str); return Soup.URI.encode (restored, ";&+"); } diff --git a/src/InstanceAccount.vala b/src/InstanceAccount.vala index ba1f385..aaa22cb 100644 --- a/src/InstanceAccount.vala +++ b/src/InstanceAccount.vala @@ -14,7 +14,7 @@ public class Tootle.InstanceAccount : API.Account, IStreamListener { protected string? stream; - public string handle { + public new string handle { owned get { return @"@$username@$short_instance"; } } public string short_instance { diff --git a/src/Views/Profile.vala b/src/Views/Profile.vala index f737d0e..4fbfdd2 100644 --- a/src/Views/Profile.vala +++ b/src/Views/Profile.vala @@ -4,26 +4,26 @@ public class Tootle.Views.Profile : Views.Timeline { public API.Account profile { get; construct set; } - protected RadioButton filter_all; - protected RadioButton filter_replies; - protected RadioButton filter_media; + ListBox profile_list; - protected Label relationship; - protected Box actions; - protected Button follow_button; - protected MenuButton options_button; + Label relationship; + Box actions; + Button follow_button; + MenuButton options_button; - protected Label posts_label; - protected Label following_label; - protected Label followers_label; - protected RadioButton posts_tab; - protected RadioButton following_tab; - protected RadioButton followers_tab; + Widgets.TimelineFilter filter; + + public bool exclude_replies { get; set; default = true; } + public bool only_media { get; set; default = false; } construct { profile.notify["rs"].connect (on_rs_updated); + filter = new Widgets.TimelineFilter.with_profile (this); + var builder = new Builder.from_resource (@"$(Build.RESOURCES)ui/views/profile_header.ui"); + profile_list = builder.get_object ("profile_list") as ListBox; + var hdr = builder.get_object ("grid") as Grid; column_view.pack_start (hdr, false, false, 0); column_view.reorder_child (hdr, 0); @@ -31,21 +31,17 @@ public class Tootle.Views.Profile : Views.Timeline { var avatar = builder.get_object ("avatar") as Widgets.Avatar; avatar.url = profile.avatar; - var name = builder.get_object ("name") as Widgets.RichLabel; - profile.bind_property ("display-name", name, "label", BindingFlags.SYNC_CREATE, (b, src, ref target) => { - var label = (string) src; - target.set_string (@"$label"); - return true; - }); + profile.bind_property ("display-name", filter.title, "label", BindingFlags.SYNC_CREATE); var handle = builder.get_object ("handle") as Widgets.RichLabel; - profile.bind_property ("acct", handle, "label", BindingFlags.SYNC_CREATE, (b, src, ref target) => { - target.set_string ("@" + (string) src); + profile.bind_property ("acct", handle, "text", BindingFlags.SYNC_CREATE, (b, src, ref target) => { + var text = "@" + (string) src; + target.set_string (@"$text"); return true; }); var note = builder.get_object ("note") as Widgets.RichLabel; - profile.bind_property ("note", note, "label", BindingFlags.SYNC_CREATE, (b, src, ref target) => { + profile.bind_property ("note", note, "text", BindingFlags.SYNC_CREATE, (b, src, ref target) => { target.set_string (Html.simplify ((string) src)); return true; }); @@ -56,57 +52,49 @@ public class Tootle.Views.Profile : Views.Timeline { options_button = builder.get_object ("options_button") as MenuButton; relationship = builder.get_object ("relationship") as Label; - posts_label = builder.get_object ("posts_label") as Label; - profile.bind_property ("statuses_count", posts_label, "label", BindingFlags.SYNC_CREATE, (b, src, ref target) => { - var val = (int64) src; - target.set_string (_("%s Posts").printf (@"$val")); - return true; - }); - following_label = builder.get_object ("following_label") as Label; - profile.bind_property ("following_count", following_label, "label", BindingFlags.SYNC_CREATE, (b, src, ref target) => { - var val = (int64) src; - target.set_string (_("%s Follows").printf (@"$val")); - return true; - }); - followers_label = builder.get_object ("followers_label") as Label; - profile.bind_property ("followers_count", followers_label, "label", BindingFlags.SYNC_CREATE, (b, src, ref target) => { - var val = (int64) src; - target.set_string (_("%s Followers").printf (@"$val")); - return true; - }); + // posts_label = builder.get_object ("posts_label") as Label; + // profile.bind_property ("statuses_count", posts_label, "label", BindingFlags.SYNC_CREATE, (b, src, ref target) => { + // var val = (int64) src; + // target.set_string (_("%s Posts").printf (@"$val")); + // return true; + // }); + // following_label = builder.get_object ("following_label") as Label; + // profile.bind_property ("following_count", following_label, "label", BindingFlags.SYNC_CREATE, (b, src, ref target) => { + // var val = (int64) src; + // target.set_string (_("%s Follows").printf (@"$val")); + // return true; + // }); + // followers_label = builder.get_object ("followers_label") as Label; + // profile.bind_property ("followers_count", followers_label, "label", BindingFlags.SYNC_CREATE, (b, src, ref target) => { + // var val = (int64) src; + // target.set_string (_("%s Followers").printf (@"$val")); + // return true; + // }); - filter_all = builder.get_object ("filter_all") as RadioButton; - filter_all.toggled.connect (on_refresh); - filter_replies = builder.get_object ("filter_replies") as RadioButton; - filter_replies.toggled.connect (on_refresh); - filter_media = builder.get_object ("filter_media") as RadioButton; - filter_media.toggled.connect (on_refresh); - - posts_tab = builder.get_object ("posts_tab") as RadioButton; - posts_tab.toggled.connect (() => { - if (posts_tab.active) on_refresh (); - }); - following_tab = builder.get_object ("following_tab") as RadioButton; - following_tab.toggled.connect (() => { - if (following_tab.active) on_refresh (); - }); - followers_tab = builder.get_object ("followers_tab") as RadioButton; - followers_tab.toggled.connect (() => { - if (followers_tab.active) on_refresh (); - }); + rebuild_fields (); } public Profile (API.Account acc) { - Object (profile: acc); + Object ( + profile: acc, + url: @"/api/v1/accounts/$(acc.id)/statuses" + ); profile.get_relationship (); } - protected void on_follow_button_clicked () { + public override void on_shown () { + window.header.custom_title = filter; + } + public override void on_hidden () { + window.header.custom_title = null; + } + + void on_follow_button_clicked () { actions.sensitive = false; profile.set_following (!profile.rs.following); } - protected void on_rs_updated () { + void on_rs_updated () { var rs = profile.rs; var label = ""; if (actions.sensitive = rs != null) { @@ -136,24 +124,15 @@ public class Tootle.Views.Profile : Views.Timeline { } relationship.label = label; + relationship.visible = label != ""; } - public override string get_req_url () { - if (page_next != null) - return page_next; - - if (following_tab.active) - return @"/api/v1/accounts/$(profile.id)/following"; - else if (followers_tab.active) - return @"/api/v1/accounts/$(profile.id)/followers"; - else - return @"/api/v1/accounts/$(profile.id)/statuses"; - } - public override Request append_params (Request req) { if (page_next == null) { - req.with_param ("exclude_replies", (!filter_replies.active).to_string ()); - req.with_param ("only_media", filter_media.active.to_string ()); + if (exclude_replies) + req.with_param ("exclude_replies", "true"); + if (only_media) + req.with_param ("only_media", "true"); return base.append_params (req); } else @@ -173,4 +152,28 @@ public class Tootle.Views.Profile : Views.Timeline { }); } + [GtkTemplate (ui = "/com/github/bleakgrey/tootle/ui/widgets/profile_field_row.ui")] + protected class Field : ListBoxRow { + + [GtkChild] + Widgets.RichLabel name_label; + [GtkChild] + Widgets.RichLabel value_label; + + public Field (API.AccountField field) { + name_label.text = field.name; + value_label.text = field.val; + } + + } + + void rebuild_fields () { + if (profile.fields != null) { + foreach (Entity e in profile.fields) { + var w = new Field (e as API.AccountField); + profile_list.insert (w, 2); + } + } + } + } diff --git a/src/Views/Timeline.vala b/src/Views/Timeline.vala index db3f067..a3a55cf 100644 --- a/src/Views/Timeline.vala +++ b/src/Views/Timeline.vala @@ -9,10 +9,10 @@ public class Tootle.Views.Timeline : IAccountListener, IStreamListener, Views.Ba protected InstanceAccount? account = null; - protected bool is_last_page { get; set; default = false; } - protected string? page_next { get; set; } - protected string? page_prev { get; set; } - protected string? stream = null; + public bool is_last_page { get; set; default = false; } + public string? page_next { get; set; } + public string? page_prev { get; set; } + public string? stream = null; construct { app.refresh.connect (on_refresh); diff --git a/src/Widgets/Avatar.vala b/src/Widgets/Avatar.vala index 0da9d46..eb9dc34 100644 --- a/src/Widgets/Avatar.vala +++ b/src/Widgets/Avatar.vala @@ -5,7 +5,7 @@ public class Tootle.Widgets.Avatar : EventBox { public string? url { get; set; } public int size { get; set; default = 48; } - + private Cache.Reference? cached; construct { @@ -20,33 +20,34 @@ public class Tootle.Widgets.Avatar : EventBox { Object (size: size); on_redraw (); } - + ~Avatar () { notify["url"].disconnect (on_url_updated); Screen.get_default ().monitors_changed.disconnect (on_redraw); cache.unload (cached); } - + private void on_url_updated () { cached = null; on_redraw (); cache.load (url, on_cache_result); } - + private void on_cache_result (Cache.Reference? result) { cached = result; on_redraw (); } - + public int get_scaled_size () { - return size * get_scale_factor (); + return size; + //return size * get_scale_factor (); } - + private void on_redraw () { set_size_request (get_scaled_size (), get_scaled_size ()); queue_draw_area (0, 0, size, size); } - + public override bool draw (Cairo.Context ctx) { var w = get_allocated_width (); var h = get_allocated_height (); diff --git a/src/Widgets/RichLabel.vala b/src/Widgets/RichLabel.vala index ae8d920..ace90b1 100644 --- a/src/Widgets/RichLabel.vala +++ b/src/Widgets/RichLabel.vala @@ -5,6 +5,15 @@ public class Tootle.Widgets.RichLabel : Label { public weak ArrayList? mentions; + public string text { + get { + return this.label; + } + set { + this.label = escape_entities (Html.simplify (value)); + } + } + construct { use_markup = true; xalign = 0; @@ -34,10 +43,6 @@ public class Tootle.Widgets.RichLabel : Label { .replace (""", "\""); } - public new void set_label (string text) { - base.set_markup (Html.simplify(escape_entities (text))); - } - public bool open_link (string url) { if ("tootle://" in url) return false; diff --git a/src/Widgets/Status.vala b/src/Widgets/Status.vala index 9f3e098..c77bf24 100644 --- a/src/Widgets/Status.vala +++ b/src/Widgets/Status.vala @@ -18,9 +18,11 @@ public class Tootle.Widgets.Status : EventBox { [GtkChild] public Widgets.Avatar avatar; [GtkChild] + protected Widgets.RichLabel name_label; + [GtkChild] protected Widgets.RichLabel handle_label; [GtkChild] - protected Label date_label; + protected Widgets.RichLabel date_label; [GtkChild] protected Image pin_indicator; [GtkChild] @@ -50,27 +52,26 @@ public class Tootle.Widgets.Status : EventBox { protected string escaped_spoiler { owned get { if (status.formal.has_spoiler) { - var text = Html.simplify (status.formal.spoiler_text ?? ""); + var text = status.formal.spoiler_text ?? ""; var label = _("[ Toggle content ]"); - text += @" $label"; + text += @" $label"; return text; } else - return Html.simplify (status.formal.content); + return status.formal.content; } } protected string escaped_content { owned get { - return status.formal.has_spoiler ? Html.simplify (status.formal.content) : ""; + return status.formal.has_spoiler ? status.formal.content : ""; } } - protected string handle { + protected string display_name { owned get { var name = Html.simplify (status.formal.account.display_name); - var handle = Html.simplify (status.formal.account.acct); - return @"$name @$handle"; + return @"$name"; } } @@ -102,10 +103,11 @@ public class Tootle.Widgets.Status : EventBox { reply_button.clicked.connect (() => new Dialogs.Compose.reply (status)); - bind_property ("escaped-spoiler", content, "label", BindingFlags.SYNC_CREATE); - bind_property ("escaped-content", revealer_content, "label", BindingFlags.SYNC_CREATE); + bind_property ("escaped-spoiler", content, "text", BindingFlags.SYNC_CREATE); + bind_property ("escaped-content", revealer_content, "text", BindingFlags.SYNC_CREATE); status.formal.account.bind_property ("avatar", avatar, "url", BindingFlags.SYNC_CREATE); - bind_property ("handle", handle_label, "label", BindingFlags.SYNC_CREATE); + status.account.bind_property ("handle", handle_label, "label", BindingFlags.SYNC_CREATE); + bind_property ("display_name", name_label, "text", BindingFlags.SYNC_CREATE); bind_property ("date", date_label, "label", BindingFlags.SYNC_CREATE); status.formal.bind_property ("pinned", pin_indicator, "visible", BindingFlags.SYNC_CREATE); diff --git a/src/Widgets/TimelineFilter.vala b/src/Widgets/TimelineFilter.vala new file mode 100644 index 0000000..0bc699a --- /dev/null +++ b/src/Widgets/TimelineFilter.vala @@ -0,0 +1,56 @@ +using Gtk; + +[GtkTemplate (ui = "/com/github/bleakgrey/tootle/ui/widgets/timeline_filter.ui")] +public class Tootle.Widgets.TimelineFilter : MenuButton { + + [GtkChild] + public Label title; + + [GtkChild] + public RadioButton radio_source; + + [GtkChild] + public Revealer post_filter; + [GtkChild] + public RadioButton radio_post_filter; + [GtkChild] + public RadioButton radio_post_only_media; + + public string source { get; set; } + + construct { + radio_source.bind_property ("active", post_filter, "reveal-child", BindingFlags.SYNC_CREATE); + } + + public TimelineFilter.with_profile (Views.Profile view) { + radio_source.get_group ().@foreach (w => { + w.toggled.connect (() => { + if (w.active) { + source = w.name; + on_changed (view); + } + }); + }); + radio_post_filter.get_group ().@foreach (w => { + w.toggled.connect (() => { + if (w.active) + on_changed (view); + }); + }); + } + + void on_changed (Views.Profile view) { + var entity = typeof (API.Status); + if (source != "statuses") + entity = typeof (API.Account); + + view.exclude_replies = radio_post_filter.active; + view.only_media = radio_post_only_media.active; + + view.page_next = view.page_prev = null; + view.url = @"/api/v1/accounts/$(view.profile.id)/$source"; + view.accepts = entity; + view.on_refresh (); + } + +} diff --git a/subprojects/libhandy.wrap b/subprojects/libhandy.wrap new file mode 100644 index 0000000..3ae38fa --- /dev/null +++ b/subprojects/libhandy.wrap @@ -0,0 +1,4 @@ +[wrap-git] +directory=libhandy +url=https://gitlab.gnome.org/GNOME/libhandy.git +revision=v0.80.0 diff --git a/uninstall.sh b/uninstall.sh new file mode 100755 index 0000000..4943f05 --- /dev/null +++ b/uninstall.sh @@ -0,0 +1,4 @@ +cd build +sudo ninja uninstall +cd .. +rm -rf build