diff --git a/app/build.gradle b/app/build.gradle index 7655f76..dfdd45c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -57,6 +57,7 @@ android { dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation 'androidx.appcompat:appcompat:1.1.0-alpha02' + implementation "com.android.support:support-compat:28.0.0" implementation "androidx.room:room-runtime:2.1.0-alpha04" implementation 'androidx.constraintlayout:constraintlayout:1.1.3' annotationProcessor "androidx.room:room-compiler:2.1.0-alpha04" diff --git a/app/src/main/java/io/github/chronosx88/influence/helpers/AppHelper.java b/app/src/main/java/io/github/chronosx88/influence/helpers/AppHelper.java index 976c3d2..414c6f3 100644 --- a/app/src/main/java/io/github/chronosx88/influence/helpers/AppHelper.java +++ b/app/src/main/java/io/github/chronosx88/influence/helpers/AppHelper.java @@ -52,6 +52,7 @@ public class AppHelper extends MultiDexApplication { private static Handler mainUIThreadHandler; private static ServiceConnection serviceConnection; private static boolean isMainActivityDestroyed = true; + private static String currentChatActivity = ""; public final static Map avatarsCache = new ConcurrentHashMap<>(); @Override @@ -150,4 +151,12 @@ public class AppHelper extends MultiDexApplication { public static void setIsMainActivityDestroyed(boolean isMainActivityDestroyed) { AppHelper.isMainActivityDestroyed = isMainActivityDestroyed; } + + public static String getCurrentChatActivity() { + return currentChatActivity; + } + + public static void setCurrentChatActivity(String currentChatActivity) { + AppHelper.currentChatActivity = currentChatActivity; + } } \ No newline at end of file diff --git a/app/src/main/java/io/github/chronosx88/influence/helpers/NetworkHandler.java b/app/src/main/java/io/github/chronosx88/influence/helpers/NetworkHandler.java index c6020b3..18bbe8a 100644 --- a/app/src/main/java/io/github/chronosx88/influence/helpers/NetworkHandler.java +++ b/app/src/main/java/io/github/chronosx88/influence/helpers/NetworkHandler.java @@ -16,6 +16,20 @@ package io.github.chronosx88.influence.helpers; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.Build; + +import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationManagerCompat; + +import com.amulyakhare.textdrawable.TextDrawable; +import com.amulyakhare.textdrawable.util.ColorGenerator; import com.instacart.library.truetime.TrueTime; import org.greenrobot.eventbus.EventBus; @@ -28,14 +42,28 @@ import org.jxmpp.jid.BareJid; import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.FullJid; import org.jxmpp.jid.Jid; +import org.jxmpp.jid.impl.JidCreate; +import org.jxmpp.stringprep.XmppStringprepException; +import java.util.Random; +import java.util.concurrent.ExecutionException; + +import io.github.chronosx88.influence.R; import io.github.chronosx88.influence.models.GenericMessage; import io.github.chronosx88.influence.models.appEvents.LastMessageEvent; import io.github.chronosx88.influence.models.appEvents.NewMessageEvent; import io.github.chronosx88.influence.models.appEvents.UserPresenceChangedEvent; +import java9.util.concurrent.CompletableFuture; public class NetworkHandler implements IncomingChatMessageListener, PresenceEventListener { private final static String LOG_TAG = "NetworkHandler"; + private final static String NOTIFICATION_CHANNEL_ID = "InfluenceNotificationsChannel"; + + private NotificationManagerCompat notificationManager = NotificationManagerCompat.from(AppHelper.getContext()); + + public NetworkHandler() { + createNotificationChannel(); + } @Override public void newIncomingMessage(EntityBareJid from, Message message, Chat chat) { @@ -49,8 +77,68 @@ public class NetworkHandler implements IncomingChatMessageListener, PresenceEven EventBus.getDefault().post(new NewMessageEvent(chatID, messageID)); EventBus.getDefault().post(new LastMessageEvent(chatID, new GenericMessage(LocalDBWrapper.getMessageByID(messageID)))); + if(!AppHelper.getCurrentChatActivity().equals(chatID)) { + byte[] avatarBytes = new byte[0]; + try { + CompletableFuture future = loadAvatar(chatID); + if(future != null) { + avatarBytes = future.get(); + } + + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } + Bitmap avatar = null; + if(avatarBytes != null) { + avatar = BitmapFactory.decodeByteArray(avatarBytes, 0, avatarBytes.length); + } + NotificationCompat.Builder notification = new NotificationCompat.Builder(AppHelper.getContext(), NOTIFICATION_CHANNEL_ID) + .setSmallIcon(R.drawable.ic_message_white_24dp) + .setContentTitle(chatID) + .setContentText(message.getBody()) + .setPriority(NotificationCompat.PRIORITY_DEFAULT); + if(avatar != null) { + notification.setLargeIcon(avatar); + } else { + String firstLetter = Character.toString(Character.toUpperCase(chatID.charAt(0))); + Drawable avatarText = TextDrawable.builder() + .beginConfig() + .width(64) + .height(64) + .endConfig() + .buildRound(firstLetter, ColorGenerator.MATERIAL.getColor(firstLetter)); + notification.setLargeIcon(drawableToBitmap(avatarText)); + } + notificationManager.notify(new Random().nextInt(), notification.build()); + } } + public static Bitmap drawableToBitmap(Drawable drawable) { + Bitmap bitmap; + + if (drawable instanceof BitmapDrawable) { + BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable; + if(bitmapDrawable.getBitmap() != null) { + return bitmapDrawable.getBitmap(); + } + } + + if(drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) { + bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); + } else { + bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); + } + + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + return bitmap; + } + + + @Override public void presenceAvailable(FullJid address, Presence availablePresence) { EventBus.getDefault().post(new UserPresenceChangedEvent(address.asBareJid().asUnescapedString(), availablePresence.isAvailable())); @@ -75,4 +163,41 @@ public class NetworkHandler implements IncomingChatMessageListener, PresenceEven public void presenceUnsubscribed(BareJid address, Presence unsubscribedPresence) { } + + private void createNotificationChannel() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + String name = AppHelper.getContext().getString(R.string.notification_channel_name); + String description = AppHelper.getContext().getString(R.string.notification_channel_desc); + NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, name, NotificationManager.IMPORTANCE_DEFAULT); + channel.setDescription(description); + NotificationManager notificationManager = AppHelper.getContext().getSystemService(NotificationManager.class); + notificationManager.createNotificationChannel(channel); + } + } + + private CompletableFuture loadAvatar(String senderID) { + if(senderID.length() != 0) { + if(AppHelper.avatarsCache.containsKey(senderID)) { + return CompletableFuture.completedFuture(AppHelper.avatarsCache.get(senderID)); + } + CompletableFuture completableFuture = CompletableFuture.supplyAsync(() -> { + while (AppHelper.getXmppConnection() == null); + while (AppHelper.getXmppConnection().isConnectionAlive() != true); + EntityBareJid jid = null; + try { + jid = JidCreate.entityBareFrom(senderID); + } catch (XmppStringprepException e) { + e.printStackTrace(); + } + return AppHelper.getXmppConnection().getAvatar(jid); + }).thenApply((avatarBytes) -> { + if(avatarBytes != null) { + AppHelper.avatarsCache.put(senderID, avatarBytes); + } + return avatarBytes; + }); + return completableFuture; + } + return null; + } } \ No newline at end of file diff --git a/app/src/main/java/io/github/chronosx88/influence/presenters/ChatPresenter.kt b/app/src/main/java/io/github/chronosx88/influence/presenters/ChatPresenter.kt index 016d734..1cef65b 100644 --- a/app/src/main/java/io/github/chronosx88/influence/presenters/ChatPresenter.kt +++ b/app/src/main/java/io/github/chronosx88/influence/presenters/ChatPresenter.kt @@ -60,6 +60,7 @@ class ChatPresenter(private val view: CoreContracts.IChatViewContract, private v view.setAdapter(chatAdapter) getUserStatus() EventBus.getDefault().register(this) + AppHelper.setCurrentChatActivity(chatID) } override fun sendMessage(text: String): Boolean { @@ -87,6 +88,7 @@ class ChatPresenter(private val view: CoreContracts.IChatViewContract, private v override fun onDestroy() { EventBus.getDefault().unregister(this) + AppHelper.setCurrentChatActivity("") } @Subscribe(threadMode = ThreadMode.MAIN) diff --git a/app/src/main/res/drawable/ic_message_white_24dp.xml b/app/src/main/res/drawable/ic_message_white_24dp.xml new file mode 100644 index 0000000..2fce1f1 --- /dev/null +++ b/app/src/main/res/drawable/ic_message_white_24dp.xml @@ -0,0 +1,21 @@ + + + + + diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index 742251b..085533a 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -27,4 +27,6 @@ Не в сети В сети Очистить чат + Уведомления Influence + Уведомления чатов Influence \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c31d346..ad3839d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -26,4 +26,6 @@ Offline Online Clear chat + Influence Notifications + Influence chat notifications