[fractal/ui-refactor: 13/15] Move all fields related to UI from AppOp to UI




commit bf4a3021d198c83e4c3f4545d294a3be2dd9d02b
Author: Alejandro Domínguez <adomu net-c com>
Date:   Tue Nov 17 23:53:26 2020 +0100

    Move all fields related to UI from AppOp to UI

 fractal-gtk/src/actions/global.rs      |  40 ++++----
 fractal-gtk/src/actions/message.rs     | 143 ++++++++++++++--------------
 fractal-gtk/src/app/mod.rs             | 164 ++++++++++++++++++---------------
 fractal-gtk/src/appop/invite.rs        |  24 ++---
 fractal-gtk/src/appop/login.rs         |   2 +-
 fractal-gtk/src/appop/media_viewer.rs  |   7 +-
 fractal-gtk/src/appop/member.rs        |   2 +-
 fractal-gtk/src/appop/message.rs       |  20 ++--
 fractal-gtk/src/appop/mod.rs           |  36 +-------
 fractal-gtk/src/appop/notifications.rs |   5 +-
 fractal-gtk/src/appop/room.rs          |  40 ++++----
 fractal-gtk/src/appop/room_settings.rs |  10 +-
 fractal-gtk/src/appop/start_chat.rs    |   6 +-
 fractal-gtk/src/appop/state.rs         |   9 +-
 fractal-gtk/src/ui/mod.rs              |  29 +++++-
 15 files changed, 274 insertions(+), 263 deletions(-)
---
diff --git a/fractal-gtk/src/actions/global.rs b/fractal-gtk/src/actions/global.rs
index 0bc53272..c8d42522 100644
--- a/fractal-gtk/src/actions/global.rs
+++ b/fractal-gtk/src/actions/global.rs
@@ -175,60 +175,60 @@ pub fn new(appop: &AppOp) {
 
     previous_room.connect_activate(clone!(@strong app_runtime => move |_, _| {
         app_runtime.update_state_with(|state| {
-            if let Some(id) = state.roomlist.prev_id() {
+            if let Some(id) = state.ui.roomlist.prev_id() {
                 state.set_active_room_by_id(id);
-            } else if let Some(last_room) = state.roomlist.last_id() {
+            } else if let Some(last_room) = state.ui.roomlist.last_id() {
                 state.set_active_room_by_id(last_room);
             }
         });
     }));
     next_room.connect_activate(clone!(@strong app_runtime => move |_, _| {
         app_runtime.update_state_with(|state| {
-            if let Some(id) = state.roomlist.next_id() {
+            if let Some(id) = state.ui.roomlist.next_id() {
                 state.set_active_room_by_id(id);
-            } else if let Some(first_room) = state.roomlist.first_id() {
+            } else if let Some(first_room) = state.ui.roomlist.first_id() {
                 state.set_active_room_by_id(first_room);
             }
         });
     }));
     prev_unread_room.connect_activate(clone!(@strong app_runtime => move |_, _| {
         app_runtime.update_state_with(|state| {
-            if let Some(id) = state.roomlist.prev_unread_id() {
+            if let Some(id) = state.ui.roomlist.prev_unread_id() {
                 state.set_active_room_by_id(id);
             }
         });
     }));
     next_unread_room.connect_activate(clone!(@strong app_runtime => move |_, _| {
         app_runtime.update_state_with(|state| {
-            if let Some(id) = state.roomlist.next_unread_id() {
+            if let Some(id) = state.ui.roomlist.next_unread_id() {
                 state.set_active_room_by_id(id);
             }
         });
     }));
     first_room.connect_activate(clone!(@strong app_runtime => move |_, _| {
         app_runtime.update_state_with(|state| {
-            if let Some(id) = state.roomlist.first_id() {
+            if let Some(id) = state.ui.roomlist.first_id() {
                 state.set_active_room_by_id(id);
             }
         });
     }));
     last_room.connect_activate(clone!(@strong app_runtime => move |_, _| {
         app_runtime.update_state_with(|state| {
-            if let Some(id) = state.roomlist.last_id() {
+            if let Some(id) = state.ui.roomlist.last_id() {
                 state.set_active_room_by_id(id);
             }
         });
     }));
     older_messages.connect_activate(clone!(@strong app_runtime => move |_, _| {
         app_runtime.update_state_with(|state| {
-            if let Some(ref mut hist) = state.history {
+            if let Some(ref mut hist) = state.ui.history {
                 hist.page_up();
             }
         });
     }));
     newer_messages.connect_activate(clone!(@strong app_runtime => move |_, _| {
         app_runtime.update_state_with(|state| {
-            if let Some(ref mut hist) = state.history {
+            if let Some(ref mut hist) = state.ui.history {
                 hist.page_down();
             }
         });
@@ -237,14 +237,14 @@ pub fn new(appop: &AppOp) {
     account.connect_activate(clone!(@strong app_runtime => move |_, _| {
         app_runtime.update_state_with(|state| {
             state.show_account_settings_dialog();
-            state.room_back_history.borrow_mut().push(AppState::AccountSettings);
+            state.ui.room_back_history.borrow_mut().push(AppState::AccountSettings);
         });
     }));
 
     directory.connect_activate(clone!(@strong app_runtime => move |_, _| {
         app_runtime.update_state_with(|state| {
             state.set_state(AppState::Directory);
-            state.room_back_history.borrow_mut().push(AppState::Directory);
+            state.ui.room_back_history.borrow_mut().push(AppState::Directory);
         });
     }));
 
@@ -259,13 +259,13 @@ pub fn new(appop: &AppOp) {
                 state.activate();
             }
             // Push a new state only if the current state is not already Room
-            let push = if let Some(last) = state.room_back_history.borrow().last() {
+            let push = if let Some(last) = state.ui.room_back_history.borrow().last() {
                 last != &AppState::Room
             } else {
                 true
             };
             if push {
-                state.room_back_history.borrow_mut().push(AppState::Room);
+                state.ui.room_back_history.borrow_mut().push(AppState::Room);
             }
         });
     }));
@@ -273,20 +273,20 @@ pub fn new(appop: &AppOp) {
     room_settings.connect_activate(clone!(@strong app_runtime => move |_, _| {
         app_runtime.update_state_with(|state| {
             state.create_room_settings();
-            state.room_back_history.borrow_mut().push(AppState::RoomSettings);
+            state.ui.room_back_history.borrow_mut().push(AppState::RoomSettings);
         });
     }));
 
     media_viewer.connect_activate(clone!(@strong app_runtime => move |_, data| {
         open_viewer(&app_runtime, data.cloned());
         app_runtime.update_state_with(|state| {
-            state.room_back_history.borrow_mut().push(AppState::MediaViewer);
+            state.ui.room_back_history.borrow_mut().push(AppState::MediaViewer);
         });
     }));
 
     deck_back.connect_activate(clone!(@strong app_runtime => move |_, _| {
         app_runtime.update_state_with(|state| {
-            let deck = state.deck.clone();
+            let deck = state.ui.deck.clone();
             if deck.get_can_swipe_back() {
                 deck.navigate(libhandy::NavigationDirection::Back);
             }
@@ -295,13 +295,13 @@ pub fn new(appop: &AppOp) {
 
     back.connect_activate(clone!(@strong app_runtime => move |_, _| {
         app_runtime.update_state_with(|state| {
-            if let Some(mut mv) = state.media_viewer.borrow_mut().take() {
+            if let Some(mut mv) = state.ui.media_viewer.borrow_mut().take() {
                 mv.disconnect_signal_id();
             }
 
             // Remove the current state from the store
-            state.room_back_history.borrow_mut().pop();
-            let app_state = state.room_back_history.borrow().last().cloned();
+            state.ui.room_back_history.borrow_mut().pop();
+            let app_state = state.ui.room_back_history.borrow().last().cloned();
             if let Some(app_state) = app_state {
                 debug!("Go back to state {:?}", app_state);
                 state.set_state(app_state);
diff --git a/fractal-gtk/src/actions/message.rs b/fractal-gtk/src/actions/message.rs
index 2ec85142..8e68287f 100644
--- a/fractal-gtk/src/actions/message.rs
+++ b/fractal-gtk/src/actions/message.rs
@@ -12,7 +12,6 @@ use crate::app::{AppRuntime, RUNTIME};
 use crate::appop::AppOp;
 use crate::backend::HandleError;
 use crate::model::message::Message;
-use crate::ui::UI;
 use crate::util::i18n::i18n;
 use gio::ActionGroupExt;
 use gio::ActionMapExt;
@@ -29,7 +28,6 @@ use crate::widgets::SourceDialog;
 /* This creates all actions the room history can perform */
 pub fn new(
     app_runtime: AppRuntime,
-    ui: UI,
     back_history: Rc<RefCell<Vec<AppState>>>,
 ) -> gio::SimpleActionGroup {
     let actions = SimpleActionGroup::new();
@@ -55,15 +53,16 @@ pub fn new(
     actions.add_action(&show_source);
     actions.add_action(&load_more_messages);
 
-    let parent: gtk::Window = ui
-        .builder
-        .get_object("main_window")
-        .expect("Can't find main_window in ui file.");
-    show_source.connect_activate(clone!(@weak parent, @strong app_runtime => move |_, data| {
-        let viewer = SourceDialog::new();
-        viewer.set_parent_window(&parent);
+    show_source.connect_activate(clone!(@strong app_runtime => move |_, data| {
         let data = data.cloned();
         app_runtime.update_state_with(move |state| {
+            let parent: gtk::Window = state
+                .ui
+                .builder
+                .get_object("main_window")
+                .expect("Can't find main_window in ui file.");
+            let viewer = SourceDialog::new();
+            viewer.set_parent_window(&parent);
             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);
@@ -73,28 +72,27 @@ pub fn new(
         });
     }));
 
-    let window = ui
-        .builder
-        .get_object::<gtk::ApplicationWindow>("main_window")
-        .expect("Couldn't find main_window in ui file.");
     reply.connect_activate(clone!(
     @weak back_history,
-    @weak window,
-    @weak ui.sventry.view as msg_entry,
     @strong app_runtime
     => move |_, data| {
-        let state = back_history.borrow().last().cloned();
-        if let Some(AppState::MediaViewer) = state {
-            if let Some(action_group) = window.get_action_group("app") {
-                action_group.activate_action("back", None);
-            } else {
-                error!("The action group app is not attached to the main window.");
+        let data = data.cloned();
+        let past_state = back_history.borrow().last().cloned();
+        app_runtime.update_state_with(move |state| {
+            let window = state
+                .ui
+                .builder
+                .get_object::<gtk::ApplicationWindow>("main_window")
+                .expect("Couldn't find main_window in ui file.");
+            if let Some(AppState::MediaViewer) = past_state {
+                if let Some(action_group) = window.get_action_group("app") {
+                    action_group.activate_action("back", None);
+                } else {
+                    error!("The action group app is not attached to the main window.");
+                }
             }
-        }
-        if let Some(buffer) = msg_entry.get_buffer() {
-            let mut start = buffer.get_start_iter();
-            let data = data.cloned();
-            app_runtime.update_state_with(move |state| {
+            if let Some(buffer) = state.ui.sventry.view.get_buffer() {
+                let mut start = buffer.get_start_iter();
                 if let Some(m) = get_message(state, data.as_ref()) {
                     let quote = m
                         .body
@@ -105,10 +103,10 @@ pub fn new(
                         + "\n"
                         + "\n";
                     buffer.insert(&mut start, &quote);
-                    msg_entry.grab_focus();
+                    state.ui.sventry.view.grab_focus();
                 }
-            });
-        }
+            }
+        });
     }));
 
     open_with.connect_activate(clone!(@strong app_runtime => move |_, data| {
@@ -131,42 +129,45 @@ pub fn new(
         });
     }));
 
-    save_as.connect_activate(
-        clone!(@weak parent as window, @strong app_runtime => move |_, data| {
-            let data = data.cloned();
-            app_runtime.update_state_with(move |state| {
-                let (url, name) = unwrap_or_unit_return!(
-                    get_message(state, data.as_ref()).and_then(|m| Some((m.url?, m.body)))
-                );
-                let session_client = 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
-                });
+    save_as.connect_activate(clone!(@strong app_runtime => move |_, data| {
+        let data = data.cloned();
+        app_runtime.update_state_with(move |state| {
+            let window = state
+                .ui
+                .builder
+                .get_object::<gtk::Window>("main_window")
+                .expect("Couldn't find main_window in ui file.");
+            let (url, name) = unwrap_or_unit_return!(
+                get_message(state, data.as_ref()).and_then(|m| Some((m.url?, m.body)))
+            );
+            let session_client = 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
+            });
 
-                glib::MainContext::default().spawn_local(async move {
-                    match response.await {
-                        Err(_) => {
-                            let msg = i18n("Could not download the file");
-                            ErrorDialog::new(false, &msg);
-                        },
-                        Ok(Ok(fname)) => {
-                            if let Some(path) = save(&window.upcast_ref(), &name, &[]) {
-                                // TODO use glib to copy file
-                                if fs::copy(fname, path).is_err() {
-                                    ErrorDialog::new(false, &i18n("Couldn’t save file"));
-                                }
+            glib::MainContext::default().spawn_local(async move {
+                match response.await {
+                    Err(_) => {
+                        let msg = i18n("Could not download the file");
+                        ErrorDialog::new(false, &msg);
+                    },
+                    Ok(Ok(fname)) => {
+                        if let Some(path) = save(&window.upcast_ref(), &name, &[]) {
+                            // TODO use glib to copy file
+                            if fs::copy(fname, path).is_err() {
+                                ErrorDialog::new(false, &i18n("Couldn’t save file"));
                             }
                         }
-                        Ok(Err(err)) => {
-                            error!("Media path could not be found due to error: {:?}", err);
-                        }
                     }
-                });
+                    Ok(Err(err)) => {
+                        error!("Media path could not be found due to error: {:?}", err);
+                    }
+                }
             });
-        }),
-    );
+        });
+    }));
 
     copy_image.connect_activate(clone!(@strong app_runtime => move |_, data| {
         let data = data.cloned();
@@ -212,19 +213,23 @@ pub fn new(
 
     delete.connect_activate(clone!(
     @weak back_history,
-    @weak window,
     @strong app_runtime
     => move |_, data| {
-        let state = back_history.borrow().last().cloned();
-        if let Some(AppState::MediaViewer) = state {
-            if let Some(action_group) = window.get_action_group("app") {
-                action_group.activate_action("back", None);
-            } else {
-                error!("The action group app is not attached to the main window.");
-            }
-        }
         let data = data.cloned();
         app_runtime.update_state_with(move |state| {
+            let window = state
+                .ui
+                .builder
+                .get_object::<gtk::ApplicationWindow>("main_window")
+                .expect("Couldn't find main_window in ui file.");
+            let past_state = back_history.borrow().last().cloned();
+            if let Some(AppState::MediaViewer) = past_state {
+                if let Some(action_group) = window.get_action_group("app") {
+                    action_group.activate_action("back", None);
+                } else {
+                    error!("The action group app is not attached to the main window.");
+                }
+            }
             let msg = unwrap_or_unit_return!(get_message(state, data.as_ref()));
             let session_client = unwrap_or_unit_return!(
                 state.login_data.as_ref().map(|ld| ld.session_client.clone())
diff --git a/fractal-gtk/src/app/mod.rs b/fractal-gtk/src/app/mod.rs
index b981fbe5..a95676e3 100644
--- a/fractal-gtk/src/app/mod.rs
+++ b/fractal-gtk/src/app/mod.rs
@@ -65,7 +65,7 @@ impl AppRuntime {
     }
 }
 
-fn new(gtk_app: gtk::Application) -> (AppRuntime, ui::UI) {
+fn new(gtk_app: gtk::Application) -> AppRuntime {
     // Set up the textdomain for gettext
     setlocale(LocaleCategory::LcAll, "");
     bindtextdomain("fractal", config::LOCALEDIR);
@@ -84,76 +84,85 @@ fn new(gtk_app: gtk::Application) -> (AppRuntime, ui::UI) {
     );
 
     let ui = ui::UI::new(gtk_app.clone());
-    let app_runtime = AppRuntime::init(ui.clone());
+    let app_runtime = AppRuntime::init(ui);
 
     let settings: gio::Settings = gio::Settings::new("org.gnome.Fractal");
     let window_state = WindowState::load_from_gsettings(&settings);
-    ui.main_window
-        .set_default_size(window_state.width, window_state.height);
-    if window_state.is_maximized {
-        ui.main_window.maximize();
-    } else if window_state.x > 0 && window_state.y > 0 {
-        ui.main_window.move_(window_state.x, window_state.y);
-    }
-    ui.main_window.show_all();
 
-    if gtk_app
-        .get_application_id()
-        .map_or(false, |s| s.ends_with("Devel"))
-    {
-        ui.main_window.get_style_context().add_class("devel");
-    }
+    app_runtime.update_state_with(move |state| {
+        state
+            .ui
+            .main_window
+            .set_default_size(window_state.width, window_state.height);
+        if window_state.is_maximized {
+            state.ui.main_window.maximize();
+        } else if window_state.x > 0 && window_state.y > 0 {
+            state.ui.main_window.move_(window_state.x, window_state.y);
+        }
+        state.ui.main_window.show_all();
 
-    let leaflet = ui
-        .builder
-        .get_object::<libhandy::Leaflet>("chat_page")
-        .expect("Can't find chat_page in ui file.");
-    let container = ui
-        .builder
-        .get_object::<gtk::Box>("history_container")
-        .expect("Can't find history_container in ui file.");
-    let popover = ui
-        .builder
-        .get_object::<gtk::Popover>("autocomplete_popover")
-        .expect("Can't find autocomplete_popover in ui file.");
-
-    if leaflet.get_folded() {
-        container.get_style_context().add_class("folded-history");
-        popover.get_style_context().add_class("narrow");
-    }
+        if gtk_app
+            .get_application_id()
+            .map_or(false, |s| s.ends_with("Devel"))
+        {
+            state.ui.main_window.get_style_context().add_class("devel");
+        }
+
+        let leaflet = state
+            .ui
+            .builder
+            .get_object::<libhandy::Leaflet>("chat_page")
+            .expect("Can't find chat_page in ui file.");
+        let container = state
+            .ui
+            .builder
+            .get_object::<gtk::Box>("history_container")
+            .expect("Can't find history_container in ui file.");
+        let popover = state
+            .ui
+            .builder
+            .get_object::<gtk::Popover>("autocomplete_popover")
+            .expect("Can't find autocomplete_popover in ui file.");
 
-    leaflet.connect_property_folded_notify(clone!(@weak container => move |leaflet| {
         if leaflet.get_folded() {
             container.get_style_context().add_class("folded-history");
             popover.get_style_context().add_class("narrow");
-        } else {
-            container.get_style_context().remove_class("folded-history");
-            popover.get_style_context().remove_class("narrow");
         }
-    }));
 
-    let view_stack = ui
-        .builder
-        .get_object::<gtk::Stack>("subview_stack")
-        .expect("Can't find subview_stack in ui file.");
+        leaflet.connect_property_folded_notify(clone!(@weak container => move |leaflet| {
+            if leaflet.get_folded() {
+                container.get_style_context().add_class("folded-history");
+                popover.get_style_context().add_class("narrow");
+            } else {
+                container.get_style_context().remove_class("folded-history");
+                popover.get_style_context().remove_class("narrow");
+            }
+        }));
+
+        let view_stack = state
+            .ui
+            .builder
+            .get_object::<gtk::Stack>("subview_stack")
+            .expect("Can't find subview_stack in ui file.");
 
-    /* Add account settings view to the view stack */
-    let child = ui
-        .builder
-        .get_object::<gtk::Box>("account_settings_box")
-        .expect("Can't find account_settings_box in ui file.");
-    view_stack.add_named(&child, "account-settings");
+        /* Add account settings view to the view stack */
+        let child = state
+            .ui
+            .builder
+            .get_object::<gtk::Box>("account_settings_box")
+            .expect("Can't find account_settings_box in ui file.");
+        view_stack.add_named(&child, "account-settings");
 
-    let main_stack = ui
-        .builder
-        .get_object::<gtk::Stack>("main_content_stack")
-        .expect("Can't find main_content_stack in ui file.");
+        let main_stack = state
+            .ui
+            .builder
+            .get_object::<gtk::Stack>("main_content_stack")
+            .expect("Can't find main_content_stack in ui file.");
 
-    // Add login view to the main stack
-    let login = widgets::LoginWidget::new(app_runtime.clone());
-    main_stack.add_named(&login.container, "login");
+        // Add login view to the main stack
+        let login = widgets::LoginWidget::new(state.app_runtime.clone());
+        main_stack.add_named(&login.container, "login");
 
-    app_runtime.update_state_with(|state| {
         state
             .ui
             .gtk_app
@@ -162,12 +171,12 @@ fn new(gtk_app: gtk::Application) -> (AppRuntime, ui::UI) {
         state.ui.connect_gtk(state.app_runtime.clone());
     });
 
-    (app_runtime, ui)
+    app_runtime
 }
 
 pub fn on_startup(gtk_app: &gtk::Application) {
     // Create application.
-    let (app_runtime, ui) = new(gtk_app.clone());
+    let app_runtime = new(gtk_app.clone());
 
     // Initialize libhandy
     libhandy::init();
@@ -178,25 +187,28 @@ pub fn on_startup(gtk_app: &gtk::Application) {
         });
     }));
 
-    ui.main_window.connect_property_has_toplevel_focus_notify(
-        clone!(@strong app_runtime => move |_| {
-            app_runtime.update_state_with(|state| {
-                state.mark_active_room_messages();
-            });
-        }),
-    );
-
-    ui.main_window.connect_delete_event(move |window, _| {
-        let settings: gio::Settings = gio::Settings::new("org.gnome.Fractal");
-        let w = window.upcast_ref();
-        let window_state = WindowState::from_window(w);
-        if let Err(err) = window_state.save_in_gsettings(&settings) {
-            error!("Can't save the window settings: {:?}", err);
-        }
-        Inhibit(false)
-    });
-
     app_runtime.update_state_with(|state| {
+        state
+            .ui
+            .main_window
+            .connect_property_has_toplevel_focus_notify(
+                clone!(@strong state.app_runtime as app_runtime => move |_| {
+                    app_runtime.update_state_with(|state| {
+                        state.mark_active_room_messages();
+                    });
+                }),
+            );
+
+        state.ui.main_window.connect_delete_event(move |window, _| {
+            let settings: gio::Settings = gio::Settings::new("org.gnome.Fractal");
+            let w = window.upcast_ref();
+            let window_state = WindowState::from_window(w);
+            if let Err(err) = window_state.save_in_gsettings(&settings) {
+                error!("Can't save the window settings: {:?}", err);
+            }
+            Inhibit(false)
+        });
+
         state.init();
     });
 
diff --git a/fractal-gtk/src/appop/invite.rs b/fractal-gtk/src/appop/invite.rs
index 95edc59b..425fe3da 100644
--- a/fractal-gtk/src/appop/invite.rs
+++ b/fractal-gtk/src/appop/invite.rs
@@ -17,7 +17,7 @@ use crate::model::member::Member;
 
 impl AppOp {
     pub fn add_to_invite(&mut self, u: Member) {
-        if self.invite_list.iter().any(|(mem, _)| *mem == u) {
+        if self.ui.invite_list.iter().any(|(mem, _)| *mem == u) {
             return;
         }
 
@@ -33,7 +33,7 @@ impl AppOp {
             .expect("Can't find invite_entry in ui file.");
 
         if let SearchType::DirectChat = self.search_type {
-            self.invite_list = vec![];
+            self.ui.invite_list = vec![];
 
             if let Some(buffer) = invite_entry.get_buffer() {
                 let mut start = buffer.get_start_iter();
@@ -77,18 +77,18 @@ impl AppOp {
 
                 invite_entry.add_child_at_anchor(&w, &anchor);
 
-                self.invite_list.push((u, anchor));
+                self.ui.invite_list.push((u, anchor));
             }
         }
     }
 
     pub fn rm_from_invite(&mut self, uid: UserId) {
-        let idx = self.invite_list.iter().position(|x| x.0.uid == uid);
+        let idx = self.ui.invite_list.iter().position(|x| x.0.uid == uid);
         if let Some(i) = idx {
-            self.invite_list.remove(i);
+            self.ui.invite_list.remove(i);
         }
 
-        if self.invite_list.is_empty() {
+        if self.ui.invite_list.is_empty() {
             if let Some(btn) = self
                 .ui
                 .builder
@@ -117,7 +117,7 @@ impl AppOp {
     }
 
     pub fn detect_removed_invite(&mut self) {
-        let invite_list = self.invite_list.clone();
+        let invite_list = self.ui.invite_list.clone();
         for (member, anchor) in invite_list {
             if anchor.get_deleted() {
                 self.rm_from_invite(member.uid);
@@ -163,7 +163,7 @@ impl AppOp {
     pub fn invite(&mut self) {
         let login_data = unwrap_or_unit_return!(self.login_data.clone());
         if let Some(ref r) = self.active_room {
-            for user in &self.invite_list {
+            for user in &self.ui.invite_list {
                 let session_client = login_data.session_client.clone();
                 let room_id = r.clone();
                 let user_id = user.0.uid.clone();
@@ -200,7 +200,7 @@ impl AppOp {
             .get_object::<gtk::Dialog>("invite_user_dialog")
             .expect("Can't find invite_user_dialog in ui file.");
 
-        self.invite_list = vec![];
+        self.ui.invite_list = vec![];
         if let Some(buffer) = invite_entry.get_buffer() {
             let mut start = buffer.get_start_iter();
             let mut end = buffer.get_end_iter();
@@ -217,7 +217,7 @@ impl AppOp {
 
     pub fn remove_inv(&mut self, room_id: &RoomId) {
         self.rooms.remove(room_id);
-        self.roomlist.remove_room(room_id);
+        self.ui.roomlist.remove_room(room_id);
     }
 
     pub fn accept_inv(&mut self, accept: bool) {
@@ -294,7 +294,7 @@ impl AppOp {
             let end = buffer.get_end_iter();
 
             if let Some(text) = buffer.get_text(&start, &end, true) {
-                if text.is_empty() && self.invite_list.is_empty() {
+                if text.is_empty() && self.ui.invite_list.is_empty() {
                     buffer.set_text(globals::PLACEHOLDER_TEXT);
 
                     let start = buffer.get_start_iter();
@@ -323,7 +323,7 @@ impl AppOp {
             let end = buffer.get_end_iter();
 
             if let Some(text) = buffer.get_text(&start, &end, true) {
-                if text == globals::PLACEHOLDER_TEXT && self.invite_list.is_empty() {
+                if text == globals::PLACEHOLDER_TEXT && self.ui.invite_list.is_empty() {
                     buffer.set_text("");
 
                     let start = buffer.get_start_iter();
diff --git a/fractal-gtk/src/appop/login.rs b/fractal-gtk/src/appop/login.rs
index be257272..61e1938b 100644
--- a/fractal-gtk/src/appop/login.rs
+++ b/fractal-gtk/src/appop/login.rs
@@ -126,6 +126,6 @@ impl AppOp {
             }
         });
         self.bk_logout();
-        *self.room_back_history.borrow_mut() = vec![];
+        *self.ui.room_back_history.borrow_mut() = vec![];
     }
 }
diff --git a/fractal-gtk/src/appop/media_viewer.rs b/fractal-gtk/src/appop/media_viewer.rs
index fa3debb5..39bc23e4 100644
--- a/fractal-gtk/src/appop/media_viewer.rs
+++ b/fractal-gtk/src/appop/media_viewer.rs
@@ -31,11 +31,10 @@ impl AppOp {
             let mut panel = widgets::MediaViewer::new(main_window, room, &msg, login_data.uid);
             panel.display_media_viewer(login_data.session_client.clone(), msg);
             let (body, header) = panel.create(login_data.session_client.clone())?;
-            *self.media_viewer.borrow_mut() = Some(panel);
+            *self.ui.media_viewer.borrow_mut() = Some(panel);
 
-            let back_history = self.room_back_history.clone();
-            let actions =
-                actions::Message::new(self.app_runtime.clone(), self.ui.clone(), back_history);
+            let back_history = self.ui.room_back_history.clone();
+            let actions = actions::Message::new(self.app_runtime.clone(), back_history);
             header.insert_action_group("message", Some(&actions));
             body.insert_action_group("message", Some(&actions));
 
diff --git a/fractal-gtk/src/appop/member.rs b/fractal-gtk/src/appop/member.rs
index ac729e1d..42b8d1d4 100644
--- a/fractal-gtk/src/appop/member.rs
+++ b/fractal-gtk/src/appop/member.rs
@@ -47,7 +47,7 @@ impl AppOp {
         self.recalculate_room_name(room_id);
 
         /* FIXME: update the current room settings insteat of creating a new one */
-        if self.room_settings.is_some() && self.state == AppState::RoomSettings {
+        if self.ui.room_settings.is_some() && self.state == AppState::RoomSettings {
             self.create_room_settings();
         }
     }
diff --git a/fractal-gtk/src/appop/message.rs b/fractal-gtk/src/appop/message.rs
index f6b20033..3a86fcf0 100644
--- a/fractal-gtk/src/appop/message.rs
+++ b/fractal-gtk/src/appop/message.rs
@@ -48,7 +48,7 @@ impl AppOp {
     pub fn add_room_message(&mut self, msg: &Message) -> Option<()> {
         let session_client = self.login_data.as_ref()?.session_client.clone();
         if let Some(ui_msg) = self.create_new_room_message(msg) {
-            if let Some(ref mut history) = self.history {
+            if let Some(ref mut history) = self.ui.history {
                 history.add_new_message(session_client, self.user_info_cache.clone(), ui_msg);
             }
         }
@@ -59,7 +59,7 @@ impl AppOp {
         let session_client =
             unwrap_or_unit_return!(self.login_data.as_ref().map(|ld| ld.session_client.clone()));
         if let Some(ui_msg) = self.create_new_room_message(msg) {
-            if let Some(ref mut history) = self.history {
+            if let Some(ref mut history) = self.ui.history {
                 history.remove_message(session_client, self.user_info_cache.clone(), ui_msg);
             }
         }
@@ -67,7 +67,7 @@ impl AppOp {
 
     pub fn add_tmp_room_message(&mut self, msg: Message) -> Option<()> {
         let login_data = self.login_data.clone()?;
-        let messages = self.history.as_ref()?.get_listbox();
+        let messages = self.ui.history.as_ref()?.get_listbox();
         if let Some(ui_msg) = self.create_new_room_message(&msg) {
             let mb = widgets::MessageBox::new().tmpwidget(
                 login_data.session_client.clone(),
@@ -91,7 +91,7 @@ impl AppOp {
     }
 
     pub fn clear_tmp_msgs(&mut self) -> Option<()> {
-        let messages = self.history.as_ref()?.get_listbox();
+        let messages = self.ui.history.as_ref()?.get_listbox();
         for t in self.msg_queue.iter_mut() {
             if let Some(ref w) = t.widget {
                 messages.remove(w);
@@ -103,7 +103,7 @@ impl AppOp {
 
     pub fn append_tmp_msgs(&mut self) -> Option<()> {
         let login_data = self.login_data.clone()?;
-        let messages = self.history.as_ref()?.get_listbox();
+        let messages = self.ui.history.as_ref()?.get_listbox();
 
         let r = self.rooms.get(self.active_room.as_ref()?)?;
         let mut widgets = vec![];
@@ -167,7 +167,7 @@ impl AppOp {
     }
 
     pub fn msg_sent(&mut self, evid: EventId) -> Option<()> {
-        let messages = self.history.as_ref()?.get_listbox();
+        let messages = self.ui.history.as_ref()?.get_listbox();
         if let Some(ref mut m) = self.msg_queue.pop() {
             if let Some(ref w) = m.widget {
                 messages.remove(w);
@@ -340,7 +340,7 @@ impl AppOp {
     /// to the matrix media server and we've the real url to use so we can
     /// replace the tmp message with the same id with this new one
     pub fn attached_file(&mut self, msg: Message) -> Option<()> {
-        let messages = self.history.as_ref()?.get_listbox();
+        let messages = self.ui.history.as_ref()?.get_listbox();
         let p = self.msg_queue.iter().position(|m| m.msg == msg);
         if let Some(i) = p {
             let w = self.msg_queue.remove(i);
@@ -394,8 +394,8 @@ impl AppOp {
                 }
             }
 
-            self.roomlist.moveup(&msg.room);
-            self.roomlist.set_bold(msg.room.clone(), true);
+            self.ui.roomlist.moveup(&msg.room);
+            self.ui.roomlist.set_bold(msg.room.clone(), true);
         }
 
         if msg_in_active {
@@ -433,7 +433,7 @@ impl AppOp {
             }
         }
 
-        if let Some(ref mut history) = self.history {
+        if let Some(ref mut history) = self.ui.history {
             history.add_old_messages_in_batch(session_client, self.user_info_cache.clone(), list);
         }
     }
diff --git a/fractal-gtk/src/appop/mod.rs b/fractal-gtk/src/appop/mod.rs
index 30897817..5982fe3d 100644
--- a/fractal-gtk/src/appop/mod.rs
+++ b/fractal-gtk/src/appop/mod.rs
@@ -1,7 +1,5 @@
-use std::cell::RefCell;
 use std::collections::HashMap;
 use std::path::PathBuf;
-use std::rc::Rc;
 use std::sync::{Arc, Mutex};
 use std::time::Duration;
 
@@ -15,17 +13,13 @@ use crate::cache::CacheMap;
 
 use crate::util::i18n;
 
-use crate::model::{
-    member::Member,
-    room::{Room, RoomList},
-};
+use crate::model::room::{Room, RoomList};
 use crate::passwd::PasswordStorage;
 
 use crate::actions::AppState;
 use crate::app::AppRuntime;
 use crate::cache;
 use crate::ui;
-use crate::widgets;
 
 mod about;
 mod account;
@@ -96,28 +90,19 @@ pub struct AppOp {
     pub active_room: Option<RoomId>,
     pub join_to_room: Option<RoomId>,
     pub rooms: RoomList,
-    pub room_settings: Option<widgets::RoomSettings>,
-    pub history: Option<widgets::RoomHistory>,
-    pub roomlist: widgets::RoomList,
     unread_rooms: usize,
     pub unsent_messages: HashMap<RoomId, (String, i32)>,
     pub typing: HashMap<RoomId, std::time::Instant>,
 
-    pub media_viewer: Rc<RefCell<Option<widgets::MediaViewer>>>,
-
     pub directory_pagination: RoomSearchPagination,
     pub state: AppState,
     pub since: Option<String>,
-    pub room_back_history: Rc<RefCell<Vec<AppState>>>,
 
     pub invitation_roomid: Option<RoomId>,
     pub md_enabled: bool,
-    pub invite_list: Vec<(Member, gtk::TextChildAnchor)>,
     search_type: SearchType,
 
     pub directory: Vec<Room>,
-    pub leaflet: libhandy::Leaflet,
-    pub deck: libhandy::Deck,
 
     pub user_info_cache: UserInfoCache,
 }
@@ -126,45 +111,28 @@ impl PasswordStorage for AppOp {}
 
 impl AppOp {
     pub fn new(ui: ui::UI, app_runtime: AppRuntime) -> AppOp {
-        let leaflet = ui
-            .builder
-            .get_object::<libhandy::Leaflet>("chat_page")
-            .expect("Couldn't find chat_page in ui file");
-        let deck = ui
-            .builder
-            .get_object::<libhandy::Deck>("main_deck")
-            .expect("Couldn't find main_deck in ui file");
-
         AppOp {
             app_runtime,
             ui,
             active_room: None,
             join_to_room: None,
             rooms: HashMap::new(),
-            room_settings: None,
-            history: None,
             login_data: None,
             syncing: false,
             msg_queue: vec![],
             sending_message: false,
             state: AppState::Login,
-            room_back_history: Rc::new(RefCell::new(vec![])),
-            roomlist: widgets::RoomList::new(None, None),
             directory_pagination: RoomSearchPagination::Initial,
             unread_rooms: 0,
             since: None,
             unsent_messages: HashMap::new(),
             typing: HashMap::new(),
-            media_viewer: Rc::new(RefCell::new(None)),
 
             md_enabled: false,
             invitation_roomid: None,
-            invite_list: vec![],
             search_type: SearchType::Invite,
 
             directory: vec![],
-            leaflet,
-            deck,
 
             user_info_cache: Arc::new(Mutex::new(
                 CacheMap::new().timeout(Duration::from_secs(60 * 60)),
@@ -210,7 +178,7 @@ impl AppOp {
     }
 
     pub fn update_title(&mut self) {
-        let unread = self.roomlist.rooms_with_notifications();
+        let unread = self.ui.roomlist.rooms_with_notifications();
         if self.unread_rooms != unread {
             let window = self.get_window();
             if unread == 0 {
diff --git a/fractal-gtk/src/appop/notifications.rs b/fractal-gtk/src/appop/notifications.rs
index 9a7b1996..1eee8107 100644
--- a/fractal-gtk/src/appop/notifications.rs
+++ b/fractal-gtk/src/appop/notifications.rs
@@ -4,7 +4,7 @@ use matrix_sdk::identifiers::RoomId;
 impl AppOp {
     pub fn clear_room_notifications(&mut self, room_id: RoomId) {
         self.set_room_notifications(room_id.clone(), 0, 0);
-        self.roomlist.set_bold(room_id, false);
+        self.ui.roomlist.set_bold(room_id, false);
         self.update_title();
     }
 
@@ -12,7 +12,8 @@ impl AppOp {
         if let Some(r) = self.rooms.get_mut(&room_id) {
             r.notifications = n;
             r.highlight = h;
-            self.roomlist
+            self.ui
+                .roomlist
                 .set_room_notifications(room_id, r.notifications, r.highlight);
         }
         self.update_title();
diff --git a/fractal-gtk/src/appop/room.rs b/fractal-gtk/src/appop/room.rs
index 715c060b..fc81484a 100644
--- a/fractal-gtk/src/appop/room.rs
+++ b/fractal-gtk/src/appop/room.rs
@@ -41,7 +41,7 @@ impl AppOp {
     pub fn remove_room(&mut self, id: RoomId) {
         self.rooms.remove(&id);
         self.unsent_messages.remove(&id);
-        self.roomlist.remove_room(&id);
+        self.ui.roomlist.remove_room(&id);
     }
 
     pub fn set_rooms(&mut self, rooms: Vec<Room>, clear_room_list: bool) {
@@ -109,8 +109,8 @@ impl AppOp {
                 if clear_room_list {
                     roomlist.push(room.clone());
                 } else {
-                    self.roomlist.add_room(room.clone());
-                    self.roomlist.moveup(&room.id);
+                    self.ui.roomlist.add_room(room.clone());
+                    self.ui.roomlist.moveup(&room.id);
                 }
                 self.rooms.insert(room.id.clone(), room);
             }
@@ -141,12 +141,12 @@ impl AppOp {
                 }
             }
 
-            self.roomlist =
+            self.ui.roomlist =
                 widgets::RoomList::new(adj, Some(login_data.session_client.homeserver().clone()));
-            self.roomlist.add_rooms(roomlist);
-            container.add(self.roomlist.widget());
+            self.ui.roomlist.add_rooms(roomlist);
+            container.add(self.ui.roomlist.widget());
 
-            self.roomlist.connect_fav(move |room, tofav| {
+            self.ui.roomlist.connect_fav(move |room, tofav| {
                 let session_client = login_data.session_client.clone();
                 let uid = login_data.uid.clone();
                 RUNTIME.spawn(async move {
@@ -242,7 +242,7 @@ impl AppOp {
         /* Transform id into the active_room */
         let active_room = id;
         // Select new active room in the sidebar
-        self.roomlist.select(&active_room);
+        self.ui.roomlist.select(&active_room);
 
         // getting room details
         let session_client = login_data.session_client.clone();
@@ -293,15 +293,14 @@ impl AppOp {
 
         /* make sure we remove the old room history first, because the lazy loading could try to
          * load messages */
-        if let Some(history) = self.history.take() {
+        if let Some(history) = self.ui.history.take() {
             history.destroy();
         }
 
-        let back_history = self.room_back_history.clone();
-        let actions =
-            actions::Message::new(self.app_runtime.clone(), self.ui.clone(), back_history);
+        let back_history = self.ui.room_back_history.clone();
+        let actions = actions::Message::new(self.app_runtime.clone(), back_history);
         let history = widgets::RoomHistory::new(actions, active_room.clone(), &self.ui);
-        self.history = if let Some(mut history) = history {
+        self.ui.history = if let Some(mut history) = history {
             history.create(
                 login_data.session_client,
                 self.user_info_cache.clone(),
@@ -330,7 +329,7 @@ impl AppOp {
         self.active_room = None;
         self.clear_tmp_msgs();
         self.set_state(AppState::NoRoom);
-        self.roomlist.remove_room(&room_id);
+        self.ui.roomlist.remove_room(&room_id);
 
         RUNTIME.spawn(async move {
             let query = room::leave_room(session_client, &room_id).await;
@@ -468,7 +467,8 @@ impl AppOp {
                 }
             }
             r.avatar = avatar;
-            self.roomlist
+            self.ui
+                .roomlist
                 .set_room_avatar(room_id.clone(), r.avatar.clone());
         }
     }
@@ -493,7 +493,7 @@ impl AppOp {
     }
 
     pub fn filter_rooms(&self, term: Option<String>) {
-        self.roomlist.filter_rooms(term);
+        self.ui.roomlist.filter_rooms(term);
     }
 
     pub fn new_room_dialog(&self) {
@@ -568,8 +568,8 @@ impl AppOp {
             self.rooms.insert(r.id.clone(), r.clone());
         }
 
-        self.roomlist.add_room(r.clone());
-        self.roomlist.moveup(&r.id);
+        self.ui.roomlist.add_room(r.clone());
+        self.ui.roomlist.moveup(&r.id);
 
         self.set_active_room_by_id(r.id);
     }
@@ -637,7 +637,7 @@ impl AppOp {
                 .set_text(&name.clone().unwrap_or_default());
         }
 
-        self.roomlist.rename_room(room_id, name);
+        self.ui.roomlist.rename_room(room_id, name);
     }
 
     pub fn room_topic_change(&mut self, room_id: RoomId, topic: Option<String>) {
@@ -707,7 +707,7 @@ impl AppOp {
     pub fn update_typing_notification(&mut self) {
         let active_room_id = unwrap_or_unit_return!(self.active_room.clone());
         let active_room = unwrap_or_unit_return!(self.rooms.get(&active_room_id));
-        let history = unwrap_or_unit_return!(self.history.as_mut());
+        let history = unwrap_or_unit_return!(self.ui.history.as_mut());
 
         let typing_users = &active_room.typing_users;
         if typing_users.is_empty() {
diff --git a/fractal-gtk/src/appop/room_settings.rs b/fractal-gtk/src/appop/room_settings.rs
index f4acc52a..2f0f476c 100644
--- a/fractal-gtk/src/appop/room_settings.rs
+++ b/fractal-gtk/src/appop/room_settings.rs
@@ -36,7 +36,7 @@ impl AppOp {
 
             stack.add_named(&page, "room-settings");
 
-            self.room_settings = Some(panel);
+            self.ui.room_settings = Some(panel);
         }
 
         self.set_state(AppState::RoomSettings);
@@ -45,25 +45,25 @@ impl AppOp {
     }
 
     pub fn show_new_room_avatar(&self) -> Option<()> {
-        let panel = self.room_settings.clone()?;
+        let panel = self.ui.room_settings.clone()?;
         panel.show_new_room_avatar();
         None
     }
 
     pub fn show_new_room_name(&self) -> Option<()> {
-        let panel = self.room_settings.clone()?;
+        let panel = self.ui.room_settings.clone()?;
         panel.show_new_room_name();
         None
     }
 
     pub fn show_new_room_topic(&self) -> Option<()> {
-        let panel = self.room_settings.clone()?;
+        let panel = self.ui.room_settings.clone()?;
         panel.show_new_room_topic();
         None
     }
 
     pub fn set_notifications_switch(&self, active: bool, sensitive: bool) -> Option<()> {
-        let panel = self.room_settings.clone()?;
+        let panel = self.ui.room_settings.clone()?;
         panel.set_notifications_switch(active, sensitive);
         None
     }
diff --git a/fractal-gtk/src/appop/start_chat.rs b/fractal-gtk/src/appop/start_chat.rs
index 1000d6f5..17ad8503 100644
--- a/fractal-gtk/src/appop/start_chat.rs
+++ b/fractal-gtk/src/appop/start_chat.rs
@@ -8,7 +8,7 @@ use crate::backend::HandleError;
 
 impl AppOp {
     pub fn start_chat(&mut self) {
-        if self.invite_list.len() != 1 {
+        if self.ui.invite_list.len() != 1 {
             return;
         }
 
@@ -16,7 +16,7 @@ impl AppOp {
             .login_data
             .as_ref()
             .map(|ld| (ld.session_client.clone(), ld.uid.clone())));
-        let member = self.invite_list[0].0.clone();
+        let member = self.ui.invite_list[0].0.clone();
 
         RUNTIME.spawn(async move {
             match room::direct_chat(session_client, &user_id, member).await {
@@ -82,7 +82,7 @@ impl AppOp {
             .get_object::<gtk::Dialog>("direct_chat_dialog")
             .expect("Can't find direct_chat_dialog in ui file.");
 
-        self.invite_list = vec![];
+        self.ui.invite_list = vec![];
         if let Some(buffer) = to_chat_entry.get_buffer() {
             let mut start = buffer.get_start_iter();
             let mut end = buffer.get_end_iter();
diff --git a/fractal-gtk/src/appop/state.rs b/fractal-gtk/src/appop/state.rs
index aa7811bd..e92b5729 100644
--- a/fractal-gtk/src/appop/state.rs
+++ b/fractal-gtk/src/appop/state.rs
@@ -96,12 +96,15 @@ impl AppOp {
         match state {
             AppState::NoRoom => {
                 self.set_state_no_room(&headerbar);
-                self.leaflet.navigate(libhandy::NavigationDirection::Back);
+                self.ui
+                    .leaflet
+                    .navigate(libhandy::NavigationDirection::Back);
                 stack.set_visible_child_name("noroom");
             }
             AppState::Room => {
                 self.set_state_room(&headerbar);
-                self.leaflet
+                self.ui
+                    .leaflet
                     .navigate(libhandy::NavigationDirection::Forward);
                 stack.set_visible_child_name("room_view");
             }
@@ -143,7 +146,7 @@ impl AppOp {
             ch.hide();
 
             // Select new active room in the sidebar
-            self.roomlist.unselect();
+            self.ui.roomlist.unselect();
         }
         self.active_room = None;
         self.clear_tmp_msgs();
diff --git a/fractal-gtk/src/ui/mod.rs b/fractal-gtk/src/ui/mod.rs
index 0dd506b0..c5ee61b4 100644
--- a/fractal-gtk/src/ui/mod.rs
+++ b/fractal-gtk/src/ui/mod.rs
@@ -1,21 +1,29 @@
-use crate::model::message::Message;
+use crate::actions::AppState;
+use crate::model::{member::Member, message::Message};
 use crate::util::i18n::i18n;
 use crate::widgets::{self, SVEntry};
 use chrono::prelude::{DateTime, Local};
 use gtk::{self, prelude::*};
 use matrix_sdk::identifiers::{EventId, UserId};
-use std::path::PathBuf;
+use std::{cell::RefCell, path::PathBuf, rc::Rc};
 use url::Url;
 
 pub mod connect;
 
-#[derive(Clone, Debug)]
 pub struct UI {
     pub builder: gtk::Builder,
     pub gtk_app: gtk::Application,
     pub main_window: libhandy::ApplicationWindow,
     pub sventry: SVEntry,
     pub sventry_box: Box<gtk::Stack>,
+    pub room_settings: Option<widgets::RoomSettings>,
+    pub history: Option<widgets::RoomHistory>,
+    pub roomlist: widgets::RoomList,
+    pub media_viewer: Rc<RefCell<Option<widgets::MediaViewer>>>,
+    pub room_back_history: Rc<RefCell<Vec<AppState>>>,
+    pub invite_list: Vec<(Member, gtk::TextChildAnchor)>,
+    pub leaflet: libhandy::Leaflet,
+    pub deck: libhandy::Deck,
 }
 
 impl UI {
@@ -100,12 +108,27 @@ impl UI {
         main_window.set_application(Some(&gtk_app));
         main_window.set_title("Fractal");
 
+        let leaflet = builder
+            .get_object::<libhandy::Leaflet>("chat_page")
+            .expect("Couldn't find chat_page in ui file");
+        let deck = builder
+            .get_object::<libhandy::Deck>("main_deck")
+            .expect("Couldn't find main_deck in ui file");
+
         UI {
             builder,
             gtk_app,
             main_window,
             sventry,
             sventry_box,
+            room_settings: None,
+            history: None,
+            roomlist: widgets::RoomList::new(None, None),
+            media_viewer: Rc::new(RefCell::new(None)),
+            room_back_history: Rc::new(RefCell::new(vec![])),
+            invite_list: vec![],
+            leaflet,
+            deck,
         }
     }
 }


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