[geary/mjog/account-command-stacks: 10/27] Implement conversation flag updating as undoable commands



commit 6fbb8b87cbe5f9d828b77c9f95bcdd4d5ed64c01
Author: Michael Gratton <mike vee net>
Date:   Sat Oct 5 19:50:50 2019 +1000

    Implement conversation flag updating as undoable commands
    
    This lets us undo marking as read/unread/starred/etc.

 src/client/application/application-controller.vala | 174 +++++++++++++++++++++
 src/client/components/main-window.vala             | 103 +++++++++++-
 .../conversation-list/conversation-list-view.vala  |  31 ++--
 3 files changed, 285 insertions(+), 23 deletions(-)
---
diff --git a/src/client/application/application-controller.vala 
b/src/client/application/application-controller.vala
index bc99ff0a..c3726e56 100644
--- a/src/client/application/application-controller.vala
+++ b/src/client/application/application-controller.vala
@@ -525,6 +525,137 @@ public class Application.Controller : Geary.BaseObject {
         return (context != null) ? context.contacts : null;
     }
 
+    /**
+     * Updates flags for a collection of conversations.
+     *
+     * If `prefer_adding` is true, this will add the flag if not set
+     * on all conversations or else will remove it. If false, this
+     * will remove the flag if not set on all conversations or else
+     * add it.
+     */
+    public async void mark_conversations(Geary.Account target,
+                                         Gee.Collection<Geary.App.Conversation> conversations,
+                                         Geary.NamedFlag flag,
+                                         bool prefer_adding)
+        throws GLib.Error {
+        Geary.Iterable<Geary.App.Conversation> selecting =
+            Geary.traverse(conversations);
+        Geary.EmailFlags flags = new Geary.EmailFlags();
+
+        if (flag.equal_to(Geary.EmailFlags.UNREAD)) {
+            selecting = selecting.filter(c => prefer_adding ^ c.is_unread());
+            flags.add(Geary.EmailFlags.UNREAD);
+        } else if (flag.equal_to(Geary.EmailFlags.FLAGGED)) {
+            selecting = selecting.filter(c => prefer_adding ^ c.is_flagged());
+            flags.add(Geary.EmailFlags.FLAGGED);
+        } else {
+            throw new Geary.EngineError.UNSUPPORTED(
+                "Marking as %s is not supported", flag.to_string()
+            );
+        }
+
+        Gee.Collection<Geary.EmailIdentifier>? messages = null;
+        Gee.Collection<Geary.App.Conversation> selected =
+            selecting.to_linked_list();
+
+        bool do_add = prefer_adding ^ selected.is_empty;
+        if (selected.is_empty) {
+            selected = conversations;
+        }
+
+        if (do_add) {
+            // Only apply to the latest in-folder message in
+            // conversations that don't already have the flag, since
+            // we don't want to flag every message in the conversation
+            messages = Geary.traverse(selected).map<Geary.EmailIdentifier>(
+                c => c.get_latest_recv_email(IN_FOLDER_OUT_OF_FOLDER).id
+            ).to_linked_list();
+        } else {
+            // Remove the flag from those that have it
+            messages = new Gee.LinkedList<Geary.EmailIdentifier>();
+            foreach (Geary.App.Conversation convo in selected) {
+                foreach (Geary.Email email in
+                         convo.get_emails(RECV_DATE_DESCENDING)) {
+                    if (email.email_flags != null &&
+                        email.email_flags.contains(flag)) {
+                        messages.add(email.id);
+                    }
+                }
+            }
+        }
+
+        AccountContext? context = this.accounts.get(target.information);
+        if (context != null) {
+            yield this.commands.execute(
+                new MarkEmailCommand(
+                    context.emails,
+                    messages,
+                    do_add ? flags : null,
+                    do_add ? null : flags,
+                    /// Translators: Label for in-app undo notification
+                    ngettext(
+                        "Conversation marked",
+                        "Conversations marked",
+                        selected.size
+                    ),
+                    /// Translators: Label for in-app undo notification
+                    ngettext(
+                        "Conversation un-marked",
+                        "Conversations un-marked",
+                        selected.size
+                    )
+
+                ),
+                context.cancellable
+            );
+        }
+    }
+
+    /**
+     * Updates flags for a collection of email.
+     *
+     * This should only be used when working with specific messages
+     * (for example, marking a specific message in a conversation)
+     * rather than when working with whole conversations. In that
+     * case, use {@link mark_conversations}.
+     */
+    public async void mark_messages(Geary.Account target,
+                                    Gee.Collection<Geary.EmailIdentifier> messages,
+                                    Geary.EmailFlags? to_add,
+                                    Geary.EmailFlags? to_remove)
+        throws GLib.Error {
+        AccountContext? context = this.accounts.get(target.information);
+        if (context != null) {
+            yield this.commands.execute(
+                new MarkEmailCommand(
+                    context.emails,
+                    messages,
+                    to_add,
+                    to_remove,
+                    /// Translators: Label for in-app undo notification
+                    ngettext(
+                        "Message marked",
+                        "Messages marked",
+                        messages.size
+                    ),
+                    /// Translators: Label for in-app undo notification
+                    ngettext(
+                        "Message un-marked",
+                        "Messages un-marked",
+                        messages.size
+                    )
+                ),
+                context.cancellable
+            );
+        }
+    }
+
+                ),
+                context.cancellable
+            );
+        }
+    }
+
     /** Expunges removed accounts while the controller remains open. */
     internal async void expunge_accounts() {
         try {
@@ -1953,3 +2084,46 @@ public class Application.Controller : Geary.BaseObject {
     }
 
 }
+
+
+private class Application.MarkEmailCommand : Command {
+
+
+    private Geary.App.EmailStore store;
+    private Gee.Collection<Geary.EmailIdentifier> messages;
+    private Geary.EmailFlags? to_add;
+    private Geary.EmailFlags? to_remove;
+
+
+    public MarkEmailCommand(Geary.App.EmailStore store,
+                            Gee.Collection<Geary.EmailIdentifier> messages,
+                            Geary.EmailFlags? to_add,
+                            Geary.EmailFlags? to_remove,
+                            string? executed_label = null,
+                            string? undone_label = null) {
+        this.store = store;
+        this.messages = messages;
+        this.to_add = to_add;
+        this.to_remove = to_remove;
+
+        this.executed_label = executed_label;
+        this.undone_label = undone_label;
+    }
+
+    public override async void execute(GLib.Cancellable? cancellable)
+        throws GLib.Error {
+        yield this.store.mark_email_async(
+            this.messages, this.to_add, this.to_remove, cancellable
+        );
+    }
+
+    public override async void undo(GLib.Cancellable? cancellable)
+        throws GLib.Error {
+        yield this.store.mark_email_async(
+            this.messages, this.to_remove, this.to_add, cancellable
+        );
+    }
+
+}
+
+
diff --git a/src/client/components/main-window.vala b/src/client/components/main-window.vala
index 1a446c34..cf30b1b5 100644
--- a/src/client/components/main-window.vala
+++ b/src/client/components/main-window.vala
@@ -544,6 +544,7 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
     private void setup_layout(Configuration config) {
         this.conversation_list_view = new ConversationListView(this);
         this.conversation_list_view.load_more.connect(on_load_more);
+        this.conversation_list_view.mark_conversations.connect(on_mark_conversations);
         this.conversation_list_view.conversations_selected.connect(on_conversations_selected);
 
         this.conversation_viewer = new ConversationViewer(
@@ -1401,16 +1402,100 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
             current_folder.special_folder_type != Geary.SpecialFolderType.OUTBOX);
     }
 
+    private void on_mark_conversations(Gee.Collection<Geary.App.Conversation> conversations,
+                                       Geary.NamedFlag flag) {
+        Geary.Account? target = this.current_account;
+        if (target != null) {
+            this.application.controller.mark_conversations.begin(
+                target,
+                conversations,
+                flag,
+                true,
+                (obj, res) => {
+                    try {
+                        this.application.controller.mark_conversations.end(res);
+                    } catch (GLib.Error err) {
+                        handle_error(target.information, err);
+                    }
+                }
+            );
+        }
+    }
+
     private void on_mark_as_read() {
+        Geary.Account? target = this.current_account;
+        if (target != null) {
+            this.application.controller.mark_conversations.begin(
+                target,
+                this.conversation_list_view.get_selected_conversations(),
+                Geary.EmailFlags.UNREAD,
+                false,
+                (obj, res) => {
+                    try {
+                        this.application.controller.mark_conversations.end(res);
+                    } catch (GLib.Error err) {
+                        handle_error(target.information, err);
+                    }
+                }
+            );
+        }
     }
 
     private void on_mark_as_unread() {
+        Geary.Account? target = this.current_account;
+        if (target != null) {
+            this.application.controller.mark_conversations.begin(
+                target,
+                this.conversation_list_view.get_selected_conversations(),
+                Geary.EmailFlags.UNREAD,
+                true,
+                (obj, res) => {
+                    try {
+                        this.application.controller.mark_conversations.end(res);
+                    } catch (GLib.Error err) {
+                        handle_error(target.information, err);
+                    }
+                }
+            );
+        }
     }
 
     private void on_mark_as_starred() {
+        Geary.Account? target = this.current_account;
+        if (target != null) {
+            this.application.controller.mark_conversations.begin(
+                target,
+                this.conversation_list_view.get_selected_conversations(),
+                Geary.EmailFlags.FLAGGED,
+                true,
+                (obj, res) => {
+                    try {
+                        this.application.controller.mark_conversations.end(res);
+                    } catch (GLib.Error err) {
+                        handle_error(target.information, err);
+                    }
+                }
+            );
+        }
     }
 
     private void on_mark_as_unstarred() {
+        Geary.Account? target = this.current_account;
+        if (target != null) {
+            this.application.controller.mark_conversations.begin(
+                target,
+                this.conversation_list_view.get_selected_conversations(),
+                Geary.EmailFlags.FLAGGED,
+                false,
+                (obj, res) => {
+                    try {
+                        this.application.controller.mark_conversations.end(res);
+                    } catch (GLib.Error err) {
+                        handle_error(target.information, err);
+                    }
+                }
+            );
+        }
     }
 
     private void on_mark_as_spam_toggle() {
@@ -1437,9 +1522,25 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
     private void on_empty_trash() {
     // Individual message view action callbacks
 
-    private void on_conversation_viewer_mark_emails(Gee.Collection<Geary.EmailIdentifier> email,
+    private void on_conversation_viewer_mark_emails(Gee.Collection<Geary.EmailIdentifier> messages,
                                                     Geary.EmailFlags? to_add,
                                                     Geary.EmailFlags? to_remove) {
+        Geary.Account? target = this.current_account;
+        if (target != null) {
+            this.application.controller.mark_messages.begin(
+                target,
+                messages,
+                to_add,
+                to_remove,
+                (obj, res) => {
+                    try {
+                        this.application.controller.mark_messages.end(res);
+                    } catch (GLib.Error err) {
+                        handle_error(target.information, err);
+                    }
+                }
+            );
+        }
     }
 
     private void on_email_load_error(ConversationEmail view, GLib.Error err) {
diff --git a/src/client/conversation-list/conversation-list-view.vala 
b/src/client/conversation-list/conversation-list-view.vala
index 39a503d9..3050935d 100644
--- a/src/client/conversation-list/conversation-list-view.vala
+++ b/src/client/conversation-list/conversation-list-view.vala
@@ -7,6 +7,7 @@
 public class ConversationListView : Gtk.TreeView, Geary.BaseInterface {
     const int LOAD_MORE_HEIGHT = 100;
 
+
     // Used to be able to refer to the action names of the MainWindow
     private weak MainWindow main_window;
 
@@ -29,7 +30,7 @@ public class ConversationListView : Gtk.TreeView, Geary.BaseInterface {
     }
 
     public signal void mark_conversations(Gee.Collection<Geary.App.Conversation> conversations,
-        Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove, bool only_mark_preview);
+                                          Geary.NamedFlag flag);
 
     public signal void visible_conversations_changed(Gee.Set<Geary.App.Conversation> visible);
 
@@ -289,32 +290,18 @@ public class ConversationListView : Gtk.TreeView, Geary.BaseInterface {
             // all selected conversations; otherwise, it just applies to this one.
             Geary.App.Conversation conversation = get_model().get_conversation_at_path(path);
             Gee.Collection<Geary.App.Conversation> to_mark;
-            if (GearyApplication.instance.controller.get_selected_conversations().contains(conversation))
-                to_mark = GearyApplication.instance.controller.get_selected_conversations();
+            if (this.selected.contains(conversation))
+                // take a copy of currently selected for handling to
+                // the signal
+                to_mark = get_selected_conversations();
             else
-                to_mark = Geary.iterate<Geary.App.Conversation>(conversation).to_array_list();
+                to_mark = Geary.Collection.single(conversation);
 
             if (read_clicked) {
-                // Read/unread.
-                Geary.EmailFlags flags = new Geary.EmailFlags();
-                flags.add(Geary.EmailFlags.UNREAD);
-
-                if (conversation.is_unread())
-                    mark_conversations(to_mark, null, flags, false);
-                else
-                    mark_conversations(to_mark, flags, null, true);
-
+                mark_conversations(to_mark, Geary.EmailFlags.UNREAD);
                 return true;
             } else if (star_clicked) {
-                // Starred/unstarred.
-                Geary.EmailFlags flags = new Geary.EmailFlags();
-                flags.add(Geary.EmailFlags.FLAGGED);
-
-                if (conversation.is_flagged())
-                    mark_conversations(to_mark, null, flags, false);
-                else
-                    mark_conversations(to_mark, flags, null, true);
-
+                mark_conversations(to_mark, Geary.EmailFlags.FLAGGED);
                 return true;
             }
         }


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