2018-04-25 13:16:57 +00:00
using Gtk ;
2022-11-13 20:57:43 +00:00
public class Tooth . Views . Profile : Views . Timeline {
2019-03-07 16:16:52 +00:00
2020-09-05 08:02:42 +00:00
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 " ; }
2020-06-29 21:43:45 +00:00
2021-07-23 11:41:03 +00:00
protected Cover cover ;
2022-12-23 01:12:42 +00:00
protected Label cover_badge ;
2021-07-23 11:41:03 +00:00
protected MenuButton menu_button ;
2021-07-25 15:35:51 +00:00
protected Widgets . RelationshipButton rs_button ;
2021-07-23 11:41:03 +00:00
protected SimpleAction media_action ;
protected SimpleAction replies_action ;
protected SimpleAction muting_action ;
protected SimpleAction hiding_reblogs_action ;
protected SimpleAction blocking_action ;
protected SimpleAction domain_blocking_action ;
2022-12-22 19:37:54 +00:00
protected SimpleAction ar_list_action ;
2022-11-29 01:02:13 +00:00
// protected SimpleAction source_action;
2020-07-10 14:22:38 +00:00
2020-09-05 08:02:42 +00:00
construct {
2021-07-23 11:41:03 +00:00
cover = build_cover ( ) ;
2022-12-23 01:12:42 +00:00
cover_badge = cover . cover_badge ;
2021-07-23 11:41:03 +00:00
column_view . prepend ( cover ) ;
2020-09-05 08:02:42 +00:00
}
public Profile ( API . Account acc ) {
Object (
profile : acc ,
rs : new API . Relationship . for_account ( acc ) ,
2021-07-23 11:41:03 +00:00
label : _ ( " Profile " ) ,
2022-11-29 01:00:33 +00:00
is_profile : true ,
2020-09-05 08:02:42 +00:00
url : @" /api/v1/accounts/$(acc.id)/statuses "
) ;
2023-01-21 16:27:46 +00:00
append_pinned ( acc . id ) ;
2021-07-23 11:41:03 +00:00
cover . bind ( profile ) ;
2022-11-29 01:02:13 +00:00
build_profile_stats ( cover . info ) ;
2022-12-23 01:12:42 +00:00
rs . invalidated . connect ( on_rs_updated ) ;
2020-09-05 08:02:42 +00:00
}
2023-02-08 23:12:43 +00:00
~ Profile ( ) {
message ( " Destroying Profile view " ) ;
}
2020-09-05 08:02:42 +00:00
2023-01-21 16:27:46 +00:00
public void append_pinned ( string acc_id ) {
new Request . GET ( @" /api/v1/accounts/$(acc_id)/statuses " )
. with_account ( account )
. with_param ( " pinned " , " true " )
. with_ctx ( this )
. then ( ( sess , msg ) = > {
Network . parse_array ( msg , node = > {
var e = entity_cache . lookup_or_insert ( node , typeof ( API . Status ) ) ;
2023-01-22 08:12:04 +00:00
var e_status = e as API . Status ;
if ( e_status ! = null ) e_status . pinned = true ;
2023-01-21 16:27:46 +00:00
model . append ( e ) ; //FIXME: use splice();
} ) ;
} )
. exec ( ) ;
}
2023-02-24 04:15:06 +00:00
[ GtkTemplate ( ui = " /dev/geopjr/Tooth/ui/views/profile_header.ui " ) ]
2021-07-23 11:41:03 +00:00
protected class Cover : Box {
[ GtkChild ] unowned Widgets . Background background ;
2022-12-23 01:12:42 +00:00
[ GtkChild ] public unowned Label cover_badge ;
2022-11-29 01:02:13 +00:00
[ GtkChild ] public unowned ListBox info ;
2022-12-23 20:29:22 +00:00
[ GtkChild ] unowned Widgets . EmojiLabel display_name ;
2021-07-23 11:41:03 +00:00
[ GtkChild ] unowned Label handle ;
[ GtkChild ] unowned Widgets . Avatar avatar ;
[ GtkChild ] unowned Widgets . MarkupView note ;
public void bind ( API . Account account ) {
2022-12-23 20:29:22 +00:00
display_name . instance_emojis = account . emojis_map ;
display_name . label = account . display_name ;
2021-07-23 11:41:03 +00:00
handle . label = account . handle ;
avatar . account = account ;
note . content = account . note ;
2022-12-05 02:00:43 +00:00
if ( account . header . contains ( " /headers/original/missing.png " ) ) {
avatar . bind_property ( " custom_image " , background , " paintable " , GLib . BindingFlags . SYNC_CREATE ) ;
} else {
image_cache . request_paintable ( account . header , on_cache_response ) ;
}
2021-07-23 11:41:03 +00:00
if ( account . fields ! = null ) {
foreach ( API . AccountField f in account . fields ) {
var row = new Adw . ActionRow ( ) ;
2022-11-13 11:00:59 +00:00
var val = new Widgets . RichLabel ( HtmlUtils . simplify ( f . val ) ) ;
2022-11-21 15:25:36 +00:00
val . wrap = true ;
val . hexpand = true ;
2021-07-23 11:41:03 +00:00
val . xalign = 1 ;
row . title = f . name ;
info . append ( row ) ;
2023-02-01 14:19:34 +00:00
2023-02-23 23:42:35 +00:00
if ( f . verified_at ! = null ) {
var verified_date = f . verified_at . slice ( 0 , f . verified_at . last_index_of ( " T " ) ) ;
var verified_label_box = new Gtk . Box ( Gtk . Orientation . HORIZONTAL , 6 ) ;
var verified_checkmark = new Gtk . Image . from_icon_name ( " tooth-check-round-outline-symbolic " ) {
tooltip_text = _ ( @" Ownership of this link was checked on $verified_date " )
} ;
verified_label_box . append ( val ) ;
verified_label_box . append ( verified_checkmark ) ;
row . add_suffix ( verified_label_box ) ;
row . add_css_class ( " ttl-verified-field " ) ;
} else {
row . add_suffix ( val ) ;
} ;
2021-07-23 11:41:03 +00:00
}
2020-10-15 15:55:13 +00:00
}
2021-07-23 11:41:03 +00:00
}
void on_cache_response ( bool is_loaded , owned Gdk . Paintable ? data ) {
background . paintable = data ;
}
2022-11-29 01:02:13 +00:00
}
protected void build_profile_stats ( ListBox info ) {
2022-12-02 14:59:04 +00:00
var row = new Gtk . ListBoxRow ( ) ;
2023-01-08 10:24:40 +00:00
var box = new Box ( Orientation . HORIZONTAL , 0 ) {
2022-11-29 01:02:13 +00:00
homogeneous = true
} ;
2023-02-08 23:12:43 +00:00
var btn = build_profile_stats_button ( @" $(profile.statuses_count) " + _ ( " Posts " ) ) ;
btn . clicked . connect ( ( ) = > change_timeline_source ( " statuses " ) ) ;
box . append ( btn ) ;
btn = build_profile_stats_button ( @" $(profile.following_count) " + _ ( " Following " ) ) ;
btn . clicked . connect ( ( ) = > change_timeline_source ( " following " ) ) ;
box . append ( btn ) ;
btn = build_profile_stats_button ( @" $(profile.followers_count) " + _ ( " Followers " ) ) ;
btn . clicked . connect ( ( ) = > change_timeline_source ( " followers " ) ) ;
box . append ( btn ) ;
2022-11-29 01:02:13 +00:00
2022-12-02 14:59:04 +00:00
row . activatable = false ;
row . child = box ;
info . append ( row ) ;
2022-11-29 01:02:13 +00:00
}
2023-02-08 23:12:43 +00:00
protected Button build_profile_stats_button ( string btn_label ) {
2022-11-29 01:02:13 +00:00
var btn = new Button . with_label ( btn_label ) ;
btn . add_css_class ( " flat " ) ;
2022-12-02 15:00:45 +00:00
btn . add_css_class ( " ttl-profile-stat-button " ) ;
2021-07-23 11:41:03 +00:00
2022-12-12 22:58:52 +00:00
var child_label = btn . child as Label ;
child_label . wrap = true ;
child_label . justify = Justification . CENTER ;
2023-02-08 23:12:43 +00:00
return btn ;
}
2022-11-29 01:02:13 +00:00
2023-02-08 23:12:43 +00:00
protected void change_timeline_source ( string t_source ) {
2023-02-09 00:37:24 +00:00
source = t_source ;
2023-02-08 23:12:43 +00:00
accepts = t_source = = " statuses " ? typeof ( API . Status ) : typeof ( API . Account ) ;
2022-11-29 01:02:13 +00:00
2023-02-08 23:12:43 +00:00
url = @" /api/v1/accounts/$(profile.id)/$t_source " ;
invalidate_actions ( true ) ;
2021-07-23 11:41:03 +00:00
}
protected override void build_header ( ) {
base . build_header ( ) ;
menu_button = new MenuButton ( ) ;
var menu_builder = new Builder . from_resource ( @" $(Build.RESOURCES)ui/menus.ui " ) ;
var menu = " profile-menu " ;
menu_button . menu_model = menu_builder . get_object ( menu ) as MenuModel ;
menu_button . popover . width_request = 250 ;
2022-11-21 01:52:40 +00:00
menu_button . icon_name = " tooth-view-more-symbolic " ;
2021-07-23 11:41:03 +00:00
header . pack_end ( menu_button ) ;
2021-07-25 15:35:51 +00:00
rs_button = new Widgets . RelationshipButton ( ) {
rs = this . rs
} ;
if ( profile . id ! = accounts . active . id )
header . pack_end ( rs_button ) ;
2021-07-23 11:41:03 +00:00
}
protected virtual Cover build_cover ( ) {
return new Cover ( ) ;
2020-10-15 15:55:13 +00:00
}
2021-07-23 11:41:03 +00:00
protected override void build_actions ( ) {
base . build_actions ( ) ;
2020-09-05 08:02:42 +00:00
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 ) ;
2022-11-29 01:02:13 +00:00
// 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));
2020-09-05 08:02:42 +00:00
2022-11-29 01:02:13 +00:00
// url = @"/api/v1/accounts/$(profile.id)/$source";
// invalidate_actions (true);
// });
// actions.add_action (source_action);
2022-12-22 23:16:00 +00:00
ar_list_action = new SimpleAction ( " ar_list " , null ) ;
2022-12-22 19:37:54 +00:00
ar_list_action . activate . connect ( v = > {
create_ar_list_dialog ( ) . show ( ) ;
} ) ;
actions . add_action ( ar_list_action ) ;
2020-09-05 08:02:42 +00:00
var mention_action = new SimpleAction ( " mention " , VariantType . STRING ) ;
mention_action . activate . connect ( v = > {
var status = new API . Status . empty ( ) ;
2021-07-23 11:41:03 +00:00
status . visibility = v . get_string ( ) ;
2020-09-05 08:02:42 +00:00
status . content = @" $(profile.handle) " ;
new Dialogs . Compose ( status ) ;
} ) ;
actions . add_action ( mention_action ) ;
2020-10-15 15:55:13 +00:00
var copy_handle_action = new SimpleAction ( " copy_handle " , null ) ;
copy_handle_action . activate . connect ( v = > {
2022-12-03 17:50:50 +00:00
Host . copy ( profile . full_handle ) ;
2020-10-15 15:55:13 +00:00
} ) ;
actions . add_action ( copy_handle_action ) ;
2020-09-05 08:02:42 +00:00
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 = > {
2021-07-23 11:41:03 +00:00
if ( ! rs . following ) {
warning ( " Trying to hide boosts while not following an account. " ) ;
return ;
}
2020-09-05 08:02:42 +00:00
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 \" ? " ) ;
2021-07-25 15:35:51 +00:00
warning ( q ) ;
2020-09-05 08:02:42 +00:00
2022-11-16 21:23:00 +00:00
var confirmed = app . question (
q . printf ( profile . handle ) ,
null ,
app . main_window ,
block ? _ ( " Block " ) : _ ( " Unblock " ) ,
Adw . ResponseAppearance . DESTRUCTIVE
) ;
confirmed . response . connect ( res = > {
if ( res = = " yes " ) {
rs . modify ( block ? " block " : " unblock " ) ;
}
confirmed . destroy ( ) ;
} ) ;
confirmed . present ( ) ;
2020-09-05 08:02:42 +00:00
} ) ;
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 \" ? " ) ;
2021-07-25 15:35:51 +00:00
warning ( q ) ;
2022-11-16 21:23:00 +00:00
var confirmed = app . question (
2020-09-05 08:02:42 +00:00
q . printf ( profile . domain ) ,
2022-11-16 21:23:00 +00:00
_ ( " 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 " ) ,
app . main_window ,
block ? _ ( " Block " ) : _ ( " Unblock " ) ,
Adw . ResponseAppearance . DESTRUCTIVE
2020-09-05 08:02:42 +00:00
) ;
2022-11-16 21:23:00 +00:00
confirmed . response . connect ( res = > {
if ( res = = " yes " ) {
var req = new Request . POST ( " /api/v1/domain_blocks " )
2020-09-05 08:02:42 +00:00
. with_account ( accounts . active )
. with_param ( " domain " , profile . domain )
. then ( ( ) = > {
rs . request ( ) ;
} ) ;
if ( ! block ) req . method = " DELETE " ;
req . exec ( ) ;
2022-11-16 21:23:00 +00:00
}
confirmed . destroy ( ) ;
} ) ;
confirmed . present ( ) ;
2020-09-05 08:02:42 +00:00
} ) ;
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 ) ;
2022-12-22 23:16:00 +00:00
ar_list_action . set_enabled ( profile . id ! = accounts . active . id & & rs . following ) ;
2020-09-05 08:02:42 +00:00
if ( refresh ) {
page_next = null ;
on_refresh ( ) ;
}
}
2019-03-07 16:16:52 +00:00
2022-12-23 01:12:42 +00:00
void on_rs_updated ( ) {
var label = " " ;
if ( rs_button . sensitive = rs ! = null ) {
if ( rs . requested )
label = _ ( " Sent follow request " ) ;
else if ( rs . followed_by & & rs . following )
label = _ ( " Mutuals " ) ;
else if ( rs . followed_by )
label = _ ( " Follows you " ) ;
}
2021-07-23 11:41:03 +00:00
2022-12-23 01:12:42 +00:00
cover_badge . label = label ;
cover_badge . visible = label ! = " " ;
2020-09-05 08:02:42 +00:00
2022-12-23 01:12:42 +00:00
invalidate_actions ( false ) ;
}
2019-03-07 16:16:52 +00:00
2020-05-29 12:19:35 +00:00
public override Request append_params ( Request req ) {
2020-09-05 08:02:42 +00:00
if ( page_next = = null & & source = = " statuses " ) {
req . with_param ( " exclude_replies " , @" $(!include_replies) " ) ;
req . with_param ( " only_media " , @" $(only_media) " ) ;
2020-06-20 10:04:58 +00:00
return base . append_params ( req ) ;
}
2020-09-05 08:02:42 +00:00
else return req ;
2020-05-29 12:19:35 +00:00
}
2020-09-05 08:02:42 +00:00
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 ) ;
2021-07-23 11:41:03 +00:00
app . main_window . open_view ( new Views . Profile ( acc ) ) ;
} ,
network . on_error ) ;
2020-06-29 21:43:45 +00:00
}
2022-12-22 19:37:54 +00:00
public class RowButton : Button {
public bool remove { get ; set ; default = false ; }
}
public Adw . Window create_ar_list_dialog ( ) {
var spinner = new Spinner ( ) {
spinning = true ,
halign = Align . CENTER ,
valign = Align . CENTER ,
vexpand = true ,
hexpand = true ,
width_request = 32 ,
height_request = 32
} ;
var box = new Box ( Orientation . VERTICAL , 6 ) ;
var headerbar = new Adw . HeaderBar ( ) ;
var toast_overlay = new Adw . ToastOverlay ( ) {
vexpand = true ,
valign = Align . CENTER
} ;
toast_overlay . child = spinner ;
box . append ( headerbar ) ;
box . append ( toast_overlay ) ;
var dialog = new Adw . Window ( ) {
title = _ ( " Add or remove \" %s \" to or from a list " ) . printf ( profile . handle ) ,
modal = true ,
transient_for = app . main_window ,
content = box ,
default_width = 600 ,
default_height = 550
} ;
spinner . start ( ) ;
var preferences_page = new Adw . PreferencesPage ( ) ;
var preferences_group = new Adw . PreferencesGroup ( ) {
title = _ ( " Select the list to add or remove \" %s \" to or from: " ) . printf ( profile . handle )
} ;
var no_lists_page = new Adw . StatusPage ( ) {
icon_name = " tooth-error-symbolic " ,
vexpand = true ,
title = _ ( " You don't have any lists " )
} ;
new Request . GET ( @" /api/v1/lists/ " )
. with_account ( accounts . active )
. with_ctx ( this )
. on_error ( on_error )
. then ( ( sess , msg ) = > {
if ( Network . get_array_size ( msg ) > 0 ) {
new Request . GET ( @" /api/v1/accounts/$(profile.id)/lists " )
. with_account ( accounts . active )
. with_ctx ( this )
. on_error ( on_error )
. then ( ( sess2 , msg2 ) = > {
var added = false ;
var in_list = new Gee . ArrayList < string > ( ) ;
Network . parse_array ( msg2 , node = > {
var list = API . List . from ( node ) ;
in_list . add ( list . id ) ;
} ) ;
Network . parse_array ( msg , node = > {
var list = API . List . from ( node ) ;
var is_already = in_list . contains ( list . id ) ;
var add_button = new RowButton ( ) {
icon_name = is_already ? " tooth-minus-large-symbolic " : " tooth-plus-large-symbolic " ,
tooltip_text = is_already ? _ ( " Remove \" %s \" from \" %s \" " ) . printf ( profile . handle , list . title ) : _ ( " Add \" %s \" to \" %s \" " ) . printf ( profile . handle , list . title ) ,
halign = Align . CENTER ,
valign = Align . CENTER
} ;
add_button . add_css_class ( " flat " ) ;
add_button . add_css_class ( " circular " ) ;
add_button . remove = is_already ;
var row = new Adw . ActionRow ( ) {
title = list . title
} ;
row . add_suffix ( add_button ) ;
add_button . clicked . connect ( ( ) = > {
handle_list_edit ( list , row , toast_overlay , add_button ) ;
} ) ;
preferences_group . add ( row ) ;
added = true ;
} ) ;
if ( added ) {
preferences_page . add ( preferences_group ) ;
toast_overlay . child = preferences_page ;
toast_overlay . valign = Align . FILL ;
} else {
toast_overlay . child = no_lists_page ;
}
} )
. exec ( ) ;
} else {
toast_overlay . child = no_lists_page ;
}
} )
. exec ( ) ;
return dialog ;
}
public void handle_list_edit ( API . List list , Adw . ActionRow row , Adw . ToastOverlay toast_overlay , RowButton button ) {
row . sensitive = false ;
var endpoint = @" /api/v1/lists/$(list.id)/accounts/?account_ids[]=$(profile.id) " ;
var req = button . remove ? new Request . DELETE ( endpoint ) : new Request . POST ( endpoint ) ;
req
. with_account ( accounts . active )
. with_ctx ( this )
. on_error ( on_error )
. then ( ( sess , msg ) = > {
var toast_msg = " " ;
if ( button . remove ) {
2023-02-24 04:19:16 +00:00
// translators: First variable is a handle, second variable is a list name
2022-12-22 19:37:54 +00:00
toast_msg = _ ( " User \" %s \" got removed from \" %s \" " ) . printf ( profile . handle , list . title ) ;
button . icon_name = " tooth-plus-large-symbolic " ;
2023-02-24 04:19:16 +00:00
// translators: First variable is a handle, second variable is a list name
2022-12-22 19:37:54 +00:00
button . tooltip_text = _ ( " Add \" %s \" to \" %s \" " ) . printf ( profile . handle , list . title ) ;
} else {
2023-02-24 04:19:16 +00:00
// translators: First variable is a handle, second variable is a list name
2022-12-22 19:37:54 +00:00
toast_msg = _ ( " User \" %s \" got added to \" %s \" " ) . printf ( profile . handle , list . title ) ;
button . icon_name = " tooth-minus-large-symbolic " ;
2023-02-24 04:19:16 +00:00
// translators: First variable is a handle, second variable is a list name
2022-12-22 19:37:54 +00:00
button . tooltip_text = _ ( " Remove \" %s \" from \" %s \" " ) . printf ( profile . handle , list . title ) ;
}
button . remove = ! button . remove ;
row . sensitive = true ;
var toast = new Adw . Toast ( toast_msg ) ;
toast_overlay . add_toast ( toast ) ;
} )
. exec ( ) ;
}
2018-04-25 13:16:57 +00:00
}