diff options
Diffstat (limited to 'main')
-rw-r--r-- | main/CMakeLists.txt | 3 | ||||
-rw-r--r-- | main/data/gresource.xml | 1 | ||||
-rw-r--r-- | main/data/icons/scalable/actions/small-x-symbolic.svg | 2 | ||||
-rw-r--r-- | main/data/style.css | 22 | ||||
-rw-r--r-- | main/meson.build | 2 | ||||
-rw-r--r-- | main/src/ui/conversation_content_view/file_default_widget.vala | 4 | ||||
-rw-r--r-- | main/src/ui/conversation_content_view/file_image_widget.vala | 253 | ||||
-rw-r--r-- | main/src/ui/conversation_content_view/file_preview_widget.vala | 90 | ||||
-rw-r--r-- | main/src/ui/conversation_content_view/file_transmission_progress.vala | 120 | ||||
-rw-r--r-- | main/src/ui/conversation_content_view/file_widget.vala | 55 | ||||
-rw-r--r-- | main/src/ui/widgets/avatar_picture.vala | 2 |
11 files changed, 372 insertions, 182 deletions
diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index fa5f56e8..70665903 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -19,6 +19,7 @@ set(RESOURCE_LIST icons/scalable/actions/dino-emoticon-add-symbolic.svg icons/scalable/actions/dino-qr-code-symbolic.svg + icons/scalable/actions/small-x-symbolic.svg icons/scalable/apps/im.dino.Dino.svg icons/scalable/apps/im.dino.Dino-symbolic.svg @@ -185,7 +186,7 @@ SOURCES src/ui/conversation_content_view/date_separator_populator.vala src/ui/conversation_content_view/file_default_widget.vala src/ui/conversation_content_view/file_image_widget.vala - src/ui/conversation_content_view/file_preview_widget.vala + src/ui/conversation_content_view/file_transmission_progress.vala src/ui/conversation_content_view/file_widget.vala src/ui/conversation_content_view/item_actions.vala src/ui/conversation_content_view/message_widget.vala diff --git a/main/data/gresource.xml b/main/data/gresource.xml index edceea3e..661185ab 100644 --- a/main/data/gresource.xml +++ b/main/data/gresource.xml @@ -24,6 +24,7 @@ <file>gtk/help-overlay.ui</file> <file>icons/scalable/actions/dino-emoticon-add-symbolic.svg</file> <file>icons/scalable/actions/dino-qr-code-symbolic.svg</file> + <file>icons/scalable/actions/small-x-symbolic.svg</file> <file>icons/scalable/apps/im.dino.Dino-symbolic.svg</file> <file>icons/scalable/apps/im.dino.Dino.svg</file> <file>icons/scalable/devices/dino-device-desktop-symbolic.svg</file> diff --git a/main/data/icons/scalable/actions/small-x-symbolic.svg b/main/data/icons/scalable/actions/small-x-symbolic.svg new file mode 100644 index 00000000..aadc5d1e --- /dev/null +++ b/main/data/icons/scalable/actions/small-x-symbolic.svg @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 4 4 h 1 h 0.03125 c 0.253906 0.011719 0.511719 0.128906 0.6875 0.3125 l 2.28125 2.28125 l 2.3125 -2.28125 c 0.265625 -0.230469 0.445312 -0.304688 0.6875 -0.3125 h 1 v 1 c 0 0.285156 -0.035156 0.550781 -0.25 0.75 l -2.28125 2.28125 l 2.25 2.25 c 0.1875 0.1875 0.28125 0.453125 0.28125 0.71875 v 1 h -1 c -0.265625 0 -0.53125 -0.09375 -0.71875 -0.28125 l -2.28125 -2.28125 l -2.28125 2.28125 c -0.1875 0.1875 -0.453125 0.28125 -0.71875 0.28125 h -1 v -1 c 0 -0.265625 0.09375 -0.53125 0.28125 -0.71875 l 2.28125 -2.25 l -2.28125 -2.28125 c -0.210938 -0.195312 -0.304688 -0.46875 -0.28125 -0.75 z m 0 0" fill="#222222"/></svg> diff --git a/main/data/style.css b/main/data/style.css index 5b9da21d..0909f8a0 100644 --- a/main/data/style.css +++ b/main/data/style.css @@ -181,6 +181,28 @@ window.dino-main .file-image-widget .file-box-outer button:hover { border-radius: 6px; } +.dino-main .file-details { + color: white; + background: alpha(black, 0.3); + border-radius: 5px; + padding: 5px 10px; +} + +.dino-main .circular-loading-indicator { + border-radius: 50%; + padding: 5px; + transition: background-image 0.5s linear; +} + +.dino-main .circular-loading-indicator > * { + border-radius: 50%; + background: @theme_bg_color; +} + +.dino-main .circular-loading-indicator button { + padding: 2px; +} + /* Call widget */ window.dino-main .call-box-outer.incoming { diff --git a/main/meson.build b/main/meson.build index 30a28032..2dcf751b 100644 --- a/main/meson.build +++ b/main/meson.build @@ -47,6 +47,7 @@ sources = files( 'src/ui/conversation_content_view/date_separator_populator.vala', 'src/ui/conversation_content_view/file_default_widget.vala', 'src/ui/conversation_content_view/file_image_widget.vala', + 'src/ui/conversation_content_view/file_transmission_progress.vala', 'src/ui/conversation_content_view/file_widget.vala', 'src/ui/conversation_content_view/item_actions.vala', 'src/ui/conversation_content_view/message_widget.vala', @@ -77,6 +78,7 @@ sources = files( 'src/ui/util/accounts_combo_box.vala', 'src/ui/util/config.vala', 'src/ui/util/data_forms.vala', + 'src/ui/util/file_metadata_providers.vala', 'src/ui/util/helper.vala', 'src/ui/util/label_hybrid.vala', 'src/ui/util/preference_group.vala', 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 352c8d7a..02249b3f 100644 --- a/main/src/ui/conversation_content_view/file_default_widget.vala +++ b/main/src/ui/conversation_content_view/file_default_widget.vala @@ -39,7 +39,7 @@ public class FileDefaultWidget : Box { }); } - public void update_file_info(string? mime_type, FileTransfer.State state, long size) { + public void update_file_info(string? mime_type, FileTransfer.State state, int64 size) { this.state = state; spinner.stop(); // A hidden spinning spinner still uses CPU. Deactivate asap @@ -132,7 +132,7 @@ public class FileDefaultWidget : Box { } } - private static string get_size_string(long size) { + public static string get_size_string(int64 size) { if (size < 1024) { return @"$(size) B"; } else if (size < 1000 * 1000) { 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 a3579185..d5415f31 100644 --- a/main/src/ui/conversation_content_view/file_image_widget.vala +++ b/main/src/ui/conversation_content_view/file_image_widget.vala @@ -1,51 +1,37 @@ using Gee; using Gdk; using Gtk; +using Xmpp; using Dino.Entities; namespace Dino.Ui { public class FileImageWidget : Box { + enum State { + EMPTY, + PREVIEW, + IMAGE + } + private State state = State.EMPTY; - public FileImageWidget() { - this.halign = Align.START; + private Stack stack = new Stack() { transition_duration=600, transition_type=StackTransitionType.CROSSFADE }; + private Overlay overlay = new Overlay(); - this.add_css_class("file-image-widget"); - this.set_cursor_from_name("zoom-in"); - } + private bool show_image_overlay_toolbar = false; + 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 }; - public async void load_from_file(File file, string file_name, int MAX_WIDTH=600, int MAX_HEIGHT=300) throws GLib.Error { - Gtk.Box image_overlay_toolbar = new Gtk.Box(Orientation.HORIZONTAL, 0) { halign=Gtk.Align.END, valign=Gtk.Align.START, margin_top=10, margin_start=10, margin_end=10, margin_bottom=10, vexpand=false, visible=false }; - image_overlay_toolbar.add_css_class("card"); - image_overlay_toolbar.add_css_class("toolbar"); - image_overlay_toolbar.add_css_class("overlay-toolbar"); - image_overlay_toolbar.set_cursor_from_name("default"); + private FileTransfer file_transfer; - FixedRatioPicture image = new FixedRatioPicture() { min_width=100, min_height=100, max_width=MAX_WIDTH, max_height=MAX_HEIGHT, file=file }; - GestureClick gesture_click_controller = new GestureClick(); - gesture_click_controller.button = 1; // listen for left clicks - gesture_click_controller.released.connect((n_press, x, y) => { - switch (gesture_click_controller.get_device().source) { - case Gdk.InputSource.TOUCHSCREEN: - case Gdk.InputSource.PEN: - if (n_press == 1) { - image_overlay_toolbar.visible = !image_overlay_toolbar.visible; - } else if (n_press == 2) { - this.activate_action("file.open", null); - image_overlay_toolbar.visible = false; - } - break; - default: - this.activate_action("file.open", null); - image_overlay_toolbar.visible = false; - break; - } - }); - image.add_controller(gesture_click_controller); + private FileTransmissionProgress transmission_progress = new FileTransmissionProgress() { halign=Align.CENTER, valign=Align.CENTER, visible=false }; + + public FileImageWidget(int MAX_WIDTH=600, int MAX_HEIGHT=300) { + this.halign = Align.START; - FileInfo file_info = file.query_info("*", FileQueryInfoFlags.NONE); + this.add_css_class("file-image-widget"); + // Setup menu button overlay MenuButton button = new MenuButton(); button.icon_name = "view-more"; Menu menu_model = new Menu(); @@ -53,27 +39,216 @@ public class FileImageWidget : Box { 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("overlay-toolbar"); + image_overlay_toolbar.set_cursor_from_name("default"); - Overlay overlay = new Overlay(); - overlay.set_child(image); + file_size_label.add_css_class("file-details"); + + overlay.set_child(stack); + overlay.set_measure_overlay(stack, true); + overlay.add_overlay(file_size_label); + overlay.add_overlay(transmission_progress); overlay.add_overlay(image_overlay_toolbar); - overlay.set_measure_overlay(image, true); overlay.set_clip_overlay(image_overlay_toolbar, true); + this.append(overlay); + + GestureClick gesture_click_controller = new GestureClick(); + gesture_click_controller.button = 1; // listen for left clicks + gesture_click_controller.released.connect(on_image_clicked); + stack.add_controller(gesture_click_controller); + EventControllerMotion this_motion_events = new EventControllerMotion(); this.add_controller(this_motion_events); this_motion_events.enter.connect(() => { - image_overlay_toolbar.visible = true; + image_overlay_toolbar.visible = show_image_overlay_toolbar; + 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; }); this_motion_events.leave.connect(() => { if (button.popover != null && button.popover.visible) return; image_overlay_toolbar.visible = false; + file_size_label.visible = false; }); + } - this.append(overlay); + 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, (_, from_value, ref to_value) => { + to_value = FileDefaultWidget.get_size_string((int64) from_value); + return true; + }); + 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 void refresh_state() { + if ((state == EMPTY || state == PREVIEW) && file_transfer.path != null) { + if (state == EMPTY) { + load_from_file.begin(file_transfer.get_file(), file_transfer.file_name); + show_image_overlay_toolbar = true; + } if (state == PREVIEW) { + Timeout.add(500, () => { + load_from_file.begin(file_transfer.get_file(), file_transfer.file_name); + show_image_overlay_toolbar = true; + return false; + }); + } + this.set_cursor_from_name("zoom-in"); + + state = IMAGE; + } else if (state == EMPTY && file_transfer.thumbnails.size > 0) { + load_from_thumbnail.begin(file_transfer); + + transmission_progress.visible = true; + show_image_overlay_toolbar = false; + + state = PREVIEW; + } + + if (file_transfer.state == IN_PROGRESS || file_transfer.state == NOT_STARTED || file_transfer.state == FAILED) { + transmission_progress.visible = true; + show_image_overlay_toolbar = false; + } else if (transmission_progress.visible) { + Timeout.add(500, () => { + transmission_progress.transferred_size = transmission_progress.file_size; + transmission_progress.visible = false; + show_image_overlay_toolbar = true; + return false; + }); + } + + if (file_transfer.state == FileTransfer.State.IN_PROGRESS) { + if (file_transfer.direction == FileTransfer.DIRECTION_RECEIVED) { + transmission_progress.state = FileTransmissionProgress.State.DOWNLOADING; + } else { + transmission_progress.state = FileTransmissionProgress.State.UPLOADING; + } + } else if (file_transfer.sfs_sources.is_empty) { + transmission_progress.state = UNKNOWN_SOURCE; + } else if (file_transfer.state == NOT_STARTED && file_transfer.direction == FileTransfer.DIRECTION_RECEIVED) { + transmission_progress.state = DOWNLOAD_NOT_STARTED; + } else if (file_transfer.state == FileTransfer.State.FAILED) { + transmission_progress.state = DOWNLOAD_NOT_STARTED_FAILED_BEFORE; + } + } + + 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 }; + image.file = file; + stack.add_child(image); + stack.set_visible_child(image); + } + + public async void load_from_thumbnail(FileTransfer file_transfer) throws GLib.Error { + this.file_transfer = file_transfer; + + Gdk.Pixbuf? pixbuf = null; + foreach (Xep.JingleContentThumbnails.Thumbnail thumbnail in file_transfer.thumbnails) { + pixbuf = parse_thumbnail(thumbnail); + if (pixbuf != null) { + break; + } + } + if (pixbuf == null) { + warning("Can't load thumbnails of file %s", file_transfer.file_name); + throw new Error(-1, 0, "Error loading preview image"); + } + // TODO: should this be executed? If yes, before or after scaling + pixbuf = pixbuf.apply_embedded_orientation(); + + if (file_transfer.width > 0 && file_transfer.height > 0) { + pixbuf = pixbuf.scale_simple(file_transfer.width, file_transfer.height, InterpType.BILINEAR); + } else { + warning("Preview: Not scaling image, width: %d, height: %d\n", file_transfer.width, file_transfer.height); + } + if (pixbuf == null) { + warning("Can't scale thumbnail %s", file_transfer.file_name); + 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 }; + image.paintable = Texture.for_pixbuf(pixbuf); + stack.add_child(image); + stack.set_visible_child(image); + } + + public void on_image_clicked(GestureClick gesture_click_controller, int n_press, double x, double y) { + if (this.file_transfer.state != COMPLETE) return; + + switch (gesture_click_controller.get_device().source) { + case Gdk.InputSource.TOUCHSCREEN: + case Gdk.InputSource.PEN: + if (n_press == 1) { + image_overlay_toolbar.visible = !image_overlay_toolbar.visible; + } else if (n_press == 2) { + this.activate_action("file.open", null); + image_overlay_toolbar.visible = false; + } + break; + default: + this.activate_action("file.open", null); + image_overlay_toolbar.visible = false; + break; + } + } + + public static Pixbuf? parse_thumbnail(Xep.JingleContentThumbnails.Thumbnail thumbnail) { + string[] splits = thumbnail.uri.split(":", 2); + if (splits.length != 2) { + warning("Thumbnail parsing error: ':' not found"); + return null; + } + if (splits[0] != "data") { + warning("Unsupported thumbnail: unimplemented uri type\n"); + return null; + } + splits = splits[1].split(";", 2); + if (splits.length != 2) { + warning("Thumbnail parsing error: ';' not found"); + return null; + } + if (splits[0] != "image/png") { + warning("Unsupported thumbnail: unsupported mime-type\n"); + return null; + } + splits = splits[1].split(",", 2); + if (splits.length != 2) { + warning("Thumbnail parsing error: ',' not found"); + return null; + } + if (splits[0] != "base64") { + warning("Unsupported thumbnail: data is not base64 encoded\n"); + return null; + } + uint8[] data = Base64.decode(splits[1]); + MemoryInputStream input_stream = new MemoryInputStream.from_data(data); + Pixbuf pixbuf = new Pixbuf.from_stream(input_stream); + return pixbuf; + } + + public static bool can_display(FileTransfer file_transfer) { + return file_transfer.mime_type != null && is_pixbuf_supported_mime_type(file_transfer.mime_type) && + (file_transfer.state == FileTransfer.State.COMPLETE || file_transfer.thumbnails.size > 0); + } + + public static bool is_pixbuf_supported_mime_type(string mime_type) { + if (mime_type == null) return false; + + foreach (PixbufFormat pixbuf_format in Pixbuf.get_formats()) { + foreach (string pixbuf_mime in pixbuf_format.get_mime_types()) { + if (pixbuf_mime == mime_type) return true; + } + } + return false; } } diff --git a/main/src/ui/conversation_content_view/file_preview_widget.vala b/main/src/ui/conversation_content_view/file_preview_widget.vala deleted file mode 100644 index e7cee93a..00000000 --- a/main/src/ui/conversation_content_view/file_preview_widget.vala +++ /dev/null @@ -1,90 +0,0 @@ -using Gee; -using Gdk; -using Gtk; -using Xmpp; - -using Dino.Entities; - -namespace Dino.Ui { - - public class FilePreviewWidget : Box { - - private ScalingImage image; - FileDefaultWidget file_default_widget; - FileDefaultWidgetController file_default_widget_controller; - - public FilePreviewWidget() { - this.halign = Align.START; - - this.add_css_class("file-preview-widget"); - } - - public async void load_from_thumbnail(FileTransfer file_transfer, StreamInteractor stream_interactor, int MAX_WIDTH=600, int MAX_HEIGHT=300) throws GLib.Error { - Thread<ScalingImage?> thread = new Thread<ScalingImage?> (null, () => { - ScalingImage image = new ScalingImage() { halign=Align.START, visible = true, max_width = MAX_WIDTH, max_height = MAX_HEIGHT }; - Gdk.Pixbuf? pixbuf = null; - foreach (Xep.JingleContentThumbnails.Thumbnail thumbnail in file_transfer.thumbnails) { - pixbuf = ImageFileMetadataProvider.parse_thumbnail(thumbnail); - if (pixbuf != null) { - break; - } - } - if (pixbuf == null) { - warning("Can't load thumbnails of file %s", file_transfer.file_name); - Idle.add(load_from_thumbnail.callback); - throw new Error(-1, 0, "Error loading preview image"); - } - // TODO: should this be executed? If yes, before or after scaling - pixbuf = pixbuf.apply_embedded_orientation(); - - if (file_transfer.width > 0 && file_transfer.height > 0) { - pixbuf = pixbuf.scale_simple(file_transfer.width, file_transfer.height, InterpType.BILINEAR); - } else { - warning("Preview: Not scaling image, width: %d, height: %d\n", file_transfer.width, file_transfer.height); - } - if (pixbuf == null) { - warning("Can't scale thumbnail %s", file_transfer.file_name); - throw new Error(-1, 0, "Error scaling preview image"); - } - - image.load(pixbuf); - Idle.add(load_from_thumbnail.callback); - return image; - }); - yield; - this.image = thread.join(); - - file_default_widget = new FileDefaultWidget() { valign=Align.END, vexpand=false, visible=false }; - file_default_widget.image_stack.visible = false; - file_default_widget_controller = new FileDefaultWidgetController(file_default_widget); - file_default_widget_controller.set_file_transfer(file_transfer, stream_interactor); - - Overlay overlay = new Overlay(); - overlay.set_child(image); - overlay.add_overlay(file_default_widget); - overlay.set_measure_overlay(image, true); - overlay.set_clip_overlay(file_default_widget, true); - - EventControllerMotion this_motion_events = new EventControllerMotion(); - this.add_controller(this_motion_events); - this_motion_events.enter.connect(() => { - file_default_widget.visible = true; - }); - this_motion_events.leave.connect(() => { - if (file_default_widget.file_menu.popover != null && file_default_widget.file_menu.popover.visible) return; - - file_default_widget.visible = false; - }); - GestureClick gesture_click_controller = new GestureClick(); - gesture_click_controller.set_button(1); // listen for left clicks - this.add_controller(gesture_click_controller); - gesture_click_controller.pressed.connect((n_press, x, y) => { - // Check whether the click was inside the file menu. Otherwise, open the file. - this.file_default_widget.clicked(); - }); - - this.append(overlay); - } - } - -} diff --git a/main/src/ui/conversation_content_view/file_transmission_progress.vala b/main/src/ui/conversation_content_view/file_transmission_progress.vala new file mode 100644 index 00000000..bf3f377b --- /dev/null +++ b/main/src/ui/conversation_content_view/file_transmission_progress.vala @@ -0,0 +1,120 @@ +using Gee; +using Gdk; +using Gtk; +using Xmpp; + +using Dino.Entities; + +namespace Dino.Ui { + + public class FileTransmissionProgress : Adw.Bin { + + public enum State { + UNKNOWN_SOURCE, + DOWNLOAD_NOT_STARTED, + DOWNLOAD_NOT_STARTED_FAILED_BEFORE, + DOWNLOADING, + UPLOADING + } + + public int64 file_size { get; set; } + public int64 transferred_size { get; set; } + public State state { get; set; } + + private CssProvider css_provider = new CssProvider(); + private Button button = new Button(); + + private uint64 next_update_time = 0; + private int64 last_progress_percent = 0; + private uint update_progress_timeout_id = -1; + + construct { + add_css_class("circular-loading-indicator"); + + button.add_css_class("circular"); + Adw.Bin holder = new Adw.Bin(); + holder.set_child(button); + this.set_child(holder); + + this.button.clicked.connect(on_button_clicked); + + this.notify["transferred-size"].connect(on_transferred_size_update); + this.notify["state"].connect(on_state_changed); + on_state_changed(); + } + + private void on_transferred_size_update() { + if (update_progress_timeout_id == -1) { + int64 progress_percent = transferred_size * 100 / file_size; + if (progress_percent != last_progress_percent) { + uint64 time_now = get_monotonic_time() / 1000; + if (next_update_time > time_now) { + update_progress_timeout_id = Timeout.add((uint) (next_update_time - time_now), () => { + update_progress(); + update_progress_timeout_id = -1; + return Source.REMOVE; + }); + } else { + update_progress(); + } + } + } + } + + private void on_state_changed() { + sensitive = state != UNKNOWN_SOURCE; + + switch (this.state) { + case UNKNOWN_SOURCE: + case DOWNLOAD_NOT_STARTED: + button.icon_name = "document-save-symbolic"; + break; + case DOWNLOADING: + case UPLOADING: + button.icon_name = "small-x-symbolic"; + break; + case DOWNLOAD_NOT_STARTED_FAILED_BEFORE: + button.icon_name = "dialog-warning-symbolic"; + break; + } + } + + private void update_progress() { + this.get_style_context().remove_provider(css_provider); + css_provider = new CssProvider(); + int64 progress_percent = transferred_size * 100 / file_size; + + css_provider.load_from_string(@" + .circular-loading-indicator { + background-image: conic-gradient(@accent_color $(progress_percent)%, transparent $(progress_percent)%); + } + "); + + this.get_style_context().add_provider(css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); + next_update_time = get_monotonic_time() / 1000 + 500; + last_progress_percent = progress_percent; + } + + private void on_button_clicked() { + switch (this.state) { + case UNKNOWN_SOURCE: + break; + case DOWNLOAD_NOT_STARTED_FAILED_BEFORE: + case DOWNLOAD_NOT_STARTED: + this.activate_action("file.download", null); + break; + case DOWNLOADING: + case UPLOADING: + this.activate_action("file.cancel", null); + break; + } + } + + public override void dispose() { + if (update_progress_timeout_id != -1) { + Source.remove(update_progress_timeout_id); + update_progress_timeout_id = -1; + } + } + } +}
\ No newline at end of file diff --git a/main/src/ui/conversation_content_view/file_widget.vala b/main/src/ui/conversation_content_view/file_widget.vala index 69781f30..583609d2 100644 --- a/main/src/ui/conversation_content_view/file_widget.vala +++ b/main/src/ui/conversation_content_view/file_widget.vala @@ -27,7 +27,7 @@ public class FileMetaItem : ConversationSummary.ContentMetaItem { } public override Gee.List<Plugins.MessageAction>? get_item_actions(Plugins.WidgetType type) { - if (file_transfer.provider != 0 || file_transfer.info == null) return null; + if ((file_transfer.provider != FileManager.HTTP_PROVIDER_ID && file_transfer.provider != FileManager.SFS_PROVIDER_ID) || file_transfer.info == null) return null; Gee.List<Plugins.MessageAction> actions = new ArrayList<Plugins.MessageAction>(); @@ -43,7 +43,6 @@ public class FileWidget : SizeRequestBox { enum State { IMAGE, - IMAGE_PREVIEW, DEFAULT } @@ -90,13 +89,15 @@ public class FileWidget : SizeRequestBox { } private async void update_widget() { - if (show_image() && state != State.IMAGE) { + bool show_image = FileImageWidget.can_display(file_transfer); + + if (show_image && state != State.IMAGE) { var content_bak = content; FileImageWidget file_image_widget = null; try { file_image_widget = new FileImageWidget(); - yield file_image_widget.load_from_file(file_transfer.get_file(), file_transfer.file_name); + yield file_image_widget.set_file_transfer(file_transfer); // If the widget changed in the meanwhile, stop if (content != content_bak) return; @@ -109,26 +110,7 @@ public class FileWidget : SizeRequestBox { } catch (Error e) { } } - if (show_preview() && state != State.IMAGE_PREVIEW) { - var content_bak = content; - - FilePreviewWidget file_preview_widget = null; - try { - file_preview_widget = new FilePreviewWidget() { visible=true }; - yield file_preview_widget.load_from_thumbnail(file_transfer, stream_interactor); - - // If the widget changed in the meanwhile, stop - if (content != content_bak) return; - - if (content != null) this.remove(content); - content = file_preview_widget; - state = State.IMAGE_PREVIEW; - this.append(content); - return; - } catch (Error e) { } - } - - if (!show_image() && state != State.DEFAULT && state != State.IMAGE_PREVIEW) { + if (!show_image && state != State.DEFAULT) { if (content != null) this.remove(content); FileDefaultWidget default_file_widget = new FileDefaultWidget(); default_widget_controller = new FileDefaultWidgetController(default_file_widget); @@ -139,31 +121,6 @@ public class FileWidget : SizeRequestBox { } } - private bool show_image() { - if (file_transfer.mime_type == null) return false; - - // If the image is being sent by this client, we already have the file - bool in_progress_from_us = file_transfer.direction == FileTransfer.DIRECTION_SENT && - file_transfer.ourpart.equals(file_transfer.account.full_jid) && - file_transfer.state == FileTransfer.State.IN_PROGRESS; - if (file_transfer.state != FileTransfer.State.COMPLETE && !in_progress_from_us) { - return false; - } - - foreach (PixbufFormat pixbuf_format in Pixbuf.get_formats()) { - foreach (string mime_type in pixbuf_format.get_mime_types()) { - if (mime_type == file_transfer.mime_type) { - return true; - } - } - } - return false; - } - - private bool show_preview() { - return !this.file_transfer.thumbnails.is_empty; - } - public override void dispose() { if (default_widget_controller != null) default_widget_controller.dispose(); default_widget_controller = null; diff --git a/main/src/ui/widgets/avatar_picture.vala b/main/src/ui/widgets/avatar_picture.vala index fb254915..ef1462ae 100644 --- a/main/src/ui/widgets/avatar_picture.vala +++ b/main/src/ui/widgets/avatar_picture.vala @@ -285,7 +285,7 @@ public class Dino.Ui.CompatAvatarDrawer { private Cairo.Surface sub_surface_idx(Cairo.Context ctx, int idx, int width, int height, int font_factor = 1) { ViewModel.AvatarPictureTileModel tile = (ViewModel.AvatarPictureTileModel) this.model.tiles.get_item(idx); - Gdk.Pixbuf? avatar = new Gdk.Pixbuf.from_file(tile.image_file.get_path()); + Gdk.Pixbuf? avatar = tile.image_file != null ? new Gdk.Pixbuf.from_file(tile.image_file.get_path()) : null; string? name = idx >= 0 ? tile.display_text : ""; Gdk.RGBA hex_color = tile.background_color; return sub_surface(ctx, font_family, avatar, name, hex_color, width, height, font_factor); |