diff --git a/vector/src/main/java/im/vector/riotx/core/extensions/LiveData.kt b/vector/src/main/java/im/vector/riotx/core/extensions/LiveData.kt index a6ab57fe..a278eab0 100644 --- a/vector/src/main/java/im/vector/riotx/core/extensions/LiveData.kt +++ b/vector/src/main/java/im/vector/riotx/core/extensions/LiveData.kt @@ -19,7 +19,7 @@ package im.vector.riotx.core.extensions import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LiveData import androidx.lifecycle.Observer -import im.vector.riotx.core.utils.Debouncer +import im.vector.riotx.core.utils.FirstThrottler import im.vector.riotx.core.utils.EventObserver import im.vector.riotx.core.utils.LiveEvent @@ -35,11 +35,11 @@ inline fun LiveData>.observeEvent(owner: LifecycleOwner, crossi this.observe(owner, EventObserver { it.run(observer) }) } -inline fun LiveData>.observeEventDebounced(owner: LifecycleOwner, minimumInterval: Long, crossinline observer: (T) -> Unit) { - val debouncer = Debouncer(minimumInterval) +inline fun LiveData>.observeEventFirstThrottle(owner: LifecycleOwner, minimumInterval: Long, crossinline observer: (T) -> Unit) { + val firstThrottler = FirstThrottler(minimumInterval) this.observe(owner, EventObserver { - if (debouncer.canHandle()) { + if (firstThrottler.canHandle()) { it.run(observer) } }) diff --git a/vector/src/main/java/im/vector/riotx/core/utils/Debouncer.kt b/vector/src/main/java/im/vector/riotx/core/utils/FirstThrottler.kt similarity index 82% rename from vector/src/main/java/im/vector/riotx/core/utils/Debouncer.kt rename to vector/src/main/java/im/vector/riotx/core/utils/FirstThrottler.kt index 60e51ad2..eba0d0d6 100644 --- a/vector/src/main/java/im/vector/riotx/core/utils/Debouncer.kt +++ b/vector/src/main/java/im/vector/riotx/core/utils/FirstThrottler.kt @@ -17,9 +17,10 @@ package im.vector.riotx.core.utils /** - * Simple Debouncer + * Simple ThrottleFirst + * See https://raw.githubusercontent.com/wiki/ReactiveX/RxJava/images/rx-operators/throttleFirst.png */ -class Debouncer(private val minimumInterval: Long = 800) { +class FirstThrottler(private val minimumInterval: Long = 800) { private var lastDate = 0L fun canHandle(): Boolean { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt index 07304ae3..b6e2e24a 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt @@ -33,7 +33,7 @@ import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.epoxy.LayoutManagerStateRestorer import im.vector.riotx.core.error.ErrorFormatter import im.vector.riotx.core.extensions.observeEvent -import im.vector.riotx.core.extensions.observeEventDebounced +import im.vector.riotx.core.extensions.observeEventFirstThrottle import im.vector.riotx.core.platform.OnBackPressed import im.vector.riotx.core.platform.StateView import im.vector.riotx.core.platform.VectorBaseFragment @@ -81,7 +81,7 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Listener, O setupCreateRoomButton() setupRecyclerView() roomListViewModel.subscribe { renderState(it) } - roomListViewModel.openRoomLiveData.observeEventDebounced(this, 800L) { + roomListViewModel.openRoomLiveData.observeEventFirstThrottle(this, 800L) { navigator.openRoom(requireActivity(), it) } diff --git a/vector/src/main/java/im/vector/riotx/features/notifications/BitmapLoader.kt b/vector/src/main/java/im/vector/riotx/features/notifications/BitmapLoader.kt index d3506be7..c3a74bd9 100644 --- a/vector/src/main/java/im/vector/riotx/features/notifications/BitmapLoader.kt +++ b/vector/src/main/java/im/vector/riotx/features/notifications/BitmapLoader.kt @@ -18,74 +18,39 @@ package im.vector.riotx.features.notifications import android.content.Context import android.graphics.Bitmap -import android.os.Handler -import android.os.HandlerThread import androidx.annotation.WorkerThread import com.bumptech.glide.Glide import com.bumptech.glide.load.DecodeFormat import timber.log.Timber +import javax.inject.Inject +import javax.inject.Singleton -/** - * FIXME It works, but it does not refresh the notification, when it's already displayed - */ -class BitmapLoader(val context: Context, - val listener: BitmapLoaderListener) { +@Singleton +class BitmapLoader @Inject constructor(val context: Context) { /** - * Avatar Url -> Icon + * Avatar Url -> Bitmap */ - private val cache = HashMap() - - // URLs to load - private val toLoad = HashSet() - - // Black list of URLs (broken URL, etc.) - private val blacklist = HashSet() - - private var uiHandler = Handler() - - private val handlerThread: HandlerThread = HandlerThread("BitmapLoader", Thread.MIN_PRIORITY) - private var backgroundHandler: Handler - - init { - handlerThread.start() - backgroundHandler = Handler(handlerThread.looper) - } + private val cache = HashMap() /** * Get icon of a room. * If already in cache, use it, else load it and call BitmapLoaderListener.onBitmapsLoaded() when ready */ + @WorkerThread fun getRoomBitmap(path: String?): Bitmap? { if (path == null) { return null } - synchronized(cache) { - if (cache[path] != null) { - return cache[path] - } - - // Add to the queue, if not blacklisted - if (!blacklist.contains(path)) { - if (toLoad.contains(path)) { - // Wait - } else { - toLoad.add(path) - - backgroundHandler.post { - loadRoomBitmap(path) - } - } - } + return cache.getOrPut(path) { + loadRoomBitmap(path) } - - return null } @WorkerThread - private fun loadRoomBitmap(path: String) { - val bitmap = path.let { + private fun loadRoomBitmap(path: String): Bitmap? { + return path.let { try { Glide.with(context) .asBitmap() @@ -98,27 +63,5 @@ class BitmapLoader(val context: Context, null } } - - synchronized(cache) { - if (bitmap == null) { - // Add to the blacklist - blacklist.add(path) - } else { - cache[path] = bitmap - } - - toLoad.remove(path) - - if (toLoad.isEmpty()) { - uiHandler.post { - listener.onBitmapsLoaded() - } - } - } } - - - interface BitmapLoaderListener { - fun onBitmapsLoaded() - } -} \ No newline at end of file +} diff --git a/vector/src/main/java/im/vector/riotx/features/notifications/IconLoader.kt b/vector/src/main/java/im/vector/riotx/features/notifications/IconLoader.kt index c606f900..0c4477f4 100644 --- a/vector/src/main/java/im/vector/riotx/features/notifications/IconLoader.kt +++ b/vector/src/main/java/im/vector/riotx/features/notifications/IconLoader.kt @@ -17,76 +17,43 @@ package im.vector.riotx.features.notifications import android.content.Context -import android.os.Handler -import android.os.HandlerThread +import android.os.Build import androidx.annotation.WorkerThread import androidx.core.graphics.drawable.IconCompat import com.bumptech.glide.Glide import com.bumptech.glide.load.DecodeFormat import com.bumptech.glide.request.RequestOptions import timber.log.Timber +import javax.inject.Inject +import javax.inject.Singleton -/** - * FIXME It works, but it does not refresh the notification, when it's already displayed - */ -class IconLoader(val context: Context, - val listener: IconLoaderListener) { +@Singleton +class IconLoader @Inject constructor(val context: Context) { /** - * Avatar Url -> Icon + * Avatar Url -> IconCompat */ - private val cache = HashMap() - - // URLs to load - private val toLoad = HashSet() - - // Black list of URLs (broken URL, etc.) - private val blacklist = HashSet() - - private var uiHandler = Handler() - - private val handlerThread: HandlerThread = HandlerThread("IconLoader", Thread.MIN_PRIORITY) - private var backgroundHandler: Handler - - init { - handlerThread.start() - backgroundHandler = Handler(handlerThread.looper) - } + private val cache = HashMap() /** * Get icon of a user. * If already in cache, use it, else load it and call IconLoaderListener.onIconsLoaded() when ready + * Before Android P, this does nothing because the icon won't be used */ + @WorkerThread fun getUserIcon(path: String?): IconCompat? { - if (path == null) { + if (path == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { return null } - synchronized(cache) { - if (cache[path] != null) { - return cache[path] - } - - // Add to the queue, if not blacklisted - if (!blacklist.contains(path)) { - if (toLoad.contains(path)) { - // Wait - } else { - toLoad.add(path) - - backgroundHandler.post { - loadUserIcon(path) - } - } - } + return cache.getOrPut(path) { + loadUserIcon(path) } - - return null } @WorkerThread - private fun loadUserIcon(path: String) { - val iconCompat = path.let { + private fun loadUserIcon(path: String): IconCompat? { + return path.let { try { Glide.with(context) .asBitmap() @@ -102,27 +69,5 @@ class IconLoader(val context: Context, IconCompat.createWithBitmap(bitmap) } } - - synchronized(cache) { - if (iconCompat == null) { - // Add to the blacklist - blacklist.add(path) - } else { - cache[path] = iconCompat - } - - toLoad.remove(path) - - if (toLoad.isEmpty()) { - uiHandler.post { - listener.onIconsLoaded() - } - } - } } - - - interface IconLoaderListener { - fun onIconsLoaded() - } -} \ No newline at end of file +} diff --git a/vector/src/main/java/im/vector/riotx/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/riotx/features/notifications/NotificationDrawerManager.kt index a704f672..1e529bc6 100644 --- a/vector/src/main/java/im/vector/riotx/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/riotx/features/notifications/NotificationDrawerManager.kt @@ -18,6 +18,9 @@ package im.vector.riotx.features.notifications import android.app.Notification import android.content.Context import android.graphics.Bitmap +import android.os.Handler +import android.os.HandlerThread +import androidx.annotation.WorkerThread import androidx.core.app.NotificationCompat import androidx.core.app.Person import im.vector.matrix.android.api.session.content.ContentUrlResolver @@ -42,8 +45,18 @@ import javax.inject.Singleton @Singleton class NotificationDrawerManager @Inject constructor(private val context: Context, private val activeSessionHolder: ActiveSessionHolder, + private val iconLoader: IconLoader, + private val bitmapLoader: BitmapLoader, private val outdatedDetector: OutdatedEventDetector?) { + private val handlerThread: HandlerThread = HandlerThread("NotificationDrawerManager", Thread.MIN_PRIORITY) + private var backgroundHandler: Handler + + init { + handlerThread.start() + backgroundHandler = Handler(handlerThread.looper) + } + //The first time the notification drawer is refreshed, we force re-render of all notifications private var firstTime = true @@ -53,22 +66,6 @@ class NotificationDrawerManager @Inject constructor(private val context: Context private var currentRoomId: String? = null - private var iconLoader = IconLoader(context, - object : IconLoader.IconLoaderListener { - override fun onIconsLoaded() { - // Force refresh - refreshNotificationDrawer() - } - }) - - private var bitmapLoader = BitmapLoader(context, - object : BitmapLoader.BitmapLoaderListener { - override fun onBitmapsLoaded() { - // Force refresh - refreshNotificationDrawer() - } - }) - /** Should be called as soon as a new event is ready to be displayed. The notification corresponding to this event will not be displayed until @@ -171,6 +168,20 @@ class NotificationDrawerManager @Inject constructor(private val context: Context fun refreshNotificationDrawer() { + // Implement last throttler + Timber.w("refreshNotificationDrawer()") + backgroundHandler.removeCallbacksAndMessages(null) + backgroundHandler.postDelayed( + { + refreshNotificationDrawerBg() + } + , 200) + } + + @WorkerThread + private fun refreshNotificationDrawerBg() { + Timber.w("refreshNotificationDrawerBg()") + val session = activeSessionHolder.getActiveSession() val user = session.getUser(session.sessionParams.credentials.userId) val myUserDisplayName = user?.displayName ?: ""