Browse Source

Display chat states in MUC, handle the chat states with a new ChatStates class

pull/784/head
Timothée Jaussoin 7 years ago
parent
commit
e534a2b1e4
  1. 1
      CHANGELOG.md
  2. 61
      app/widgets/Chat/Chat.php
  3. 11
      app/widgets/Chat/_chat.tpl
  4. 7
      app/widgets/Chat/chat.js
  5. 18
      app/widgets/Chats/Chats.php
  6. 10
      lib/moxl/src/Moxl/Stanza/Muc.php
  7. 14
      lib/moxl/src/Moxl/Xec/Action/Message/Composing.php
  8. 14
      lib/moxl/src/Moxl/Xec/Action/Message/Paused.php
  9. 24
      lib/moxl/src/Moxl/Xec/Payload/Attention.php
  10. 14
      lib/moxl/src/Moxl/Xec/Payload/Message.php
  11. 136
      src/Movim/ChatStates.php
  12. 1
      src/Movim/Session.php

1
CHANGELOG.md

@ -22,6 +22,7 @@ v0.14.1 (trunk)
* Add support for embeded images in Forms (for CAPTCHA)
* Chat bubbles a bit more compact
* Display single emojis as small stickers
* Display chat states in MUC, handle the chat states with a new ChatStates class
v0.14
---------------------------

61
app/widgets/Chat/Chat.php

@ -16,6 +16,8 @@ use Respect\Validation\Validator;
use Illuminate\Database\Capsule\Manager as DB;
use Movim\Picture;
use Movim\Session;
use Movim\ChatStates;
include_once WIDGETS_PATH.'ContactActions/ContactActions.php';
@ -38,7 +40,6 @@ class Chat extends \Movim\Widget\Base
$this->registerEvent('mam_get_handle', 'onMAMRetrieved', 'chat');
$this->registerEvent('composing', 'onComposing', 'chat');
$this->registerEvent('paused', 'onPaused', 'chat');
$this->registerEvent('gone', 'onGone', 'chat');
$this->registerEvent('subject', 'onConferenceSubject', 'chat');
$this->registerEvent('muc_setsubject_handle', 'onConferenceSubject', 'chat');
$this->registerEvent('muc_getconfig_handle', 'onRoomConfig', 'chat');
@ -84,6 +85,7 @@ class Chat extends \Movim\Widget\Base
{
$message = $packet->content;
$from = null;
$chatStates = ChatStates::getInstance();
if ($message->isEmpty()) return;
@ -106,6 +108,8 @@ class Chat extends \Movim\Widget\Base
&& !$message->isOTR()
&& $message->type != 'groupchat'
&& !$message->oldid) {
$chatStates->clearState($from);
Notification::append(
'chat|'.$from,
$roster ? $roster->truename : $contact->truename,
@ -119,6 +123,7 @@ class Chat extends \Movim\Widget\Base
elseif ($message->type == 'groupchat'
&& $message->quoted
&& !$receipt) {
$conference = $this->user->session
->conferences()->where('conference', $from)
->first();
@ -131,9 +136,11 @@ class Chat extends \Movim\Widget\Base
$message->resource.': '.$message->body,
false,
4);
} elseif ($message->type == 'groupchat') {
$chatStates->clearState($from, $message->resource);
}
$this->rpc('MovimTpl.fill', '#' . cleanupId($from.'_state'), $contact->jid);
$this->onPaused($chatStates->getState($from));
} else {
// If the message is from me we reset the notif counter
$n = new Notification;
@ -151,19 +158,24 @@ class Chat extends \Movim\Widget\Base
$this->ajaxGet($to);
}
public function onComposing($array)
{
$this->setState($array, $this->__('message.composing'));
}
public function onPaused($array)
public function onComposing(array $array)
{
$this->setState($array, $this->__('message.paused'));
$this->setState(
$array[0],
is_array($array[1]) && !empty($array[1])
? '…' . implode(', ', array_keys($array[1]))
: $this->__('message.composing')
);
}
public function onGone($array)
public function onPaused(array $array)
{
$this->setState($array, $this->__('message.gone'));
$this->setState(
$array[0],
is_array($array[1]) && !empty($array[1])
? '…' . implode(', ', array_keys($array[1]))
: ''
);
}
public function onConferenceSubject($packet)
@ -206,18 +218,9 @@ class Chat extends \Movim\Widget\Base
Notification::append(false, $this->__('chatroom.config_saved'));
}
private function setState($array, $message)
private function setState(string $jid, string $message)
{
list($from, $to) = $array;
$jid = ($from == $this->user->id) ? $to : $from;
$view = $this->tpl();
$view->assign('message', $message);
$html = $view->draw('_chat_state');
$this->rpc('MovimTpl.fill', '#' . cleanupId($jid.'_state'), $html);
$this->rpc('MovimTpl.fill', '#' . cleanupId($jid.'_state'), $message);
}
public function ajaxInit()
@ -445,11 +448,16 @@ class Chat extends \Movim\Widget\Base
* @param string $to
* @return void
*/
public function ajaxSendComposing($to)
public function ajaxSendComposing($to, $muc = false)
{
if (!$this->validateJid($to)) return;
$mc = new Composing;
if ($muc) {
$mc->setMuc();
}
$mc->setTo($to)->request();
}
@ -459,11 +467,16 @@ class Chat extends \Movim\Widget\Base
* @param string $to
* @return void
*/
public function ajaxSendPaused($to)
public function ajaxSendPaused($to, $muc = false)
{
if (!$this->validateJid($to)) return;
$mp = new Paused;
if ($muc) {
$mp->setMuc();
}
$mp->setTo($to)->request();
}

11
app/widgets/Chat/_chat.tpl

@ -56,9 +56,13 @@
{if="$conference && $conference->name"}
<p class="line" title="{$room}">
{$conference->name}
<span class="second" id="{$jid|cleanupId}-state"></span>
</p>
{else}
<p class="line">{$room}</p>
<p class="line">
{$room}
<span class="second" id="{$jid|cleanupId}-state"></span>
</p>
{/if}
{if="$conference && !$conference->connected"}
@ -74,7 +78,7 @@
{$conference->subject}
</p>
{else}
<p class="line">
<p class="line" id="{$jid|cleanupId}-state">
{if="$conference->info && !$conference->info->mucsemianonymous"}
<span title="{$c->__('room.public_muc_text')}">
{$c->__('room.public_muc')} <i class="material-icons">wifi_tethering</i>
@ -168,8 +172,9 @@
{else}
{$contact->truename}
{/if}
<span class="second" id="{$jid|cleanupId}-state"></span>
</p>
<p class="line" id="{$jid|cleanupId}-state">{$contact->jid}</p>
<p class="line">{$contact->jid}</p>
</li>
</ul>
<ul class="list context_menu active">

7
app/widgets/Chat/chat.js

@ -207,10 +207,10 @@ var Chat = {
Chat.sendMessage();
return false;
} else if (!Boolean(this.dataset.muc)) {
} else {
if (Chat.state == 0 || Chat.state == 2) {
Chat.state = 1;
Chat_ajaxSendComposing(this.dataset.jid);
Chat_ajaxSendComposing(this.dataset.jid, Boolean(this.dataset.muc));
Chat.since = new Date().getTime();
}
}
@ -223,11 +223,10 @@ var Chat = {
var textarea = document.querySelector('#chat_textarea');
if (textarea
&& !Boolean(textarea.dataset.muc)
&& Chat.state == 1
&& Chat.since + 5000 < new Date().getTime()) {
Chat.state = 2;
Chat_ajaxSendPaused(textarea.dataset.jid);
Chat_ajaxSendPaused(textarea.dataset.jid, Boolean(textarea.dataset.muc));
}
},5000);

18
app/widgets/Chats/Chats.php

@ -61,25 +61,18 @@ class Chats extends Base
}
}
public function onComposing($array)
public function onComposing(array $array)
{
$this->setState($array, $this->__('chats.composing'));
$this->setState($array[0], $this->__('chats.composing'));
}
public function onPaused($array)
public function onPaused(array $array)
{
$this->setState($array, $this->__('chats.paused'));
$this->setState($array[0]);
}
private function setState($array, $message)
private function setState(string $jid, $message = null)
{
list($from, $to) = $array;
if ($from == $this->user->id) {
$jid = $to;
} else {
$jid = $from;
}
$this->rpc(
'MovimTpl.replace', '#' . cleanupId($jid.'_chat_item'),
$this->prepareChat(
@ -89,6 +82,7 @@ class Chats extends Base
$message
)
);
$this->rpc('Chats.refresh');
}

10
lib/moxl/src/Moxl/Stanza/Muc.php

@ -13,6 +13,16 @@ class Muc
Message::maker($to, $content, $html, 'groupchat', false, false, $id, false, $file);
}
public static function composing($to)
{
Message::maker($to, false, false, 'groupchat', 'composing');
}
public static function paused($to)
{
Message::maker($to, false, false, 'groupchat', 'paused');
}
static function setSubject($to, $subject)
{
$session = Session::start();

14
lib/moxl/src/Moxl/Xec/Action/Message/Composing.php

@ -4,13 +4,25 @@ namespace Moxl\Xec\Action\Message;
use Moxl\Xec\Action;
use Moxl\Stanza\Message;
use Moxl\Stanza\Muc;
class Composing extends Action
{
protected $_to;
protected $_muc = false;
public function request()
{
Message::composing($this->_to);
if ($this->_muc) {
Muc::composing($this->_to);
} else {
Message::composing($this->_to);
}
}
public function setMuc()
{
$this->_muc = true;
return $this;
}
}

14
lib/moxl/src/Moxl/Xec/Action/Message/Paused.php

@ -4,13 +4,25 @@ namespace Moxl\Xec\Action\Message;
use Moxl\Xec\Action;
use Moxl\Stanza\Message;
use Moxl\Stanza\Muc;
class Paused extends Action
{
protected $_to;
protected $_muc = false;
public function request()
{
Message::paused($this->_to);
if ($this->_muc) {
Muc::paused($this->_to);
} else {
Message::paused($this->_to);
}
}
public function setMuc()
{
$this->_muc = true;
return $this;
}
}

24
lib/moxl/src/Moxl/Xec/Payload/Attention.php

@ -1,28 +1,4 @@
<?php
/*
* @file Attention.php
*
* @brief Handle incoming attention
*
* Copyright 2012 edhelas <edhelas@edhelas-laptop>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
*
*/
namespace Moxl\Xec\Payload;

14
lib/moxl/src/Moxl/Xec/Payload/Message.php

@ -2,11 +2,15 @@
namespace Moxl\Xec\Payload;
use Movim\ChatStates;
class Message extends Payload
{
public function handle($stanza, $parent = false)
{
$jid = explode('/',(string)$stanza->attributes()->from);
$from = (string)$stanza->attributes()->type == 'groupchat'
? (string)$stanza->attributes()->from
: current(explode('/',(string)$stanza->attributes()->from));
$to = current(explode('/',(string)$stanza->attributes()->to));
if ($stanza->confirm
@ -15,15 +19,11 @@ class Message extends Payload
}
if ($stanza->composing) {
$this->event('composing', [$jid[0], $to]);
(ChatStates::getInstance())->composing($from, $to);
}
if ($stanza->paused) {
$this->event('paused', [$jid[0], $to]);
}
if ($stanza->gone) {
$this->event('gone', [$jid[0], $to]);
(ChatStates::getInstance())->paused($from, $to);
}
$message = \App\Message::findByStanza($stanza);

136
src/Movim/ChatStates.php

@ -0,0 +1,136 @@
<?php
namespace Movim;
use Movim\Widget\Wrapper;
use React\EventLoop\Timer\Timer;
/**
* This class handle all the incoming chatstates
* heal them and merge lists for MUC
*/
class ChatStates
{
protected static $instance; // Singleton to keep state at all time
private $_composing = [];
private $_timeout = 30;
public static function getInstance()
{
if (!isset(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}
public function clearState(string $jid, $resource = null)
{
global $loop;
if (array_key_exists($jid, $this->_composing)) {
if ($resource !== null) {
\Utils::debug($resource);
if (array_key_exists($resource, $this->_composing[$jid])
&& $this->_composing[$jid][$resource] instanceof Timer) {
\Utils::debug('clear '.$resource);
$loop->cancelTimer($this->_composing[$jid][$resource]);
unset($this->_composing[$jid][$resource]);
if (empty($this->_composing[$jid])) {
unset($this->_composing[$jid]);
}
}
} elseif ($this->_composing[$jid] instanceof Timer) {
$loop->cancelTimer($this->_composing[$jid]);
unset($this->_composing[$jid]);
}
}
}
public function getState(string $jid)
{
return [$jid,
array_key_exists($jid, $this->_composing)
? $this->_composing[$jid]
: null
];
}
public function composing(string $from, string $to)
{
global $loop;
$explodedFrom = explodeJid($from);
$jid = $this->resolveJid($explodedFrom['jid'], $to);
$timer = $loop->addTimer($this->_timeout, function () use ($from, $to) {
$this->paused($from, $to);
});
if (isset($explodedFrom['resource'])) {
if (!array_key_exists($jid, $this->_composing)) {
$this->_composing[$jid] = [];
}
/*if (array_key_exists($explodedFrom['resource'], $this->_composing[$jid])
&& $this->_composing[$jid][$explodedFrom['resource']] instanceof Timer) {
$loop->cancelTimer($this->_composing[$jid][$explodedFrom['resource']]);
unset($this->_composing[$jid][$explodedFrom['resource']]);
}*/
$this->clearState($jid, $explodedFrom['resource']);
$this->_composing[$jid][$explodedFrom['resource']] = $timer;
} else {
/*if (array_key_exists($jid, $this->_composing)
&& $this->_composing[$jid] instanceof Timer) {
$loop->cancelTimer($this->_composing[$jid]);
unset($this->_composing[$jid]);
}*/
$this->clearState($jid);
$this->_composing[$jid] = $timer;
}
$wrapper = Wrapper::getInstance();
$wrapper->iterate('composing', [$jid, $this->_composing[$jid]]);
}
public function paused(string $from, string $to)
{
global $loop;
$explodedFrom = explodeJid($from);
$jid = $this->resolveJid($explodedFrom['jid'], $to);
//if (array_key_exists($jid, $this->_composing)) {
/*if (isset($explodedFrom['resource'])
&& array_key_exists($explodedFrom['resource'], $this->_composing[$jid])) {
$loop->cancelTimer($this->_composing[$jid][$explodedFrom['resource']]);
unset($this->_composing[$jid][$explodedFrom['resource']]);
if (empty($this->_composing[$jid])) {
unset($this->_composing[$jid]);
}
} elseif($this->_composing[$jid] instanceof Timer) {
$loop->cancelTimer($this->_composing[$jid]);
unset($this->_composing[$jid]);
}*/
$this->clearState($jid, $explodedFrom['resource']);
$wrapper = Wrapper::getInstance();
$wrapper->iterate('paused', [
$jid,
array_key_exists($jid, $this->_composing)
? $this->_composing[$jid]
: null
]);
//}
}
private function resolveJid(string $from, string $to)
{
return ($from == \App\User::me()->id) ? $to : $from;
}
}

1
src/Movim/Session.php

@ -28,6 +28,7 @@ class Session
if (array_key_exists($varname, $this->values)) {
return unserialize(base64_decode($this->values[$varname]));
}
return false;
}

Loading…
Cancel
Save