Drop Granite completely

This commit is contained in:
Bleak Grey 2020-09-05 11:02:42 +03:00 committed by GitHub
parent e0a7a4326f
commit 748808d845
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 699 additions and 719 deletions

View File

@ -20,7 +20,6 @@ If the options above are not available to you, you can build the app from source
- [x] valac
- [x] libgtk-3-dev
- [x] libsoup2.4-dev
- [x] libgranite-dev
- [x] libjson-glib-dev
- [ ] libhandy-1.0-dev (>= 0.83.0)

View File

@ -2,6 +2,12 @@
border-radius: 4px;
}
.chip {
padding: 4px 12px;
border-radius: 4px;
border: 1px solid black;
}
.attachment {
border-radius: 6px;
background: rgba (150, 150, 150, 0.2);
@ -11,10 +17,6 @@
.attachment .pic {
border-radius: 6px;
}
.attachment .chip {
padding: 6px;
border-radius:6px;
}
.header-title-button {
margin: 0px;

View File

@ -9,7 +9,7 @@
<file preprocess="xml-stripblanks">ui/widgets/accounts_button.ui</file>
<file preprocess="xml-stripblanks">ui/widgets/accounts_button_item.ui</file>
<file preprocess="xml-stripblanks">ui/widgets/profile_field_row.ui</file>
<file preprocess="xml-stripblanks">ui/widgets/timeline_filter.ui</file>
<file preprocess="xml-stripblanks">ui/widgets/timeline_menu.ui</file>
<file preprocess="xml-stripblanks">ui/widgets/list_item.ui</file>
<file preprocess="xml-stripblanks">ui/widgets/list_editor_item.ui</file>
<file preprocess="xml-stripblanks">ui/widgets/attachment_slot.ui</file>
@ -18,5 +18,6 @@
<file preprocess="xml-stripblanks">ui/dialogs/main.ui</file>
<file preprocess="xml-stripblanks">ui/dialogs/preferences.ui</file>
<file preprocess="xml-stripblanks">ui/dialogs/list_editor.ui</file>
<file preprocess="xml-stripblanks">ui/menus.ui</file>
</gresource>
</gresources>

View File

@ -4,7 +4,6 @@
<requires lib="gtk+" version="3.20"/>
<template class="TootleDialogsCompose" parent="GtkWindow">
<property name="can_focus">False</property>
<property name="modal">True</property>
<property name="default_width">500</property>
<property name="default_height">250</property>
<property name="type_hint">dialog</property>
@ -360,6 +359,7 @@
<object class="GtkLabel" id="commit_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Publish</property>
</object>
<packing>
<property name="name">ready</property>

View File

@ -29,7 +29,7 @@
</object>
<packing>
<property name="name">0</property>
<property name="title" translatable="no">0</property>
<property name="title">0</property>
</packing>
</child>
</object>
@ -145,6 +145,7 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Compose</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>

View File

@ -42,7 +42,7 @@
<property name="title" translatable="yes">Behavior</property>
<child>
<object class="HdyActionRow">
<property name="visible">True</property>
<property name="visible">False</property>
<property name="activatable_widget">autostart</property>
<property name="title" translatable="yes">Autostart</property>
<property name="subtitle" translatable="yes">Start minimized at boot</property>
@ -56,7 +56,7 @@
</child>
<child>
<object class="HdyActionRow">
<property name="visible">True</property>
<property name="visible">False</property>
<property name="activatable_widget">work_in_background</property>
<property name="title" translatable="yes">Background work</property>
<property name="subtitle" translatable="yes">Receive notifications even when the app window is closed</property>

88
data/ui/menus.ui Normal file
View File

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<menu id="profile-menu">
<section>
<submenu id="profile-filter-menu">
<attribute name="label" translatable="yes">Filter</attribute>
<item>
<attribute name="label" translatable="yes">Posts</attribute>
<attribute name="action">view.source</attribute>
<attribute name="target">statuses</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Following</attribute>
<attribute name="action">view.source</attribute>
<attribute name="target">following</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Followers</attribute>
<attribute name="action">view.source</attribute>
<attribute name="target">followers</attribute>
</item>
<section>
<item>
<attribute name="label" translatable="yes">With Replies</attribute>
<attribute name="action">view.include-replies</attribute>
<attribute name="hidden-when">action-disabled</attribute>
</item>
<item>
<attribute name="label" translatable="yes">With Media</attribute>
<attribute name="action">view.only-media</attribute>
<attribute name="hidden-when">action-disabled</attribute>
</item>
</section>
</submenu>
</section>
<section>
<item>
<attribute name="label" translatable="yes">Mention…</attribute>
<attribute name="action">view.mention</attribute>
<attribute name="target">public</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Direct Message…</attribute>
<attribute name="action">view.mention</attribute>
<attribute name="target">direct</attribute>
</item>
</section>
<section>
<!-- <submenu> -->
<!-- <attribute name="label" translatable="yes">Moderation</attribute> -->
<item>
<attribute name="label" translatable="yes">Hide Boosts</attribute>
<attribute name="action">view.hiding_reblogs</attribute>
<attribute name="hidden-when">action-disabled</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Mute</attribute>
<attribute name="action">view.muting</attribute>
</item>
<section>
<item>
<attribute name="label" translatable="yes">Report</attribute>
<attribute name="action">view.report</attribute>
<!-- TODO: Reporting users -->
<attribute name="hidden-when">action-missing</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Block</attribute>
<attribute name="action">view.blocking</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Block Domain</attribute>
<attribute name="action">view.domain_blocking</attribute>
<attribute name="hidden-when">action-disabled</attribute>
</item>
</section>
<!-- </submenu> -->
</section>
</menu>
</interface>

View File

@ -54,10 +54,12 @@
<property name="height_request">128</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="opacity">0.30196078431372547</property>
<property name="pixel_size">128</property>
<property name="icon_name">com.github.bleakgrey.tootle-symbolic</property>
<property name="icon_size">0</property>
<style>
<class name="dim-label"/>
</style>
</object>
<packing>
<property name="expand">False</property>
@ -69,7 +71,6 @@
<object class="GtkStack" id="status_stack">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="opacity">0.5019607843137255</property>
<property name="transition_type">crossfade</property>
<child>
<object class="GtkLabel" id="status_message_label">
@ -79,7 +80,6 @@
</object>
<packing>
<property name="name">message</property>
<property name="title" translatable="no">page0</property>
</packing>
</child>
<child>
@ -92,10 +92,12 @@
</object>
<packing>
<property name="name">spinner</property>
<property name="title" translatable="no">page1</property>
<property name="position">1</property>
</packing>
</child>
<style>
<class name="dim-label"/>
</style>
</object>
<packing>
<property name="expand">False</property>

View File

@ -1,320 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.36.0 -->
<interface>
<requires lib="gtk+" version="3.22"/>
<object class="GtkPopover" id="popover">
<property name="can_focus">False</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_start">8</property>
<property name="margin_end">8</property>
<property name="margin_top">8</property>
<property name="margin_bottom">8</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">8</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Show:</property>
<property name="xalign">0</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">8</property>
<child>
<object class="GtkRadioButton" id="radio_source">
<property name="name">statuses</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">0 Posts</property>
<property name="xalign">0</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="radio_source_following">
<property name="name">following</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<property name="group">radio_source</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">0 Follows</property>
<property name="xalign">0</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="radio_source_followers">
<property name="name">followers</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<property name="group">radio_source</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">0 Followers</property>
<property name="xalign">0</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkRevealer" id="post_filter">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkSeparator">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">8</property>
<property name="margin_bottom">8</property>
<property name="orientation">vertical</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Filter:</property>
<property name="xalign">0</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">8</property>
<property name="orientation">vertical</property>
<property name="spacing">8</property>
<child>
<object class="GtkRadioButton" id="radio_post_filter">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">None</property>
<property name="xalign">0</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="radio_post_filter_with_replies">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<property name="group">radio_post_filter</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Include Replies</property>
<property name="xalign">0</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="radio_post_only_media">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<property name="group">radio_post_filter</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Only Media</property>
<property name="xalign">0</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<template class="TootleWidgetsTimelineFilter" parent="GtkMenuButton">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="receives_default">True</property>
<property name="draw_indicator">True</property>
<property name="popover">popover</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="title">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label">User</property>
<property name="ellipsize">end</property>
<property name="single_line_mode">True</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">pan-down-symbolic</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<style>
<class name="flat"/>
<class name="header-title-buttonx"/>
</style>
</template>
</interface>

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.36.0 -->
<interface>
<requires lib="gtk+" version="3.22"/>
<template class="TootleWidgetsTimelineMenu" parent="GtkMenuButton">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="receives_default">True</property>
<property name="draw_indicator">True</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="title">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label">User</property>
<property name="ellipsize">end</property>
<property name="single_line_mode">True</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">pan-down-symbolic</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<style>
<class name="flat"/>
<class name="header-title-buttonx"/>
</style>
</template>
<object class="GtkPopover" id="popover">
<property name="can_focus">False</property>
<child>
<placeholder/>
</child>
</object>
</interface>

View File

@ -1,4 +1,15 @@
project('com.github.bleakgrey.tootle', 'vala', 'c')
project('com.github.bleakgrey.tootle', 'vala', 'c', version: '1.0.0')
config = configuration_data()
config.set('EXEC_NAME', meson.project_name())
config.set('GETTEXT_PACKAGE', meson.project_name())
config.set('RESOURCES', '/' + '/'.join(meson.project_name().split('.')) + '/' )
config.set('VERSION', meson.project_version())
config.set('PREFIX', get_option('prefix'))
config.set('NAME', 'Tootle')
config.set('WEBSITE', 'https://github.com/bleakgrey/tootle')
config.set('SUPPORT_WEBSITE', 'https://github.com/bleakgrey/tootle/issues')
config.set('COPYRIGHT', '© 2018-2020 bleak_grey')
gnome = import('gnome')
i18n = import('i18n')
@ -22,6 +33,12 @@ asresources = gnome.compile_resources(
c_name: 'as'
)
build_file = configure_file(
input: 'src/Build.vala',
output: 'Build.vala',
configuration: config
)
libhandy_dep = dependency('libhandy-1', version: '>= 0.83.0', required: false)
if not libhandy_dep.found()
libhandy = subproject(
@ -44,12 +61,13 @@ endif
executable(
meson.project_name(),
asresources,
'src/Build.vala',
build_file,
'src/Application.vala',
'src/Desktop.vala',
'src/Drawing.vala',
'src/Html.vala',
'src/Request.vala',
'src/DateTime.vala',
'src/InstanceAccount.vala',
'src/Services/Streams.vala',
'src/Services/Settings.vala',
@ -75,7 +93,7 @@ executable(
'src/Widgets/Widgetizable.vala',
'src/Widgets/Avatar.vala',
'src/Widgets/AccountsButton.vala',
'src/Widgets/TimelineFilter.vala',
'src/Widgets/TimelineMenu.vala',
'src/Widgets/RichLabel.vala',
'src/Widgets/Status.vala',
'src/Widgets/Notification.vala',
@ -86,6 +104,7 @@ executable(
'src/Widgets/Attachment/Picture.vala',
'src/Dialogs/ISavedWindow.vala',
'src/Dialogs/MainWindow.vala',
'src/Dialogs/About.vala',
'src/Dialogs/Compose.vala',
'src/Dialogs/Preferences.vala',
'src/Dialogs/ListEditor.vala',
@ -109,7 +128,6 @@ executable(
dependency('gtk+-3.0', version: '>=3.22.0'),
dependency('glib-2.0', version: '>=2.30.0'),
dependency('gee-0.8', version: '>=0.8.5'),
dependency('granite', version: '>=5.2.0'),
dependency('libsoup-2.4'),
dependency('json-glib-1.0', version: '>=1.4.4'),
libhandy_dep,

View File

@ -28,7 +28,9 @@ src/Drawing.vala
src/Html.vala
src/InstanceAccount.vala
src/Request.vala
src/Time.vala
src/Dialogs/About.vala
src/Dialogs/Compose.vala
src/Dialogs/ListEditor.vala
src/Dialogs/MainWindow.vala

View File

@ -20,7 +20,6 @@ public class Tootle.API.Account : Entity, Widgetizable {
public int64 followers_count { get; set; }
public int64 following_count { get; set; }
public int64 statuses_count { get; set; }
public Relationship? rs { get; set; default = null; }
public Gee.ArrayList<API.AccountField>? fields { get; set; default = null; }
public string handle {
@ -28,6 +27,12 @@ public class Tootle.API.Account : Entity, Widgetizable {
return "@" + acct;
}
}
public string domain {
owned get {
var uri = new Soup.URI (url);
return uri.get_host ();
}
}
public static Account from (Json.Node node) throws Error {
return Entity.from_json (typeof (API.Account), node) as API.Account;
@ -38,7 +43,7 @@ public class Tootle.API.Account : Entity, Widgetizable {
}
public override bool is_local (InstanceAccount account) {
return account.short_instance in url;
return account.domain in url;
}
public override Gtk.Widget to_widget () {
@ -61,53 +66,4 @@ public class Tootle.API.Account : Entity, Widgetizable {
}
}
public Request get_relationship () {
return new Request.GET ("/api/v1/accounts/relationships")
.with_account (accounts.active)
.with_param ("id", id.to_string ())
.then ((sess, msg) => {
Network.parse_array (msg, node => {
rs = API.Relationship.from (node);
});
})
.on_error (network.on_error)
.exec ();
}
public Request set_following (bool state = true) {
var action = state ? "follow" : "unfollow";
return new Request.POST (@"/api/v1/accounts/$id/$action")
.with_account (accounts.active)
.then ((sess, msg) => {
var node = network.parse_node (msg);
rs = API.Relationship.from (node);
})
.on_error (network.on_error)
.exec ();
}
public Request set_muted (bool state = true) {
var action = state ? "mute" : "unmute";
return new Request.POST (@"/api/v1/accounts/$id/$action")
.with_account (accounts.active)
.then ((sess, msg) => {
var node = network.parse_node (msg);
rs = API.Relationship.from (node);
})
.on_error (network.on_error)
.exec ();
}
public Request set_blocked (bool state = true) {
var action = state ? "block" : "unblock";
return new Request.POST (@"/api/v1/accounts/$id/$action")
.with_account (accounts.active)
.then ((sess, msg) => {
var node = network.parse_node (msg);
rs = API.Relationship.from (node);
})
.on_error (network.on_error)
.exec ();
}
}

View File

@ -2,7 +2,7 @@ using Json;
public class Tootle.Entity : GLib.Object, Widgetizable, Json.Serializable {
public static string[] ignore_props = {"formal", "handle", "short-instance", "has-spoiler"};
public static string[] ignore_props = {"formal", "handle", "domain", "has-spoiler"};
public virtual bool is_local (InstanceAccount account) {
return true;

View File

@ -5,6 +5,12 @@ public class Tootle.API.Mention : Entity, Widgetizable {
public string acct { get; construct set; }
public string url { get; construct set; }
public string handle {
owned get {
return "@" + acct;
}
}
public Mention.from_account (API.Account account) {
Object (
id: account.id,

View File

@ -1,16 +1,55 @@
public class Tootle.API.Relationship : Entity {
public string id { get; set; }
public bool following { get; set; default = false; }
public bool followed_by { get; set; default = false; }
public bool muting { get; set; default = false; }
public bool muting_notifications { get; set; default = false; }
public bool requested { get; set; default = false; }
public bool blocking { get; set; default = false; }
public bool domain_blocking { get; set; default = false; }
public string id { get; set; default = ""; }
public bool following { get; set; default = false; }
public bool followed_by { get; set; default = false; }
public bool showing_reblogs { get; set; default = true; }
public bool muting { get; set; default = false; }
public bool muting_notifications { get; set; default = false; }
public bool requested { get; set; default = false; }
public bool blocking { get; set; default = false; }
public bool domain_blocking { get; set; default = false; }
public static Relationship from (Json.Node node) throws Error {
return Entity.from_json (typeof (API.Relationship), node) as API.Relationship;
}
public Relationship.for_account (API.Account acc) {
Object (id: acc.id);
request ();
}
public void request () {
new Request.GET ("/api/v1/accounts/relationships")
.with_account (accounts.active)
.with_param ("id", id)
.then ((sess, msg) => {
Network.parse_array (msg, node => {
invalidate (node);
});
})
.exec ();
}
void invalidate (Json.Node node) throws Error {
var rs = Relationship.from (node);
patch (rs);
notify_property ("id");
}
public void modify (string operation, string? param = null, string? val = null) {
var req = new Request.POST (@"/api/v1/accounts/$id/$operation")
.with_account (accounts.active)
.then ((sess, msg) => {
var node = network.parse_node (msg);
invalidate (node);
message (@"Performed \"$operation\" on Relationship $id");
});
if (param != null)
req.with_param (param, val);
req.exec ();
}
}

View File

@ -93,7 +93,7 @@ public class Tootle.API.Status : Entity, Widgetizable {
public string get_reply_mentions () {
var result = "";
if (account.acct != accounts.active.acct)
result = "@%s ".printf (account.acct);
result = @"$(account.handle) ";
if (mentions != null) {
foreach (var mention in mentions) {
@ -101,7 +101,7 @@ public class Tootle.API.Status : Entity, Widgetizable {
var already_mentioned = mention.acct in result;
if (!equals_current && ! already_mentioned)
result += "@%s ".printf (mention.acct);
result += @"$(mention.handle) ";
}
}

View File

@ -1,163 +1,177 @@
using Gtk;
using Granite;
namespace Tootle {
public errordomain Oopsie {
USER,
PARSING,
INSTANCE,
INTERNAL
}
public errordomain Oopsie {
USER,
PARSING,
INSTANCE,
INTERNAL
}
public static Application app;
public static Dialogs.MainWindow? window;
public static Window window_dummy;
public static Application app;
public static Dialogs.MainWindow? window;
public static Window window_dummy;
public static Settings settings;
public static Accounts accounts;
public static Network network;
public static Cache cache;
public static Streams streams;
public static Settings settings;
public static Accounts accounts;
public static Network network;
public static Cache cache;
public static Streams streams;
public static bool start_hidden = false;
public static bool start_hidden = false;
public class Application : Gtk.Application {
public class Application : Gtk.Application {
// These are used for the GTK Inspector
public Settings app_settings { get {return Tootle.settings; } }
public Accounts app_accounts { get {return Tootle.accounts; } }
public Network app_network { get {return Tootle.network; } }
public Cache app_cache { get {return Tootle.cache; } }
public Streams app_streams { get {return Tootle.streams; } }
// These are used for the GTK Inspector
public Settings app_settings { get {return Tootle.settings; } }
public Accounts app_accounts { get {return Tootle.accounts; } }
public Network app_network { get {return Tootle.network; } }
public Cache app_cache { get {return Tootle.cache; } }
public Streams app_streams { get {return Tootle.streams; } }
public signal void refresh ();
public signal void toast (string title);
public signal void error (string title, string text);
public signal void refresh ();
public signal void toast (string title);
public signal void error (string title, string text);
public const GLib.OptionEntry[] app_options = {
{ "hidden", 0, 0, OptionArg.NONE, ref start_hidden, "Do not show main window on start", null },
{ null }
};
public const GLib.OptionEntry[] app_options = {
{ "hidden", 0, 0, OptionArg.NONE, ref start_hidden, "Do not show main window on start", null },
{ null }
};
public const GLib.ActionEntry[] app_entries = {
{"compose", compose_activated },
{"back", back_activated },
{"refresh", refresh_activated },
{"switch-timeline", switch_timeline_activated, "i" }
};
public const GLib.ActionEntry[] app_entries = {
{ "about", about_activated },
{ "compose", compose_activated },
{ "back", back_activated },
{ "refresh", refresh_activated },
{ "switch-timeline", switch_timeline_activated, "i" }
};
construct {
application_id = Build.DOMAIN;
flags = ApplicationFlags.FLAGS_NONE;
}
public string[] ACCEL_NEW_POST = {"<Ctrl>T"};
public string[] ACCEL_BACK = {"<Alt>BackSpace", "<Alt>Left"};
public string[] ACCEL_REFRESH = {"<Ctrl>R", "F5"};
public string[] ACCEL_TIMELINE_0 = {"<Alt>1"};
public string[] ACCEL_TIMELINE_1 = {"<Alt>2"};
public string[] ACCEL_TIMELINE_2 = {"<Alt>3"};
public string[] ACCEL_TIMELINE_3 = {"<Alt>4"};
public static int main (string[] args) {
Gtk.init (ref args);
try {
var opt_context = new OptionContext ("- Options");
opt_context.add_main_entries (app_options, null);
opt_context.parse (ref args);
}
catch (GLib.OptionError e) {
warning (e.message);
}
app = new Application ();
return app.run (args);
}
protected override void startup () {
base.startup ();
Build.print_info ();
Hdy.init ();
settings = new Settings ();
streams = new Streams ();
accounts = new Accounts ();
network = new Network ();
cache = new Cache ();
accounts.init ();
app.error.connect (app.on_error);
window_dummy = new Window ();
add_window (window_dummy);
set_accels_for_action ("app.compose", ACCEL_NEW_POST);
set_accels_for_action ("app.back", ACCEL_BACK);
set_accels_for_action ("app.refresh", ACCEL_REFRESH);
set_accels_for_action ("app.switch-timeline(0)", ACCEL_TIMELINE_0);
set_accels_for_action ("app.switch-timeline(1)", ACCEL_TIMELINE_1);
set_accels_for_action ("app.switch-timeline(2)", ACCEL_TIMELINE_2);
set_accels_for_action ("app.switch-timeline(3)", ACCEL_TIMELINE_3);
add_action_entries (app_entries, this);
}
protected override void activate () {
if (window != null) {
window.present ();
return;
}
if (start_hidden) {
start_hidden = false;
return;
}
info ("Creating new window");
window = new Dialogs.MainWindow (this);
window.present ();
}
protected void on_error (string title, string msg){
var message_dialog = new Granite.MessageDialog.with_image_from_icon_name (title, msg, "dialog-warning");
message_dialog.transient_for = window;
message_dialog.run ();
message_dialog.destroy ();
}
void compose_activated () {
new Dialogs.Compose ();
}
void back_activated () {
window.back ();
}
void refresh_activated () {
refresh ();
}
void switch_timeline_activated (SimpleAction a, Variant? v) {
int32 num = v.get_int32 ();
window.switch_timeline (num);
}
public bool question (string text, string? secondary = null, Gtk.Window? win = window) {
var dlg = new Gtk.MessageDialog (
window,
Gtk.DialogFlags.MODAL,
Gtk.MessageType.QUESTION,
Gtk.ButtonsType.YES_NO,
null
);
dlg.text = text;
dlg.secondary_text = secondary;
dlg.transient_for = win;
var i = dlg.run ();
dlg.destroy ();
return i == ResponseType.YES;
construct {
application_id = Build.DOMAIN;
flags = ApplicationFlags.FLAGS_NONE;
}
}
public string[] ACCEL_ABOUT = {"F1"};
public string[] ACCEL_NEW_POST = {"<Ctrl>T"};
public string[] ACCEL_BACK = {"<Alt>BackSpace", "<Alt>Left"};
public string[] ACCEL_REFRESH = {"<Ctrl>R", "F5"};
public string[] ACCEL_TIMELINE_0 = {"<Alt>1"};
public string[] ACCEL_TIMELINE_1 = {"<Alt>2"};
public string[] ACCEL_TIMELINE_2 = {"<Alt>3"};
public string[] ACCEL_TIMELINE_3 = {"<Alt>4"};
public static int main (string[] args) {
Gtk.init (ref args);
try {
var opt_context = new OptionContext ("- Options");
opt_context.add_main_entries (app_options, null);
opt_context.parse (ref args);
}
catch (GLib.OptionError e) {
warning (e.message);
}
app = new Application ();
return app.run (args);
}
protected override void startup () {
base.startup ();
Build.print_info ();
Hdy.init ();
settings = new Settings ();
streams = new Streams ();
accounts = new Accounts ();
network = new Network ();
cache = new Cache ();
accounts.init ();
app.error.connect (app.on_error);
window_dummy = new Window ();
add_window (window_dummy);
set_accels_for_action ("app.about", ACCEL_ABOUT);
set_accels_for_action ("app.compose", ACCEL_NEW_POST);
set_accels_for_action ("app.back", ACCEL_BACK);
set_accels_for_action ("app.refresh", ACCEL_REFRESH);
set_accels_for_action ("app.switch-timeline(0)", ACCEL_TIMELINE_0);
set_accels_for_action ("app.switch-timeline(1)", ACCEL_TIMELINE_1);
set_accels_for_action ("app.switch-timeline(2)", ACCEL_TIMELINE_2);
set_accels_for_action ("app.switch-timeline(3)", ACCEL_TIMELINE_3);
add_action_entries (app_entries, this);
}
protected override void activate () {
if (window != null) {
window.present ();
return;
}
if (start_hidden) {
start_hidden = false;
return;
}
info ("Creating new window");
window = new Dialogs.MainWindow (this);
window.present ();
}
void compose_activated () {
new Dialogs.Compose ();
}
void back_activated () {
window.back ();
}
void refresh_activated () {
refresh ();
}
void switch_timeline_activated (SimpleAction a, Variant? v) {
int32 num = v.get_int32 ();
window.switch_timeline (num);
}
void about_activated () {
new Dialogs.About ();
}
public void on_error (string title, string msg){
var dlg = new Gtk.MessageDialog (
window,
Gtk.DialogFlags.MODAL,
Gtk.MessageType.ERROR,
Gtk.ButtonsType.OK,
null
);
dlg.text = title;
dlg.secondary_text = msg;
dlg.transient_for = window;
dlg.run ();
dlg.destroy ();
}
public bool question (string text, string? secondary = null, Gtk.Window? win = window) {
var dlg = new Gtk.MessageDialog (
window,
Gtk.DialogFlags.MODAL,
Gtk.MessageType.QUESTION,
Gtk.ButtonsType.YES_NO,
null
);
dlg.text = text;
dlg.secondary_text = secondary;
dlg.transient_for = win;
var i = dlg.run ();
dlg.destroy ();
return i == ResponseType.YES;
}
}
}

View File

@ -1,22 +1,41 @@
public class Build {
public const string NAME = "Tootle";
public const string WEBSITE = "https://github.com/bleakgrey/tootle";
public const string DOMAIN = "com.github.bleakgrey.tootle";
public const string RESOURCES = "/com/github/bleakgrey/tootle/";
public const string VERSION = "1.0.0";
public const string NAME = "@NAME@";
public const string VERSION = "@VERSION@";
public const string DOMAIN = "@EXEC_NAME@";
public const string RESOURCES = "@RESOURCES@";
public const string WEBSITE = "@WEBSITE@";
public const string SUPPORT_WEBSITE = "@SUPPORT_WEBSITE@";
public const string COPYRIGHT = "@COPYRIGHT@";
public const string PREFIX = "@PREFIX@";
// Please do not remove the credits below. You may add your own, but keep the existing ones intact.
// TRANSLATORS: Replace this with your name. It will be displayed in the About dialog.
public const string TRANSLATOR = _(" ");
public static string[] get_authors () {
return new string[] {
"bleak_grey"
};
}
public static string[] get_artists () {
return new string[] {
"Tobias Bernard"
};
}
public static void print_info () {
var os_name = get_os_info ("NAME");
var os_ver = get_os_info ("VERSION");
message (@"$NAME $VERSION");
message (@"Running on: $os_name $os_ver");
message (@"Build type: FROM_SOURCE");
message (@"Build prefix: \"$PREFIX\"");
}
static string get_os_info (string key) {
var result = GLib.Environment.get_os_info (key);
return result == null ? "Unknown" : result;
return GLib.Environment.get_os_info (key) ?? "Unknown";
}
}

35
src/DateTime.vala Normal file
View File

@ -0,0 +1,35 @@
using GLib;
public class Tootle.DateTime {
public static string humanize (string iso8601) {
var date = new GLib.DateTime.from_iso8601 (iso8601, null);
var now = new GLib.DateTime.now_local ();
var delta = now.difference (date);
if (delta <= TimeSpan.MINUTE)
return _("Just now");
else if (delta < TimeSpan.HOUR) {
var minutes = delta / TimeSpan.MINUTE;
return _(@"$(minutes)m");
}
else if (delta <= TimeSpan.DAY) {
var hours = delta / TimeSpan.HOUR;
return _(@"$(hours)h");
}
else if (is_same_day (now, date.add_days (1))) {
return _("Yesterday");
}
else if (date.get_year () == now.get_year ()) {
return date.format (_("%b %e"));
}
else {
return date.format (_("%b %e, %Y"));
}
}
public static bool is_same_day (GLib.DateTime d1, GLib.DateTime d2) {
return (d1.get_day_of_year () == d2.get_day_of_year ()) && (d1.get_year () == d2.get_year ());
}
}

View File

@ -93,8 +93,4 @@ public class Tootle.Desktop {
return theme.load_icon (name, 32, Gtk.IconLookupFlags.GENERIC_FALLBACK);
}
public static void set_hotkey_tooltip (Gtk.Widget widget, string? description, string[] accelerators) {
widget.tooltip_markup = Granite.markup_accel_tooltip (accelerators, description);
}
}

28
src/Dialogs/About.vala Normal file
View File

@ -0,0 +1,28 @@
using Gtk;
public class Tootle.Dialogs.About : AboutDialog {
public About () {
Object (
transient_for: window,
modal: true,
logo_icon_name: Build.DOMAIN,
program_name: Build.NAME,
version: Build.VERSION,
website: Build.SUPPORT_WEBSITE,
website_label: _("Report an issue"),
license_type: License.GPL_3_0_ONLY,
copyright: Build.COPYRIGHT
);
// For some obscure reason, const arrays produce duplicates in the credits.
// Static functions seem to avoid this peculiar behavior.
authors = Build.get_authors ();
artists = Build.get_artists ();
translator_credits = Build.TRANSLATOR != " " ? Build.TRANSLATOR : null;
present ();
}
}

View File

@ -20,8 +20,6 @@ public class Tootle.Dialogs.Compose : Window {
Button commit;
[GtkChild]
Stack commit_stack;
[GtkChild]
Label commit_label;
[GtkChild]
Revealer cw_revealer;
@ -93,7 +91,7 @@ public class Tootle.Dialogs.Compose : Window {
notify["working"].connect (on_state_change);
commit_label.label = label;
mode_switcher.title = label;
commit.get_style_context ().add_class (style_class);
visibility_popover = new Widgets.VisibilityPopover.with_button (visibility_button);
@ -117,15 +115,16 @@ public class Tootle.Dialogs.Compose : Window {
validate ();
set_media_mode (status.has_media ());
show ();
content.grab_focus ();
}
public Compose () {
public Compose (API.Status template = new API.Status.empty ()) {
Object (
status: new API.Status.empty (),
status: template,
style_class: STYLE_CLASS_SUGGESTED_ACTION,
label: _("Publish")
label: _("Compose")
);
message ("Editing empty status");
message ("Composing status template");
set_visibility (status.visibility);
}
@ -135,8 +134,8 @@ public class Tootle.Dialogs.Compose : Window {
style_class: STYLE_CLASS_DESTRUCTIVE_ACTION,
label: _("Redraft")
);
set_visibility (status.visibility);
message (@"Redrafting status $(status.id)");
set_visibility (status.visibility);
status.media_attachments.@foreach (a => {
media_list.insert (new MediaItem (this, null, a), 0);
return true;
@ -147,14 +146,15 @@ public class Tootle.Dialogs.Compose : Window {
var template = new API.Status.empty ();
template.in_reply_to_id = to.id.to_string ();
template.in_reply_to_account_id = to.account.id.to_string ();
template.spoiler_text = to.spoiler_text;
template.content = to.formal.get_reply_mentions ();
Object (
status: template,
style_class: STYLE_CLASS_SUGGESTED_ACTION,
label: _("Reply")
);
set_visibility (to.visibility);
message (@"Replying to status $(status.in_reply_to_id)");
set_visibility (to.visibility);
}
void set_visibility (API.Visibility v) {

View File

@ -34,10 +34,7 @@ public class Tootle.Dialogs.MainWindow: Gtk.Window, ISavedWindow {
construct {
back_button.clicked.connect (() => back ());
Desktop.set_hotkey_tooltip (back_button, _("Back"), app.ACCEL_BACK);
compose_button.clicked.connect (() => new Dialogs.Compose ());
Desktop.set_hotkey_tooltip (compose_button, _("Compose"), app.ACCEL_NEW_POST);
timeline_switcher.stack = timeline_stack;
timeline_switcher.valign = Align.FILL;

View File

@ -15,17 +15,10 @@ public class Tootle.InstanceAccount : API.Account, IStreamListener {
protected string? stream;
public new string handle {
owned get { return @"@$username@$short_instance"; }
}
public string short_instance {
owned get {
return instance
.replace ("https://", "")
.replace ("/","");
}
owned get { return @"@$username@$domain"; }
}
public static InstanceAccount from (Json.Node node) throws Error {
public new static InstanceAccount from (Json.Node node) throws Error {
return Entity.from_json (typeof (InstanceAccount), node) as InstanceAccount;
}
@ -74,7 +67,7 @@ public class Tootle.InstanceAccount : API.Account, IStreamListener {
var notification = new GLib.Notification (title);
if (obj.status != null) {
var body = "";
body += short_instance;
body += domain;
body += "\n";
body += Html.remove_tags (obj.status.content);
notification.set_body (body);

View File

@ -2,37 +2,43 @@ using Gtk;
public class Tootle.Views.Profile : Views.Timeline {
public API.Account profile { get; construct set; }
public API.Account profile { get; construct set; }
public API.Relationship rs { get; construct set; }
public bool include_replies { get; set; default = false; }
public bool only_media { get; set; default = false; }
public string source { get; set; default = "statuses"; }
SimpleActionGroup actions;
SimpleAction media_action;
SimpleAction replies_action;
SimpleAction muting_action;
SimpleAction hiding_reblogs_action;
SimpleAction blocking_action;
SimpleAction domain_blocking_action;
ListBox profile_list;
Label relationship;
Widgets.TimelineFilter filter;
Label relationship;
Widgets.TimelineMenu menu_button;
Button rs_button;
Label rs_button_label;
weak ListBoxRow note_row;
public bool exclude_replies { get; set; default = true; }
public bool only_media { get; set; default = false; }
construct {
build_actions ();
menu_button = new Widgets.TimelineMenu ("profile-menu");
construct {
profile.notify["rs"].connect (on_rs_updated);
var builder = new Builder.from_resource (@"$(Build.RESOURCES)ui/views/profile_header.ui");
profile_list = builder.get_object ("profile_list") as ListBox;
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;
var hdr = builder.get_object ("grid") as Grid;
column_view.pack_start (hdr, false, false, 0);
column_view.reorder_child (hdr, 0);
var avatar = builder.get_object ("avatar") as Widgets.Avatar;
avatar.url = profile.avatar;
profile.bind_property ("display-name", filter.title, "label", BindingFlags.SYNC_CREATE);
profile.bind_property ("display-name", menu_button.title, "label", BindingFlags.SYNC_CREATE);
var handle = builder.get_object ("handle") as Widgets.RichLabel;
profile.bind_property ("acct", handle, "text", BindingFlags.SYNC_CREATE, (b, src, ref target) => {
@ -51,62 +57,149 @@ public class Tootle.Views.Profile : Views.Timeline {
});
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 (@"<b>$val</b>"));
// 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 (@"<b>$val</b>"));
// 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 (@"<b>$val</b>"));
// return true;
// });
rs_button = builder.get_object ("rs_button") as Button;
rs_button.clicked.connect (on_rs_button_clicked);
rs_button_label = builder.get_object ("rs_button_label") as Label;
rs.notify["id"].connect (on_rs_updated);
rebuild_fields ();
}
}
public Profile (API.Account acc) {
Object (
profile: acc,
label: acc.acct,
url: @"/api/v1/accounts/$(acc.id)/statuses"
);
profile.get_relationship ();
}
~Profile () {
filter.destroy ();
}
public Profile (API.Account acc) {
Object (
profile: acc,
rs: new API.Relationship.for_account (acc),
label: acc.acct,
url: @"/api/v1/accounts/$(acc.id)/statuses"
);
}
~Profile () {
menu_button.destroy ();
}
void build_actions () {
actions = new SimpleActionGroup ();
media_action = new SimpleAction.stateful ("only-media", null, false);
media_action.change_state.connect (v => {
media_action.set_state (only_media = v.get_boolean ());
invalidate_actions (true);
});
actions.add_action (media_action);
replies_action = new SimpleAction.stateful ("include-replies", null, false);
replies_action.change_state.connect (v => {
replies_action.set_state (include_replies = v.get_boolean ());
invalidate_actions (true);
});
actions.add_action (replies_action);
var source_action = new SimpleAction.stateful ("source", VariantType.STRING, source);
source_action.change_state.connect (v => {
source = v.get_string ();
source_action.set_state (source);
accepts = source == "statuses" ? typeof (API.Status) : typeof (API.Account);
url = @"/api/v1/accounts/$(profile.id)/$source";
invalidate_actions (true);
});
actions.add_action (source_action);
var mention_action = new SimpleAction ("mention", VariantType.STRING);
mention_action.activate.connect (v => {
var status = new API.Status.empty ();
status.visibility = API.Visibility.from_string (v.get_string ());
status.content = @"$(profile.handle) ";
new Dialogs.Compose (status);
});
actions.add_action (mention_action);
muting_action = new SimpleAction.stateful ("muting", null, false);
muting_action.change_state.connect (v => {
var state = v.get_boolean ();
rs.modify (state ? "mute" : "unmute");
});
actions.add_action (muting_action);
hiding_reblogs_action = new SimpleAction.stateful ("hiding_reblogs", null, false);
hiding_reblogs_action.change_state.connect (v => {
var state = !v.get_boolean ();
rs.modify ("follow", "reblogs", @"$state");
});
actions.add_action (hiding_reblogs_action);
blocking_action = new SimpleAction.stateful ("blocking", null, false);
blocking_action.change_state.connect (v => {
var block = v.get_boolean ();
var q = block ? _("Block \"%s\"?") : _("Unblock \"%s\"?");
var yes = app.question (q.printf (profile.handle));
if (yes)
rs.modify (block ? "block" : "unblock");
});
actions.add_action (blocking_action);
domain_blocking_action = new SimpleAction.stateful ("domain_blocking", null, false);
domain_blocking_action.change_state.connect (v => {
var block = v.get_boolean ();
var q = block ? _("Block Entire \"%s\"?") : _("Unblock Entire \"%s\"?");
var yes = app.question (
q.printf (profile.domain),
_("Blocking a domain will:\n\n• Remove its public posts and notifications from your timelines\n• Remove its followers from your account\n• Prevent you from following its users")
);
if (yes) {
var req = new Request.POST ("/api/v1/domain_blocks")
.with_account (accounts.active)
.with_param ("domain", profile.domain)
.then (() => {
rs.request ();
});
if (!block) req.method = "DELETE";
req.exec ();
}
});
actions.add_action (domain_blocking_action);
invalidate_actions (false);
}
void invalidate_actions (bool refresh) {
replies_action.set_enabled (accepts == typeof (API.Status));
media_action.set_enabled (accepts == typeof (API.Status));
muting_action.set_state (rs.muting);
hiding_reblogs_action.set_state (!rs.showing_reblogs);
hiding_reblogs_action.set_enabled (rs.following);
blocking_action.set_state (rs.blocking);
domain_blocking_action.set_state (rs.domain_blocking);
domain_blocking_action.set_enabled (accounts.active.domain != profile.domain);
if (refresh) {
page_next = null;
on_refresh ();
}
}
public override void on_shown () {
window.header.custom_title = filter;
filter.valign = Align.FILL;
window.insert_action_group ("view", actions);
window.header.custom_title = menu_button;
menu_button.valign = Align.FILL;
window.set_header_controls (rs_button);
}
public override void on_hidden () {
window.insert_action_group ("view", null);
window.header.custom_title = null;
window.reset_header_controls ();
}
void on_rs_button_clicked () {
rs_button.sensitive = false;
profile.set_following (!profile.rs.following);
rs.modify (rs.following ? "unfollow" : "follow");
}
void on_rs_updated () {
var rs = profile.rs;
var label = "";
if (rs_button.sensitive = rs != null) {
if (rs.requested)
@ -116,42 +209,39 @@ public class Tootle.Views.Profile : Views.Timeline {
else if (rs.followed_by)
label = _("Follows you");
foreach (Widget w in new Widget[] { rs_button }) {
var ctx = w.get_style_context ();
ctx.remove_class (STYLE_CLASS_SUGGESTED_ACTION);
ctx.remove_class (STYLE_CLASS_DESTRUCTIVE_ACTION);
ctx.add_class (rs.following ? STYLE_CLASS_DESTRUCTIVE_ACTION : STYLE_CLASS_SUGGESTED_ACTION);
}
var ctx = rs_button.get_style_context ();
ctx.remove_class (STYLE_CLASS_SUGGESTED_ACTION);
ctx.remove_class (STYLE_CLASS_DESTRUCTIVE_ACTION);
ctx.add_class (rs.following ? STYLE_CLASS_DESTRUCTIVE_ACTION : STYLE_CLASS_SUGGESTED_ACTION);
rs_button_label.label = rs.following ? _("Unfollow") : _("Follow");
}
relationship.label = label;
relationship.visible = label != "";
invalidate_actions (false);
}
public override Request append_params (Request req) {
if (page_next == null) {
if (exclude_replies)
req.with_param ("exclude_replies", "true");
if (only_media)
req.with_param ("only_media", "true");
if (page_next == null && source == "statuses") {
req.with_param ("exclude_replies", @"$(!include_replies)");
req.with_param ("only_media", @"$(only_media)");
return base.append_params (req);
}
else
return req;
else return req;
}
public static void open_from_id (string id) {
var msg = new Soup.Message ("GET", @"$(accounts.active.instance)/api/v1/accounts/$id");
network.queue (msg, (sess, mess) => {
var node = network.parse_node (mess);
var acc = API.Account.from (node);
window.open_view (new Views.Profile (acc));
}, (status, reason) => {
network.on_error (status, reason);
});
}
public static void open_from_id (string id) {
var msg = new Soup.Message ("GET", @"$(accounts.active.instance)/api/v1/accounts/$id");
network.queue (msg, (sess, mess) => {
var node = network.parse_node (mess);
var acc = API.Account.from (node);
window.open_view (new Views.Profile (acc));
}, (status, reason) => {
network.on_error (status, reason);
});
}
[GtkTemplate (ui = "/com/github/bleakgrey/tootle/ui/widgets/profile_field_row.ui")]
protected class Field : ListBoxRow {

View File

@ -111,10 +111,12 @@ public class Tootle.Views.Timeline : IAccountListener, IStreamListener, Views.Ba
})
.on_error (on_error);
req.exec ();
return GLib.Source.REMOVE;
}
public virtual void on_refresh () {
scrolled.vadjustment.value = 0;
status_button.sensitive = false;
clear ();
status_message = STATUS_LOADING;

View File

@ -86,7 +86,6 @@ public class Tootle.Widgets.AccountsButton : Gtk.MenuButton, IAccountListener {
item_refresh.clicked.connect (() => {
app.refresh ();
});
Desktop.set_hotkey_tooltip (item_refresh, null, app.ACCEL_REFRESH);
item_favs.clicked.connect (() => {
window.open_view (new Views.Favorites ());

View File

@ -1,5 +1,4 @@
using Gtk;
using Granite;
public class Tootle.Widgets.Notification : Widgets.Status {

View File

@ -74,9 +74,8 @@ public class Tootle.Widgets.Status : ListBoxRow {
protected string date {
owned get {
var date = new GLib.DateTime.from_iso8601 (status.formal.created_at, null);
var humanized = Granite.DateTime.get_relative_datetime (date);
return @"<small>$humanized</small>";
var date = status.formal.created_at;
return @"<small>$(DateTime.humanize (date))</small>";
}
}

View File

@ -1,59 +0,0 @@
using Gtk;
[GtkTemplate (ui = "/com/github/bleakgrey/tootle/ui/widgets/timeline_filter.ui")]
public class Tootle.Widgets.TimelineFilter : MenuButton {
weak Views.Profile view;
[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 profile) {
this.view = profile;
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 ();
}
}

View File

@ -0,0 +1,14 @@
using Gtk;
[GtkTemplate (ui = "/com/github/bleakgrey/tootle/ui/widgets/timeline_menu.ui")]
public class Tootle.Widgets.TimelineMenu : MenuButton {
[GtkChild]
public Label title;
public TimelineMenu (string id) {
var builder = new Builder.from_resource (@"$(Build.RESOURCES)ui/menus.ui");
menu_model = builder.get_object (id) as MenuModel;
}
}