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.
-### 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.
+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
-
+
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
-
-
-
-
-
- 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 @@
+
+
+
+
+
+ True
+ False
+
+
+ True
+ False
+ 8
+ 8
+ 8
+
+
+ 180
+ True
+ False
+ start
+ 8
+ 8
+ True
+ Name
+ True
+ word-char
+ 1
+ 1
+
+
+
+
+
+ 0
+ 0
+
+
+
+
+ True
+ False
+ 8
+ 8
+ True
+ Value
+ True
+ word-char
+ 0
+
+
+ 2
+ 0
+
+
+
+
+ True
+ False
+ vertical
+
+
+ 1
+ 0
+
+
+
+
+
+
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
+ 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
+
+
+
+
+
+
+ True
+ True
+ False
+ True
+ True
+ popover
+
+
+ True
+ False
+ 100
+ crossfade
+
+
+ True
+ False
+ end
+ True
+
+
+
+
+
+ title
+
+
+
+
+ True
+ False
+ center
+ 8
+
+
+ True
+ False
+ mail-send-symbolic
+
+
+ False
+ True
+ 0
+
+
+
+
+ True
+ False
+ 1K
+ end
+ True
+
+
+
+
+
+ False
+ True
+ 1
+
+
+
+
+ title_scrolled
+ 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