[geary/wip/730682-refine-convo-list: 9/12] Re-implement conversation lazy loading on scroll.



commit c1365b40716d8220e4c234104df172235eb5b604
Author: Michael James Gratton <mike vee net>
Date:   Sun Oct 15 07:58:36 2017 +1030

    Re-implement conversation lazy loading on scroll.
    
    * src/client/conversation-list/conversation-list.vala (ConversationList):
      Move lazy loading code over from old ConversationListView class. Use a
      percentage of the viewport size rather than an absolute value so that
      loading is triggered in around the same place regardless of the window
      size.
    
    * src/client/application/geary-controller.vala (GearyController): Move
      lazy-loading related code to MainWindow.
    
    * src/client/components/main-window.vala (MainWindow): Hook up
      lazy-loading signals to new conversation list.

 src/client/application/geary-controller.vala       |   23 -----
 src/client/components/main-window.vala             |   25 ++++++
 .../conversation-list/conversation-list.vala       |   85 ++++++++++++++++++++
 3 files changed, 110 insertions(+), 23 deletions(-)
---
diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala
index bc99e2f..f3b4a55 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -226,7 +226,6 @@ public class GearyController : Geary.BaseObject {
         Geary.Engine.instance.untrusted_host.connect(on_untrusted_host);
         
         // Connect to various UI signals.
-        main_window.conversation_list_view.load_more.connect(on_load_more);
         main_window.conversation_list_view.mark_conversations.connect(on_mark_conversations);
         
main_window.conversation_list_view.visible_conversations_changed.connect(on_visible_conversations_changed);
         main_window.folder_list.folder_selected.connect(on_folder_selected);
@@ -298,7 +297,6 @@ public class GearyController : Geary.BaseObject {
         Geary.Engine.instance.untrusted_host.disconnect(on_untrusted_host);
 
         // Disconnect from various UI signals.
-        main_window.conversation_list_view.load_more.disconnect(on_load_more);
         main_window.conversation_list_view.mark_conversations.disconnect(on_mark_conversations);
         
main_window.conversation_list_view.visible_conversations_changed.disconnect(on_visible_conversations_changed);
         main_window.folder_list.folder_selected.disconnect(on_folder_selected);
@@ -1446,9 +1444,6 @@ public class GearyController : Geary.BaseObject {
             // Inbox selected, clear new messages if visible
             clear_new_messages("do_select_folder (inbox)", null);
         }
-        
-        current_conversations.scan_error.connect(on_scan_error);
-        current_conversations.seed_completed.connect(on_seed_completed);
 
         if (!current_conversations.is_monitoring)
             yield current_conversations.start_monitoring_async(conversation_cancellable);
@@ -1458,19 +1453,6 @@ public class GearyController : Geary.BaseObject {
         debug("Switched to %s", folder.to_string());
     }
 
-    private void on_scan_error(Error err) {
-        debug("Scan error: %s", err.message);
-    }
-    
-    private void on_seed_completed() {
-        // Done scanning.  Check if we have enough messages to fill the conversation list; if not,
-        // trigger a load_more();
-        if (!main_window.conversation_list_has_scrollbar()) {
-            debug("Not enough messages, loading more for folder %s", current_folder.to_string());
-            on_load_more();
-        }
-    }
-
     private void on_libnotify_invoked(Geary.Folder? folder, Geary.Email? email) {
         new_messages_monitor.clear_all_new_messages();
         
@@ -1500,11 +1482,6 @@ public class GearyController : Geary.BaseObject {
         main_window.folder_list.select_folder(folder);
     }
     
-    private void on_load_more() {
-        debug("on_load_more");
-        current_conversations.min_window_count += MIN_CONVERSATION_COUNT;
-    }
-    
     private void on_select_folder_completed(Object? source, AsyncResult result) {
         try {
             do_select_folder.end(result);
diff --git a/src/client/components/main-window.vala b/src/client/components/main-window.vala
index c333b6a..5031c7f 100644
--- a/src/client/components/main-window.vala
+++ b/src/client/components/main-window.vala
@@ -66,6 +66,7 @@ public class MainWindow : Gtk.ApplicationWindow {
         this.conversation_list.conversation_selection_changed.connect(on_conversation_selection_changed);
         this.conversation_list.conversation_activated.connect(on_conversation_activated);
 
+        this.conversation_list.load_more.connect(on_load_more);
         load_config(application.config);
         restore_saved_window_state();
 
@@ -85,6 +86,7 @@ public class MainWindow : Gtk.ApplicationWindow {
     ~MainWindow() {
         this.conversation_list.conversation_selection_changed.disconnect(on_conversation_selection_changed);
         this.conversation_list.conversation_activated.disconnect(on_conversation_activated);
+        this.conversation_list.load_more.disconnect(on_load_more);
     }
 
     /**
@@ -288,6 +290,8 @@ public class MainWindow : Gtk.ApplicationWindow {
         Geary.App.ConversationMonitor? old_monitor = (this.conversation_list != null)
             ? this.conversation_list.model.monitor : null;
         if (old_monitor != null) {
+            old_monitor.scan_error.disconnect(on_scan_error);
+            old_monitor.seed_completed.disconnect(on_seed_completed);
             old_monitor.seed_completed.disconnect(on_conversation_count_changed);
             old_monitor.scan_completed.disconnect(on_conversation_count_changed);
             old_monitor.conversations_added.disconnect(on_conversation_count_changed);
@@ -305,6 +309,9 @@ public class MainWindow : Gtk.ApplicationWindow {
             this.conversation_list_view.set_model(new_model);
 
             this.conversation_list.bind_model(new_monitor);
+
+            new_monitor.scan_error.connect(on_scan_error);
+            new_monitor.seed_completed.connect(on_seed_completed);
             new_monitor.seed_completed.connect(on_conversation_count_changed);
             new_monitor.scan_completed.connect(on_conversation_count_changed);
             new_monitor.conversations_added.connect(on_conversation_count_changed);
@@ -489,6 +496,24 @@ public class MainWindow : Gtk.ApplicationWindow {
         }
     }
 
+    private void on_load_more() {
+        debug("on_load_more");
+        this.application.controller.current_conversations.min_window_count += 
GearyController.MIN_CONVERSATION_COUNT;
+    }
+
+    private void on_scan_error(Error err) {
+        debug("Scan error: %s", err.message);
+    }
+
+    private void on_seed_completed() {
+        // Done scanning.  Check if we have enough messages to fill the conversation list; if not,
+        // trigger a load_more();
+        if (!conversation_list_has_scrollbar()) {
+            debug("Not enough messages, loading more for folder %s", current_folder.to_string());
+            on_load_more();
+        }
+    }
+
     private void on_conversation_count_changed() {
         if (this.application.controller.current_conversations != null) {
             int count = this.application.controller.current_conversations.get_conversation_count();
diff --git a/src/client/conversation-list/conversation-list.vala 
b/src/client/conversation-list/conversation-list.vala
index 525cd55..c404b97 100644
--- a/src/client/conversation-list/conversation-list.vala
+++ b/src/client/conversation-list/conversation-list.vala
@@ -19,6 +19,9 @@ public class ConversationList : Gtk.ListBox {
 
     private Configuration config;
 
+    private bool enable_load_more = true;
+    private bool reset_adjustment = false;
+    private double adj_last_upper = -1.0;
 
 
     /** Fired when a user changes the list's selection. */
@@ -27,6 +30,10 @@ public class ConversationList : Gtk.ListBox {
     /** Fired when a user activates a row in the list. */
     public signal void conversation_activated(Geary.App.Conversation activated);
 
+    /** Fired when additional conversations are required. */
+    public virtual signal void load_more() {
+        this.enable_load_more = false;
+    }
 
     public ConversationList(Configuration config) {
         this.config = config;
@@ -47,10 +54,17 @@ public class ConversationList : Gtk.ListBox {
                 }
                 this.conversation_selection_changed(new_selection);
             });
+
+        this.show.connect(on_show);
     }
 
     public new void bind_model(Geary.App.ConversationMonitor monitor) {
+        monitor.scan_started.connect(on_scan_started);
+        monitor.scan_completed.connect(on_scan_completed);
+
         this.model = new ConversationListModel(monitor);
+        this.model.items_changed.connect(on_model_items_changed);
+
         Geary.Folder displayed = monitor.folder;
         Gee.List<Geary.RFC822.MailboxAddress> account_addresses = 
displayed.account.information.get_all_mailboxes();
         bool use_to = (displayed != null) && displayed.special_folder_type.is_outgoing();
@@ -63,4 +77,75 @@ public class ConversationList : Gtk.ListBox {
         );
     }
 
+    private void on_show() {
+        // Wait until we're visible to set this signal up.
+        get_adjustment().value_changed.connect(on_adjustment_value_changed);
+    }
+
+    private void on_adjustment_value_changed() {
+        Gtk.Adjustment? adjustment = get_adjustment();
+        if (this.enable_load_more && adjustment != null) {
+            // Check if we're towards the bottom of the list. If we
+            // are, it's time to issue a load_more signal.
+            double value = adjustment.get_value();
+            double upper = adjustment.get_upper();
+            if ((value / upper) >= 0.85 &&
+                upper > this.adj_last_upper) {
+                load_more();
+                this.adj_last_upper = upper;
+            }
+        }
+    }
+
+    private void on_scan_started() {
+        this.enable_load_more = false;
+    }
+
+    private void on_scan_completed() {
+        this.enable_load_more = true;
+
+        // Select the first conversation, if autoselect is enabled,
+        // nothing has been selected yet and we're not composing. Do
+        // this here instead of in on_seed_completed since we want to
+        // to select the first row on folder change as soon as
+        // possible.
+        if (this.config.autoselect && get_selected_row() == null) {
+            Gtk.ListBoxRow? first = get_row_at_index(0);
+            if (first != null) {
+                select_row(first);
+            }
+        }
+    }
+
+    private void on_model_items_changed(uint pos, uint removed, uint added) {
+        if (added > 0) {
+            // Conversations were added
+            Gtk.Adjustment? adjustment = get_adjustment();
+            if (pos == 0) {
+                // We were at the top and we want to stay there after
+                // conversations are added
+                this.reset_adjustment = (adjustment != null) && (adjustment.get_value() == 0);
+            } else if (this.reset_adjustment && adjustment != null) {
+                // Pump the loop to make sure the new conversations are
+                // taking up space in the window.  Without this, setting
+                // the adjustment here is a no-op because as far as it's
+                // concerned, it's already at the top.
+                while (Gtk.events_pending())
+                    Gtk.main_iteration();
+
+                adjustment.set_value(0);
+            }
+            this.reset_adjustment = false;
+        }
+
+        if (removed >= 0) {
+            // Conversations were removed.
+
+            // Reset the last upper limit so scrolling to the bottom
+            // will always activate a reload (this is particularly
+            // important if the model is cleared)
+            this.adj_last_upper = -1.0;
+        }
+    }
+
 }


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