Refactor - extracted common method to load avatars for notifications

Signed-off-by: Dariusz Olszewski <starypatyk@users.noreply.github.com>
This commit is contained in:
Dariusz Olszewski 2022-05-09 21:51:07 +02:00 committed by Andy Scherzinger
parent 3b7ca4a31a
commit d4bdd88588
No known key found for this signature in database
GPG key ID: 6CADC7E3523C308B
3 changed files with 43 additions and 72 deletions

View file

@ -37,14 +37,6 @@ import android.util.Base64;
import android.util.Log; import android.util.Log;
import com.bluelinelabs.logansquare.LoganSquare; import com.bluelinelabs.logansquare.LoganSquare;
import com.facebook.common.executors.UiThreadImmediateExecutorService;
import com.facebook.common.references.CloseableReference;
import com.facebook.datasource.DataSource;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber;
import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.postprocessors.RoundAsCirclePostprocessor;
import com.facebook.imagepipeline.request.ImageRequest;
import com.nextcloud.talk.R; import com.nextcloud.talk.R;
import com.nextcloud.talk.activities.CallActivity; import com.nextcloud.talk.activities.CallActivity;
import com.nextcloud.talk.activities.MainActivity; import com.nextcloud.talk.activities.MainActivity;
@ -61,7 +53,6 @@ import com.nextcloud.talk.models.json.push.DecryptedPushMessage;
import com.nextcloud.talk.models.json.push.NotificationUser; import com.nextcloud.talk.models.json.push.NotificationUser;
import com.nextcloud.talk.receivers.DirectReplyReceiver; import com.nextcloud.talk.receivers.DirectReplyReceiver;
import com.nextcloud.talk.utils.ApiUtils; import com.nextcloud.talk.utils.ApiUtils;
import com.nextcloud.talk.utils.DisplayUtils;
import com.nextcloud.talk.utils.DoNotDisturbUtils; import com.nextcloud.talk.utils.DoNotDisturbUtils;
import com.nextcloud.talk.utils.NotificationUtils; import com.nextcloud.talk.utils.NotificationUtils;
import com.nextcloud.talk.utils.PushUtils; import com.nextcloud.talk.utils.PushUtils;
@ -93,7 +84,6 @@ import androidx.core.app.NotificationCompat.MessagingStyle;
import androidx.core.app.NotificationManagerCompat; import androidx.core.app.NotificationManagerCompat;
import androidx.core.app.Person; import androidx.core.app.Person;
import androidx.core.app.RemoteInput; import androidx.core.app.RemoteInput;
import androidx.core.graphics.drawable.IconCompat;
import androidx.emoji.text.EmojiCompat; import androidx.emoji.text.EmojiCompat;
import androidx.work.Data; import androidx.work.Data;
import androidx.work.Worker; import androidx.work.Worker;
@ -395,9 +385,10 @@ public class NotificationWorker extends Worker {
final NotificationUser notificationUser = decryptedPushMessage.getNotificationUser(); final NotificationUser notificationUser = decryptedPushMessage.getNotificationUser();
final String userType = notificationUser.getType(); final String userType = notificationUser.getType();
MessagingStyle style = activeStatusBarNotification != null ? MessagingStyle style = null;
MessagingStyle.extractMessagingStyleFromNotification(activeStatusBarNotification.getNotification()) : if (activeStatusBarNotification != null) {
null; style = MessagingStyle.extractMessagingStyleFromNotification(activeStatusBarNotification.getNotification());
}
Person.Builder person = Person.Builder person =
new Person.Builder() new Person.Builder()
@ -413,32 +404,12 @@ public class NotificationWorker extends Worker {
String avatarUrl = "user".equals(userType) ? String avatarUrl = "user".equals(userType) ?
ApiUtils.getUrlForAvatar(baseUrl, notificationUser.getId(), false) : ApiUtils.getUrlForAvatar(baseUrl, notificationUser.getId(), false) :
ApiUtils.getUrlForGuestAvatar(baseUrl, notificationUser.getName(), false); ApiUtils.getUrlForGuestAvatar(baseUrl, notificationUser.getName(), false);
person.setIcon(NotificationUtils.INSTANCE.loadAvatarSync(avatarUrl));
ImageRequest imageRequest = DisplayUtils.getImageRequestForUrl(avatarUrl, null);
Fresco.getImagePipeline().fetchDecodedImage(imageRequest, context).subscribe(
new BaseBitmapDataSubscriber() {
@Override
protected void onNewResultImpl(Bitmap bitmap) {
if (bitmap != null) {
new RoundAsCirclePostprocessor(true).process(bitmap);
person.setIcon(IconCompat.createWithBitmap(bitmap));
notificationBuilder.setStyle(getStyle(person.build(), style));
sendNotificationWithId(notificationId, notificationBuilder.build());
}
} }
@Override
protected void onFailureImpl(DataSource<CloseableReference<CloseableImage>> dataSource) {
notificationBuilder.setStyle(getStyle(person.build(), style)); notificationBuilder.setStyle(getStyle(person.build(), style));
sendNotificationWithId(notificationId, notificationBuilder.build()); sendNotificationWithId(notificationId, notificationBuilder.build());
} }
},
UiThreadImmediateExecutorService.getInstance());
} else {
notificationBuilder.setStyle(getStyle(person.build(), style));
sendNotificationWithId(notificationId, notificationBuilder.build());
}
}
private void addReplyAction(NotificationCompat.Builder notificationBuilder, int notificationId) { private void addReplyAction(NotificationCompat.Builder notificationBuilder, int notificationId) {
String replyLabel = context.getResources().getString(R.string.nc_reply); String replyLabel = context.getResources().getString(R.string.nc_reply);

View file

@ -25,28 +25,18 @@ import android.app.NotificationManager
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Bitmap
import android.os.Build import android.os.Build
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.core.app.Person import androidx.core.app.Person
import androidx.core.app.RemoteInput import androidx.core.app.RemoteInput
import androidx.core.graphics.drawable.IconCompat
import autodagger.AutoInjector import autodagger.AutoInjector
import com.facebook.common.executors.UiThreadImmediateExecutorService
import com.facebook.common.references.CloseableReference
import com.facebook.datasource.DataSource
import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber
import com.facebook.imagepipeline.image.CloseableImage
import com.facebook.imagepipeline.postprocessors.RoundAsCirclePostprocessor
import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.models.database.UserEntity import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.models.json.generic.GenericOverall import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.NotificationUtils import com.nextcloud.talk.utils.NotificationUtils
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_NOTIFICATION_ID import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_NOTIFICATION_ID
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
@ -110,7 +100,7 @@ class DirectReplyReceiver : BroadcastReceiver() {
@RequiresApi(Build.VERSION_CODES.N) @RequiresApi(Build.VERSION_CODES.N)
override fun onNext(genericOverall: GenericOverall) { override fun onNext(genericOverall: GenericOverall) {
loadAvatar(::confirmReplySent) confirmReplySent()
} }
override fun onError(e: Throwable) { override fun onError(e: Throwable) {
@ -124,27 +114,6 @@ class DirectReplyReceiver : BroadcastReceiver() {
}) })
} }
private fun loadAvatar(callback: (avatarIcon: IconCompat) -> Unit) {
val avatarUrl = ApiUtils.getUrlForAvatar(currentUser.baseUrl, currentUser.userId, false)
val imageRequest = DisplayUtils.getImageRequestForUrl(avatarUrl, currentUser)
val dataSource = Fresco.getImagePipeline().fetchDecodedImage(imageRequest, null)
dataSource.subscribe(
object : BaseBitmapDataSubscriber() {
override fun onNewResultImpl(bitmap: Bitmap?) {
if (bitmap != null) {
RoundAsCirclePostprocessor(true).process(bitmap)
callback(IconCompat.createWithBitmap(bitmap))
}
}
override fun onFailureImpl(dataSource: DataSource<CloseableReference<CloseableImage?>>) {
// unused atm
}
},
UiThreadImmediateExecutorService.getInstance()
)
}
@RequiresApi(Build.VERSION_CODES.N) @RequiresApi(Build.VERSION_CODES.N)
private fun findActiveNotification(notificationId: Int): Notification? { private fun findActiveNotification(notificationId: Int): Notification? {
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
@ -152,7 +121,7 @@ class DirectReplyReceiver : BroadcastReceiver() {
} }
@RequiresApi(Build.VERSION_CODES.N) @RequiresApi(Build.VERSION_CODES.N)
private fun confirmReplySent(avatarIcon: IconCompat) { private fun confirmReplySent() {
// Implementation inspired by the SO question and article below: // Implementation inspired by the SO question and article below:
// https://stackoverflow.com/questions/51549456/android-o-notification-for-direct-reply-message // https://stackoverflow.com/questions/51549456/android-o-notification-for-direct-reply-message
// https://medium.com/@sidorovroman3/android-how-to-use-messagingstyle-for-notifications-without-caching-messages-c414ef2b816c // https://medium.com/@sidorovroman3/android-how-to-use-messagingstyle-for-notifications-without-caching-messages-c414ef2b816c
@ -171,10 +140,10 @@ class DirectReplyReceiver : BroadcastReceiver() {
.extractMessagingStyleFromNotification(previousNotification) .extractMessagingStyleFromNotification(previousNotification)
// Add reply // Add reply
val avatarUrl = ApiUtils.getUrlForAvatar(currentUser.baseUrl, currentUser.userId, false)
val me = Person.Builder() val me = Person.Builder()
.setName(currentUser.displayName) .setName(currentUser.displayName)
// .setIcon(IconCompat.createWithResource(context, R.drawable.ic_user)) .setIcon(NotificationUtils.loadAvatarSync(avatarUrl))
.setIcon(avatarIcon)
.build() .build()
val message = NotificationCompat.MessagingStyle.Message(replyMessage, System.currentTimeMillis(), me) val message = NotificationCompat.MessagingStyle.Message(replyMessage, System.currentTimeMillis(), me)
previousStyle?.addMessage(message) previousStyle?.addMessage(message)

View file

@ -32,7 +32,13 @@ import android.net.Uri
import android.os.Build import android.os.Build
import android.service.notification.StatusBarNotification import android.service.notification.StatusBarNotification
import android.text.TextUtils import android.text.TextUtils
import androidx.core.graphics.drawable.IconCompat
import com.bluelinelabs.logansquare.LoganSquare import com.bluelinelabs.logansquare.LoganSquare
import com.facebook.common.references.CloseableReference
import com.facebook.datasource.DataSources
import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.imagepipeline.image.CloseableBitmap
import com.facebook.imagepipeline.postprocessors.RoundAsCirclePostprocessor
import com.nextcloud.talk.BuildConfig import com.nextcloud.talk.BuildConfig
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.models.RingtoneSettings import com.nextcloud.talk.models.RingtoneSettings
@ -297,6 +303,31 @@ object NotificationUtils {
) )
} }
/*
* Load user avatar synchronously.
* Inspired by:
* https://frescolib.org/docs/using-image-pipeline.html
* https://github.com/facebook/fresco/issues/830
* https://localcoder.org/using-facebooks-fresco-to-load-a-bitmap
*/
fun loadAvatarSync(avatarUrl: String): IconCompat? {
// TODO - how to handle errors here?
var avatarIcon: IconCompat? = null
val imageRequest = DisplayUtils.getImageRequestForUrl(avatarUrl, null)
val dataSource = Fresco.getImagePipeline().fetchDecodedImage(imageRequest, null)
val closeableImageRef = DataSources.waitForFinalResult(dataSource) as CloseableReference<CloseableBitmap>?
val bitmap = closeableImageRef?.get()?.underlyingBitmap
if (bitmap != null) {
// According to Fresco documentation a copy of the bitmap should be made before closing the references.
// However, it seems to work without making a copy... ;-)
RoundAsCirclePostprocessor(true).process(bitmap)
avatarIcon = IconCompat.createWithBitmap(bitmap)
}
CloseableReference.closeSafely(closeableImageRef)
dataSource.close()
return avatarIcon
}
private data class Channel( private data class Channel(
val id: String, val id: String,
val name: String, val name: String,