diff --git a/src/conversation-list/conversation-list-model.vala b/src/conversation-list/conversation-list-model.vala index 643f971..3fe3dd8 100644 --- a/src/conversation-list/conversation-list-model.vala +++ b/src/conversation-list/conversation-list-model.vala @@ -21,29 +21,90 @@ public class ConversationListModel : Object, ListModel public void load_conversations() { try { - Conversation[] conversations = Repository.get_instance().get_conversations(); + Conversation[] new_conversations = Repository.get_instance().get_conversations(); - // Clear existing set - uint old_count = _conversations.size; - _conversations.clear(); - - // Notify of removal - if (old_count > 0) { - items_changed(0, old_count, 0); + // Create a map of old conversations for quick lookup + var old_conversations_map = new HashMap(); + foreach (var conv in _conversations) { + old_conversations_map[conv.guid] = conv; } - // Process each conversation - uint position = 0; - - for (int i = 0; i < conversations.length; i++) { - var conversation = conversations[i]; - _conversations.add(conversation); - position++; + // Create a map of new conversations for quick lookup + var new_conversations_map = new HashMap(); + foreach (var conv in new_conversations) { + new_conversations_map[conv.guid] = conv; } - // Notify of additions - if (position > 0) { - items_changed(0, 0, position); + // Find removed conversations + var removed_positions = new ArrayList(); + var current_position = 0; + foreach (var old_conv in _conversations) { + if (!new_conversations_map.has_key(old_conv.guid)) { + removed_positions.add(current_position); + } + current_position++; + } + + // Remove conversations in reverse order to maintain correct positions + for (int i = removed_positions.size - 1; i >= 0; i--) { + var pos = removed_positions[i]; + _conversations.remove(_conversations.to_array()[pos]); + items_changed(pos, 1, 0); + } + + // Find added conversations and changed conversations + var added_conversations = new ArrayList(); + var changed_conversations = new ArrayList(); + foreach (var new_conv in new_conversations) { + if (!old_conversations_map.has_key(new_conv.guid)) { + added_conversations.add(new_conv); + } else { + var old_conv = old_conversations_map[new_conv.guid]; + if (!old_conv.equals(new_conv)) { + changed_conversations.add(new_conv); + } + } + } + + // Add new conversations + foreach (var conv in added_conversations) { + _conversations.add(conv); + // Find the position by counting how many items are before this one + uint pos = 0; + foreach (var existing_conv in _conversations) { + if (existing_conv.guid == conv.guid) break; + pos++; + } + items_changed(pos, 0, 1); + } + + // Update changed conversations + GLib.message("Changed conversations: %d", changed_conversations.size); + foreach (var conv in changed_conversations) { + // Find position of old conversation + uint old_pos = 0; + var old_conv = old_conversations_map[conv.guid]; + foreach (var existing_conv in _conversations) { + if (existing_conv.guid == old_conv.guid) break; + old_pos++; + } + + // Remove the old one + _conversations.remove(old_conv); + + // Add the new one + _conversations.add(conv); + + // Find the new (sorted) position + uint new_pos = 0; + foreach (var existing_conv in _conversations) { + if (existing_conv.guid == conv.guid) break; + new_pos++; + } + + // Notify of the change + items_changed(old_pos, 1, 0); + items_changed(new_pos, 0, 1); } } catch (Error e) { warning("Failed to load conversations: %s", e.message); diff --git a/src/conversation-list/conversation-list-view.vala b/src/conversation-list/conversation-list-view.vala index d667870..90a0450 100644 --- a/src/conversation-list/conversation-list-view.vala +++ b/src/conversation-list/conversation-list-view.vala @@ -11,6 +11,9 @@ public class ConversationListView : Adw.Bin private Adw.HeaderBar header_bar; private ConversationListModel conversation_model; + private string? selected_conversation_guid = null; + private bool selection_update_queued = false; + public ConversationListView () { container = new Adw.ToolbarView (); set_child (container); @@ -25,7 +28,10 @@ public class ConversationListView : Adw.Bin list_box.row_selected.connect ((row) => { var conversation_row = (ConversationRow?) row; - conversation_selected(conversation_row != null ? conversation_row.conversation.guid : null); + if (conversation_row != null) { + selected_conversation_guid = conversation_row.conversation.guid; + conversation_selected(selected_conversation_guid); + } }); header_bar = new Adw.HeaderBar (); @@ -44,9 +50,43 @@ public class ConversationListView : Adw.Bin // Set up model and bind to list conversation_model = new ConversationListModel (); + conversation_model.items_changed.connect (on_items_changed); list_box.bind_model (conversation_model, create_conversation_row); } + private void on_items_changed (uint position, uint removed, uint added) { + enqueue_selection_update(); + } + + private void enqueue_selection_update() { + if (selection_update_queued) { + return; + } + + selection_update_queued = true; + GLib.Idle.add(() => { + update_selection(); + selection_update_queued = false; + return false; + }, GLib.Priority.HIGH); + } + + private void update_selection() { + // Re-select selected_conversation_guid, if it has changed. + if (selected_conversation_guid != null) { + for (uint i = 0; i < conversation_model.get_n_items(); i++) { + var conversation = (Conversation) conversation_model.get_item(i); + if (conversation.guid == selected_conversation_guid) { + var row = list_box.get_row_at_index((int)i); + if (row != null) { + list_box.select_row(row); + } + } + } + } + } + + private Widget create_conversation_row (Object item) { Conversation conversation = (Conversation) item; return new ConversationRow (conversation); diff --git a/src/models/conversation.vala b/src/models/conversation.vala index 3e489ea..c7c68a0 100644 --- a/src/models/conversation.vala +++ b/src/models/conversation.vala @@ -52,4 +52,21 @@ public class Conversation : Object { _display_name = conversation_data["display_name"].get_string(); } } + + public bool equals(Conversation other) { + if (other == null) return false; + if (guid != other.guid) return false; + if (date != other.date) return false; + if (unread_count != other.unread_count) return false; + if (last_message_preview != other.last_message_preview) return false; + if (_display_name != other._display_name) return false; + + // Compare participants arrays + if (participants.length != other.participants.length) return false; + for (int i = 0; i < participants.length; i++) { + if (participants[i] != other.participants[i]) return false; + } + + return true; + } } \ No newline at end of file