comparison cagou/plugins/plugin_wid_chat.py @ 326:d9d2b56f46db

plugin chat: infinite scroll: each when use scroll on top of current history, 30 new messages are prepended.
author Goffi <goffi@goffi.org>
date Fri, 06 Dec 2019 13:25:33 +0100
parents 5868a5575e01
children 51520ce98154
comparison
equal deleted inserted replaced
325:5868a5575e01 326:d9d2b56f46db
38 from cagou.core import cagou_widget 38 from cagou.core import cagou_widget
39 from cagou.core import xmlui 39 from cagou.core import xmlui
40 from cagou.core.image import Image 40 from cagou.core.image import Image
41 from cagou.core.common import SymbolButton, JidButton 41 from cagou.core.common import SymbolButton, JidButton
42 from cagou.core import menu 42 from cagou.core import menu
43 # from random import randrange
44 43
45 log = logging.getLogger(__name__) 44 log = logging.getLogger(__name__)
46 45
47 PLUGIN_INFO = { 46 PLUGIN_INFO = {
48 "name": _("chat"), 47 "name": _("chat"),
64 SYMBOL_ENCRYPTED = 'lock' 63 SYMBOL_ENCRYPTED = 'lock'
65 SYMBOL_ENCRYPTED_TRUSTED = 'lock-filled' 64 SYMBOL_ENCRYPTED_TRUSTED = 'lock-filled'
66 COLOR_UNENCRYPTED = (0.4, 0.4, 0.4, 1) 65 COLOR_UNENCRYPTED = (0.4, 0.4, 0.4, 1)
67 COLOR_ENCRYPTED = (0.4, 0.4, 0.4, 1) 66 COLOR_ENCRYPTED = (0.4, 0.4, 0.4, 1)
68 COLOR_ENCRYPTED_TRUSTED = (0.29,0.87,0.0,1) 67 COLOR_ENCRYPTED_TRUSTED = (0.29,0.87,0.0,1)
68
69 # below this limit, new messages will be prepended
70 INFINITE_SCROLL_LIMIT = dp(600)
69 71
70 72
71 class MessAvatar(Image): 73 class MessAvatar(Image):
72 pass 74 pass
73 75
399 401
400 402
401 class Chat(quick_chat.QuickChat, cagou_widget.CagouWidget): 403 class Chat(quick_chat.QuickChat, cagou_widget.CagouWidget):
402 message_input = properties.ObjectProperty() 404 message_input = properties.ObjectProperty()
403 messages_widget = properties.ObjectProperty() 405 messages_widget = properties.ObjectProperty()
406 history_scroll = properties.ObjectProperty()
404 407
405 def __init__(self, host, target, type_=C.CHAT_ONE2ONE, nick=None, occupants=None, 408 def __init__(self, host, target, type_=C.CHAT_ONE2ONE, nick=None, occupants=None,
406 subject=None, profiles=None): 409 subject=None, profiles=None):
407 quick_chat.QuickChat.__init__( 410 quick_chat.QuickChat.__init__(
408 self, host, target, type_, nick, occupants, subject, profiles=profiles) 411 self, host, target, type_, nick, occupants, subject, profiles=profiles)
421 self.headerInputAddExtra(self.encryption_btn) 424 self.headerInputAddExtra(self.encryption_btn)
422 self.extra_menu = ExtraMenu(chat=self) 425 self.extra_menu = ExtraMenu(chat=self)
423 extra_btn = ExtraButton(chat=self) 426 extra_btn = ExtraButton(chat=self)
424 self.headerInputAddExtra(extra_btn) 427 self.headerInputAddExtra(extra_btn)
425 self.header_input.hint_text = target 428 self.header_input.hint_text = target
429 self._history_prepend_lock = False
426 Clock.schedule_once(lambda dt: self.postInit(), 0) 430 Clock.schedule_once(lambda dt: self.postInit(), 0)
431 self.history_count = 0
427 432
428 def __str__(self): 433 def __str__(self):
429 return "Chat({})".format(self.target) 434 return "Chat({})".format(self.target)
430 435
431 def __repr__(self): 436 def __repr__(self):
562 """ 567 """
563 if self.handleUserMoved(mess_data): 568 if self.handleUserMoved(mess_data):
564 return 569 return
565 self.messages_widget.add_widget(MessageWidget(mess_data=mess_data)) 570 self.messages_widget.add_widget(MessageWidget(mess_data=mess_data))
566 self.notify(mess_data) 571 self.notify(mess_data)
572
573 def prependMessage(self, mess_data):
574 """Prepend a message Widget to the history
575
576 @param mess_data(quick_chat.Message): message data
577 """
578 mess_wid = self.messages_widget
579 last_idx = len(mess_wid.children)
580 mess_wid.add_widget(MessageWidget(mess_data=mess_data), index=last_idx)
567 581
568 def _get_notif_msg(self, mess_data): 582 def _get_notif_msg(self, mess_data):
569 return _("{nick}: {message}").format( 583 return _("{nick}: {message}").format(
570 nick=mess_data.nick, 584 nick=mess_data.nick,
571 message=mess_data.main_message) 585 message=mess_data.main_message)
740 if nb_instances > 1: 754 if nb_instances > 1:
741 return super(Chat, self).onDelete() 755 return super(Chat, self).onDelete()
742 else: 756 else:
743 return False 757 return False
744 758
759 def _history_unlock(self, __):
760 self._history_prepend_lock = False
761 log.debug("history prepend unlocked")
762 # we call manually onScroll, to check if we are still in the scrolling zone
763 self.onScroll(self.history_scroll, self.history_scroll.scroll_y)
764
765 def _history_scroll_adjust(self, __, scroll_start_height):
766 # history scroll position must correspond to where it was before new messages
767 # have been appended
768 self.history_scroll.scroll_y = (
769 scroll_start_height / self.messages_widget.height
770 )
771
772 # we want a small delay before unlocking, to avoid re-fetching history
773 # again
774 Clock.schedule_once(self._history_unlock, 1.5)
775
776 def _backHistoryGetCb_post(self, __, history, scroll_start_height):
777 if len(history) == 0:
778 # we don't unlock self._history_prepend_lock if there is no history, as there
779 # is no sense to try to retrieve more in this case.
780 log.debug(f"we've reached top of history for {self.target.bare} chat")
781 else:
782 # we have to schedule again for _history_scroll_adjust, else messages_widget
783 # is not resized (self.messages_widget.height is not yet updated)
784 # as a result, the scroll_to can't work correctly
785 Clock.schedule_once(partial(
786 self._history_scroll_adjust,
787 scroll_start_height=scroll_start_height))
788 log.debug(
789 f"{len(history)} messages prepended to history (last: {history[0][0]})")
790
791 def _backHistoryGetCb(self, history):
792 # TODO: factorise with QuickChat._historyGetCb
793 scroll_start_height = self.messages_widget.height * self.history_scroll.scroll_y
794 for data in reversed(history):
795 uid, timestamp, from_jid, to_jid, message, subject, type_, extra = data
796 from_jid = jid.JID(from_jid)
797 to_jid = jid.JID(to_jid)
798 extra["history"] = True
799 self.messages[uid] = message = quick_chat.Message(
800 self,
801 uid,
802 timestamp,
803 from_jid,
804 to_jid,
805 message,
806 subject,
807 type_,
808 extra,
809 self.profile,
810 )
811 self.messages.move_to_end(uid, last=False)
812 self.prependMessage(message)
813 Clock.schedule_once(partial(
814 self._backHistoryGetCb_post,
815 history=history,
816 scroll_start_height=scroll_start_height))
817
818 def _backHistoryGetEb(self, failure_):
819 G.host.addNote(
820 _("Problem while getting back history"),
821 _("Can't back history for {target}: {problem}").format(
822 target=self.target, problem=failure_),
823 C.XMLUI_DATA_LVL_ERROR)
824 # we don't unlock self._history_prepend_lock on purpose, no need
825 # to try to get more history if something is wrong
826
827 def onScroll(self, scroll_view, scroll_y):
828 if self._history_prepend_lock:
829 return
830 if (1-scroll_y) * self.messages_widget.height < INFINITE_SCROLL_LIMIT:
831 self._history_prepend_lock = True
832 log.debug(f"Retrieving back history for {self} [{self.history_count}]")
833 self.history_count += 1
834 first_uid = next(iter(self.messages.keys()))
835 filters = self.history_filters.copy()
836 filters['before_uid'] = first_uid
837 self.host.bridge.historyGet(
838 str(self.host.profiles[self.profile].whoami.bare),
839 str(self.target),
840 30,
841 True,
842 {k: str(v) for k,v in filters.items()},
843 self.profile,
844 callback=self._backHistoryGetCb,
845 errback=self._backHistoryGetEb,
846 )
847
745 848
746 PLUGIN_INFO["factory"] = Chat.factory 849 PLUGIN_INFO["factory"] = Chat.factory
747 quick_widgets.register(quick_chat.QuickChat, Chat) 850 quick_widgets.register(quick_chat.QuickChat, Chat)