Implemented showing user presence

This commit is contained in:
ChronosX88 2019-05-24 22:51:13 +04:00
parent 501c2eb5bc
commit dc66de02fb
12 changed files with 150 additions and 65 deletions

View File

@ -38,7 +38,9 @@ import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration; import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
import org.jivesoftware.smackx.mam.MamManager; import org.jivesoftware.smackx.mam.MamManager;
import org.jivesoftware.smackx.vcardtemp.VCardManager; import org.jivesoftware.smackx.vcardtemp.VCardManager;
import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.impl.JidCreate;
import java.io.IOException; import java.io.IOException;
import java.util.Set; import java.util.Set;
@ -117,6 +119,7 @@ public class XMPPConnection implements ConnectionListener {
reconnectionManager.enableAutomaticReconnection(); reconnectionManager.enableAutomaticReconnection();
roster = roster.getInstanceFor(connection); roster = roster.getInstanceFor(connection);
roster.setSubscriptionMode(Roster.SubscriptionMode.accept_all); roster.setSubscriptionMode(Roster.SubscriptionMode.accept_all);
roster.addPresenceEventListener(networkHandler);
mamManager = MamManager.getInstanceFor(connection); mamManager = MamManager.getInstanceFor(connection);
try { try {
if(mamManager.isSupported()) { if(mamManager.isSupported()) {
@ -217,4 +220,8 @@ public class XMPPConnection implements ConnectionListener {
return false; return false;
} }
} }
public Presence getUserPresence(BareJid jid) {
return roster.getPresence(jid);
}
} }

View File

@ -78,6 +78,7 @@ interface CoreContracts {
interface IChatLogicContract { interface IChatLogicContract {
fun sendMessage(text: String): MessageEntity? fun sendMessage(text: String): MessageEntity?
fun getUserStatus(): Boolean
} }
interface IChatPresenterContract { interface IChatPresenterContract {
@ -88,6 +89,7 @@ interface CoreContracts {
interface IChatViewContract { interface IChatViewContract {
fun setAdapter(adapter: MessagesListAdapter<GenericMessage>) fun setAdapter(adapter: MessagesListAdapter<GenericMessage>)
fun setUserStatus(status: String)
} }
// -----SettingsFragment----- // -----SettingsFragment-----

View File

@ -1,55 +0,0 @@
/*
* Copyright 2019 ChronosX88
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.github.chronosx88.influence.helpers;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class HashUtils {
public static String sha1(final String text) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(text.getBytes("UTF-8"), 0, text.length());
byte[] sha1hash = md.digest();
return hashToString(sha1hash);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
private static String hashToString(final byte[] buf) {
if (buf == null) return "";
int l = buf.length;
StringBuffer result = new StringBuffer(2 * l);
for (int i = 0; i < buf.length; i++) {
appendByte(result, buf[i]);
}
return result.toString();
}
private final static String HEX_PACK = "0123456789ABCDEF";
private static void appendByte(final StringBuffer sb, final byte b) {
sb
.append(HEX_PACK.charAt((b >> 4) & 0x0f))
.append(HEX_PACK.charAt(b & 0x0f));
}
}

View File

@ -19,16 +19,23 @@ package io.github.chronosx88.influence.helpers;
import com.instacart.library.truetime.TrueTime; import com.instacart.library.truetime.TrueTime;
import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.EventBus;
import org.jivesoftware.smack.PresenceListener;
import org.jivesoftware.smack.chat2.Chat; import org.jivesoftware.smack.chat2.Chat;
import org.jivesoftware.smack.chat2.IncomingChatMessageListener; import org.jivesoftware.smack.chat2.IncomingChatMessageListener;
import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.roster.PresenceEventListener;
import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.FullJid;
import org.jxmpp.jid.Jid;
import io.github.chronosx88.influence.models.GenericMessage; import io.github.chronosx88.influence.models.GenericMessage;
import io.github.chronosx88.influence.models.appEvents.LastMessageEvent; import io.github.chronosx88.influence.models.appEvents.LastMessageEvent;
import io.github.chronosx88.influence.models.appEvents.NewMessageEvent; import io.github.chronosx88.influence.models.appEvents.NewMessageEvent;
import io.github.chronosx88.influence.models.appEvents.UserPresenceChangedEvent;
public class NetworkHandler implements IncomingChatMessageListener { public class NetworkHandler implements IncomingChatMessageListener, PresenceEventListener {
private final static String LOG_TAG = "NetworkHandler"; private final static String LOG_TAG = "NetworkHandler";
@Override @Override
@ -44,4 +51,29 @@ public class NetworkHandler implements IncomingChatMessageListener {
EventBus.getDefault().post(new NewMessageEvent(chatID, messageID)); EventBus.getDefault().post(new NewMessageEvent(chatID, messageID));
EventBus.getDefault().post(new LastMessageEvent(chatID, new GenericMessage(LocalDBWrapper.getMessageByID(messageID)))); EventBus.getDefault().post(new LastMessageEvent(chatID, new GenericMessage(LocalDBWrapper.getMessageByID(messageID))));
} }
@Override
public void presenceAvailable(FullJid address, Presence availablePresence) {
EventBus.getDefault().post(new UserPresenceChangedEvent(address.asBareJid().asUnescapedString(), availablePresence.isAvailable()));
}
@Override
public void presenceUnavailable(FullJid address, Presence presence) {
EventBus.getDefault().post(new UserPresenceChangedEvent(address.asBareJid().asUnescapedString(), presence.isAvailable()));
}
@Override
public void presenceError(Jid address, Presence errorPresence) {
EventBus.getDefault().post(new UserPresenceChangedEvent(address.asBareJid().asUnescapedString(), errorPresence.isAvailable()));
}
@Override
public void presenceSubscribed(BareJid address, Presence subscribedPresence) {
}
@Override
public void presenceUnsubscribed(BareJid address, Presence unsubscribedPresence) {
}
} }

View File

@ -18,6 +18,7 @@ package io.github.chronosx88.influence.logic;
import com.instacart.library.truetime.TrueTime; import com.instacart.library.truetime.TrueTime;
import org.jivesoftware.smack.packet.Presence;
import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.impl.JidCreate; import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException; import org.jxmpp.stringprep.XmppStringprepException;
@ -58,10 +59,26 @@ public class ChatLogic implements CoreContracts.IChatLogicContract {
} }
}).start(); }).start();
} }
long messageID = LocalDBWrapper.createMessageEntry(chatID, AppHelper.getJid(), TrueTime.now().getTime(), text, false, false); long messageID = LocalDBWrapper.createMessageEntry(chatID, AppHelper.getJid(), TrueTime.now().getTime(), text, true, false);
return LocalDBWrapper.getMessageByID(messageID); return LocalDBWrapper.getMessageByID(messageID);
} else { } else {
return null; return null;
} }
} }
@Override
public boolean getUserStatus() {
if(AppHelper.getXmppConnection() != null) {
if(AppHelper.getXmppConnection().isConnectionAlive()) {
Presence presence = null;
try {
presence = AppHelper.getXmppConnection().getUserPresence(JidCreate.bareFrom(chatID));
} catch (XmppStringprepException e) {
e.printStackTrace();
}
return presence.isAvailable();
}
}
return false;
}
} }

View File

@ -0,0 +1,27 @@
/*
* Copyright 2019 ChronosX88
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.github.chronosx88.influence.models.appEvents;
public class UserPresenceChangedEvent {
public final String jid;
public final boolean status;
public UserPresenceChangedEvent(String jid, boolean status) {
this.jid = jid;
this.status = status;
}
}

View File

@ -28,11 +28,16 @@ import io.github.chronosx88.influence.logic.ChatLogic
import io.github.chronosx88.influence.models.GenericMessage import io.github.chronosx88.influence.models.GenericMessage
import io.github.chronosx88.influence.models.appEvents.LastMessageEvent import io.github.chronosx88.influence.models.appEvents.LastMessageEvent
import io.github.chronosx88.influence.models.appEvents.NewMessageEvent import io.github.chronosx88.influence.models.appEvents.NewMessageEvent
import io.github.chronosx88.influence.models.appEvents.UserPresenceChangedEvent
import io.github.chronosx88.influence.models.roomEntities.ChatEntity import io.github.chronosx88.influence.models.roomEntities.ChatEntity
import io.github.chronosx88.influence.models.roomEntities.MessageEntity import io.github.chronosx88.influence.models.roomEntities.MessageEntity
import java9.util.concurrent.CompletableFuture
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode import org.greenrobot.eventbus.ThreadMode
import kotlin.math.log
class ChatPresenter(private val view: CoreContracts.IChatViewContract, private val chatID: String) : CoreContracts.IChatPresenterContract { class ChatPresenter(private val view: CoreContracts.IChatViewContract, private val chatID: String) : CoreContracts.IChatPresenterContract {
private val logic: CoreContracts.IChatLogicContract private val logic: CoreContracts.IChatLogicContract
@ -48,6 +53,7 @@ class ChatPresenter(private val view: CoreContracts.IChatViewContract, private v
holdersConfig.setIncomingTextLayout(R.layout.item_incoming_text_message_custom) holdersConfig.setIncomingTextLayout(R.layout.item_incoming_text_message_custom)
chatAdapter = MessagesListAdapter(AppHelper.getJid(), holdersConfig, AvatarImageLoader()) chatAdapter = MessagesListAdapter(AppHelper.getJid(), holdersConfig, AvatarImageLoader())
view.setAdapter(chatAdapter) view.setAdapter(chatAdapter)
getUserStatus()
EventBus.getDefault().register(this) EventBus.getDefault().register(this)
} }
@ -85,4 +91,25 @@ class ChatPresenter(private val view: CoreContracts.IChatViewContract, private v
LocalDBWrapper.updateChatUnreadMessagesCount(chatEntity.jid, 0) LocalDBWrapper.updateChatUnreadMessagesCount(chatEntity.jid, 0)
} }
} }
@Subscribe(threadMode = ThreadMode.MAIN)
public fun onPresenceChanged(event: UserPresenceChangedEvent) {
if(event.jid == (chatID)) {
if(event.status) view.setUserStatus(AppHelper.getContext().getString(R.string.online)) else view.setUserStatus(AppHelper.getContext().getString(R.string.offline))
}
}
private fun getUserStatus() {
CompletableFuture.supplyAsync {
return@supplyAsync logic.getUserStatus()
}.thenAccept { status ->
AppHelper.getMainUIThread().post({
if(status) {
view.setUserStatus(AppHelper.getContext().getString(R.string.online))
} else {
view.setUserStatus(AppHelper.getContext().getString(R.string.offline))
}
})
}
}
} }

View File

@ -52,7 +52,12 @@ public class DialogListPresenter implements CoreContracts.IDialogListPresenterCo
private CoreContracts.IChatListViewContract view; private CoreContracts.IChatListViewContract view;
private CoreContracts.IDialogListLogicContract logic; private CoreContracts.IDialogListLogicContract logic;
private DialogsListAdapter<GenericDialog> dialogListAdapter = new DialogsListAdapter<>(R.layout.item_dialog_custom, new AvatarImageLoader()); private DialogsListAdapter<GenericDialog> dialogListAdapter = new DialogsListAdapter<>(R.layout.item_dialog_custom, new AvatarImageLoader());
private Comparator<GenericDialog> dialogComparator = (dialog1, dialog2) -> Long.compare(dialog2.getLastMessage().getCreatedAt().getTime(), dialog1.getLastMessage().getCreatedAt().getTime()); private Comparator<GenericDialog> dialogComparator = (dialog1, dialog2) -> {
if(dialog2.getLastMessage() != null && dialog1.getLastMessage() != null) {
return Long.compare(dialog2.getLastMessage().getCreatedAt().getTime(), dialog1.getLastMessage().getCreatedAt().getTime());
}
return 0;
};
public DialogListPresenter(CoreContracts.IChatListViewContract view) { public DialogListPresenter(CoreContracts.IChatListViewContract view) {
this.view = view; this.view = view;

View File

@ -38,12 +38,14 @@ import io.github.chronosx88.influence.models.GenericMessage
import io.github.chronosx88.influence.models.roomEntities.MessageEntity import io.github.chronosx88.influence.models.roomEntities.MessageEntity
import io.github.chronosx88.influence.presenters.ChatPresenter import io.github.chronosx88.influence.presenters.ChatPresenter
import kotlinx.android.synthetic.main.activity_chat.view.* import kotlinx.android.synthetic.main.activity_chat.view.*
import org.jetbrains.anko.find
class ChatActivity : AppCompatActivity(), CoreContracts.IChatViewContract { class ChatActivity : AppCompatActivity(), CoreContracts.IChatViewContract {
private var messageList: MessagesList? = null private var messageList: MessagesList? = null
private var messageInput: MessageInput? = null private var messageInput: MessageInput? = null
private var chatNameTextView: TextView? = null private var chatNameTextView: TextView? = null
private var chatAvatar: ImageView? = null private var chatAvatar: ImageView? = null
private var userStatus: TextView? = null
private var presenter: ChatPresenter? = null private var presenter: ChatPresenter? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -59,7 +61,8 @@ class ChatActivity : AppCompatActivity(), CoreContracts.IChatViewContract {
supportActionBar!!.setHomeButtonEnabled(true) supportActionBar!!.setHomeButtonEnabled(true)
messageList = findViewById(R.id.messages_list) messageList = findViewById(R.id.messages_list)
messageList!!.layoutManager = LinearLayoutManager(this) messageList!!.layoutManager = LinearLayoutManager(this)
chatNameTextView = findViewById(R.id.appbar_username) chatNameTextView = find(R.id.appbar_username)
userStatus = find(R.id.user_status_text)
chatAvatar = findViewById(R.id.profile_image_chat_activity) chatAvatar = findViewById(R.id.profile_image_chat_activity)
messageInput = findViewById(R.id.message_input) messageInput = findViewById(R.id.message_input)
messageInput!!.setInputListener { messageInput!!.setInputListener {
@ -107,4 +110,8 @@ class ChatActivity : AppCompatActivity(), CoreContracts.IChatViewContract {
.buildRound(firstLetter, ColorGenerator.MATERIAL.getColor(firstLetter))) .buildRound(firstLetter, ColorGenerator.MATERIAL.getColor(firstLetter)))
} }
} }
override fun setUserStatus(status: String) {
userStatus!!.text = status
}
} }

View File

@ -39,15 +39,27 @@
android:layout_height="40dp" android:layout_height="40dp"
android:id="@+id/profile_image_chat_activity"/> android:id="@+id/profile_image_chat_activity"/>
<TextView <LinearLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:id="@+id/appbar_username" android:layout_marginStart="25dp"
android:textSize="18sp"
android:layout_marginLeft="25dp" android:layout_marginLeft="25dp"
android:textColor="#FFFFFF" android:orientation="vertical">
android:textStyle="bold" <TextView
android:layout_marginStart="25dp" /> android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/appbar_username"
android:textSize="18sp"
android:textColor="#FFFFFF"
android:textStyle="bold"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/user_status_text"
android:textSize="18sp"
android:textColor="#FFFFFF"
android:text="@string/offline"/>
</LinearLayout>
</androidx.appcompat.widget.Toolbar> </androidx.appcompat.widget.Toolbar>

View File

@ -24,4 +24,6 @@
<string name="logout">Выйти из аккаунта</string> <string name="logout">Выйти из аккаунта</string>
<string name="sign_in_button">Войти</string> <string name="sign_in_button">Войти</string>
<string name="invalid_jid_error">Неверный JabberID!</string> <string name="invalid_jid_error">Неверный JabberID!</string>
<string name="offline">Не в сети</string>
<string name="online">В сети</string>
</resources> </resources>

View File

@ -23,4 +23,6 @@
<string name="logout">Log out from account</string> <string name="logout">Log out from account</string>
<string name="sign_in_button">Sign In</string> <string name="sign_in_button">Sign In</string>
<string name="invalid_jid_error">Invalid JabberID!</string> <string name="invalid_jid_error">Invalid JabberID!</string>
<string name="offline">Offline</string>
<string name="online">Online</string>
</resources> </resources>