From 8d49d83b3b45ab22d7b9d945c4b32296b07cb49e Mon Sep 17 00:00:00 2001 From: fiaxh Date: Fri, 14 Aug 2026 11:45:08 +0200 Subject: Add initial support for multi-file transfers --- main/src/ui/application.vala | 10 ++ main/src/ui/chat_input/chat_input_controller.vala | 8 +- .../content_populator.vala | 2 + .../file_default_widget.vala | 40 ++++--- .../file_group_widget.vala | 120 +++++++++++++++++++++ .../file_image_widget.vala | 105 ++++++++++++------ .../file_transmission_progress.vala | 6 +- .../ui/conversation_content_view/file_widget.vala | 71 +----------- .../conversation_selector_row.vala | 19 ++++ main/src/ui/conversation_view.vala | 21 ++++ main/src/ui/conversation_view_controller.vala | 4 - 11 files changed, 283 insertions(+), 123 deletions(-) create mode 100644 main/src/ui/conversation_content_view/file_group_widget.vala (limited to 'main/src') diff --git a/main/src/ui/application.vala b/main/src/ui/application.vala index 1b4dc049..62495871 100644 --- a/main/src/ui/application.vala +++ b/main/src/ui/application.vala @@ -244,6 +244,16 @@ public class Dino.Ui.Application : Adw.Application, Dino.Application { call_state.reject(); }); add_action(deny_call_action); + + SimpleAction save_file_action = new SimpleAction("file_open_save_dialog", VariantType.INT32); + save_file_action.activate.connect((variant) => { + print("action open save dialog\n"); + int file_transfer_id = variant.get_int32(); + FileTransfer? file_transfer = stream_interactor.get_module(FileTransferStorage.IDENTITY).get_file_by_id(file_transfer_id, null); + + window.conversation_view.open_save_file_dialog(file_transfer); + }); + add_action(save_file_action); } private void show_preferences_window() { diff --git a/main/src/ui/chat_input/chat_input_controller.vala b/main/src/ui/chat_input/chat_input_controller.vala index c93a9e22..afbef637 100644 --- a/main/src/ui/chat_input/chat_input_controller.vala +++ b/main/src/ui/chat_input/chat_input_controller.vala @@ -245,11 +245,11 @@ public class ChatInputController : Object { send_text(); } - foreach (File file in files_to_send) { - stream_interactor.get_module(FileManager.IDENTITY).send_file.begin(file, conversation); + if (!files_to_send.is_empty) { + stream_interactor.get_module(FileManager2.IDENTITY).send_files.begin(files_to_send, conversation); + files_to_send = new ArrayList(); + chat_input.clear_files(); } - files_to_send = new ArrayList(); - chat_input.clear_files(); } private void on_text_input_changed() { diff --git a/main/src/ui/conversation_content_view/content_populator.vala b/main/src/ui/conversation_content_view/content_populator.vala index ef859bde..4d1b8c90 100644 --- a/main/src/ui/conversation_content_view/content_populator.vala +++ b/main/src/ui/conversation_content_view/content_populator.vala @@ -70,6 +70,8 @@ public class ContentProvider : ContentItemCollection, Object { return new FileMetaItem(content_item, stream_interactor); } else if (content_item.type_ == CallItem.TYPE) { return new CallMetaItem(content_item, stream_interactor); + } else if (content_item.type_ == FileGroupItem.TYPE) { + return new FileGroupMetaItem(content_item, stream_interactor); } critical("Got unknown content item type %s", content_item.type_); return null; diff --git a/main/src/ui/conversation_content_view/file_default_widget.vala b/main/src/ui/conversation_content_view/file_default_widget.vala index a21e2afb..1ad3d6c2 100644 --- a/main/src/ui/conversation_content_view/file_default_widget.vala +++ b/main/src/ui/conversation_content_view/file_default_widget.vala @@ -51,13 +51,13 @@ public class FileDefaultWidget : Box { mime_label.label = content_type != null ? content_type.get_description() : null; } - public void update_file_info(Xmpp.FileContentType? content_type, FileTransfer.State state, bool direction, int64 size, int64 transferred_bytes) { - this.state = state; + public void update_file_info(FileTransfer file_transfer) { + this.state = file_transfer.state; spinner.stop(); // A hidden spinning spinner still uses CPU. Deactivate asap - content_type_image.icon_name = get_file_icon_name(content_type); - string? mime_description = content_type != null ? content_type.get_description() : null; + content_type_image.icon_name = get_file_icon_name(file_transfer.content_type); + string? mime_description = file_transfer.content_type != null ? file_transfer.content_type.get_description() : null; switch (state) { case FileTransfer.State.COMPLETE: @@ -66,23 +66,31 @@ public class FileDefaultWidget : Box { // Create a menu Menu menu_model = new Menu(); - menu_model.append(_("Open"), "file.open"); - menu_model.append(_("Save as…"), "file.save_as"); + + MenuItem open_file_item = new MenuItem(_("Open"), "app.file_open_externally"); + open_file_item.set_action_and_target_value("app.file_open_externally", new Variant.int32(file_transfer.id)); + + MenuItem save_as_item = new MenuItem(_("Save as…"), "app.file_save_as"); + save_as_item.set_action_and_target_value("app.file_open_save_dialog", new Variant.int32(file_transfer.id)); + + menu_model.append_item(open_file_item); + menu_model.append_item(save_as_item); + Gtk.PopoverMenu popover_menu = new Gtk.PopoverMenu.from_model(menu_model); file_menu.popover = popover_menu; popover_menu.closed.connect(on_pointer_left); break; case FileTransfer.State.IN_PROGRESS: - if (direction == FileTransfer.DIRECTION_RECEIVED) { - if (size > 0) { - int64 progress = transferred_bytes * 100 / size; - mime_label.label = _("Downloading %s… (%u%%)").printf(get_size_string(size), progress); + if (file_transfer.direction == FileTransfer.DIRECTION_RECEIVED) { + if (file_transfer.size > 0) { + int64 progress = file_transfer.transferred_bytes * 100 / file_transfer.size; + mime_label.label = _("Downloading %s… (%u%%)").printf(get_size_string(file_transfer.size), progress); } else { - mime_label.label = _("Downloading %s…").printf(get_size_string(size)); + mime_label.label = _("Downloading %s…").printf(get_size_string(file_transfer.size)); } } else { - int64 progress = transferred_bytes * 100 / size; - mime_label.label = _("Uploading %s… (%u%%)").printf(get_size_string(size), progress); + int64 progress = file_transfer.transferred_bytes * 100 / file_transfer.size; + mime_label.label = _("Uploading %s… (%u%%)").printf(get_size_string(file_transfer.size), progress); } spinner.start(); image_stack.set_visible_child_name("spinner"); @@ -96,9 +104,9 @@ public class FileDefaultWidget : Box { break; case FileTransfer.State.NOT_STARTED: if (mime_description != null) { - mime_label.label = _("%s offered: %s").printf(mime_description, get_size_string(size)); - } else if (size != -1) { - mime_label.label = _("File offered: %s").printf(get_size_string(size)); + mime_label.label = _("%s offered: %s").printf(mime_description, get_size_string(file_transfer.size)); + } else if (file_transfer.size != -1) { + mime_label.label = _("File offered: %s").printf(get_size_string(file_transfer.size)); } else { mime_label.label = _("File offered"); } diff --git a/main/src/ui/conversation_content_view/file_group_widget.vala b/main/src/ui/conversation_content_view/file_group_widget.vala new file mode 100644 index 00000000..80c41a7a --- /dev/null +++ b/main/src/ui/conversation_content_view/file_group_widget.vala @@ -0,0 +1,120 @@ +using Gee; +using Gdk; +using Gtk; +using Pango; +using Xmpp; + +using Dino.Entities; + +namespace Dino.Ui { + + public class FileGroupMetaItem : ConversationSummary.ContentMetaItem { + + private StreamInteractor stream_interactor; + private FileGroupItem file_group_item; + + public FileGroupMetaItem(ContentItem content_item, StreamInteractor stream_interactor) { + base(content_item); + this.stream_interactor = stream_interactor; + this.file_group_item = content_item as FileGroupItem; + } + + public override Object? get_widget(Plugins.ConversationItemWidgetInterface outer, Plugins.WidgetType type) { + var file_transfers = file_group_item.file_group.file_transfers; + + if (file_transfers.size > 1 && all_files_images(file_transfers)) { + return new ImageGroupWidget(file_transfers); + } else { + Box box = new Box(Orientation.VERTICAL, 6); + foreach (FileTransfer file_transfer in file_transfers) { + FileWidget widget = new FileWidget(file_transfer) { halign = Align.START }; + widget.add_css_class("file-image-widget"); + FileWidgetController widget_controller = new FileWidgetController(widget, file_transfer, stream_interactor); + box.append(widget); + } + return box; + } + } + + public override Gee.List? get_item_actions(Plugins.WidgetType type) { + Gee.List actions = new ArrayList(); + return actions; + } + } + + public class ImageGroupWidget : Box { + + private Gee.ArrayList file_transfers; + + public ImageGroupWidget(Gee.ArrayList file_transfers) { + this.file_transfers = file_transfers; + + if (file_transfers.size == 3) { + Box box = new Box(Orientation.HORIZONTAL, 4) { + overflow = Overflow.HIDDEN, + halign = Align.START + }; + box.add_css_class("image-group"); + + var picture1 = new FileImageWidget.from_file_transfer(file_transfers[0], 200); + box.append(picture1); + + Box box1 = new Box(Orientation.VERTICAL, 4); + + var picture2 = new FileImageWidget.from_file_transfer(file_transfers[1], 100); + box1.append(picture2); + var picture3 = new FileImageWidget.from_file_transfer(file_transfers[2], 100); + box1.append(picture3); + box.append(box1); + + this.append(box); + } else { + Box box = new Box(Orientation.VERTICAL, 4) { + overflow = Overflow.HIDDEN, + halign = Align.START + }; + box.add_css_class("image-group"); + + Box row_upper_row = new Box(Orientation.HORIZONTAL, 4); + + var picture1 = new FileImageWidget.from_file_transfer(file_transfers[0], 150); + row_upper_row.append(picture1); + + var picture2 = new FileImageWidget.from_file_transfer(file_transfers[1], 150); + row_upper_row.append(picture2); + box.append(row_upper_row); + + if (file_transfers.size >= 4) { + + int size = 150; + if (file_transfers.size == 5) size = 99; + + Box row_lower_row = new Box(Orientation.HORIZONTAL, 4); + + var picture3 = new FileImageWidget.from_file_transfer(file_transfers[2], size); + row_lower_row.append(picture3); + + var picture4 = new FileImageWidget.from_file_transfer(file_transfers[3], size); + row_lower_row.append(picture4); + + if (file_transfers.size == 5) { + var picture5 = new FileImageWidget.from_file_transfer(file_transfers[4], size); + row_lower_row.append(picture5); + } + + box.append(row_lower_row); + } + this.append(box); + } + } + } + + public static bool all_files_images(Gee.ArrayList file_transfers) { + foreach (FileTransfer file_transfer in file_transfers) { + if (!Dino.Util.is_pixbuf_supported_content_type(file_transfer.content_type)) { + return false; + } + } + return true; + } +} \ No newline at end of file diff --git a/main/src/ui/conversation_content_view/file_image_widget.vala b/main/src/ui/conversation_content_view/file_image_widget.vala index e936ca25..9f12f892 100644 --- a/main/src/ui/conversation_content_view/file_image_widget.vala +++ b/main/src/ui/conversation_content_view/file_image_widget.vala @@ -22,6 +22,8 @@ public class FileImageWidget : Widget { private Gtk.Box image_overlay_toolbar = new Gtk.Box(Orientation.VERTICAL, 0) { halign=Align.END, valign=Align.START, margin_top=10, margin_start=10, margin_end=10, margin_bottom=10, vexpand=false, visible=false }; private Label file_size_label = new Label(null) { halign=Align.START, valign=Align.END, margin_bottom=4, margin_start=4, visible=false }; + private int size = -1; + private FileTransfer file_transfer; private FileTransmissionProgress transmission_progress = new FileTransmissionProgress() { halign=Align.CENTER, valign=Align.CENTER, visible=false }; @@ -34,22 +36,6 @@ public class FileImageWidget : Widget { public FileImageWidget(int MAX_WIDTH=600, int MAX_HEIGHT=300) { this.halign = Align.START; - this.add_css_class("file-image-widget"); - - // Setup menu button overlay - MenuButton button = new MenuButton(); - button.icon_name = "dino-view-more"; - Menu menu_model = new Menu(); - menu_model.append(_("Open"), "file.open"); - menu_model.append(_("Save as…"), "file.save_as"); - Gtk.PopoverMenu popover_menu = new Gtk.PopoverMenu.from_model(menu_model); - button.popover = popover_menu; - image_overlay_toolbar.append(button); - image_overlay_toolbar.add_css_class("card"); - image_overlay_toolbar.add_css_class("toolbar"); - image_overlay_toolbar.add_css_class("compact-card-toolbar"); - image_overlay_toolbar.set_cursor_from_name("default"); - file_size_label.add_css_class("file-details"); overlay.set_child(stack); @@ -65,12 +51,65 @@ public class FileImageWidget : Widget { gesture_click_controller.button = 1; // listen for left clicks gesture_click_controller.released.connect(on_image_clicked); stack.add_controller(gesture_click_controller); + } + + public FileImageWidget.from_file_transfer(FileTransfer file_transfer, int size = -1) { + this(); + + this.file_transfer = file_transfer; + this.size = size; + + this.file_transfer.bind_property("size", file_size_label, "label", BindingFlags.SYNC_CREATE, file_size_label_transform); + this.file_transfer.bind_property("size", transmission_progress, "file-size", BindingFlags.SYNC_CREATE); + this.file_transfer.bind_property("transferred-bytes", transmission_progress, "transferred-size"); + + setup_signals(transmission_progress, file_transfer); + setup_menu_button(); + + file_transfer.notify["state"].connect(refresh_state); + file_transfer.sources_changed.connect(refresh_state); + refresh_state(); + } + + private static void setup_signals(FileTransmissionProgress transmission_progress, FileTransfer file_transfer) { + var file_transfer_id = file_transfer.id; + + transmission_progress.start_download.connect(() => { + Dino.Application.get_default().activate_action("file_start_download", new Variant.int32(file_transfer_id)); + }); + transmission_progress.cancel_transfer.connect(() => { + Dino.Application.get_default().activate_action("file_cancel_transfer", new Variant.int32(file_transfer_id)); + }); + } + + private void setup_menu_button() { + MenuButton button = new MenuButton(); + button.icon_name = "dino-view-more"; + Menu menu_model = new Menu(); + + MenuItem open_file_item = new MenuItem(_("Open"), "app.file_open_externally"); + open_file_item.set_action_and_target_value("app.file_open_externally", new Variant.int32(file_transfer.id)); + + MenuItem save_as_item = new MenuItem(_("Save as…"), "app.file_save_as"); + save_as_item.set_action_and_target_value("app.file_open_save_dialog", new Variant.int32(file_transfer.id)); + + menu_model.append_item(open_file_item); + menu_model.append_item(save_as_item); + + Gtk.PopoverMenu popover_menu = new Gtk.PopoverMenu.from_model(menu_model); + button.popover = popover_menu; + image_overlay_toolbar.append(button); + image_overlay_toolbar.add_css_class("card"); + image_overlay_toolbar.add_css_class("toolbar"); + image_overlay_toolbar.add_css_class("compact-card-toolbar"); + image_overlay_toolbar.set_cursor_from_name("default"); EventControllerMotion this_motion_events = new EventControllerMotion(); this.add_controller(this_motion_events); this_motion_events.enter.connect((controller, x, y) => { (controller.widget as FileImageWidget).on_motion_event_enter(); }); + attach_on_motion_event_leave(this_motion_events, button); } @@ -88,18 +127,6 @@ public class FileImageWidget : Widget { file_size_label.visible = file_transfer != null && file_transfer.direction == FileTransfer.DIRECTION_RECEIVED && file_transfer.state == FileTransfer.State.NOT_STARTED && !file_transfer.sfs_sources.is_empty; } - public async void set_file_transfer(FileTransfer file_transfer) { - this.file_transfer = file_transfer; - - this.file_transfer.bind_property("size", file_size_label, "label", BindingFlags.SYNC_CREATE, file_size_label_transform); - this.file_transfer.bind_property("size", transmission_progress, "file-size", BindingFlags.SYNC_CREATE); - this.file_transfer.bind_property("transferred-bytes", transmission_progress, "transferred-size"); - - file_transfer.notify["state"].connect(refresh_state); - file_transfer.sources_changed.connect(refresh_state); - refresh_state(); - } - private static bool file_size_label_transform(Binding binding, Value from_value, ref Value to_value) { to_value = FileDefaultWidget.get_size_string((int64) from_value); return true; @@ -153,7 +180,7 @@ public class FileImageWidget : Widget { } public async void load_from_file(File file, string file_name) throws GLib.Error { - FixedRatioPicture image = new FixedRatioPicture() { min_width=100, min_height=100, max_width=600, max_height=300 }; + FixedRatioPicture image = create_picture(); FileInputStream file_stream = null; try { @@ -201,7 +228,7 @@ public class FileImageWidget : Widget { throw new Error(-1, 0, "Error scaling preview image"); } - FixedRatioPicture image = new FixedRatioPicture() { min_width=100, min_height=100, max_width=600, max_height=300 }; + FixedRatioPicture image = create_picture(); image.paintable = Texture.for_pixbuf(pixbuf); stack.add_child(image); stack.set_visible_child(image); @@ -216,12 +243,12 @@ public class FileImageWidget : Widget { if (n_press == 1) { image_overlay_toolbar.visible = !image_overlay_toolbar.visible; } else if (n_press == 2) { - this.activate_action("file.open", null); + Dino.Application.get_default().activate_action("file_open_externally", new Variant.int32(file_transfer.id)); image_overlay_toolbar.visible = false; } break; default: - this.activate_action("file.open", null); + Dino.Application.get_default().activate_action("file_open_externally", new Variant.int32(file_transfer.id)); image_overlay_toolbar.visible = false; break; } @@ -237,6 +264,20 @@ public class FileImageWidget : Widget { (file_transfer.state == FileTransfer.State.COMPLETE || file_transfer.thumbnails.size > 0); } + private FixedRatioPicture create_picture() { + if (size == -1) { + return new FixedRatioPicture() { + min_width=100, max_width=600, + min_height=100, max_height=300 + }; + } else { + return new FixedRatioPicture() { + height_request = size, width_request = size, + content_fit = ContentFit.COVER, halign = Align.START + }; + } + } + public override void dispose() { if (overlay != null && overlay.parent != null) overlay.unparent(); base.dispose(); diff --git a/main/src/ui/conversation_content_view/file_transmission_progress.vala b/main/src/ui/conversation_content_view/file_transmission_progress.vala index e3f33448..43885c1b 100644 --- a/main/src/ui/conversation_content_view/file_transmission_progress.vala +++ b/main/src/ui/conversation_content_view/file_transmission_progress.vala @@ -9,6 +9,9 @@ namespace Dino.Ui { public class FileTransmissionProgress : Adw.Bin { + public signal void start_download(); + public signal void cancel_transfer(); + public enum State { UNKNOWN_SOURCE, DOWNLOAD_NOT_STARTED, @@ -124,11 +127,12 @@ namespace Dino.Ui { break; case DOWNLOAD_NOT_STARTED_FAILED_BEFORE: case DOWNLOAD_NOT_STARTED: + start_download(); this.activate_action("file.download", null); break; case DOWNLOADING: case UPLOADING: - this.activate_action("file.cancel", null); + cancel_transfer(); break; } } diff --git a/main/src/ui/conversation_content_view/file_widget.vala b/main/src/ui/conversation_content_view/file_widget.vala index ecc2d3e9..04185343 100644 --- a/main/src/ui/conversation_content_view/file_widget.vala +++ b/main/src/ui/conversation_content_view/file_widget.vala @@ -57,18 +57,6 @@ public class FileWidget : SizeRequestBin { private FileDefaultWidgetController default_widget_controller; private Widget? content = null; - public signal void open_file(); - public signal void save_file_as(); - public signal void start_download(); - public signal void cancel_download(); - - class construct { - install_action("file.open", null, (widget, action_name) => { ((FileWidget) widget).open_file(); }); - install_action("file.save_as", null, (widget, action_name) => { ((FileWidget) widget).save_file_as(); }); - install_action("file.download", null, (widget, action_name) => { ((FileWidget) widget).start_download(); }); - install_action("file.cancel", null, (widget, action_name) => { ((FileWidget) widget).cancel_download(); }); - } - construct { margin_top = 4; size_request_mode = SizeRequestMode.HEIGHT_FOR_WIDTH; @@ -79,11 +67,6 @@ public class FileWidget : SizeRequestBin { this.file_transfer = file_transfer; update_widget.begin(); -// size_allocate.connect((allocation) => { -// if (allocation.height > parent.get_allocated_height()) { -// Idle.add(() => { parent.queue_resize(); return false; }); -// } -// }); file_transfer.bind_property("state", this, "file-transfer-state"); file_transfer.bind_property("content-type", this, "file-transfer-content-type"); @@ -100,8 +83,8 @@ public class FileWidget : SizeRequestBin { FileImageWidget file_image_widget = null; try { - file_image_widget = new FileImageWidget(); - yield file_image_widget.set_file_transfer(file_transfer); + file_image_widget = new FileImageWidget.from_file_transfer(file_transfer); + file_image_widget.add_css_class("file-image-widget"); // If the widget changed in the meanwhile, stop if (content != content_bak) return; @@ -152,50 +135,6 @@ public class FileWidgetController : Object { }); this.file_transfer = file_transfer; this.stream_interactor = stream_interactor; - - widget.open_file.connect(open_file); - widget.save_file_as.connect(save_file); - widget.start_download.connect(start_download); - widget.cancel_download.connect(cancel_download); - } - - private void open_file() { - try{ - AppInfo.launch_default_for_uri(file_transfer.get_file().get_uri(), null); - } catch (Error err) { - warning("Failed to open %s - %s", file_transfer.get_file().get_uri(), err.message); - } - } - - private void save_file() { - var save_dialog = new FileChooserNative(_("Save as…"), widget.get_root() as Gtk.Window, FileChooserAction.SAVE, null, null); - save_dialog.set_modal(true); - save_dialog.set_current_name(file_transfer.file_name); - - save_dialog.response.connect(() => { - File? target_file = save_dialog.get_file(); - if (target_file == null) { - warning("No file returned from save dialog."); - return; - } - try { - file_transfer.get_file().copy(save_dialog.get_file(), GLib.FileCopyFlags.OVERWRITE, null); - } catch (Error err) { - warning("Failed copy file %s - %s", file_transfer.get_file().get_uri(), err.message); - } - }); - - save_dialog.show(); - } - - private void start_download() { - if (stream_interactor != null) { - stream_interactor.get_module(FileManager.IDENTITY).download_file.begin(file_transfer); - } - } - - private void cancel_download() { - file_transfer.cancellable.cancel(); } } @@ -234,16 +173,16 @@ public class FileDefaultWidgetController : Object { private void update_file_info() { state = file_transfer.state; - widget.update_file_info(file_transfer.content_type, file_transfer.state, file_transfer.direction, file_transfer.size, file_transfer.transferred_bytes); + widget.update_file_info(file_transfer); } private void on_clicked() { switch (state) { case FileTransfer.State.COMPLETE: - widget.activate_action("file.open", null); + Dino.Application.get_default().activate_action("file_open_externally", new GLib.Variant.int32(file_transfer.id)); break; case FileTransfer.State.NOT_STARTED: - widget.activate_action("file.download", null); + Dino.Application.get_default().activate_action("file_start_download", new GLib.Variant.int32(file_transfer.id)); break; default: // Clicking doesn't do anything in FAILED and IN_PROGRESS states diff --git a/main/src/ui/conversation_selector/conversation_selector_row.vala b/main/src/ui/conversation_selector/conversation_selector_row.vala index fd5814fa..0e565c84 100644 --- a/main/src/ui/conversation_selector/conversation_selector_row.vala +++ b/main/src/ui/conversation_selector/conversation_selector_row.vala @@ -226,6 +226,25 @@ public class ConversationSelectorRow : ListBoxRow { message_label.label = (file_is_image ? _("Image received") : _("File received") ); } break; + case FileGroupItem.TYPE: + FileGroupItem file_group_item = last_content_item as FileGroupItem; + FileTransfer first_transfer = file_group_item.file_group.file_transfers.get(0); + + if (conversation.type_ == Conversation.Type.GROUPCHAT) { + // TODO properly display nick for oneself + nick_label.label = Util.get_participant_display_name(stream_interactor, conversation, first_transfer.from, true) + ": "; + } else { + nick_label.label = first_transfer.direction == Message.DIRECTION_SENT ? _("Me") + ": " : ""; + } + + bool all_files_images = Ui.all_files_images(file_group_item.file_group.file_transfers); + change_label_attribute(message_label, attr_style_new(Pango.Style.ITALIC)); + if (first_transfer.direction == Message.DIRECTION_SENT) { + message_label.label = (all_files_images ? _("Images sent") : _("Files sent") ); + } else { + message_label.label = (all_files_images ? _("Images received") : _("Files received") ); + } + break; case CallItem.TYPE: CallItem call_item = (CallItem) last_content_item; Call call = call_item.call; diff --git a/main/src/ui/conversation_view.vala b/main/src/ui/conversation_view.vala index 26043666..4bb1f537 100644 --- a/main/src/ui/conversation_view.vala +++ b/main/src/ui/conversation_view.vala @@ -17,6 +17,27 @@ public class ConversationView : Widget { construct { this.layout_manager = new BinLayout(); } + + public void open_save_file_dialog(FileTransfer file_transfer) { + var save_dialog = new FileChooserNative(_("Save as…"), (Gtk.Window) this.get_root(), FileChooserAction.SAVE, null, null); + save_dialog.set_modal(true); + save_dialog.set_current_name(file_transfer.file_name); + + save_dialog.response.connect(() => { + File? target_file = save_dialog.get_file(); + if (target_file == null) { + warning("No file returned from save dialog."); + return; + } + try { + file_transfer.get_file().copy(save_dialog.get_file(), GLib.FileCopyFlags.OVERWRITE, null); + } catch (Error err) { + warning("Failed copy file %s - %s", file_transfer.get_file().get_uri(), err.message); + } + }); + + save_dialog.show(); + } } } diff --git a/main/src/ui/conversation_view_controller.vala b/main/src/ui/conversation_view_controller.vala index ef13b026..9626a39d 100644 --- a/main/src/ui/conversation_view_controller.vala +++ b/main/src/ui/conversation_view_controller.vala @@ -216,10 +216,6 @@ public class ConversationViewController : Object { chooser.show(); } - private void send_file(File file) { - stream_interactor.get_module(FileManager.IDENTITY).send_file.begin(file, conversation); - } - private bool forward_key_press_to_chat_input(EventControllerKey key_controller, uint keyval, uint keycode, Gdk.ModifierType state) { if (view.get_root().get_focus() is TextView) { return false; -- cgit v1.3