using Gtk; using Adw; using Gee; class TranscriptContainerView : Adw.Bin { public TranscriptView transcript_view; public signal void on_send(TranscriptContainerView view); private Box container; private Button send_button; private FlowBox attachment_flow_box; private Entry message_entry; private HashSet pending_uploads; private HashMap attachment_previews; private ArrayList completed_attachments; public string message_body { get { return message_entry.text; } } public ArrayList attachment_guids { owned get { var attachment_guids = new ArrayList(); completed_attachments.foreach((attachment) => { attachment_guids.add(attachment.attachment_guid); return true; }); return attachment_guids; } } public TranscriptContainerView () { container = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); set_child (container); pending_uploads = new HashSet(); attachment_previews = new HashMap(); completed_attachments = new ArrayList(); // Create message list view transcript_view = new TranscriptView(); transcript_view.set_vexpand(true); container.append(transcript_view); // Create attachment preview row (initially hidden) setup_attachment_row(); // Create bottom box for input var input_box = new Box(Orientation.HORIZONTAL, 6); input_box.add_css_class("message-input-box"); input_box.set_valign(Align.END); input_box.set_spacing(6); container.append(input_box); // Setup drag and drop setup_drag_and_drop(); // Connect to repository signals Repository.get_instance().attachment_uploaded.connect(on_attachment_uploaded); // Create message entry message_entry = new Entry(); message_entry.add_css_class("message-input-entry"); message_entry.set_placeholder_text("Type a message..."); message_entry.set_hexpand(true); message_entry.changed.connect(on_text_changed); message_entry.activate.connect(on_request_send); input_box.append(message_entry); // Create send button send_button = new Button(); send_button.set_label("Send"); send_button.set_sensitive(false); send_button.add_css_class("suggested-action"); send_button.clicked.connect(on_request_send); input_box.append(send_button); } private void setup_attachment_row() { attachment_flow_box = new FlowBox(); attachment_flow_box.set_max_children_per_line(6); attachment_flow_box.set_row_spacing(6); attachment_flow_box.set_column_spacing(6); attachment_flow_box.halign = Align.START; attachment_flow_box.add_css_class("attachment-preview-row"); container.append(attachment_flow_box); } private void setup_drag_and_drop() { var drop_target = new DropTarget(typeof(File), Gdk.DragAction.COPY); drop_target.drop.connect(on_file_dropped); this.add_controller(drop_target); } private bool on_file_dropped(Value val, double x, double y) { if (!val.holds(typeof(File))) { return false; } var file = (File)val.get_object(); if (file == null) { return false; } // Check if it's an image file try { var file_info = file.query_info(FileAttribute.STANDARD_CONTENT_TYPE, FileQueryInfoFlags.NONE); string content_type = file_info.get_content_type(); if (!content_type.has_prefix("image/")) { return false; } upload_file(file); return true; } catch (Error e) { warning("Failed to get file info: %s", e.message); return false; } } private void upload_file(File file) { try { string upload_guid = Repository.get_instance().upload_attachment(file.get_path()); pending_uploads.add(upload_guid); var preview = new AttachmentPreview(file, upload_guid); preview.remove_requested.connect(() => { remove_attachment(upload_guid); }); attachment_previews[upload_guid] = preview; attachment_flow_box.append(preview); update_attachment_row_visibility(); } catch (Error e) { warning("Failed to upload attachment: %s", e.message); } } private void on_attachment_uploaded(string upload_guid, string attachment_guid) { if (attachment_previews.has_key(upload_guid)) { var preview = attachment_previews[upload_guid]; preview.set_completed(attachment_guid); completed_attachments.add(new UploadedAttachment(upload_guid, attachment_guid)); pending_uploads.remove(upload_guid); update_send_button_sensitivity(); } } private void remove_attachment(string upload_guid) { if (attachment_previews.has_key(upload_guid)) { var preview = attachment_previews[upload_guid]; attachment_flow_box.remove(preview); attachment_previews.unset(upload_guid); completed_attachments.foreach((attachment) => { if (attachment.upload_guid == upload_guid) { completed_attachments.remove(attachment); return false; } return true; }); update_attachment_row_visibility(); update_send_button_sensitivity(); } } private void update_attachment_row_visibility() { bool has_attachments = attachment_previews.size > 0; attachment_flow_box.set_visible(has_attachments); } private void on_text_changed() { update_send_button_sensitivity(); } private void update_send_button_sensitivity() { send_button.set_sensitive(message_entry.text.length > 0 && pending_uploads.size == 0); } private void on_request_send() { if (message_entry.text.length > 0 && pending_uploads.size == 0) { on_send(this); // Clear the message entry message_entry.text = ""; // Clear the attachment previews attachment_flow_box.remove_all(); attachment_previews.clear(); completed_attachments.clear(); pending_uploads.clear(); update_send_button_sensitivity(); } } } class UploadedAttachment { public string upload_guid; public string attachment_guid; public UploadedAttachment(string upload_guid, string attachment_guid) { this.upload_guid = upload_guid; this.attachment_guid = attachment_guid; } }