# HG changeset patch # User Goffi # Date 1582751235 -3600 # Node ID d3a6ae8595566e281aaa7f6b7d01a39e72032b96 # Parent 13884aac1220187438aaa2fa80f6dce1d74399ac chat: image attachments collection, first draft: when more than one image is attached in a message, they are collected and a dedicated attachment item is shown. Opening this item will launch the carousel with all collected images. diff -r 13884aac1220 -r d3a6ae859556 cagou/plugins/plugin_wid_chat.kv --- a/cagou/plugins/plugin_wid_chat.kv Wed Feb 26 16:47:39 2020 +0100 +++ b/cagou/plugins/plugin_wid_chat.kv Wed Feb 26 22:07:15 2020 +0100 @@ -67,6 +67,25 @@ anim_delay: -1 +: + cols: 2 + size_hint: None, None + size: dp(150), dp(150) + padding: dp(5) + spacing: dp(2) + canvas.before: + Color: + rgb: app.c_prim + RoundedRectangle: + radius: [dp(5)] + pos: self.pos + size: self.size + Color: + rgb: 0, 0, 0, 1 + Line: + rounded_rectangle: self.x, self.y, self.width, self.height, dp(5) + + : attachments: self size_hint: 1, None diff -r 13884aac1220 -r d3a6ae859556 cagou/plugins/plugin_wid_chat.py --- a/cagou/plugins/plugin_wid_chat.py Wed Feb 26 16:47:39 2020 +0100 +++ b/cagou/plugins/plugin_wid_chat.py Wed Feb 26 22:07:15 2020 +0100 @@ -23,8 +23,10 @@ import uuid from urllib.parse import urlparse from kivy.uix.boxlayout import BoxLayout +from kivy.uix.gridlayout import GridLayout +from kivy.uix.screenmanager import Screen, NoTransition from kivy.uix.textinput import TextInput -from kivy.uix.screenmanager import Screen, NoTransition +from kivy.uix.label import Label from kivy.uix import screenmanager from kivy.uix.behaviors import ButtonBehavior from kivy.metrics import sp, dp @@ -44,8 +46,8 @@ from ..core.constants import Const as C from ..core import cagou_widget from ..core import xmlui -from ..core.image import Image -from ..core.common import SymbolButton, JidButton, ContactButton +from ..core.image import Image, AsyncImage +from ..core.common import Symbol, SymbolButton, JidButton, ContactButton from ..core.behaviors import FilterBehavior from ..core import menu from ..core.common_widgets import ImagesGallery @@ -130,6 +132,70 @@ G.host.showExtraUI(gallery) +class AttachmentImagesCollectionItem(ButtonBehavior, GridLayout): + attachments = properties.ListProperty([]) + chat = properties.ObjectProperty() + mess_data = properties.ObjectProperty() + + def _setDecryptedPath(self, attachment, wid, path): + attachment['path'] = path + if wid is not None: + wid.source = path + + def on_kv_post(self, __): + attachments = self.attachments + self.clear_widgets() + for idx, attachment in enumerate(attachments): + url = attachment['url'] + to_decrypt = url.startswith("aesgcm:") + + if idx < 3 or len(attachments) <= 4: + if ((self.mess_data.own_mess + or self.chat.contact_list.isInRoster(self.mess_data.from_jid))): + wid_kwargs = { + "size_hint": (1, 1), + } + if not to_decrypt: + wid_kwargs["source"] = url + wid = AsyncImage(**wid_kwargs) + else: + # we don't download automatically the image if the contact is not + # in roster, to avoid leaking the ip + wid = Symbol(symbol="file-image") + self.add_widget(wid) + else: + wid = None + + if to_decrypt: + # the file needs to be decrypted, it will be done by downloadURL + # and the widget source and attachment path will then be completed + del attachment['url'] + G.host.downloadURL( + url, + callback=partial(self._setDecryptedPath, attachment, wid), + dest=C.FILE_DEST_CACHE, + profile=self.chat.profile, + ) + + if len(attachments) > 4: + counter = Label( + bold=True, + text=f"+{len(attachments) - 3}", + ) + self.add_widget(counter) + + def on_press(self): + sources = [] + for attachment in self.attachments: + source = attachment.get('url', attachment.get('path')) + if not source: + log.warning(f"no source for {attachment}") + else: + sources.append(source) + gallery = ImagesGallery(sources=sources) + G.host.showExtraUI(gallery) + + class AttachmentToSendItem(AttachmentItem): # True when the item is being sent sending = properties.BooleanProperty(False) @@ -209,9 +275,9 @@ status = update_dict['status'] self.delivery.text = '\u2714' if status == 'delivered' else '' - def _setPath(self, item, path): + def _setPath(self, data, path): """Set path of decrypted file to an item""" - item.data['path'] = path + data['path'] = path def add_attachments(self): """Add attachments layout + attachments item""" @@ -221,17 +287,37 @@ root_layout = AttachmentsLayout() self.right_part.add_widget(root_layout) layout = root_layout.attachments + + image_attachments = [] + other_attachments = [] + # we first separate images and other attachments, so we know if we need + # to use an image collection for attachment in attachments: media_type = attachment.get(C.MESS_KEY_MEDIA_TYPE, '') main_type = media_type.split('/', 1)[0] - if ((main_type == 'image' - # GIF images are really badly handled by Kivy, the - # memory consumption explode, and the images frequencies - # are not handled correctly, thus we can't display them - # and user will have to open the item. - and media_type != "image/gif" - and (self.mess_data.own_mess or self.chat.contact_list.isInRoster( - self.mess_data.from_jid)))): + # GIF images are really badly handled by Kivy, the memory + # consumption explode, and the images frequencies are not handled + # correctly, thus we can't display them and we consider them as + # other attachment, so user can open the item with appropriate + # software. + if main_type == 'image' and media_type != "image/gif": + image_attachments.append(attachment) + else: + other_attachments.append(attachment) + + if len(image_attachments) > 1: + collection = AttachmentImagesCollectionItem( + attachments=image_attachments, + chat=self.chat, + mess_data=self.mess_data, + ) + layout.add_widget(collection) + elif image_attachments: + attachment = image_attachments[0] + # to avoid leaking IP address, we only display image if the contact is in + # roster + if ((self.mess_data.own_mess + or self.chat.contact_list.isInRoster(self.mess_data.from_jid))): url = urlparse(attachment['url']) if url.scheme == "aesgcm": # we remove the URL now, we'll replace it by @@ -240,7 +326,7 @@ item = AttachmentImageItem(data=attachment) G.host.downloadURL( url.geturl(), - callback=partial(self._setPath, item), + callback=partial(self._setPath, item.data), dest=C.FILE_DEST_CACHE, profile=self.chat.profile, ) @@ -248,6 +334,11 @@ item = AttachmentImageItem(data=attachment) else: item = AttachmentItem(data=attachment) + + layout.add_widget(item) + + for attachment in other_attachments: + item = AttachmentItem(data=attachment) layout.add_widget(item)