[fractal/ui-refactor: 8/16] Use AppRuntime for better ergonomics for the reactive loop




commit 0f9db912176c7dd539860843a0727d04d49c37c8
Author: Alejandro Domínguez <adomu net-c com>
Date:   Thu Oct 15 10:43:14 2020 +0200

    Use AppRuntime for better ergonomics for the reactive loop

 fractal-gtk/src/actions/account_settings.rs      | 10 +--
 fractal-gtk/src/actions/global.rs                | 24 +++----
 fractal-gtk/src/actions/message.rs               | 74 ++++++++++-----------
 fractal-gtk/src/app/mod.rs                       | 65 +++++++++++--------
 fractal-gtk/src/appop/account.rs                 |  2 +-
 fractal-gtk/src/appop/connect/account.rs         | 40 ++++++------
 fractal-gtk/src/appop/connect/direct.rs          | 32 ++++-----
 fractal-gtk/src/appop/connect/directory.rs       | 14 ++--
 fractal-gtk/src/appop/connect/invite.rs          | 82 ++++++++++++------------
 fractal-gtk/src/appop/connect/join_room.rs       |  8 +--
 fractal-gtk/src/appop/connect/language.rs        | 26 ++++----
 fractal-gtk/src/appop/connect/leave_room.rs      |  4 +-
 fractal-gtk/src/appop/connect/markdown.rs        | 14 ++--
 fractal-gtk/src/appop/connect/new_room.rs        |  8 +--
 fractal-gtk/src/appop/connect/roomlist_search.rs |  4 +-
 fractal-gtk/src/appop/connect/send.rs            | 12 ++--
 fractal-gtk/src/appop/media_viewer.rs            |  3 +-
 fractal-gtk/src/appop/mod.rs                     |  8 +--
 fractal-gtk/src/appop/room.rs                    |  3 +-
 19 files changed, 224 insertions(+), 209 deletions(-)
---
diff --git a/fractal-gtk/src/actions/account_settings.rs b/fractal-gtk/src/actions/account_settings.rs
index 04cd63da..5dfe7322 100644
--- a/fractal-gtk/src/actions/account_settings.rs
+++ b/fractal-gtk/src/actions/account_settings.rs
@@ -5,14 +5,14 @@ use gio::SimpleAction;
 use gio::SimpleActionGroup;
 use glib::clone;
 
-use crate::app::{UpdateApp, RUNTIME};
+use crate::app::{AppRuntime, RUNTIME};
 
 use crate::widgets::FileDialog::open;
 
 use crate::actions::ButtonState;
 
 // This creates all actions a user can perform in the account settings
-pub fn new(window: &gtk::Window, app_tx: glib::Sender<UpdateApp>) -> gio::SimpleActionGroup {
+pub fn new(window: &gtk::Window, app_runtime: AppRuntime) -> gio::SimpleActionGroup {
     let actions = SimpleActionGroup::new();
     // TODO create two stats loading interaction and connect it to the avatar box
     let change_avatar =
@@ -21,9 +21,9 @@ pub fn new(window: &gtk::Window, app_tx: glib::Sender<UpdateApp>) -> gio::Simple
     actions.add_action(&change_avatar);
 
     change_avatar.connect_activate(clone!(@weak window => move |a, _| {
-        let _ = app_tx.send(Box::new(clone!(@weak a => move |op| {
+        app_runtime.update_state_with(clone!(@weak a => move |state| {
             let (session_client, uid) = unwrap_or_unit_return!(
-                op.login_data.as_ref().map(|ld| (ld.session_client.clone(), ld.uid.clone()))
+                state.login_data.as_ref().map(|ld| (ld.session_client.clone(), ld.uid.clone()))
             );
 
             let filter = gtk::FileFilter::new();
@@ -42,7 +42,7 @@ pub fn new(window: &gtk::Window, app_tx: glib::Sender<UpdateApp>) -> gio::Simple
                     }
                 });
             }
-        })));
+        }));
     }));
 
     actions
diff --git a/fractal-gtk/src/actions/global.rs b/fractal-gtk/src/actions/global.rs
index 9f3f1a9a..0c24ff37 100644
--- a/fractal-gtk/src/actions/global.rs
+++ b/fractal-gtk/src/actions/global.rs
@@ -4,7 +4,7 @@ use std::convert::TryInto;
 use std::rc::Rc;
 use std::sync::{Arc, Mutex};
 
-use crate::app::UpdateApp;
+use crate::app::AppRuntime;
 use crate::appop::AppOp;
 use crate::model::message::Message;
 use crate::util::i18n::i18n;
@@ -65,7 +65,7 @@ impl From<AppState> for glib::Variant {
 
 /* This creates globale actions which are connected to the application */
 /* TODO: Remove op */
-pub fn new(app: &gtk::Application, app_tx: glib::Sender<UpdateApp>, op: &Arc<Mutex<AppOp>>) {
+pub fn new(app: &gtk::Application, app_runtime: AppRuntime, op: &Arc<Mutex<AppOp>>) {
     let settings = SimpleAction::new("settings", None);
     let chat = SimpleAction::new("start_chat", None);
     let newr = SimpleAction::new("new_room", None);
@@ -267,7 +267,7 @@ pub fn new(app: &gtk::Application, app_tx: glib::Sender<UpdateApp>, op: &Arc<Mut
     media_viewer.connect_activate(clone!(
     @weak back_history as back
     => move |_, data| {
-        open_viewer(&app_tx, data.cloned());
+        open_viewer(&app_runtime, data.cloned());
         back.borrow_mut().push(AppState::MediaViewer);
     }));
 
@@ -382,24 +382,24 @@ pub(super) fn get_message_by_id(op: &AppOp, id: &EventId) -> Option<Message> {
     op.get_message_by_id(room_id, id)
 }
 
-fn open_viewer(app_tx: &glib::Sender<UpdateApp>, data: Option<glib::Variant>) {
-    let _ = app_tx.send(Box::new(move |op| {
+fn open_viewer(app_runtime: &AppRuntime, data: Option<glib::Variant>) {
+    app_runtime.update_state_with(move |state| {
         if let Some(msg) = get_event_id(data.as_ref())
             .as_ref()
-            .and_then(|evid| get_message_by_id(op, evid))
+            .and_then(|evid| get_message_by_id(state, evid))
         {
-            op.create_media_viewer(msg);
+            state.create_media_viewer(msg);
         }
-    }));
+    });
 }
 
 pub fn activate_action(
-    app_tx: &glib::Sender<UpdateApp>,
+    app_runtime: &AppRuntime,
     action_group_name: &'static str,
     action_name: &'static str,
 ) {
-    let _ = app_tx.send(Box::new(move |op| {
-        let main_window = op
+    app_runtime.update_state_with(move |state| {
+        let main_window = state
             .ui
             .builder
             .get_object::<gtk::Window>("main_window")
@@ -407,5 +407,5 @@ pub fn activate_action(
         if let Some(action_group) = main_window.get_action_group(action_group_name) {
             action_group.activate_action(action_name, None);
         }
-    }));
+    });
 }
diff --git a/fractal-gtk/src/actions/message.rs b/fractal-gtk/src/actions/message.rs
index 319f4d22..cc63cf04 100644
--- a/fractal-gtk/src/actions/message.rs
+++ b/fractal-gtk/src/actions/message.rs
@@ -8,7 +8,7 @@ use std::process::Command;
 use std::rc::Rc;
 
 use crate::actions::AppState;
-use crate::app::{UpdateApp, RUNTIME};
+use crate::app::{AppRuntime, RUNTIME};
 use crate::appop::AppOp;
 use crate::backend::HandleError;
 use crate::model::message::Message;
@@ -28,7 +28,7 @@ use crate::widgets::SourceDialog;
 
 /* This creates all actions the room history can perform */
 pub fn new(
-    app_tx: glib::Sender<UpdateApp>,
+    app_runtime: AppRuntime,
     ui: UI,
     back_history: Rc<RefCell<Vec<AppState>>>,
 ) -> gio::SimpleActionGroup {
@@ -59,18 +59,18 @@ pub fn new(
         .builder
         .get_object("main_window")
         .expect("Can't find main_window in ui file.");
-    show_source.connect_activate(clone!(@weak parent, @strong app_tx => move |_, data| {
+    show_source.connect_activate(clone!(@weak parent, @strong app_runtime => move |_, data| {
         let viewer = SourceDialog::new();
         viewer.set_parent_window(&parent);
         let data = data.cloned();
-        let _ = app_tx.send(Box::new(move |op| {
-            if let Some(m) = get_message(op, data.as_ref()) {
+        app_runtime.update_state_with(move |state| {
+            if let Some(m) = get_message(state, data.as_ref()) {
                 let error = i18n("This message has no source.");
                 let source = m.source.as_ref().unwrap_or(&error);
 
                 viewer.show(source);
             }
-        }));
+        });
     }));
 
     let window = ui
@@ -81,7 +81,7 @@ pub fn new(
     @weak back_history,
     @weak window,
     @weak ui.sventry.view as msg_entry,
-    @strong app_tx
+    @strong app_runtime
     => move |_, data| {
         let state = back_history.borrow().last().cloned();
         if let Some(AppState::MediaViewer) = state {
@@ -94,8 +94,8 @@ pub fn new(
         if let Some(buffer) = msg_entry.get_buffer() {
             let mut start = buffer.get_start_iter();
             let data = data.cloned();
-            let _ = app_tx.send(Box::new(move |op| {
-                if let Some(m) = get_message(op, data.as_ref()) {
+            app_runtime.update_state_with(move |state| {
+                if let Some(m) = get_message(state, data.as_ref()) {
                     let quote = m
                         .body
                         .lines()
@@ -107,16 +107,16 @@ pub fn new(
                     buffer.insert(&mut start, &quote);
                     msg_entry.grab_focus();
                 }
-            }));
+            });
         }
     }));
 
-    open_with.connect_activate(clone!(@strong app_tx => move |_, data| {
+    open_with.connect_activate(clone!(@strong app_runtime => move |_, data| {
         let data = data.cloned();
-        let _ = app_tx.send(Box::new(move |op| {
-            let url = unwrap_or_unit_return!(get_message(op, data.as_ref()).and_then(|m| m.url));
+        app_runtime.update_state_with(move |state| {
+            let url = unwrap_or_unit_return!(get_message(state, data.as_ref()).and_then(|m| m.url));
             let session_client =
-                unwrap_or_unit_return!(op.login_data.as_ref().map(|ld| ld.session_client.clone()));
+                unwrap_or_unit_return!(state.login_data.as_ref().map(|ld| ld.session_client.clone()));
             RUNTIME.spawn(async move {
                 match dw_media(session_client, &url, ContentType::Download, None).await {
                     Ok(fname) => {
@@ -128,18 +128,18 @@ pub fn new(
                     Err(err) => err.handle_error(),
                 }
             });
-        }));
+        });
     }));
 
     save_as.connect_activate(
-        clone!(@weak parent as window, @strong app_tx => move |_, data| {
+        clone!(@weak parent as window, @strong app_runtime => move |_, data| {
             let data = data.cloned();
-            let _ = app_tx.send(Box::new(move |op| {
+            app_runtime.update_state_with(move |state| {
                 let (url, name) = unwrap_or_unit_return!(
-                    get_message(op, data.as_ref()).and_then(|m| Some((m.url?, m.body)))
+                    get_message(state, data.as_ref()).and_then(|m| Some((m.url?, m.body)))
                 );
                 let session_client = unwrap_or_unit_return!(
-                    op.login_data.as_ref().map(|ld| ld.session_client.clone())
+                    state.login_data.as_ref().map(|ld| ld.session_client.clone())
                 );
                 let response = RUNTIME.spawn(async move {
                     media::get_media(session_client, &url).await
@@ -164,16 +164,16 @@ pub fn new(
                         }
                     }
                 });
-            }));
+            });
         }),
     );
 
-    copy_image.connect_activate(clone!(@strong app_tx => move |_, data| {
+    copy_image.connect_activate(clone!(@strong app_runtime => move |_, data| {
         let data = data.cloned();
-        let _ = app_tx.send(Box::new(move |op| {
-            let url = unwrap_or_unit_return!(get_message(op, data.as_ref()).and_then(|m| m.url));
+        app_runtime.update_state_with(move |state| {
+            let url = unwrap_or_unit_return!(get_message(state, data.as_ref()).and_then(|m| m.url));
             let session_client =
-                unwrap_or_unit_return!(op.login_data.as_ref().map(|ld| ld.session_client.clone()));
+                unwrap_or_unit_return!(state.login_data.as_ref().map(|ld| ld.session_client.clone()));
             let response =
                 RUNTIME.spawn(async move { media::get_media(session_client, &url).await });
 
@@ -195,25 +195,25 @@ pub fn new(
                     }
                 }
             });
-        }));
+        });
     }));
 
-    copy_text.connect_activate(clone!(@strong app_tx => move |_, data| {
+    copy_text.connect_activate(clone!(@strong app_runtime => move |_, data| {
         let data = data.cloned();
-        let _ = app_tx.send(Box::new(move |op| {
-            if let Some(m) = get_message(op, data.as_ref()) {
+        app_runtime.update_state_with(move |state| {
+            if let Some(m) = get_message(state, data.as_ref()) {
                 let atom = gdk::Atom::intern("CLIPBOARD");
                 let clipboard = gtk::Clipboard::get(&atom);
 
                 clipboard.set_text(&m.body);
             }
-        }));
+        });
     }));
 
     delete.connect_activate(clone!(
     @weak back_history,
     @weak window,
-    @strong app_tx
+    @strong app_runtime
     => move |_, data| {
         let state = back_history.borrow().last().cloned();
         if let Some(AppState::MediaViewer) = state {
@@ -224,10 +224,10 @@ pub fn new(
             }
         }
         let data = data.cloned();
-        let _ = app_tx.send(Box::new(move |op| {
-            let msg = unwrap_or_unit_return!(get_message(op, data.as_ref()));
+        app_runtime.update_state_with(move |state| {
+            let msg = unwrap_or_unit_return!(get_message(state, data.as_ref()));
             let session_client = unwrap_or_unit_return!(
-                op.login_data.as_ref().map(|ld| ld.session_client.clone())
+                state.login_data.as_ref().map(|ld| ld.session_client.clone())
             );
             RUNTIME.spawn(async move {
                 let query = room::redact_msg(session_client, msg).await;
@@ -235,15 +235,15 @@ pub fn new(
                     err.handle_error();
                 }
             });
-        }));
+        });
     }));
 
     load_more_messages.connect_activate(move |_, data| {
         let data = data.cloned();
-        let _ = app_tx.send(Box::new(move |op| {
+        app_runtime.update_state_with(move |state| {
             let id = get_room_id(data.as_ref());
-            request_more_messages(op, id);
-        }));
+            request_more_messages(state, id);
+        });
     });
 
     actions
diff --git a/fractal-gtk/src/app/mod.rs b/fractal-gtk/src/app/mod.rs
index 5af5efa8..bec097b1 100644
--- a/fractal-gtk/src/app/mod.rs
+++ b/fractal-gtk/src/app/mod.rs
@@ -22,12 +22,11 @@ mod windowstate;
 
 use windowstate::WindowState;
 
-type GlobalAppOp = Arc<Mutex<AppOp>>;
-pub type UpdateApp = Box<dyn FnOnce(&mut AppOp)>;
+type GlobalState = Arc<Mutex<AppOp>>;
 
-static mut APP_TX: Option<glib::Sender<UpdateApp>> = None;
+static mut APP_RUNTIME: Option<AppRuntime> = None;
 // TODO: Deprecated. It should be removed
-static mut OP: Option<GlobalAppOp> = None;
+static mut OP: Option<GlobalState> = None;
 
 lazy_static! {
     pub static ref RUNTIME: TokioRuntime = TokioRuntime::new().unwrap();
@@ -37,15 +36,42 @@ lazy_static! {
 macro_rules! APPOP {
     ($fn: ident, ($($x:ident),*) ) => {{
         $( let $x = $x.clone(); )*
-        let _ = crate::app::get_app_tx().send(Box::new(move |op| {
-            crate::appop::AppOp::$fn(op, $($x),*);
-        }));
+        crate::app::get_app_runtime().update_state_with(move |op| {
+            op.$fn($($x),*);
+        });
     }};
     ($fn: ident) => {{
         APPOP!($fn, ( ) );
     }}
 }
 
+#[derive(Clone)]
+pub struct AppRuntime(glib::Sender<Box<dyn FnOnce(&mut AppOp)>>);
+
+impl AppRuntime {
+    fn init(ui: uibuilder::UI) {
+        let (app_tx, app_rx) = glib::MainContext::channel(Default::default());
+        let app_runtime = Self(app_tx);
+        let state = AppOp::new(ui, app_runtime.clone());
+
+        unsafe {
+            OP = Some(Arc::new(Mutex::new(state)));
+            APP_RUNTIME = Some(app_runtime);
+        }
+
+        let state = get_op();
+        app_rx.attach(None, move |update_state| {
+            update_state(&mut state.lock().unwrap());
+
+            glib::Continue(true)
+        });
+    }
+
+    pub fn update_state_with(&self, update_fn: impl FnOnce(&mut AppOp) + 'static) {
+        let _ = self.0.send(Box::new(update_fn));
+    }
+}
+
 // Our application struct for containing all the state we have to carry around.
 // TODO: subclass gtk::Application once possible
 pub struct App {
@@ -73,21 +99,8 @@ impl App {
             gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
         );
 
-        let (app_tx, app_rx) = glib::MainContext::channel(Default::default());
         let ui = uibuilder::UI::new();
-        let op = AppOp::new(ui.clone(), app_tx.clone());
-
-        unsafe {
-            OP = Some(Arc::new(Mutex::new(op)));
-            APP_TX = Some(app_tx);
-        }
-
-        let op = get_op();
-        app_rx.attach(None, move |update_op: UpdateApp| {
-            update_op(&mut op.lock().unwrap());
-
-            glib::Continue(true)
-        });
+        AppRuntime::init(ui.clone());
 
         ui.main_window.set_application(Some(gtk_app));
 
@@ -162,11 +175,11 @@ impl App {
 
         gtk_app.set_accels_for_action("login.back", &["Escape"]);
 
-        actions::Global::new(gtk_app, get_app_tx().clone(), get_op());
+        actions::Global::new(gtk_app, get_app_runtime().clone(), get_op());
 
         let app = AppRef::new(Self { ui });
 
-        let _ = get_app_tx().send(Box::new(|op| op.connect_gtk()));
+        get_app_runtime().update_state_with(|state| state.connect_gtk());
 
         app
     }
@@ -222,13 +235,13 @@ impl App {
 }
 
 // TODO: Deprecated. It should be removed
-pub fn get_op() -> &'static GlobalAppOp {
+pub fn get_op() -> &'static GlobalState {
     unsafe { OP.as_ref().expect("Fatal: AppOp has not been initialized") }
 }
 
-pub fn get_app_tx() -> &'static glib::Sender<UpdateApp> {
+pub fn get_app_runtime() -> &'static AppRuntime {
     unsafe {
-        APP_TX
+        APP_RUNTIME
             .as_ref()
             .expect("Fatal: AppRuntime has not been initialized")
     }
diff --git a/fractal-gtk/src/appop/account.rs b/fractal-gtk/src/appop/account.rs
index 6e3c07e6..7b5610b3 100644
--- a/fractal-gtk/src/appop/account.rs
+++ b/fractal-gtk/src/appop/account.rs
@@ -201,7 +201,7 @@ impl AppOp {
         let dialog = self.create_error_dialog(error);
         dialog.connect_response(move |w, _| w.close());
         dialog.show_all();
-        activate_action(&self.app_tx, "app", "back");
+        activate_action(&self.app_runtime, "app", "back");
     }
 
     pub fn create_error_dialog(&self, error: String) -> gtk::MessageDialog {
diff --git a/fractal-gtk/src/appop/connect/account.rs b/fractal-gtk/src/appop/connect/account.rs
index f1e431e0..a665ab5e 100644
--- a/fractal-gtk/src/appop/connect/account.rs
+++ b/fractal-gtk/src/appop/connect/account.rs
@@ -7,7 +7,7 @@ use crate::appop::AppOp;
 use crate::actions::{AccountSettings, StateExt};
 
 pub fn connect(appop: &AppOp) {
-    let app_tx = appop.app_tx.clone();
+    let app_runtime = appop.app_runtime.clone();
     let builder = &appop.ui.builder;
     let cancel_password = appop
         .ui
@@ -71,7 +71,7 @@ pub fn connect(appop: &AppOp) {
         .expect("Can't find account_settings_delete_btn in ui file.");
 
     let window = appop.ui.main_window.upcast_ref::<gtk::Window>();
-    let actions = AccountSettings::new(&window, app_tx.clone());
+    let actions = AccountSettings::new(&window, app_runtime.clone());
     let container = appop
         .ui
         .builder
@@ -102,11 +102,11 @@ pub fn connect(appop: &AppOp) {
     }
 
     name_entry.connect_property_text_notify(
-        clone!(@strong app_tx, @strong name_btn as button => move |w| {
-            let _ = app_tx.send(Box::new(clone!(@strong w, @strong button => move |op| {
+        clone!(@strong app_runtime, @strong name_btn as button => move |w| {
+            app_runtime.update_state_with(clone!(@strong w, @strong button => move |state| {
                 let username = w.get_text();
                 if !username.is_empty()
-                    && op
+                    && state
                         .login_data
                         .as_ref()
                         .and_then(|login_data| login_data.username.as_ref())
@@ -117,7 +117,7 @@ pub fn connect(appop: &AppOp) {
                     return;
                 }
                 button.hide();
-            })));
+            }));
         }),
     );
 
@@ -126,8 +126,8 @@ pub fn connect(appop: &AppOp) {
         let _ = button.emit("clicked", &[]);
     });
 
-    name_btn.connect_clicked(clone!(@strong app_tx => move |_w| {
-        let _ = app_tx.send(Box::new(|op| op.update_username_account_settings()));
+    name_btn.connect_clicked(clone!(@strong app_runtime => move |_w| {
+        app_runtime.update_state_with(|state| state.update_username_account_settings());
     }));
 
     /*
@@ -184,25 +184,25 @@ pub fn connect(appop: &AppOp) {
     }
 
     /* Passsword dialog */
-    password_btn.connect_clicked(clone!(@strong app_tx => move |_| {
-        let _ = app_tx.send(Box::new(|op| op.show_password_dialog()));
+    password_btn.connect_clicked(clone!(@strong app_runtime => move |_| {
+        app_runtime.update_state_with(|state| state.show_password_dialog());
     }));
 
-    password_dialog.connect_delete_event(clone!(@strong app_tx => move |_, _| {
-        let _ = app_tx.send(Box::new(|op| op.close_password_dialog()));
+    password_dialog.connect_delete_event(clone!(@strong app_runtime => move |_, _| {
+        app_runtime.update_state_with(|state| state.close_password_dialog());
         glib::signal::Inhibit(true)
     }));
 
     /* Headerbar */
-    cancel_password.connect_clicked(clone!(@strong app_tx => move |_| {
-        let _ = app_tx.send(Box::new(|op| op.close_password_dialog()));
+    cancel_password.connect_clicked(clone!(@strong app_runtime => move |_| {
+        app_runtime.update_state_with(|state| state.close_password_dialog());
     }));
 
-    confirm_password.connect_clicked(clone!(@strong app_tx => move |_| {
-        let _ = app_tx.send(Box::new(|op| {
-            op.set_new_password();
-            op.close_password_dialog();
-        }));
+    confirm_password.connect_clicked(clone!(@strong app_runtime => move |_| {
+        app_runtime.update_state_with(|state| {
+            state.set_new_password();
+            state.close_password_dialog();
+        });
     }));
 
     /* Body */
@@ -225,6 +225,6 @@ pub fn connect(appop: &AppOp) {
     }));
 
     destruction_btn.connect_clicked(move |_| {
-        let _ = app_tx.send(Box::new(|op| op.account_destruction()));
+        app_runtime.update_state_with(|state| state.account_destruction());
     });
 }
diff --git a/fractal-gtk/src/appop/connect/direct.rs b/fractal-gtk/src/appop/connect/direct.rs
index eefce9e6..58eeb800 100644
--- a/fractal-gtk/src/appop/connect/direct.rs
+++ b/fractal-gtk/src/appop/connect/direct.rs
@@ -7,7 +7,7 @@ use std::sync::{Arc, Mutex};
 use crate::appop::AppOp;
 
 pub fn connect(appop: &AppOp) {
-    let app_tx = appop.app_tx.clone();
+    let app_runtime = appop.app_runtime.clone();
     let cancel = appop
         .ui
         .builder
@@ -52,7 +52,7 @@ pub fn connect(appop: &AppOp) {
     // this is used to cancel the timeout and not search for every key input. We'll wait 500ms
     // without key release event to launch the search
     let source_id: Arc<Mutex<Option<glib::source::SourceId>>> = Arc::new(Mutex::new(None));
-    to_chat_entry.connect_key_release_event(clone!(@strong app_tx => move |entry, _| {
+    to_chat_entry.connect_key_release_event(clone!(@strong app_runtime => move |entry, _| {
         {
             let mut id = source_id.lock().unwrap();
             if let Some(sid) = id.take() {
@@ -65,7 +65,7 @@ pub fn connect(appop: &AppOp) {
             clone!(
             @strong entry,
             @strong source_id,
-            @strong app_tx
+            @strong app_runtime
             => move || {
                 if let Some(buffer) = entry.get_buffer() {
                     let start = buffer.get_start_iter();
@@ -74,7 +74,7 @@ pub fn connect(appop: &AppOp) {
                     if let Some(text) =
                         buffer.get_text(&start, &end, false).map(|gstr| gstr.to_string())
                     {
-                        let _ = app_tx.send(Box::new(|op| op.search_invite_user(text)));
+                        app_runtime.update_state_with(|state| state.search_invite_user(text));
                     }
                 }
 
@@ -88,43 +88,43 @@ pub fn connect(appop: &AppOp) {
     }));
 
     to_chat_entry.connect_focus_in_event(
-        clone!(@strong to_chat_entry_box, @strong app_tx => move |_, _| {
+        clone!(@strong to_chat_entry_box, @strong app_runtime => move |_, _| {
             to_chat_entry_box.get_style_context().add_class("message-input-focused");
 
-            let _ = app_tx.send(Box::new(|op| op.remove_invite_user_dialog_placeholder()));
+            app_runtime.update_state_with(|state| state.remove_invite_user_dialog_placeholder());
 
             Inhibit(false)
         }),
     );
 
     to_chat_entry.connect_focus_out_event(
-        clone!(@strong to_chat_entry_box, @strong app_tx => move |_, _| {
+        clone!(@strong to_chat_entry_box, @strong app_runtime => move |_, _| {
             to_chat_entry_box.get_style_context().remove_class("message-input-focused");
 
-            let _ = app_tx.send(Box::new(|op| op.set_invite_user_dialog_placeholder()));
+            app_runtime.update_state_with(|state| state.set_invite_user_dialog_placeholder());
 
             Inhibit(false)
         }),
     );
 
     if let Some(buffer) = to_chat_entry.get_buffer() {
-        buffer.connect_delete_range(clone!(@strong app_tx => move |_, _, _| {
-            glib::idle_add_local(clone!(@strong app_tx => move || {
-                let _ = app_tx.send(Box::new(|op| op.detect_removed_invite()));
+        buffer.connect_delete_range(clone!(@strong app_runtime => move |_, _, _| {
+            glib::idle_add_local(clone!(@strong app_runtime => move || {
+                app_runtime.update_state_with(|state| state.detect_removed_invite());
                 Continue(false)
             }));
         }));
     }
 
-    dialog.connect_delete_event(clone!(@strong app_tx => move |_, _| {
-        let _ = app_tx.send(Box::new(|op| op.close_direct_chat_dialog()));
+    dialog.connect_delete_event(clone!(@strong app_runtime => move |_, _| {
+        app_runtime.update_state_with(|state| state.close_direct_chat_dialog());
         glib::signal::Inhibit(true)
     }));
-    cancel.connect_clicked(clone!(@strong app_tx => move |_| {
-        let _ = app_tx.send(Box::new(|op| op.close_direct_chat_dialog()));
+    cancel.connect_clicked(clone!(@strong app_runtime => move |_| {
+        app_runtime.update_state_with(|state| state.close_direct_chat_dialog());
     }));
     invite.set_sensitive(false);
     invite.connect_clicked(move |_| {
-        let _ = app_tx.send(Box::new(|op| op.start_chat()));
+        app_runtime.update_state_with(|state| state.start_chat());
     });
 }
diff --git a/fractal-gtk/src/appop/connect/directory.rs b/fractal-gtk/src/appop/connect/directory.rs
index b987ccc5..3b0eb330 100644
--- a/fractal-gtk/src/appop/connect/directory.rs
+++ b/fractal-gtk/src/appop/connect/directory.rs
@@ -8,7 +8,7 @@ use libhandy::prelude::*;
 use crate::appop::{AppOp, RoomSearchPagination};
 
 pub fn connect(appop: &AppOp) {
-    let app_tx = appop.app_tx.clone();
+    let app_runtime = appop.app_runtime.clone();
     let q = appop
         .ui
         .builder
@@ -104,17 +104,17 @@ pub fn connect(appop: &AppOp) {
         .get_object::<gtk::ScrolledWindow>("directory_scroll")
         .expect("Can't find directory_scroll in ui file.");
 
-    scroll.connect_edge_reached(clone!(@strong app_tx => move |_, dir| {
+    scroll.connect_edge_reached(clone!(@strong app_runtime => move |_, dir| {
         if dir == gtk::PositionType::Bottom {
-            let _ = app_tx.send(Box::new(|op| op.load_more_rooms()));
+            app_runtime.update_state_with(|state| state.load_more_rooms());
         }
     }));
 
     q.connect_activate(move |_| {
-        let _ = app_tx.send(Box::new(|op| {
-            op.directory_pagination = RoomSearchPagination::Initial;
-            op.search_rooms();
-        }));
+        app_runtime.update_state_with(|state| {
+            state.directory_pagination = RoomSearchPagination::Initial;
+            state.search_rooms();
+        });
     });
 
     default_matrix_server_radio.connect_toggled(clone!(
diff --git a/fractal-gtk/src/appop/connect/invite.rs b/fractal-gtk/src/appop/connect/invite.rs
index b3d23e06..9117c00b 100644
--- a/fractal-gtk/src/appop/connect/invite.rs
+++ b/fractal-gtk/src/appop/connect/invite.rs
@@ -7,7 +7,7 @@ use std::sync::{Arc, Mutex};
 use crate::appop::AppOp;
 
 pub fn connect_dialog(appop: &AppOp) {
-    let app_tx = appop.app_tx.clone();
+    let app_runtime = appop.app_runtime.clone();
     let dialog = appop
         .ui
         .builder
@@ -24,24 +24,24 @@ pub fn connect_dialog(appop: &AppOp) {
         .get_object::<gtk::Button>("invite_reject")
         .expect("Can't find invite_reject in ui file.");
 
-    reject.connect_clicked(clone!(@strong dialog, @strong app_tx => move |_| {
-        let _ = app_tx.send(Box::new(|op| op.accept_inv(false)));
+    reject.connect_clicked(clone!(@strong dialog, @strong app_runtime => move |_| {
+        app_runtime.update_state_with(|state| state.accept_inv(false));
         dialog.hide();
     }));
-    dialog.connect_delete_event(clone!(@strong dialog, @strong app_tx => move |_, _| {
-        let _ = app_tx.send(Box::new(|op| op.accept_inv(false)));
+    dialog.connect_delete_event(clone!(@strong dialog, @strong app_runtime => move |_, _| {
+        app_runtime.update_state_with(|state| state.accept_inv(false));
         dialog.hide();
         glib::signal::Inhibit(true)
     }));
 
     accept.connect_clicked(clone!(@strong dialog => move |_| {
-        let _ = app_tx.send(Box::new(|op| op.accept_inv(true)));
+        app_runtime.update_state_with(|state| state.accept_inv(true));
         dialog.hide();
     }));
 }
 
 pub fn connect_user(appop: &AppOp) {
-    let app_tx = appop.app_tx.clone();
+    let app_runtime = appop.app_runtime.clone();
     let cancel = appop
         .ui
         .builder
@@ -86,73 +86,73 @@ pub fn connect_user(appop: &AppOp) {
     // this is used to cancel the timeout and not search for every key input. We'll wait 500ms
     // without key release event to launch the search
     let source_id: Arc<Mutex<Option<glib::source::SourceId>>> = Arc::new(Mutex::new(None));
-    invite_entry.connect_key_release_event(clone!(@strong app_tx => move |entry, _| {
-            {
-                let mut id = source_id.lock().unwrap();
-                if let Some(sid) = id.take() {
-                    glib::source::source_remove(sid);
-                }
+    invite_entry.connect_key_release_event(clone!(@strong app_runtime => move |entry, _| {
+        {
+            let mut id = source_id.lock().unwrap();
+            if let Some(sid) = id.take() {
+                glib::source::source_remove(sid);
             }
+        }
 
-            let sid = glib::timeout_add_local(
-                500,
-                clone!(@strong entry, @strong source_id, @strong app_tx => move || {
-                    if let Some(buffer) = entry.get_buffer() {
-                        let start = buffer.get_start_iter();
-                        let end = buffer.get_end_iter();
+        let sid = glib::timeout_add_local(
+            500,
+            clone!(@strong entry, @strong source_id, @strong app_runtime => move || {
+                if let Some(buffer) = entry.get_buffer() {
+                    let start = buffer.get_start_iter();
+                    let end = buffer.get_end_iter();
 
-                        if let Some(text) = buffer.get_text(&start, &end, false).map(|gstr| 
gstr.to_string()) {
-                            let _ = app_tx.send(Box::new(|op| op.search_invite_user(text)));
-                        }
+                    if let Some(text) = buffer.get_text(&start, &end, false).map(|gstr| gstr.to_string()) {
+                        app_runtime.update_state_with(|state| state.search_invite_user(text));
                     }
+                }
 
-                    *(source_id.lock().unwrap()) = None;
-                    Continue(false)
-                }),
-            );
+                *(source_id.lock().unwrap()) = None;
+                Continue(false)
+            }),
+        );
 
-            *(source_id.lock().unwrap()) = Some(sid);
-            glib::signal::Inhibit(false)
-        }));
+        *(source_id.lock().unwrap()) = Some(sid);
+        glib::signal::Inhibit(false)
+    }));
 
     invite_entry.connect_focus_in_event(
-        clone!(@strong invite_entry_box, @strong app_tx => move |_, _| {
+        clone!(@strong invite_entry_box, @strong app_runtime => move |_, _| {
             invite_entry_box.get_style_context().add_class("message-input-focused");
 
-            let _ = app_tx.send(Box::new(|op| op.remove_invite_user_dialog_placeholder()));
+            app_runtime.update_state_with(|state| state.remove_invite_user_dialog_placeholder());
 
             Inhibit(false)
         }),
     );
 
     invite_entry.connect_focus_out_event(
-        clone!(@strong invite_entry_box, @strong app_tx => move |_, _| {
+        clone!(@strong invite_entry_box, @strong app_runtime => move |_, _| {
             invite_entry_box.get_style_context().remove_class("message-input-focused");
 
-            let _ = app_tx.send(Box::new(|op| op.set_invite_user_dialog_placeholder()));
+            app_runtime.update_state_with(|state| state.set_invite_user_dialog_placeholder());
 
             Inhibit(false)
         }),
     );
 
     if let Some(buffer) = invite_entry.get_buffer() {
-        buffer.connect_delete_range(clone!(@strong app_tx => move |_, _, _| {
-            glib::idle_add_local(clone!(@strong app_tx => move || {
-                let _ = app_tx.send(Box::new(|op| op.detect_removed_invite()));
+        buffer.connect_delete_range(clone!(@strong app_runtime => move |_, _, _| {
+            glib::idle_add_local(clone!(@strong app_runtime => move || {
+                app_runtime.update_state_with(|state| state.detect_removed_invite());
                 Continue(false)
             }));
         }));
     }
 
-    dialog.connect_delete_event(clone!(@strong app_tx => move |_, _| {
-        let _ = app_tx.send(Box::new(|op| op.close_invite_dialog()));
+    dialog.connect_delete_event(clone!(@strong app_runtime => move |_, _| {
+        app_runtime.update_state_with(|state| state.close_invite_dialog());
         glib::signal::Inhibit(true)
     }));
-    cancel.connect_clicked(clone!(@strong app_tx => move |_| {
-        let _ = app_tx.send(Box::new(|op| op.close_invite_dialog()));
+    cancel.connect_clicked(clone!(@strong app_runtime => move |_| {
+        app_runtime.update_state_with(|state| state.close_invite_dialog());
     }));
     invite.set_sensitive(false);
     invite.connect_clicked(move |_| {
-        let _ = app_tx.send(Box::new(|op| op.invite()));
+        app_runtime.update_state_with(|state| state.invite());
     });
 }
diff --git a/fractal-gtk/src/appop/connect/join_room.rs b/fractal-gtk/src/appop/connect/join_room.rs
index 87306933..18518801 100644
--- a/fractal-gtk/src/appop/connect/join_room.rs
+++ b/fractal-gtk/src/appop/connect/join_room.rs
@@ -4,7 +4,7 @@ use gtk::prelude::*;
 use crate::appop::AppOp;
 
 pub fn connect(appop: &AppOp) {
-    let app_tx = appop.app_tx.clone();
+    let app_runtime = appop.app_runtime.clone();
     let dialog = appop
         .ui
         .builder
@@ -37,16 +37,16 @@ pub fn connect(appop: &AppOp) {
     }));
 
     confirm.connect_clicked(
-        clone!(@strong entry, @strong dialog, @strong app_tx => move |_| {
+        clone!(@strong entry, @strong dialog, @strong app_runtime => move |_| {
             dialog.hide();
-            let _ = app_tx.send(Box::new(|op| op.join_to_room()));
+            app_runtime.update_state_with(|state| state.join_to_room());
             entry.set_text("");
         }),
     );
 
     entry.connect_activate(clone!(@strong dialog => move |entry| {
         dialog.hide();
-        let _ = app_tx.send(Box::new(|op| op.join_to_room()));
+        app_runtime.update_state_with(|state| state.join_to_room());
         entry.set_text("");
     }));
     entry.connect_changed(clone!(@strong confirm => move |entry| {
diff --git a/fractal-gtk/src/appop/connect/language.rs b/fractal-gtk/src/appop/connect/language.rs
index e7f90ad1..f1915742 100644
--- a/fractal-gtk/src/appop/connect/language.rs
+++ b/fractal-gtk/src/appop/connect/language.rs
@@ -8,7 +8,7 @@ use gtk::prelude::*;
 use gspell::{CheckerExt, TextBuffer, TextBufferExt as GspellTextBufferExt};
 
 pub fn connect(appop: &AppOp) {
-    let app_tx = appop.app_tx.clone();
+    let app_runtime = appop.app_runtime.clone();
     let textview = appop.ui.sventry.view.upcast_ref::<gtk::TextView>();
     if let Some(checker) = textview
         .get_buffer()
@@ -16,24 +16,24 @@ pub fn connect(appop: &AppOp) {
         .and_then(|gs_buffer| gs_buffer.get_spell_checker())
     {
         let _signal_handler = checker.connect_property_language_notify(move |checker| {
-                let _ = app_tx.send(Box::new(clone!(@weak checker => move |op| {
+                app_runtime.update_state_with(clone!(@weak checker => move |state| {
                     if let Some(lang_code) = checker
                         .get_language()
                         .and_then(|lang| lang.get_code())
                         .map(String::from)
                     {
-                            if let (Some(active_room), Some(login_data)) = (op.active_room.clone(), 
op.login_data.as_ref()) {
-                                let session_client = login_data.session_client.clone();
-                                let uid = login_data.uid.clone();
-                                RUNTIME.spawn(async move {
-                                    let query = room::set_language(session_client, &uid, &active_room, 
lang_code).await;
-                                    if let Err(err) = query {
-                                        err.handle_error();
-                                    }
-                                });
-                            }
+                        if let (Some(active_room), Some(login_data)) = (state.active_room.clone(), 
state.login_data.as_ref()) {
+                            let session_client = login_data.session_client.clone();
+                            let uid = login_data.uid.clone();
+                            RUNTIME.spawn(async move {
+                                let query = room::set_language(session_client, &uid, &active_room, 
lang_code).await;
+                                if let Err(err) = query {
+                                    err.handle_error();
+                                }
+                            });
+                        }
                     }
-                })));
+                }));
             });
     }
 }
diff --git a/fractal-gtk/src/appop/connect/leave_room.rs b/fractal-gtk/src/appop/connect/leave_room.rs
index ae528b74..6c33e5c3 100644
--- a/fractal-gtk/src/appop/connect/leave_room.rs
+++ b/fractal-gtk/src/appop/connect/leave_room.rs
@@ -4,7 +4,7 @@ use gtk::prelude::*;
 use crate::appop::AppOp;
 
 pub fn connect(appop: &AppOp) {
-    let app_tx = appop.app_tx.clone();
+    let app_runtime = appop.app_runtime.clone();
     let dialog = appop
         .ui
         .builder
@@ -31,6 +31,6 @@ pub fn connect(appop: &AppOp) {
 
     confirm.connect_clicked(clone!(@strong dialog => move |_| {
         dialog.hide();
-        let _ = app_tx.send(Box::new(|op| op.really_leave_active_room()));
+        app_runtime.update_state_with(move |state| state.really_leave_active_room());
     }));
 }
diff --git a/fractal-gtk/src/appop/connect/markdown.rs b/fractal-gtk/src/appop/connect/markdown.rs
index dbcbd9fd..03af0a75 100644
--- a/fractal-gtk/src/appop/connect/markdown.rs
+++ b/fractal-gtk/src/appop/connect/markdown.rs
@@ -7,7 +7,7 @@ use crate::util;
 use crate::appop::AppOp;
 
 pub fn connect(appop: &AppOp) {
-    let app_tx = appop.app_tx.clone();
+    let app_runtime = appop.app_runtime.clone();
     let md_popover_btn = &appop.ui.sventry.markdown;
     let md_img = appop.ui.sventry.markdown_img.clone();
     let buffer = appop.ui.sventry.buffer.clone();
@@ -37,9 +37,9 @@ pub fn connect(appop: &AppOp) {
 
     let md_active = util::get_markdown_schema();
     if md_active {
-        let _ = app_tx.send(Box::new(|op| {
-            op.md_enabled = true;
-        }));
+        app_runtime.update_state_with(|state| {
+            state.md_enabled = true;
+        });
         markdown_switch.set_active(true);
         md_img.set_from_icon_name(Some("format-indent-more-symbolic"), gtk::IconSize::Menu);
         txt.get_style_context().remove_class("dim-label");
@@ -53,9 +53,9 @@ pub fn connect(appop: &AppOp) {
 
     markdown_switch.connect_property_active_notify(clone!(@strong markdown_switch => move |_| {
         let md_active = markdown_switch.get_active();
-        let _ = app_tx.send(Box::new(move |op| {
-            op.md_enabled = md_active;
-        }));
+        app_runtime.update_state_with(move |state| {
+            state.md_enabled = md_active;
+        });
 
         if markdown_switch.get_active() {
             md_img.set_from_icon_name(
diff --git a/fractal-gtk/src/appop/connect/new_room.rs b/fractal-gtk/src/appop/connect/new_room.rs
index aff74984..15ae479e 100644
--- a/fractal-gtk/src/appop/connect/new_room.rs
+++ b/fractal-gtk/src/appop/connect/new_room.rs
@@ -4,7 +4,7 @@ use gtk::prelude::*;
 use crate::appop::AppOp;
 
 pub fn connect(appop: &AppOp) {
-    let app_tx = appop.app_tx.clone();
+    let app_runtime = appop.app_runtime.clone();
     let dialog = appop
         .ui
         .builder
@@ -49,9 +49,9 @@ pub fn connect(appop: &AppOp) {
     );
 
     confirm.connect_clicked(
-        clone!(@strong entry, @strong dialog, @strong private, @strong app_tx => move |_| {
+        clone!(@strong entry, @strong dialog, @strong private, @strong app_runtime => move |_| {
             dialog.hide();
-            let _ = app_tx.send(Box::new(|op| op.create_new_room()));
+            app_runtime.update_state_with(|state| state.create_new_room());
             entry.set_text("");
             private.set_active(true);
         }),
@@ -59,7 +59,7 @@ pub fn connect(appop: &AppOp) {
 
     entry.connect_activate(clone!(@strong dialog => move |entry| {
         dialog.hide();
-        let _ = app_tx.send(Box::new(|op| op.create_new_room()));
+        app_runtime.update_state_with(|state| state.create_new_room());
         entry.set_text("");
         private.set_active(true);
     }));
diff --git a/fractal-gtk/src/appop/connect/roomlist_search.rs 
b/fractal-gtk/src/appop/connect/roomlist_search.rs
index 774eecc4..f657b6aa 100644
--- a/fractal-gtk/src/appop/connect/roomlist_search.rs
+++ b/fractal-gtk/src/appop/connect/roomlist_search.rs
@@ -4,7 +4,7 @@ use gtk::prelude::*;
 use crate::appop::AppOp;
 
 pub fn connect(appop: &AppOp) {
-    let app_tx = appop.app_tx.clone();
+    let app_runtime = appop.app_runtime.clone();
     let search_btn = appop
         .ui
         .builder
@@ -33,7 +33,7 @@ pub fn connect(appop: &AppOp) {
 
     search_entry.connect_search_changed(move |entry| {
         let search_text = Some(entry.get_text().to_string());
-        let _ = app_tx.send(Box::new(|op| op.filter_rooms(search_text)));
+        app_runtime.update_state_with(|state| state.filter_rooms(search_text));
     });
 
     // hidding left and right boxes to align with top buttons
diff --git a/fractal-gtk/src/appop/connect/send.rs b/fractal-gtk/src/appop/connect/send.rs
index 2682a4f0..c00cdb6d 100644
--- a/fractal-gtk/src/appop/connect/send.rs
+++ b/fractal-gtk/src/appop/connect/send.rs
@@ -8,7 +8,7 @@ use crate::appop::AppOp;
 const MAX_INPUT_HEIGHT: i32 = 100;
 
 pub fn connect(appop: &AppOp) {
-    let app_tx = appop.app_tx.clone();
+    let app_runtime = appop.app_runtime.clone();
     appop.ui.sventry.container.set_redraw_on_allocate(true);
     let msg_entry = appop.ui.sventry.view.clone();
     let buffer = &appop.ui.sventry.buffer;
@@ -32,27 +32,27 @@ pub fn connect(appop: &AppOp) {
         .expect("Can't find autocomplete_popover in ui file.");
 
     msg_entry.connect_key_press_event(
-        clone!(@strong app_tx => move |_, key| match key.get_keyval() {
+        clone!(@strong app_runtime => move |_, key| match key.get_keyval() {
             gdk::keys::constants::Return | gdk::keys::constants::KP_Enter
                 if !key.get_state().contains(gdk::ModifierType::SHIFT_MASK)
                     && !autocomplete_popover.is_visible() =>
             {
-                activate_action(&app_tx, "app", "send-message");
+                activate_action(&app_runtime, "app", "send-message");
                 Inhibit(true)
             }
             _ => Inhibit(false),
         }),
     );
 
-    msg_entry.connect_key_release_event(clone!(@strong app_tx => move |_, ev| {
+    msg_entry.connect_key_release_event(clone!(@strong app_runtime => move |_, ev| {
         if ev.get_keyval().to_unicode().is_some() {
-            let _ = app_tx.send(Box::new(|op| op.send_typing()));
+            app_runtime.update_state_with(|state| state.send_typing());
         }
         Inhibit(false)
     }));
 
     msg_entry.connect_paste_clipboard(move |_| {
-        let _ = app_tx.send(Box::new(|op| op.paste()));
+        app_runtime.update_state_with(|state| state.paste());
     });
 
     msg_entry.connect_focus_in_event(clone!(@strong msg_entry_box => move |_, _| {
diff --git a/fractal-gtk/src/appop/media_viewer.rs b/fractal-gtk/src/appop/media_viewer.rs
index 5e7ad473..fa3debb5 100644
--- a/fractal-gtk/src/appop/media_viewer.rs
+++ b/fractal-gtk/src/appop/media_viewer.rs
@@ -34,7 +34,8 @@ impl AppOp {
             *self.media_viewer.borrow_mut() = Some(panel);
 
             let back_history = self.room_back_history.clone();
-            let actions = actions::Message::new(self.app_tx.clone(), self.ui.clone(), back_history);
+            let actions =
+                actions::Message::new(self.app_runtime.clone(), self.ui.clone(), back_history);
             header.insert_action_group("message", Some(&actions));
             body.insert_action_group("message", Some(&actions));
 
diff --git a/fractal-gtk/src/appop/mod.rs b/fractal-gtk/src/appop/mod.rs
index 46e66c78..2060ebe9 100644
--- a/fractal-gtk/src/appop/mod.rs
+++ b/fractal-gtk/src/appop/mod.rs
@@ -22,7 +22,7 @@ use crate::model::{
 use crate::passwd::PasswordStorage;
 
 use crate::actions::AppState;
-use crate::app::UpdateApp;
+use crate::app::AppRuntime;
 use crate::cache;
 use crate::uibuilder;
 use crate::widgets;
@@ -85,7 +85,7 @@ pub struct LoginData {
 }
 
 pub struct AppOp {
-    pub app_tx: glib::Sender<UpdateApp>,
+    pub app_runtime: AppRuntime,
     pub ui: uibuilder::UI,
 
     pub syncing: bool, // TODO: Replace with a Mutex
@@ -126,7 +126,7 @@ pub struct AppOp {
 impl PasswordStorage for AppOp {}
 
 impl AppOp {
-    pub fn new(ui: uibuilder::UI, app_tx: glib::Sender<UpdateApp>) -> AppOp {
+    pub fn new(ui: uibuilder::UI, app_runtime: AppRuntime) -> AppOp {
         let leaflet = ui
             .builder
             .get_object::<libhandy::Leaflet>("chat_page")
@@ -137,7 +137,7 @@ impl AppOp {
             .expect("Couldn't find main_deck in ui file");
 
         AppOp {
-            app_tx,
+            app_runtime,
             ui,
             active_room: None,
             join_to_room: None,
diff --git a/fractal-gtk/src/appop/room.rs b/fractal-gtk/src/appop/room.rs
index 020e9b12..715c060b 100644
--- a/fractal-gtk/src/appop/room.rs
+++ b/fractal-gtk/src/appop/room.rs
@@ -298,7 +298,8 @@ impl AppOp {
         }
 
         let back_history = self.room_back_history.clone();
-        let actions = actions::Message::new(self.app_tx.clone(), self.ui.clone(), back_history);
+        let actions =
+            actions::Message::new(self.app_runtime.clone(), self.ui.clone(), back_history);
         let history = widgets::RoomHistory::new(actions, active_room.clone(), &self.ui);
         self.history = if let Some(mut history) = history {
             history.create(



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]