@ -1,17 +1,22 @@
use adw ::{ prelude ::* , subclass ::prelude ::* } ;
use adw ::{ prelude ::* , subclass ::prelude ::* } ;
use gettextrs ::gettext ;
use gettextrs ::gettext ;
use gtk ::{ glib , glib ::clone , CompositeTemplate } ;
use gtk ::{ gio , g lib , glib ::clone , CompositeTemplate } ;
use tracing ::error ;
use tracing ::error ;
use crate ::{
use crate ::{
components ::{ LoadingBin , Spinner } ,
components ::{ LoadingBin , Spinner } ,
i18n ::gettext_f ,
session ::model ::{ NotificationsGlobalSetting , NotificationsSettings } ,
session ::model ::{ NotificationsGlobalSetting , NotificationsSettings } ,
spawn , toast ,
spawn , toast ,
utils ::BoundObjectWeakRef ,
utils ::{ BoundObjectWeakRef , DummyObject } ,
} ;
} ;
mod imp {
mod imp {
use std ::{ cell ::Cell , marker ::PhantomData } ;
use std ::{
cell ::{ Cell , RefCell } ,
collections ::HashMap ,
marker ::PhantomData ,
} ;
use glib ::subclass ::InitializingObject ;
use glib ::subclass ::InitializingObject ;
@ -41,6 +46,13 @@ mod imp {
pub global_mentions_bin : TemplateChild < LoadingBin > ,
pub global_mentions_bin : TemplateChild < LoadingBin > ,
#[ template_child ]
#[ template_child ]
pub global_mentions_radio : TemplateChild < gtk ::CheckButton > ,
pub global_mentions_radio : TemplateChild < gtk ::CheckButton > ,
#[ template_child ]
pub keywords : TemplateChild < gtk ::ListBox > ,
#[ template_child ]
pub keywords_add_entry : TemplateChild < adw ::EntryRow > ,
#[ template_child ]
pub keywords_add_bin : TemplateChild < LoadingBin > ,
pub keywords_suffixes : RefCell < HashMap < glib ::GString , LoadingBin > > ,
/// The notifications settings of the current session.
/// The notifications settings of the current session.
#[ property(get, set = Self::set_notifications_settings, explicit_notify) ]
#[ property(get, set = Self::set_notifications_settings, explicit_notify) ]
pub notifications_settings : BoundObjectWeakRef < NotificationsSettings > ,
pub notifications_settings : BoundObjectWeakRef < NotificationsSettings > ,
@ -76,7 +88,17 @@ mod imp {
}
}
#[ glib::derived_properties ]
#[ glib::derived_properties ]
impl ObjectImpl for NotificationsPage { }
impl ObjectImpl for NotificationsPage {
fn constructed ( & self ) {
self . parent_constructed ( ) ;
let obj = self . obj ( ) ;
self . keywords_add_entry
. connect_changed ( clone ! ( @ weak obj = > move | _ | {
obj . update_keywords ( ) ;
} ) ) ;
}
}
impl WidgetImpl for NotificationsPage { }
impl WidgetImpl for NotificationsPage { }
impl PreferencesPageImpl for NotificationsPage { }
impl PreferencesPageImpl for NotificationsPage { }
@ -116,6 +138,24 @@ mod imp {
global_setting_handler ,
global_setting_handler ,
] ,
] ,
) ;
) ;
let extra_items = gio ::ListStore ::new ::< glib ::Object > ( ) ;
extra_items . append ( & DummyObject ::new ( "add" ) ) ;
let all_items = gio ::ListStore ::new ::< glib ::Object > ( ) ;
all_items . append ( & settings . keywords_list ( ) ) ;
all_items . append ( & extra_items ) ;
let flattened_list = gtk ::FlattenListModel ::new ( Some ( all_items ) ) ;
self . keywords . bind_model (
Some ( & flattened_list ) ,
clone ! ( @ weak obj = > @ default - return { adw ::ActionRow ::new ( ) . upcast ( ) } , move | item | obj . create_keyword_row ( item ) ) ,
) ;
} else {
self . keywords . bind_model (
Option ::< & gio ::ListModel > ::None ,
clone ! ( @ weak obj = > @ default - return { adw ::ActionRow ::new ( ) . upcast ( ) } , move | item | obj . create_keyword_row ( item ) ) ,
) ;
}
}
obj . update_account ( ) ;
obj . update_account ( ) ;
@ -186,6 +226,7 @@ impl NotificationsPage {
// Other sections will be disabled or not.
// Other sections will be disabled or not.
self . update_global ( ) ;
self . update_global ( ) ;
self . update_keywords ( ) ;
}
}
/// Update the section about global.
/// Update the section about global.
@ -203,6 +244,24 @@ impl NotificationsPage {
imp . global . set_sensitive ( sensitive ) ;
imp . global . set_sensitive ( sensitive ) ;
}
}
/// Update the section about keywords.
fn update_keywords ( & self ) {
let Some ( settings ) = self . notifications_settings ( ) else {
return ;
} ;
let imp = self . imp ( ) ;
let sensitive = settings . account_enabled ( ) & & settings . session_enabled ( ) ;
imp . keywords . set_sensitive ( sensitive ) ;
if ! sensitive {
// Nothing else to update.
return ;
}
imp . keywords_add_bin . set_sensitive ( self . can_add_keyword ( ) ) ;
}
fn set_account_loading ( & self , loading : bool ) {
fn set_account_loading ( & self , loading : bool ) {
self . imp ( ) . account_loading . set ( loading ) ;
self . imp ( ) . account_loading . set ( loading ) ;
self . notify_account_loading ( ) ;
self . notify_account_loading ( ) ;
@ -291,4 +350,145 @@ impl NotificationsPage {
obj . update_global ( ) ;
obj . update_global ( ) ;
} ) ) ;
} ) ) ;
}
}
/// Create a row in the keywords list for the given item.
fn create_keyword_row ( & self , item : & glib ::Object ) -> gtk ::Widget {
let imp = self . imp ( ) ;
if let Some ( string_obj ) = item . downcast_ref ::< gtk ::StringObject > ( ) {
let keyword = string_obj . string ( ) ;
let row = adw ::ActionRow ::builder ( ) . title ( keyword . clone ( ) ) . build ( ) ;
let suffix = LoadingBin ::new ( ) ;
let remove_button = gtk ::Button ::builder ( )
. icon_name ( "close-symbolic" )
. valign ( gtk ::Align ::Center )
. halign ( gtk ::Align ::Center )
. css_classes ( [ "flat" ] )
. tooltip_text ( gettext_f ( "Remove “{keyword}”" , & [ ( "keyword" , & keyword ) ] ) )
. build ( ) ;
remove_button . connect_clicked ( clone ! ( @ weak self as obj , @ weak row = > move | _ | {
obj . remove_keyword ( row . title ( ) ) ;
} ) ) ;
suffix . set_child ( Some ( remove_button ) ) ;
row . add_suffix ( & suffix ) ;
// We need to keep track of suffixes to change their loading state.
imp . keywords_suffixes . borrow_mut ( ) . insert ( keyword , suffix ) ;
row . upcast ( )
} else {
// It can only be the dummy item to add a new keyword.
imp . keywords_add_entry . clone ( ) . upcast ( )
}
}
/// Remove the given keyword.
fn remove_keyword ( & self , keyword : glib ::GString ) {
let Some ( settings ) = self . notifications_settings ( ) else {
return ;
} ;
let Some ( suffix ) = self . imp ( ) . keywords_suffixes . borrow ( ) . get ( & keyword ) . cloned ( ) else {
return ;
} ;
suffix . set_is_loading ( true ) ;
spawn ! (
clone ! ( @ weak self as obj , @ weak settings , @ weak suffix = > async move {
if settings . remove_keyword ( keyword . to_string ( ) ) . await . is_err ( ) {
toast ! (
obj ,
gettext ( "Could not remove notification keyword" )
) ;
} else {
// The row should be removed.
obj . imp ( ) . keywords_suffixes . borrow_mut ( ) . remove ( & keyword ) ;
}
suffix . set_is_loading ( false ) ;
} )
) ;
}
/// Whether we can add the keyword that is currently in the entry.
fn can_add_keyword ( & self ) -> bool {
let imp = self . imp ( ) ;
// Cannot add a keyword is section is disabled.
if ! imp . keywords . is_sensitive ( ) {
return false ;
}
// Cannot add a keyword if a keyword is already being added.
if imp . keywords_add_bin . is_loading ( ) {
return false ;
}
let text = imp . keywords_add_entry . text ( ) . to_lowercase ( ) ;
// Cannot add an empty keyword.
if text . is_empty ( ) {
return false ;
}
// Cannot add a keyword without the API.
let Some ( settings ) = self . notifications_settings ( ) else {
return false ;
} ;
// Cannot add a keyword that already exists.
let keywords_list = settings . keywords_list ( ) ;
for keyword_obj in keywords_list . iter ::< glib ::Object > ( ) {
let Ok ( keyword_obj ) = keyword_obj else {
break ;
} ;
if let Some ( keyword ) = keyword_obj
. downcast_ref ::< gtk ::StringObject > ( )
. map ( | o | o . string ( ) )
{
if keyword . to_lowercase ( ) = = text {
return false ;
}
}
}
true
}
/// Add the keyword that is currently in the entry.
#[ template_callback ]
fn add_keyword ( & self ) {
let Some ( settings ) = self . notifications_settings ( ) else {
return ;
} ;
if ! self . can_add_keyword ( ) {
return ;
}
let imp = self . imp ( ) ;
imp . keywords_add_entry . set_sensitive ( false ) ;
imp . keywords_add_bin . set_is_loading ( true ) ;
spawn ! ( clone ! ( @ weak self as obj , @ weak settings = > async move {
let imp = obj . imp ( ) ;
let keyword = imp . keywords_add_entry . text ( ) . into ( ) ;
if settings . add_keyword ( keyword ) . await . is_err ( ) {
toast ! (
obj ,
gettext ( "Could not add notification keyword" )
) ;
} else {
// Adding the keyword was successful, reset the entry.
imp . keywords_add_entry . set_text ( "" ) ;
}
imp . keywords_add_bin . set_is_loading ( false ) ;
imp . keywords_add_entry . set_sensitive ( true ) ;
obj . update_keywords ( ) ;
} ) ) ;
}
}
}